
简介YOLO猫狗目标检测数据集面向目标检测初学者与算法工程师基于5000张真实猫狗场景图片制作使用标注软件逐框标注标注质量高、场景覆盖丰富既可用于课程实验也可用于实际项目验证适合YOLO系列模型的训练与评估。压缩包内共2000个文件其中1986个xml标注文件为主体另外包含6个html教程文档、5个txt数据集划分列表和3个py划分脚本整体约115.12MB目录结构清晰便于按需取用。标签按VOC、COCO、YOLO三种格式分目录存放无需转换即可接入常见检测框架划分脚本支持自定义生成训练集、验证集、测试集方便调整数据比例。随附教程覆盖Linux与Windows两种环境并提供可修改的训练案例能帮助新手从环境搭建一路跑通模型训练。目前已有535人学习下载适合作为算法练习、课程设计或小型项目的数据基础与流程参考。1. “图片有了标签也齐了”这个YOLO猫狗目标检测数据集包到底能帮你少走多少弯路很多入门目标检测的人第一反应是拿猫狗练手。但真做起来才发现光有“图片”远远不够标注格式要选、训练集验证集要拆、训练参数要看懂随便一步卡住一个下午就没了。这套“YOLO猫狗目标检测数据集含5000张图片 对应VOC、COCO和YOLO三种格式标签 划分脚本 训练教程”的压缩包实际上是一份从原始图片到训练完成的全链路材料5000张猫狗图片同一批标注同时给了三种格式能直接喂给主流检测框架划分脚本负责把数据拆成训练、验证、测试三份训练教程则帮你把ultralytics环境里的命令跑通。它适合三类人课程设计或毕设需要目标检测案例的学生、第一次接触YOLO标注格式的入门工程师以及想快速验证一套检测流程是否可行的爱好者。接下来我按“格式怎么读、数据怎么划、标签怎么转、模型怎么训”的顺序把每一步的细节和坑都摊开讲。2. 一套图片三套标注VOC、COCO和YOLO格式到底差在哪2.1 VOC格式一个文件夹就是一套标准PASCAL VOC格式是目标检测里最“古老”也最直观的格式。它的核心是一个目录套娃Annotations里放XML标注文件JPEGImages里放原始图片ImageSets/Main里放数据集划分清单里面是train.txt、val.txt这类纯文件名列表。每个XML文件对应一张图片文件里每出现一个object标签就代表图片里有一个目标bndbox给出目标的左上角和右下角坐标name是类别名比如cat或dog。VOC格式最大的好处是肉眼可读。用文本编辑器打开XML你能直接看到坐标值也能用LabelImg这类标注工具打开检查。很多标注工具默认导出的就是VOC格式所以它成了“标注侧”的事实标准。缺点也很明显一张图一个XML文件5000张图就是5000个文件传输和读取都啰嗦而且逐个解析XML的开销比直接读txt大不少。2.2 COCO格式一个大JSON装下所有信息COCO格式则走向另一个极端把整个数据集的标注全部塞进一个JSON文件里。顶层只有三个数组images记录每张图片的id、宽高、文件名annotations里每一条对应一个目标字段包含image_id、bbox、area、iscrowd等categories建立类别id和类别名的映射关系。这种格式的管理成本低一个文件就能做数据版本管理也能方便地用脚本统计类别分布。很多开源数据集和学术竞赛都用COCO格式发布数据。但代价是JSON嵌套深人眼几乎没法直接读手动修改容易出错解析也需要写一点JSON遍历的代码。你可能已经发现COCO格式更像“程序员的格式”而VOC是“标注员的格式”。2.3 YOLO格式归一化坐标训练时少算一步YOLO格式指Ultralytics YOLO使用的txt标签格式可能是工程上最省心的。每张图片对应一个同名txt文件每一行代表一个目标格式固定为class x_center y_center width height。关键是后面的四个数值全部做了归一化都是相对于图片宽高的比例取值范围在0到1之间。为什么YOLO要用归一化坐标因为模型训练时输入图片会resize到640×640或其他固定尺寸如果标签存的是原始像素坐标每次训练都要根据缩放比例重新换算。用归一化坐标不管图片原始分辨率是多少标签都不用变。train时省一步换算推理时输出坐标也按相同的归一化逻辑解码整套流程干净利落。2.4 三格式对比与转换关系维度VOC XMLCOCO JSONYOLO txt标注单位每个目标一个object每个目标一条annotation每个目标一行文本坐标形式左上角xmin/ymin 右下角xmax/ymax左上角x/y 宽度w 高度h中心点x/y 宽度w 高度h归一化文件组织每图一个XML整个数据集一个JSON每图一个txt肉眼可读性高低中训练读取效率低逐个解析中一次性加载高纯文本按行读取典型用途标注工具导出、小数据集竞赛/开源数据集发布训练框架默认输入这三种格式之间的换算关系是固定的数学关系左上角右下角可以通过x_center(xminxmax)/2变成中心点COCO的bbox用xw/2求中心x。这就是为什么这个数据包值得称赞——同一个数据集给三种格式意味着你无论用什么工具、什么框架都能找到对口的那一份不用自己动手解析别人家的标注。3. 数据划分脚本用一份代码把5000张图拆成训练集、验证集和测试集3.1 划分比例怎么定为什么不能拍脑袋常见做法是8:1:1或9:0.5:0.5即训练集占八成左右验证集和测试集各留一成。对小数据集5000张来说验证集和测试集各留10%已经足够评估模型剩下80%喂给训练能保证模型见过足够多的猫狗姿态。如果你打算后面做超参数调优验证集比例可以适当调到15%——但要记住测试集永远只能碰一次它代表“模型在真实环境的表现”拿它反复调参等于作弊。有个容易被忽视的点是类别均衡。这个数据集的猫和狗各2500张做全局随机洗牌后训练集里猫狗比例基本还是1:1。但如果你以后自己扩数据遇到某个类别只有200张另一个类别有4800张的情况随机洗牌可能导致训练集里小类样本少到学不出来。那种场景下就需要分层抽样按类别分别洗牌再按同一比例从每个类别里取。3.2 划分脚本实操固定随机种子结果可复现import os import random from glob import glob # 配置区 img_dir JPEGImages # 原始图片目录 train_ratio, val_ratio 0.8, 0.1 # 训练80%验证10%剩下10%给测试 seed 42 # 固定随机种子保证多次运行结果一致 output_dir ImageSets/Main # 输出目录VOC风格 # 1. 收集所有jpg图片排序保证可复现 images sorted(glob(os.path.join(img_dir, *.jpg))) random.seed(seed) random.shuffle(images) # 2. 按比例拆三段 n len(images) train_end int(n * train_ratio) val_end int(n * (train_ratio val_ratio)) train_split images[:train_end] val_split images[train_end:val_end] test_split images[val_end:] # 3. 写文件名清单不带目录和扩展名这是VOC的约定 os.makedirs(output_dir, exist_okTrue) for split_name, split_list in [(train, train_split), (val, val_split), (test, test_split)]: with open(os.path.join(output_dir, f{split_name}.txt), w) as f: for img_path in split_list: basename os.path.splitext(os.path.basename(img_path))[0] f.write(basename \n)这段代码的逻辑很简单先把所有图片路径收集起来洗牌然后按比例切片取三段。random.seed(42)这行很关键——没有它每次运行划分结果都不同你今天训练完明天复现不出同一个实验代码就失去了可复现性。glob的匹配模式是*.jpg如果你的数据集里有.jpeg或.png后缀的图片要换成*.jpg *.jpeg *.png否则会漏图导致后面的训练报“label not found”。划分结果写入ImageSets/Main目录VOC格式的划分清单就是这种不带目录不带扩展名的纯文件名列表。后面做VOC转YOLO时这个清单可以直接用来决定哪些图进训练目录、哪些进验证目录。3.3 划分完成后为什么要重排目录很多第一次接触ultralytics训练的人把VOC格式的三份txt一生成就想去训练结果发现YOLO的训练框架根本不读ImageSets/Main。Ultralytics默认约定的目录是images/train、images/val、labels/train、labels/val这种结构图片在images下对应的txt标签在labels下名字一一对应。所以拿到这个数据包后正确的姿势是用划分脚本生成的三份清单把图片按训练/验证/测试分组拷到images/train、images/val、images/test三个目录里。对应YOLO格式的txt标签也同步地拷到labels/train、labels/val、labels/test。这样你写data.yaml时只要把train指到images/train目录框架就能自动在labels/train里找到同名标签不需要额外配置。现实里很多人忽略的是“一一对应”这四个字。划分清单生成了但图片有5000张手动拷贝难免漏掉几张更常见的坑是图片拷贝过去了对应的txt因为文件名没对上而落在另一个目录。建议拷贝完成后执行一次循环检查遍历images/train里每个文件名确认labels/train里存在同名txt缺一个就补一个这一步能省掉后面训练时报“Image ... labels not found”的心力交瘁。4. 三种格式互相转换VOC转YOLO、COCO转YOLO的落地脚本与边界坑4.1 为什么多数项目最终还是得转成YOLO格式Ultralytics框架的detect训练默认读取YOLO格式的txt标签。你手上哪怕有VOC格式的XML也得先转成txt再喂给yolo train。而标注工具如LabelImg默认导出VOC格式所以“VOC转YOLO”是整个链路里最高频的转换。COCO转YOLO也不少见因为很多开源数据集发布时用的是COCO JSON。转换这件事本身不复杂但边界情况很多坐标越界、类别名对不上、一个XML里漏读了某个目标都会在训练时以各种诡异报错的形式还给你。我下面给出两个能直接用的脚本并把你必然会撞上的坑提前标出来。4.2 VOC转YOLO注意“左上右下”到“中心宽高”的换算import os import xml.etree.ElementTree as ET # 类别列表顺序就是YOLO标签的ID # cat固定为0dog固定为1顺序不能乱否则训练结果会和你的预期错位 class_names [cat, dog] def convert_voc_to_yolo(xml_path, out_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_names: continue # 容忍脏数据不在类别表里的直接跳过 cls_id class_names.index(name) box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 核心换算左上角右下角 - 中心点 宽高再归一化 x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) with open(out_path, w) as f: f.write(\n.join(lines))这段代码里最值得看的两处一是for obj in root.findall(object)它遍历的是所有object节点而不是只取第一个。很多新手写的转换脚本只处理了root.find(object)结果多目标图片里只有第一个框被转换——这在猫狗图里特别隐蔽因为有的图片确实只有一只猫训练时loss下降正常但mAP死活上不去。二是归一化的分母用的是XML里的size节点这个值必须和真实图片宽高一致。如果标注工具写错了size转换出来的坐标就会整体偏移。最稳妥的做法是转换后用PIL打开图片核一遍尺寸不一致就以实际图片为准。4.3 COCO转YOLO注意bbox是“左上角宽高”而非中心点import json import os def convert_coco_to_yolo(annotation_file, out_dir): with open(annotation_file, r, encodingutf-8) as f: coco json.load(f) # 先建立 image_id - 图片信息 的索引 img_info {img[id]: img for img in coco[images]} # 再建立 category_id - 类别名 的映射 cat_id_to_name {cat[id]: cat[name] for cat in coco[categories]} # 类别名 - 目标输出的YOLO类ID cat_name_to_target {cat: 0, dog: 1} # 按图片聚合所有标注方便一个图一个txt anns_by_image {} for ann in coco[annotations]: anns_by_image.setdefault(ann[image_id], []).append(ann) for image_id, anns in anns_by_image.items(): img img_info[image_id] img_w, img_h img[width], img[height] lines [] for ann in anns: name cat_id_to_name[ann[category_id]] if name not in cat_name_to_target: continue target_id cat_name_to_target[name] # COCO的bbox是 [x, y, width, height]起点是左上角 x, y, w, h ann[bbox] x_center (x w / 2.0) / img_w y_center (y h / 2.0) / img_h width w / img_w height h / img_h lines.append(f{target_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) img_filename img[file_name] txt_name os.path.splitext(img_filename)[0] .txt with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(lines))COCO转YOLO的代码结构和VOC版本几乎一样唯一的数学差异在于bbox的含义。VOC给的是xmin, ymin, xmax, ymaxCOCO给的是x, y, w, h所以求中心x要写成x w/2而不是(xmin xmax)/2。这个区别说大不大但写错之后所有框都会整体偏移半个框的宽度训练出来的模型预测位置全偏。还有两个隐藏问题要提醒第一COCO JSON里的image_id是数字不是字符串建立索引时保持int类型别在转换中无意转成str导致查不到键。第二COCO的categories里类别id往往不是从0开始连续编号比如cat的category_id可能是1dog可能是2中间还夹着别的类别——所以转换脚本一定要经过cat_id_to_name再映射到cat_name_to_target不能直接拿category_id当YOLO的class id。4.4 转换完成后必须做的三件事转换脚本跑完不要急着开训。花两分钟做三项检查能帮你挡掉百分之九十的无效训练第一校验坐标范围。写个几行的检查逻辑遍历所有txt确认每个值都在0到1之间特别要警惕width或height出现负值——这通常是XML里xmax小于xmin、或者COCO bbox宽度为负造成的。第二统计类别分布。打印每个类别的目标总数如果cat有5000个框而dog只有3000个说明原始标注里类别严重不平衡训练时得考虑调权重。第三可视化验证。这是最直观的一步用OpenCV随机挑10张图把txt里的坐标画框显示出来。框的位置如果明显错位那就是转换逻辑或size取错了框是对的再进训练流程。这三步做完你手上就是一套干干净净的YOLO数据集了。接下来进入训练环节。5. 训练教程与避坑指南从ultralytics环境配置到loss曲线解读5.1 ultralytics环境配置一台没有GPU的电脑也能跑吗训练目标检测模型常见的做法是用ultralytics这个库一条pip命令就能装好环境pip install ultralytics装完以后yolo命令就可用。它把所有子命令统一成yolo detect train、yolo detect val、yolo predict这种结构化形式对零基础用户非常友好。版本方面当前ultralytics主推的模型系列是yolo11训练时指定yolo11n.pt即可自动下载预训练权重。GPU不是硬性要求但CPU训练猫狗检测这种5000张图的规模一个epoch可能要跑十分钟以上全流程练下来十几个小时起步。如果你手头只有CPU我的建议是用Google Colab的免费GPU跑或者调低imgsz到416并减少epochs做快速验证确认流程通了再上完整训练。5.2 数据集yaml文件路径写不对训练第一步就翻车ultralytics用yaml文件来描述数据集位置内容很简单# cat_dog_dataset.yaml path: /Users/me/catdog_dataset # 数据集根目录换成你自己的 train: images/train val: images/val test: images/test names: 0: cat 1: dog这里的train和val是相对path的目录不是绝对路径。最容易踩的坑是path写了相对路径而训练命令又在别的目录下执行导致框架找不到图片。最稳妥的方案是直接写绝对路径一劳永逸。names里的id顺序必须和之前转换脚本里的class id一致cat是0、dog是1顺序反了模型也能训练但预测结果会把猫认成狗这种错误最隐蔽。5.3 最小训练命令与关键参数环境配好、yaml写好后用这条命令启动训练yolo detect train \ datacat_dog_dataset.yaml \ modelyolo11n.pt \ epochs100 \ batch16 \ imgsz640 \ device0逐个参数说清楚epochs100表示把整个训练集过100遍。5000张图的小数据集100个epoch足够看到收敛通常会跑到60到70轮时mAP上升变缓。batch16是每轮迭代喂给模型的图片数8G显存跑yolo11n配这个值比较稳妥显存小就降到8imgsz640是YOLO系列的标准输入尺度想提速可以降到416但精度会损失一点device0指定用第一块GPU纯CPU环境改成devicecpu。为什么要从yolo11n.pt预训练权重开始而不是随机初始化因为预训练权重已经在COCO数据集上学到了通用的底层视觉特征——边缘、纹理、形状——这些特征对猫狗检测同样有效。从预训练权重继续微调一般几十个epoch就能收敛随机初始化的话同样的数据量可能要多训两三倍的时间且精度未必追得上。这就是迁移学习的现实收益。损失函数那边yolo的loss由box loss框回归和cls loss分类组成训练日志里的cls_loss震荡下降是正常的别看到波动就慌。5.4 训练过程看什么loss曲线和mAP的判读训练启动后每轮epoch结束会打印一行指标box_loss、cls_loss、dfl_loss、precision、recall、mAP50、mAP50-95。新手最容易犯的错是死盯loss实际上更该看的是验证集mAP50——它代表“预测框和真实框的IoU超过0.5时算正确的平均精度”是一个更直观的模型能力指标。正常情况下训练集loss持续下降验证集mAP50稳步上升如果训练集loss还在降但验证集mAP50在一轮高点后转头向下那就是过拟合的早期信号。这时候要么增加数据增强要么把epochs调低用早停机制patience参数让框架在mAP不再上升时自动停掉。另外cls_loss如果长期不降还伴随震荡多半是类别样本顺序有问题——检查一下是不是一个batch里几乎全是猫另一个batch全是狗这种极端情况会让分类头的梯度来回拉扯。解决办法是把shuffleTrue打开ultralytics默认开或者手动把猫狗样本在数据上做一次交叉混排。5.5 避坑训练中高频撞上的5个坑坑1训练一启动就报错提示label文件为空或shape不匹配现象Assertion labels.shape (n, 5)或all labels are empty。原因转换脚本产出的txt文件是空的或者有坐标越界值在预处理时被过滤掉了。解决第一优先检查txt里是否真的写入了坐标特别要确认XML的findall(object)返回了节点第二检查归一化后坐标是否在0到1区间内越界的直接用第4.4节的校验脚本清一遍。坑2训练能跑但mAP卡在0.5上下怎么调都不涨现象loss在降mAP50却长期维持低位。原因八成是类别ID错位。比如VOC里的name是“cat”但你的class_names列表写的是[dog, cat]导致模型学到的猫被标记成了狗。解决随机取一张训练集中的图片打印txt内容和原图对比确认0号标签对应的是猫1号标签对应的是狗。坑3一张图里有三只猫模型只框出来一只现象训练时loss很正常但预测结果漏检严重尤其多目标图片。原因转换脚本只处理了root.find(object)只读到了第一个目标。解决换成第4.2节里root.findall(object)的写法遍历所有目标节点重新生成标签。坑4训练到中途显存爆掉CUDA out of memory现象RuntimeError: CUDA out of memory。原因batch16加imgsz640在8G显存上就是极限你的显卡算力不够。解决把batch降到8再不行降到4或者imgsz降到416。一个血泪经验是小模型配小batch跑通流程比大batch直接跑爆显存要高效得多。坑5训练完在验证集上mAP很高放到真实照片里一塌糊涂现象验证集指标漂亮但用自己手机拍的猫狗图预测框乱飞。原因训练集和验证集划分不当——如果数据源里有同一场景的连续帧随机划分会把“[场景A第1帧、第3帧”分到训练集第2帧分到验证集]验证集失去了代表性。解决按场景或按时间分组划分确保同一场景的图只出现在一个集合里。这也是第3章里为什么强调按文件名前缀分组做数据划分的原因。6. 训练完怎么验收用混淆矩阵、mAP和导出的ONNX模型堵住“模型好像能跑”的嘴训练结束后代码包里最重的活已经干完了但“训练完”不等于“能用”。我一般会再做三步验收确认这个模型不是训练日志里的纸面指标。第一步跑一次正式的验证命令拿到完整指标报告yolo detect val modelruns/detect/train/weights/best.pt datacat_dog_dataset.yaml命令跑完会生成一个runs/detect/val目录里面有confusion_matrix.png混淆矩阵图和results.csv。混淆矩阵里最值得看的是对角线如果猫那一行的预测主要落在“猫”列说明模型真的学到了猫的特征而不是靠背景作弊。如果猫狗两类的混淆矩阵对角线都超过0.85这个模型的基本盘就稳了。同时看results.csv里最后几行的mAP50-95这个指标比mAP50更严格对框的精准度更敏感——猫狗检测这种任务mAP50-95能到0.7以上就算可用。第二步把模型导出成ONNX并做一次真刀真枪的推理yolo export modelruns/detect/train/weights/best.pt formatonnx yolo predict modelruns/detect/train/weights/best.pt sourcemy_cat_test.jpg导出ONNX的意义在于它不依赖ultralytics这个训练框架可以直接被ONNX Runtime或TensorRT加载是部署到服务端或边缘设备的通用格式。预测时用一张训练时完全没见过的图最好是你自己拍的、光线和背景都和训练集不太一样的照片。如果这张图都能框得准说明模型具备一定的泛化能力而不是把训练集背下来了。第三步养成一个习惯每次跑完转换和训练随机抽样本图看一眼预测框。我自己最开始做这个方向的时候偷懒省掉了可视化检查结果训练出来在手机照片上识别率惨不忍睹后来发现是划分脚本没有按场景分层把同一个来源的图全放进了训练集。从那以后我每次跑完一个数据集都先抽10张图看一眼框再决定要不要往下走。这个习惯帮我挡掉了至少三次无效训练希望也能帮到你少走一段弯路。本文还有配套的精品资源点击获取