ARTICLE DETAIL

资讯详情

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

从零自建YOLO猫狗检测数据集:标注、格式转换与训练实践

从零自建YOLO猫狗检测数据集:标注、格式转换与训练实践 做目标检测这些年我最常被问的一个问题就是“我该去哪里搞一份干净的数据集”说实话公开数据集不是没有但要么太大几百 GB 下到怀疑人生要么标注质量参差不齐背景、尺寸、类别定义都和你实际场景对不上。所以我干脆动手自建了一份猫狗检测数据集4300 张图片YOLO 格式专门用于宠物识别和目标检测训练。这篇文章我会把数据集的构建思路、标注细节、格式转换、训练调参和踩坑记录完整写出来希望能帮你省下几个月的弯路。这份数据集适合的人群其实挺广刚入门目标检测、想跑通 YOLO 全流程的初学者做宠物智能硬件自动喂食器、宠物门禁的开发者还有想在边缘设备上做轻量级宠物识别的小伙伴都能直接拿去用。4300 张图不算大但配合 YOLOv8 这类模型在消费级显卡上很快就能出结果性价比很高。我没有用现成公开集而是从数据源收集、清洗、标注、格式转换一路自己做下来过程中积累的经验比数据集本身还值钱。1. 项目概述为什么需要一份自己的猫狗数据集1.1 公开数据集解决不了的痛点市面上的猫狗数据集不是没有著名的 Oxford Pets、CIFAR-10 里的猫狗子集、甚至 Kaggle 上的 Dogs vs Cats都是大家经常用的。但真拿来做 YOLO 检测训练时问题就来了。第一个问题是格式。很多经典数据集是分类格式只告诉你这张图里是猫还是狗但目标检测需要的是“目标在哪里”也就是边界框坐标。没有框标注的数据必须自己重新标一遍这等于前面的工作全白费。第二个问题是场景偏差。Kaggle 那套猫狗分类数据大部分是居中、单只、清晰的大图和你实际要检测的“院子里两只猫一只狗、树荫下有遮挡、光线忽明忽暗”完全是两码事。训练出来模型在实验室指标上很好看一到真实现场就露馅。第三个问题是版权和许可有些数据集只允许学术研究商用的话心里不踏实。所以自建数据集的核心动机就是三个字可控性。图片来源可控、标注标准可控、类别定义可控后续不管做检测还是扩展成分割、追踪底子都是自己的。1.2 4300 张这个规模有什么讲究你可能觉得 4300 张对深度学习来说是不是太少了但检测任务和分类任务不一样一张图里往往有多只目标、多个角度实际提供的监督信号远大于图片数量本身。4300 张图如果平均每张出现 1.5 到 2 只宠物那目标实例大约在 6500 到 8500 个之间训练 YOLOv8s 或者 YOLOv8m 这个级别的模型是够用的。另一个现实约束是标注成本。我当时一个人操作用标注工具画框一张图快的话 10 秒复杂场景 40 秒4300 张图累计下来至少 30 到 40 个小时的纯手工劳动。这个规模刚好是“一个人能扛下来”的上限再翻一倍就容易标到崩溃标注质量也会断崖式下降。所以 4300 张不是拍脑袋定的是在训练效果和人力成本之间取的一个平衡点。2. 数据集构建的思路与选型2.1 数据从哪里来多源采集与清洗数据采集我用了三个渠道公开免费图库、网络爬虫、自己拍的补充图。公开图库的好处是授权清晰商用风险低但缺点是很多图审美在线、场景单一猫永远蹲在沙发上一脸岁月静好缺少实战场景。网络爬虫能补充大量日常生活场景的图比如街上的流浪猫、宠物店里笼子里的狗、公园里奔跑的宠物但噪声很大需要花时间清洗。自己拍的照片虽然数量少但能填补前两者覆盖不到的角度和光照条件。清洗这一步我踩过最大的坑就是“重复图”和“近似图”。很多人喜欢把同一只猫的不同角度照片都收进来但同一个体、同一个背景的图如果占比太高模型会偷懒学到的是“这个背景猫”而不是“猫的纹理和轮廓”。所以我用感知哈希算法跑了一遍去重再把剩余图片人工翻一遍剔除模糊、严重过曝、目标占比过小远到几乎看不清的图。实际经验是宁缺毋滥清洗掉 10% 的低质量图对最终精度的提升可能比多塞 20% 的噪声图还明显。2.2 类别定义两个类别还是更细很多初学者一上来就直接定义 cat 和 dog 两个类别这是最省事的做法但不是最合理的。想一想你的实际应用场景如果是做一个“宠物门禁”只要区分猫和狗就够了但如果是做个“宠物相册自动分类”那用户可能希望系统能区分橘猫、布偶、金毛、柯基。类别粒度的选择会直接决定标注成本、训练难度和最终体验所以我最终的方案是先用 cat/dog 两个大类跑通整个流程同时把图片文件按品种信息挂了标签后续想细粒度扩展时可以直接基于这批数据二次标注。这里有一个很重要的经验初始数据集不要一上来就追求极致的类别细分。先跑通两个类别的完整训练、验证、部署流程再逐步增加类别这是最稳健的路线。我见过太多人第一天就想做“20 种宠物品种识别”结果数据收集和标注环节就耗了三个月项目直接烂尾。2.3 YOLO 格式为什么是首选数据集的最终格式我选了 YOLO 格式原因很直接YOLO 系列是目前目标检测领域生态最成熟、部署链路最完整的方案而 YOLO 的官方训练脚本默认接收的标注格式就是“每张图片对应一个同名 txt 文件”。相比 COCO 的 JSON 格式YOLO 格式更轻量改起来也直观相比 VOC 的 XML 格式YOLO 格式少了很多嵌套结构磁盘占用也更小。更关键的一点是YOLO 格式的 txt 文件可以直接配合 ultralytics 框架使用几乎零修改就能开训。当然YOLO 格式也有它的短板最明显的是它不支持多边形分割标注只支持矩形框。如果你的项目后续要做实例分割需要矩形框之外的多边形信息那就要在数据结构层面提前留好扩展余地我当时的做法是把标注工具的原始导出文件含多边形点坐标也一并保留后续做分割时不用重新标一遍。3. 数据标注与 YOLO 格式转换实操3.1 标注工具从 LabelImg 到 X-AnyLabeling标注工具的选择直接影响效率。最经典的 LabelImg 我已经用了很多年纯 Python 写的老牌工具支持 VOC 和 YOLO 格式直接导出操作逻辑简单只要设置好类别先按 W 键画框、按 D 键下一张就行。但如果标注量上千张LabelImg 的效率就有点不够看了没有自动保存快捷键配置也略显朴素。我这次的 4300 张图前几百张用的 LabelImg之后换成了新一代的 X-AnyLabeling体验提升非常明显。X-AnyLabeling 集成了很多预训练的检测分割模型可以先用模型自动预标注然后人工只做修正工作。实测下来对于画面干净、目标明显的图自动标注准确率大概有 70% 到 80%人工需要做的只是拖动边框角点微调工作量直接从“画框”变成了“改框”整体省了至少三分之一时间。它的导出格式也丰富COCO、VOC、YOLO 都支持适合在一套工具里完成从标注到转换的全流程。3.2 YOLO 标注格式的核心逻辑YOLO 格式本质上就是每张图片一个 txt 文件文件里每一行代表一个目标。这行一共有五个数字类别 ID、归一化的中心点 x 坐标、归一化的中心点 y 坐标、归一化的宽度、归一化的高度。所有坐标值都是相对于图片宽高的比例范围在 0 到 1 之间这样不管训练时输入分辨率是 640 还是 1280标注信息都能无损缩放。举个例子一张 1920x1080 的图上有一只猫边界框左上角坐标是 (960, 270)右下角是 (1440, 810)。换算公式很简单# 原始像素坐标转 YOLO 格式 box_width 1440 - 960 # 480 box_height 810 - 270 # 540 x_center 960 box_width / 2 # 1200 y_center 270 box_height / 2 # 540 # 归一化 x_center_norm x_center / 1920 # 0.625 y_center_norm y_center / 1080 # 0.5 width_norm box_width / 1920 # 0.25 height_norm box_height / 1080 # 0.5 # 对应的 txt 行内容 line f0 {x_center_norm:.6f} {y_center_norm:.6f} {width_norm:.6f} {height_norm:.6f} # 0 0.625000 0.500000 0.250000 0.500000这里最关键的注意点是坐标必须归一化到 0-1 之间并且在标签里目标框需要用中心坐标加宽高表示不是左上角右下角坐标。很多初学者第一次手动写标签文件时经常在这个环节搞混导致训练时 loss 巨大、模型完全不收敛。建议不要手工写用标注工具导出或者写脚本转换。3.3 标注质量控制双人复核与边界规则标注这活最怕的不是慢而是不一致。同一个人在不同时间段的标注风格都会漂移上午画框喜欢紧贴毛茸茸的身体下午可能就多留了一圈背景遇到猫尾巴翘起来的时候有人把尾巴包进去有人把尾巴切掉。这些不一致会直接导致训练时边界框回归的方差变大。我的做法是强行定了一套标注规则然后二轮复核。规则很简单边界框必须紧贴目标可见部分的外轮廓猫狗的耳朵可以包进去尾巴如果和身体相连就包进去完全分离的尾巴不看目标被遮挡超过 50% 就不标目标小于图片短边 5% 的不标。二轮复核就是把所有已标注图片重新过一遍重点看边界是否飘、类别是否标错。这个环节我花了两天时间但对最终模型的框回归精度贡献巨大。3.4 多格式标注一键转换脚本如果你手里已经有 COCO 或者 VOC 格式的标注数据想转成 YOLO 格式没必要重新标注。我写了一个 Python 脚本从 COCO JSON 转 YOLO txt核心逻辑如下import json import os def coco_to_yolo(coco_json_path, output_dir, img_dir): with open(coco_json_path, r) as f: coco json.load(f) # 构建图片 ID 到文件名的映射 img_id_to_name {} for img in coco[images]: img_id_to_name[img[id]] img[file_name] # 类别映射COCO 的类别 id 不是连续的一定要转成从 0 开始的类别索引 cat_id_to_index {} for idx, cat in enumerate(coco[categories]): cat_id_to_index[cat[id]] idx # 按图片分组所有标注框 annotations_by_img {} for ann in coco[annotations]: img_id ann[image_id] annotations_by_img.setdefault(img_id, []).append(ann) # 写入 YOLO 格式 for img_id, anns in annotations_by_img.items(): img_name img_id_to_name[img_id] txt_path os.path.join(output_dir, img_name.replace(.jpg, .txt)) with open(txt_path, w) as f: for ann in anns: # COCO 的 bbox 格式是 [x, y, width, height] x, y, w, h ann[bbox] img next(i for i in coco[images] if i[id] img_id) img_w, img_h img[width], img[height] x_center (x w / 2) / img_w y_center (y h / 2) / img_h width_norm w / img_w height_norm h / img_h cat_index cat_id_to_index[ann[category_id]] f.write(f{cat_index} {x_center:.6f} {y_center:.6f} {width_norm:.6f} {height_norm:.6f}\n)这段代码有个关键细节COCO 的类别 ID 经常是不从 0 开始且不连续的比如类别 ID 是 1 和 3但 YOLO 的类别索引必须从 0 开始。所以类别映射那一步一定要重新编号否则训练出来的模型类别全错位。我最初就吃过这个亏模型跑了 100 轮精度看着还行一测试发现把狗都识别成了猫检查半天才发现是类别索引没对齐。4. 数据划分、清洗与增强策略4.1 训练验证测试集怎么分划分数据集最忌讳的做法是随机洗牌后直接按比例切分因为来源不同的图片在光照、场景、画质上差异很大随机划分很可能把同一个场景、同一个个体的图片同时塞进训练集和验证集让验证指标虚高。我按来源分组再随机切分保证训练集和验证集的图片没有来自同一批相似场景的。比例上我用了 8:1:1训练集 3440 张、验证集 430 张、测试集 430 张。训练集负责学特征验证集用于调整超参数和早停测试集最后模型训完才碰一次用来评估泛化精度。市面上有些教学案例会把验证集和测试集混为一谈这会导致你对模型真实水平的判断过于乐观。少 400 张训练图对精度影响很小但能让你对模型的实际表现有一个不带水分的认知。4.2 类别平衡猫和狗数量要大体均衡我的数据源里狗的图片明显多于猫如果不做处理训练出来后模型会偏向把模糊目标判成狗。类别不平衡的目标检测数据集中常见的处理方式有欠采样、过采样和调整损失权重。我这次没那么麻烦直接在收集阶段做二次补充专门补拍和爬取了一批猫的图片最终把类别比例控制在猫 51%、狗 49% 左右。但要注意这里说的平衡是“目标实例数”的平衡不是“图片数”的平衡。因为一张图里可能有三只猫两只狗只按图片数来平衡是不准的。我当时写了个小脚本统计每张图中的目标数最后发现猫的实例数略微偏少又手工删掉了一部分狗多的图片这才真正均衡下来。统计目标实例数量这个动作非常关键建议每个做数据集的人都不要跳过。4.3 数据增强哪些有效哪些是心理安慰YOLOv8 自带了一套训练时的在线增强包括马赛克增强 Mosaic、HSV 色彩空间扰动、随机平移、随机缩放、随机翻转等。Mosaic 是 YOLO 系列训练中最重要的增强手段把四张图拼在一起模型被迫在小尺寸上学习目标特征对小目标和遮挡情况有奇效。但 Mosaic 不是越强越好训练后期如果一直开启模型可能会对高难度样本过拟合所以 ultralytics 提供了 close_mosaic 参数我一般最后 10 个 epoch 关掉它。HSV 扰动也是我强烈推荐开启的尤其对猫狗这类毛色差异极大的目标。通过对色调、饱和度、亮度的轻微随机扰动模型能学到更鲁棒的色彩特征不至于一换环境光线就找不到猫。实测下来只开 Mosaic 和 HSV 扰动相比不做增强mAP50 能提升 4 到 6 个百分点这个收益几乎是白捡的。5. 用 YOLOv8 训练自己的数据集5.1 环境准备ultralytics 一条龙训练我用的 YOLOv8直接通过 ultralytics 这个库就能跑通。它的优点是把数据加载、模型定义、训练流程、指标可视化全部封装好了你只需要准备好数据集和配置文件一行命令就能开训。安装也简单pip install ultralytics如果要用 GPU 训练CUDA 环境必须先装好。我个人建议用 PyTorch 官方推荐的安装方式装 GPU 版因为 pip 默认安装的 PyTorch 很可能是 CPU 版本装完 GPU 完全没用上。你可以在 Python 里跑一下import torch print(torch.cuda.is_available()) # 必须输出 True如果输出 False大概率是 PyTorch 版本和 CUDA 版本不匹配不要硬着头皮训练先解决环境问题。5.2 数据集配置文件怎么写YOLOv8 训练时需要一份 data.yaml 文件用来告诉训练器数据在哪、类别有哪些。我的配置长这样path: /path/to/cat_dog_dataset # 数据集根目录 train: images/train # 训练集图片目录 val: images/val # 验证集图片目录 test: images/test # 测试集图片目录可选 nc: 2 # 类别数量 names: [cat, dog] # 类别名称顺序必须和标注里的类别 ID 对应这里最关键的注意点names 的顺序必须和你标注文件里的类别 ID 一一对应ID 0 对应 names 里第一个ID 1 对应第二个。如果你前面标注时定义的是 cat0, dog1配置文件里就不能写成 names: [dog, cat]否则训练过程完全错乱。这个错误极其隐蔽因为训练 loss 会下降、精确率又正常只有推理时才会发现类别标签完全对不上。同时还要确认图片目录的各级文件夹是平级分离的train/val/test 每套都有独立的 images 目录标注文件必须和图片同名同目录YOLO 训练脚本会在图片目录下自动寻找同名 txt 文件不需要单独指定 label 路径前提是 txt 文件就在 images 同级目录的 labels 文件夹下。实际目录结构大概是cat_dog_dataset/ ├── data.yaml ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/5.3 关键训练参数每个都值得理解训练命令很简单yolo detect train datadata.yaml modelyolov8s.pt epochs100 imgsz640 batch16但参数背后的意义值得展开说。imgsz640 是平衡精度和显存的默认选择对小目标多的场景可以抬到 1280但训练显存占用会翻三到四倍推理速度也会明显下降。batch 的大小直接决定一步看到多少样本显存紧张时从 16 降到 8 也问题不大但注意过小的 batch 可能让 BN 层统计不稳定。epochs 我建议至少 100搭配早停参数 patience20也就是 20 轮指标不提升就自动停省时间。学习率方面ultralytics 默认的 lr00.01 对大多数情况都适用。如果你用的是预训练权重比如 yolov8s.pt原本的权重已经有了很好的特征提取能力训练其实只需要微调检测头部分。我第一次训练时心急直接用了随机初始化权重从头训结果 100 轮下来 mAP50 才 0.7换上预训练权重之后同参数下能到 0.9 以上。除非你有充分的理由否则就用预训练权重不要从零开始。5.4 训练过程中的损失函数怎么盯YOLOv8 的损失函数由三部分组成边界框回归损失、分类损失、分布焦点损失。这三部分加权相加构成总 loss。很多初学者一看到训练日志里一堆 loss 就懵其实核心看两个趋势即可。第一个看总 loss 是否持续下降且尾部趋于平缓如果出现下降一段时间后突然反弹升高大概率是学习率过大或者数据里混入了有问题的标注。第二个看验证集 loss 与训练集 loss 的差距如果验证 loss 一路下降但在后半程明显回升而训练 loss 还在降这就是典型的过拟合信号应该早停或者加大增强。我自己训练时每次都会开一张 loss 曲线图保存训练结束后的 results.png曲线比任何数字都直观。训练完成后最重要的指标不是训练 loss而是验证集上的 mAP50 和 mAP50-95。mAP50 表示 IoU 阈值 0.5 时的平均精确率数值相对偏高大家习惯用它做快速参考mAP50-95 是从 0.5 到 0.95 每隔 0.05 取一次 IoU 阈值的平均更严格能体现框的定位精度。我最终的模型 mAP50 到了 0.93mAP50-95 在 0.75 左右定位精度还有提升空间主要瓶颈在于部分遮挡场景的标注框本身就很难画准属于数据空间的物理上限。6. 常见问题与排查技巧实录6.1 训练不收敛或 loss 巨大怎么查最典型的症状loss 起步很大训了几十轮也不下降或者下降一点点就卡死。遇到这种情况优先排查三类问题。第一类是标签格式错误最常见的是坐标没有归一化或者类别 ID 超范围打开几个 txt 文件人工看一下就能发现问题。第二类是数据路径错误训练脚本读不到标注文件模型在空白标签下训练等于一直在学“图里没目标”loss 低但检测结果为空。第三类是学习率设置不当把 lr0 调小一个数量级重试。另外还有一个小众但致命的问题图片通道数异常。有些爬虫抓下来的图带有 PNG 的透明通道或者灰度图只有单通道YOLO 训练时默认期望三通道 RGB 图如果混入这些图训练脚本可能报错或者 silently 出错。我给所有图片跑了一遍统一转换脚本全部转成 RGB 三通道 JPEG从源头杜绝了这个隐患。6.2 漏检和误检的常见原因模型训练完部署到实际场景最常见的抱怨就是“漏检了”“误检了”。漏检优先看目标尺寸如果远距离小目标经常漏掉可以考虑把训练输入分辨率调高到 1280或者使用更大的模型yolov8m、yolov8l。误检则通常出在相似背景干扰上比如狗纹路的皮包、猫形状的毛绒玩具这种情况要针对性搜集一批“难负样本”加入训练集让模型见过这些容易混淆的东西。还有一个很多老手都会踩的坑训练时的增强策略和实际使用环境差距太大。如果你数据集里大量图片是高清、光线充足、猫狗居中的但部署场景是夜间监控摄像头、视角是俯视那模型表现不好是必然的。我补数据时就专门在黄昏、阴天场景下拍了一批照片后又加了一些带轻微运动模糊的图让模型对“不太完美的画面”也见过世面。数据永远比模型更值得投入这个理念我反复强调是因为它真的能救你于水火。6.3 显存不足怎么办显存不足几乎是每个人训练时都遇到过的事。我的经验是按这个顺序来先降 batch把 16 降到 8、4看显存占用是否降下来然后降 imgsz从 640 降到 512最后考虑换更小的模型比如从 YOLOv8m 换回 YOLOv8s。还有一个几乎免费的技巧是开启梯度累积把 batch 设为 4 但累积 4 步等效于 batch16 的效果对训练稳定性很友好。如果显存还是很紧张也可以尝试在训练命令里加上cacheFalse参数让数据不提前缓存进显存。这个参数默认情况下可能会省下大量显存代价是每个 epoch 读盘多几次但训练速度的影响并没有想象中那么大。6.4 框不准、框太大太小怎么治框回归不准首先要看标注本身的质量。我之前提过的标注不一致问题如果多人标注或者同一人不按规则来框的边缘就容易忽大忽小。遇到框普遍偏大或者偏小的情况可以统计一下所有标注框的宽高比分布和面积分布看看是否和真实目标占比一致。如果发现大面积框比如画面 80% 都是猫特别多模型可能对“大目标”有偏置应适当删除一部分极端尺寸的样本让尺寸分布更均匀。针对小目标检测精度偏低的问题还可以考虑给训练集增加一些人工放大框周围区域的裁剪样本。我试过一个简单但有效的操作把包含小目标的图片局部裁剪出来作为独立样本加入训练集相当于让小目标在模型看来变成大目标模型对小目标的表达能力会有明显改善。7. 后续扩展从检测到更完整的宠物识别方案数据集做完了模型也能稳定跑出框了但我的经验是这件事还远没有结束。检测只是第一步很多实际场景要的是更多。你可以在标注数据基础上继续做实例分割把猫狗的轮廓从矩形框精化到像素级掩码这对需要抠图、替换背景的产品形态很有价值。也可以加进目标追踪模块比如用 ByteTrack 或者 BoT-SORT 对检测结果做跨帧关联实现宠物活动轨迹统计分析。另一个更实际的扩展方向是模型轻量化部署。YOLOv8s 在 GPU 上很快但到了树莓派或手机端就不够看了。我尝试过把训练好的权重导出为 ONNX再用 ONNX Runtime 推理速度提升不小进一步量化成 INT8 之后模型体积可以压缩到原来的四分之一左右端侧跑 30 毫秒以内的延迟没有太大压力。对这些轻量化部署的细节我后面打算单独写一篇实操记录这里先点到为止。最后说一个我在整个项目中感受最深的事数据和标注所花费的时间精力远超模型训练本身。很多人以为做 AI 项目最重要的是调模型、改网络结构但实际上决定一个项目上限的往往是数据基础是否扎实、标注是否一致、验证是否严谨。4300 张猫狗数据集不算大但当你亲手完成从图片收集到模型部署的全流程后你对目标检测的理解会发生质变——不再把模型当成黑盒而是清楚地知道每一张图、每一个框、每一个参数在模型行为里分别扮演了什么角色。这份“知其所以然”的底气才是做数据集这件事最值得的收获。
返回列表