ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

YOLO猫狗检测实战:VOC/COCO/YOLO标签转换与数据集划分全解析

YOLO猫狗检测实战:VOC/COCO/YOLO标签转换与数据集划分全解析 简介这份资源面向计算机视觉入门与进阶学习者提供一套可直接用于YOLO系列目标检测训练的猫狗数据集帮助解决自建数据集标注耗时、格式转换繁琐、训练集划分混乱等常见问题。压缩包共2000个文件约23.05MB包含1000张真实场景图片及对应标注其中xml为VOC格式、txt为YOLO格式另有json形式的COCO标签与yaml数据配置文件并附带多个Python划分脚本可一键生成训练集、验证集、测试集及ImageSets索引文件。资源还配套Windows与Linux双平台的YOLO环境搭建、训练教程及案例修改说明方便读者按自身系统快速跑通流程。目前已有1355人学习下载适合课程设计、毕业项目或算法验证场景拿到后即可直接投入模型训练与效果对比。1. 从一堆散装图片到可训练数据集YOLO猫狗检测到底要过几道手你手里如果有一批猫狗照片想直接丢进 YOLO 训练大概率第一步就卡住——图片是散的标签没有格式不对划分脚本还得自己写。这个标题说的就是这件事1000 张猫狗图片配上 VOC、COCO、YOLO 三种格式的标签再加一个划分脚本和训练教程。听起来像是一个打包好的资源但真正值钱的地方不在图片本身而在于三种标签格式之间的转换逻辑和划分策略。猫狗检测是目标检测里最经典的二分类场景类别少、目标大、背景相对干净适合用来跑通全流程。但很多人拿到数据集之后直接train一跑要么路径报错要么标签对不上要么验证集 mAP 死活上不去。问题往往不出在模型上而是数据格式和划分方式埋了雷。这篇内容面向的是想用猫狗数据快速验证 YOLO 训练链路的人不管你是刚接触目标检测还是已经跑过几个项目但总在数据环节翻车下面的步骤和参数都能直接抄。2. 三种标签格式到底怎么选VOC、COCO、YOLO 的适用边界2.1 为什么同一个数据集要存三份标签VOC 格式是 XML每个图片对应一个 XML 文件里面用bndbox存xmin、ymin、xmax、ymax。它的好处是可读性强标注工具支持广很多老牌检测框架和评测脚本默认吃 VOC。缺点是文件数量翻倍1000 张图就是 1000 个 XML磁盘上碎文件多批量处理时 I/O 压力明显。COCO 格式是一个大的 JSON 文件所有图片、标注、类别都塞在一起。它的优势是结构紧凑适合做数据集的统一管理和跨框架迁移比如你后面想换 Detectron2 或者 MMDetectionCOCO JSON 基本是通用货币。但 JSON 文件一旦大了手动改一个标注就得写脚本没法像 XML 那样直接文本编辑器打开改。YOLO 格式最轻每张图对应一个.txt每行是class_id x_center y_center width height全部归一化到 0~1。YOLO 训练时直接读这个格式速度快解析简单。但它的缺点也明显没有图片尺寸信息没有难例标记类别靠classes.txt的索引顺序一旦索引错位模型学到的就是“猫狗”。我一般会这样处理原始标注先存 VOC因为标注工具导出方便然后用脚本转一份 COCO JSON 做备份和跨框架用最后转 YOLO txt 用于实际训练。三份标签同源但用途不同不是重复劳动。2.2 格式转换的核心参数与坐标换算从 VOC 转 YOLO 的坐标换算公式是固定的但有几个参数必须盯死x_center (xmin xmax) / 2 / img_widthy_center (ymin ymax) / 2 / img_heightwidth (xmax - xmin) / img_widthheight (ymax - ymin) / img_height这里最容易翻车的是img_width和img_height的来源。VOC XML 里虽然有size节点但有些标注工具导出的宽高和实际图片不一致尤其是图片被裁剪过但 XML 没更新。稳妥做法是用 PIL 或 OpenCV 重新读一遍图片尺寸不要信 XML 里的size。下面是一个 VOC 转 YOLO 的 Python 脚本片段我平时会直接拿来用import os import xml.etree.ElementTree as ET from PIL import Image # 类别映射顺序必须和训练时的 classes.txt 一致 class_map {cat: 0, dog: 1} def voc_to_yolo(xml_path, img_path, out_txt_path): # 用实际图片尺寸不用 XML 里的 size with Image.open(img_path) as im: img_w, img_h im.size tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): cls_name obj.find(name).text.strip() if cls_name not in class_map: continue # 忽略未定义类别避免索引错位 cls_id class_map[cls_name] bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 边界裁剪防止标注超出图片范围 xmin max(0, min(xmin, img_w)) xmax max(0, min(xmax, img_w)) ymin max(0, min(ymin, img_h)) ymax max(0, min(ymax, img_h)) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(out_txt_path, w) as f: f.write(\n.join(lines))这段代码里class_map的顺序就是最终 YOLO 训练时的类别索引cat必须是 0dog必须是 1不能反。Image.open读实际尺寸这一步不能省我见过太多因为 XML 里宽高写错导致框全偏的案例。边界裁剪是后悔药防止标注员手抖把xmax写到图片外面去YOLO 虽然能容忍轻微越界但归一化后出现负数或大于 1 的值训练时 loss 会异常。2.3 COCO JSON 的生成要点COCO 格式的结构比 VOC 复杂核心字段包括images、annotations、categories。images里每张图要有id、file_name、width、heightannotations里每条要有id、image_id、category_id、bbox、area、iscrowd。其中bbox是[x, y, width, height]注意这里是左上角坐标加宽高不是中心点。生成 COCO JSON 时image_id和annotation_id必须唯一通常用递增整数。category_id建议从 1 开始因为 COCO 官方把 0 留给背景。area就是width * heightiscrowd一般填 0。这些字段缺一个后面用 pycocotools 评估时就会报错。3. 1000 张猫狗图片怎么划分训练集、验证集、测试集的比例与脚本3.1 划分比例不是拍脑袋要看数据量和类别分布1000 张图不算多如果按 8:1:1 分训练集 800、验证集 100、测试集 100。但猫狗检测里如果猫和狗的数量不均衡比如猫 700 狗 300直接随机划分可能导致验证集里狗太少mAP 波动大。我一般会先统计每个类别的图片数然后按类别分层抽样。分层抽样的逻辑是对每个类别分别按比例抽取训练、验证、测试。这样能保证验证集和测试集里猫狗比例与整体一致。如果某个类别图片特别少比如只有 50 张那验证集和测试集各留 5 张就够了剩下的全给训练。3.2 划分脚本怎么写才可复现划分脚本必须固定随机种子否则每次跑出来的划分结果不一样实验没法对比。下面是一个按类别分层划分的脚本import os import random import shutil from collections import defaultdict random.seed(42) # 固定种子保证可复现 img_dir images txt_dir labels_yolo out_dir dataset_split # 收集每个类别的图片列表 cls_to_imgs defaultdict(list) for txt_file in os.listdir(txt_dir): if not txt_file.endswith(.txt): continue img_name txt_file.replace(.txt, .jpg) with open(os.path.join(txt_dir, txt_file)) as f: classes set(line.split()[0] for line in f if line.strip()) for c in classes: cls_to_imgs[c].append(img_name) # 按类别分层划分 split_ratio {train: 0.8, val: 0.1, test: 0.1} for cls, imgs in cls_to_imgs.items(): random.shuffle(imgs) n len(imgs) n_train int(n * split_ratio[train]) n_val int(n * split_ratio[val]) splits { train: imgs[:n_train], val: imgs[n_train:n_train n_val], test: imgs[n_train n_val:] } for split_name, split_imgs in splits.items(): for img in split_imgs: # 复制图片和对应标签到目标目录 src_img os.path.join(img_dir, img) src_txt os.path.join(txt_dir, img.replace(.jpg, .txt)) dst_img_dir os.path.join(out_dir, split_name, images) dst_txt_dir os.path.join(out_dir, split_name, labels) os.makedirs(dst_img_dir, exist_okTrue) os.makedirs(dst_txt_dir, exist_okTrue) shutil.copy(src_img, dst_img_dir) shutil.copy(src_txt, dst_txt_dir)random.seed(42)是必须的42 只是习惯换成任何固定值都行关键是别每次变。cls_to_imgs用defaultdict(list)收集每个类别出现的图片一张图里同时有猫和狗就会同时进两个列表划分时可能被分到不同集合这会导致同一张图在训练集和验证集里都出现。解决办法是划分前先按图片维度去重或者以图片为单位划分再检查类别分布。我一般会先按图片划分然后统计每个集合的类别数如果偏差太大就调整种子重跑。3.3 划分后的目录结构划分完的目录建议长这样dataset_split/ ├── train/ │ ├── images/ │ └── labels/ ├── val/ │ ├── images/ │ └── labels/ └── test/ ├── images/ └── labels/YOLO 训练时data.yaml里直接指向这三个目录的images路径YOLO 会自动去找同级的labels。注意images和labels必须同级且文件名除了后缀外完全一致cat_001.jpg对应cat_001.txt差一个字符都找不到。4. 训练教程里的参数怎么设从 data.yaml 到超参数4.1 data.yaml 的四个必填项YOLO 训练的第一步是写data.yaml里面至少要有train、val、nc、names四个字段train: dataset_split/train/images val: dataset_split/val/images nc: 2 names: [cat, dog]nc是类别数猫狗就是 2。names的顺序必须和class_map一致cat在前dog在后不能反。train和val可以用绝对路径也可以用相对路径相对路径是相对于你执行训练命令时的工作目录不是相对于data.yaml文件位置。这一点很多人搞混导致路径报错。4.2 训练命令与关键超参数用 YOLOv8 训练的话命令大概是这样yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/cat_dog \ nameexp1modelyolov8n.pt是加载预训练权重猫狗检测数据量不大从预训练模型微调比从头训练收敛快得多。imgsz640是输入分辨率1000 张图用 640 足够再大显存吃不消。batch16是常见起点显存不够就降到 8 或 4。lr00.01是初始学习率微调时如果 loss 震荡厉害可以降到 0.001。patience20是早停耐心值验证集 mAP 连续 20 轮不提升就停防止过拟合。4.3 训练过程中看什么指标训练日志里重点看三个box_loss、cls_loss、mAP50。box_loss是边界框回归损失cls_loss是分类损失两个都应该稳步下降。如果box_loss下降但cls_loss不降可能是类别不平衡或者标签有错。mAP50是 IoU 阈值 0.5 时的平均精度猫狗检测正常能到 0.9 以上如果卡在 0.5 左右先回去查标签格式。混淆矩阵也是重要参考YOLO 训练完会自动生成。如果猫被大量预测成狗说明类别索引可能反了或者names顺序和训练时不一致。这个坑我踩过训练完发现猫狗全反回头查data.yaml发现names写成了[dog, cat]但标签里cat是 0模型学到的就是反的。5. 避坑与排查猫狗数据集训练中最容易翻车的五个点5.1 现象训练 loss 正常下降但验证集 mAP 始终为 0原因验证集路径下没有标签文件或者标签文件名和图片名不匹配。YOLO 在验证时找不到对应labels会当作背景处理mAP 自然为 0。解决检查val/images和val/labels是否同级文件名是否一一对应。可以用ls val/images | wc -l和ls val/labels | wc -l对比数量数量不一致就说明有遗漏。5.2 现象训练报错AssertionError: train: No labels found原因data.yaml里train路径指向了images目录但 YOLO 期望的是包含images和labels的父目录或者路径写成了绝对路径但实际不存在。解决确认train路径是dataset_split/train/images这种形式YOLO 会自动把images替换成labels去找标签。如果路径里有中文或空格尽量改成英文和下划线。5.3 现象训练出来的模型把猫狗都框成同一个类别原因class_map和data.yaml的names顺序不一致或者 VOC 转 YOLO 时类别索引写错。比如class_map里cat:0, dog:1但names写成了[dog, cat]模型学到的索引和实际类别对不上。解决统一用一份classes.txt里面按行写cat、dog转换脚本和data.yaml都从这个文件读不要手写两遍。5.4 现象训练到一半 loss 突然变成 NaN原因学习率太大或者标注框归一化后出现负值/大于 1 的值。猫狗检测里如果标注框超出图片边界归一化后x_center可能小于 0 或大于 1YOLO 计算 loss 时会产生异常梯度。解决在转换脚本里加边界裁剪确保xmin 0、xmax img_w、ymin 0、ymax img_h。学习率从 0.01 降到 0.001 再试。5.5 现象验证集 mAP 波动很大每次跑结果都不一样原因划分脚本没有固定随机种子或者训练时shuffle没固定。数据量小的时候划分方式对结果影响很大。解决划分脚本里加random.seed(42)训练命令里加seed42。如果还是波动考虑用 K 折交叉验证但 1000 张图做 5 折每折训练集只有 800 张可能欠拟合量力而行。6. 进阶技巧用混淆矩阵和 PR 曲线反查标签质量训练完之后runs/cat_dog/exp1目录下会生成confusion_matrix.png和PR_curve.png。这两个图不只是给别人看的自己用来反查标签质量非常有效。混淆矩阵的横轴是预测类别纵轴是真实类别。如果cat那一行有大量被预测成dog说明模型分不清猫狗可能是图片里猫狗同时出现且标注框重叠或者标注时把猫标成了狗。这时候不要急着调模型先抽几十张被分错的图打开对应的.txt看类别索引大概率是标注错误。PR 曲线看的是每个类别的精度和召回率。如果cat的 AP 很高但dog的 AP 很低说明狗的正样本太少或者标注质量差。1000 张图里如果狗只有 200 张可以考虑对狗做数据增强比如随机裁剪、颜色抖动但不要用翻转猫狗翻转后还是猫狗但左右翻转对检测本身没坏处可以用。还有一个技巧是看val_batch0_pred.jpg和val_batch0_labels.jpg前者是模型预测结果后者是真实标签。两张图对比着看一眼就能发现漏检和误检。我习惯训练完先看这两张图比看 mAP 数字直观得多。最后说一个我自己的习惯每次拿到新数据集先花 10 分钟写个脚本统计类别分布、图片尺寸分布、标注框宽高比分布。这三个分布一出来基本就能判断这个数据集能不能直接训还是需要先清洗。猫狗检测看起来简单但标注框宽高比如果集中在极端值比如全是细长条那可能是标注员把猫尾巴单独框出来了这种标签训出来的模型只会检测尾巴。希望帮到你。本文还有配套的精品资源点击获取
返回列表