news 2026/10/2 18:18:22

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 或者 MMDetection,COCO 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_width
  • y_center = (ymin + ymax) / 2 / img_height
  • width = (xmax - xmin) / img_width
  • height = (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必须是 0,dog必须是 1,不能反。Image.open读实际尺寸这一步不能省,我见过太多因为 XML 里宽高写错导致框全偏的案例。边界裁剪是后悔药,防止标注员手抖把xmax写到图片外面去,YOLO 虽然能容忍轻微越界,但归一化后出现负数或大于 1 的值,训练时 loss 会异常。

2.3 COCO JSON 的生成要点

COCO 格式的结构比 VOC 复杂,核心字段包括images、annotations、categories。images里每张图要有id、file_name、width、height;annotations里每条要有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 * height,iscrowd一般填 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_ok=True) os.makedirs(dst_txt_dir, exist_ok=True) 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 \ data=data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/cat_dog \ name=exp1

model=yolov8n.pt是加载预训练权重,猫狗检测数据量不大,从预训练模型微调比从头训练收敛快得多。imgsz=640是输入分辨率,1000 张图用 640 足够,再大显存吃不消。batch=16是常见起点,显存不够就降到 8 或 4。lr0=0.01是初始学习率,微调时如果 loss 震荡厉害,可以降到 0.001。patience=20是早停耐心值,验证集 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 或大于 1,YOLO 计算 loss 时会产生异常梯度。

解决:在转换脚本里加边界裁剪,确保xmin >= 0、xmax <= img_w、ymin >= 0、ymax <= img_h。学习率从 0.01 降到 0.001 再试。

5.5 现象:验证集 mAP 波动很大,每次跑结果都不一样

原因:划分脚本没有固定随机种子,或者训练时shuffle没固定。数据量小的时候,划分方式对结果影响很大。

解决:划分脚本里加random.seed(42),训练命令里加seed=42。如果还是波动,考虑用 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 分钟写个脚本统计类别分布、图片尺寸分布、标注框宽高比分布。这三个分布一出来,基本就能判断这个数据集能不能直接训,还是需要先清洗。猫狗检测看起来简单,但标注框宽高比如果集中在极端值,比如全是细长条,那可能是标注员把猫尾巴单独框出来了,这种标签训出来的模型只会检测尾巴。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 18:18:20

BEM命名法深度实践:从组件拆分到工程化落地

很多人最开始接触 BEM 命名法&#xff0c;都是冲着“解决 CSS 命名冲突”去的&#xff0c;用了两年之后又会开始犹豫&#xff1a;为什么项目里照样乱&#xff1f;为什么照样有人写出 .card__header__title__text 这样的类名&#xff1f;我的经验是&#xff0c;BEM 被严重低估…

作者头像 李华
网站建设 2026/10/2 18:18:11

openclaw技能添加实战:从环境部署到Obsidian笔记

最近不少朋友在折腾本地部署的智能体网关&#xff0c;问到最多的问题就是“openclaw怎么添加技能”。网上教程五花八门&#xff0c;但大多数只讲了装完环境怎么跑起来&#xff0c;真正到“让代理学会一个新技能、能在对话里被调起来”这一步&#xff0c;很多人卡住了。我前前后…

作者头像 李华
网站建设 2026/10/2 18:16:44

微小型双足鸭形机器人:强化学习从仿真到实机部署全解析

微型双足机器人这两年最热闹的赛道&#xff0c;其实是往“小”里卷。大尺寸人形机器人有波士顿动力和几家头部厂商在前面顶着&#xff0c;硬件成本和运动控制门槛都极高&#xff1b;反倒是微小型双足鸭形机器人这种形态&#xff0c;整机质量可以压在几百克以内&#xff0c;关节…

作者头像 李华
网站建设 2026/10/2 18:15:44

口岸高峰那篇毕业论文,到底该让哪个 AI 帮你?[特殊字符]

边防管理专业的同学大概都懂一种“夹在中间”的写作状态&#xff1a;题目既要像法学&#xff0c;又要像公安学&#xff1b;既要讲法律法规&#xff0c;又要懂口岸运行、边检执法、边境治理和部门协同。 比如这篇毕业论文就很典型&#xff1a; 《陆路口岸高峰期大客流应急协同治…

作者头像 李华
网站建设 2026/10/2 18:14:43

Tello TT无人机+YOLOv5:目标识别追踪与单目测距实战

简介&#xff1a;这份资源面向K12阶段学生与AI入门教师&#xff0c;提供基于YOLOv5与大疆教育无人机Tello TT的目标识别、检测、追踪与测距完整方案&#xff0c;将深度学习理论落地到真实飞行场景&#xff0c;解决从模型训练到无人机部署的全流程问题。压缩包共1667个文件&…

作者头像 李华
网站建设 2026/10/2 18:10:15

从梦狐残神到任务暂缓,ABAP 如何让业务安静等待,并在合适的时候恢复

我们正在讨论的「梦狐残神」,有一个很适合拿来理解企业软件的细节。目标仍然存在,战斗仍然继续,但这个目标暂时不能正常行动。程序开发里也经常需要这样的处理,订单不能立刻释放,接口消息不能立刻重试,后台任务不能立刻再次调用故障中的外部服务。业务没有被删除,也没有…

作者头像 李华