ARTICLE DETAIL

资讯详情

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

从数据集到部署:YOLO宠物检测实战指南

从数据集到部署:YOLO宠物检测实战指南 1. 这份宠物识别数据集4300张图是怎么凑齐的搞过宠物识别项目的朋友应该都体会过数据集往往是最先卡住你的地方。要么图片量不够要么背景太单一要么标注格式乱七八糟。这次我整理并清洗了一套猫狗检测数据集总共4300张图片专门用来做YOLO系列模型的训练与验证。我按照训练、验证、测试三个子集做了划分把每张图里猫和狗的位置都标成了矩形框格式统一成YOLO训练直接能用的txt标签文件。你拿回去之后不需要再做繁琐的格式转换改一下data.yaml里的路径就能开始跑。先说结论这套数据集的定位不是“从零训出一个亿级参数的检测大模型”而是面向宠物识别、家庭摄像头、喂食器、宠物门禁这类落地场景用来在预训练权重基础上做微调。4300张图看起来不算多但配合COCO预训练权重完全够用。我前后训练过三轮用YOLOv8n作为baseline在测试集上mAP50能做到0.94左右。下面我会把数据集的构成、标注细节、训练踩坑和部署注意点都拆开讲清楚希望能帮你少走点弯路。1.1 图片来源与筛选标准数据集里的图一部分来自公开的图片搜索和开放协议图库另一部分来自我从网上收集的无版权或可商用图片最后统一做了一个非常大的清洗动作。清洗这一步很多人会跳过但我劝你不要省。原始素材里大概有接近两成的图片不能用主要集中在以下几类分辨率太低物体面积小于20x20像素这种图训练时对loss的贡献几乎可以忽略还容易干扰模型对目标的感知。图片带严重水印或时间戳模型会把水印纹理当成一种特征部署时一旦遇到干净画面反而误检。宠物只露了极小的一个角或者被遮挡得只剩耳朵、尾巴这种只有主观依据、没有明确边界的图标注一致性很难保证。多只宠物叠在一起且相互遮挡严重如果标注框过度重叠会给正样本分配带来麻烦。筛选之后我再按场景做了一层均衡。你会发现纯白色背景的“商品图”我刻意控制比例因为真实监控摄像头拍到的画面通常是室内自然环境有沙发、地毯、玩具、门框这些干扰物。如果训练数据全是干净的白底图模型会在部署时把深色沙发误判成猫把玩具狗误判成狗。我自己实测过把背景多样性从30%提高到70%测试集mAP50大概提升了3个百分点误检率下降更明显。1.2 类别分布与划分比例这套数据集的基本构成如下表数据子集图片数猫标注框数狗标注框数平均每张框数train3650162024801.12val4001752651.10test2501181621.12合计4300191329071.12为什么猫狗的框数不按1比1对半分因为真实部署场景里狗的体型差异更大有吉娃娃也有金毛需要更多样本把“体型”这个维度撑起来。而猫的体型相对集中动作姿态反而更丰富所以标注的时候我重点保留了“趴着、伸懒腰、回头、钻纸箱”这类姿态而不是单纯追求数量相等。训练、验证、测试的比例定在8:2:0.5左右。有朋友问我为什么不干脆9:1。我的习惯是测试集单独拿出来不参与任何调参。因为验证集会用来做早停和模型选择反复看过之后它也会被“污染”。真正的泛化能力只有在没碰过的测试集上测才有意义。250张测试图不算多但胜在场景覆盖到位已经能反映大致水平了。如果你想把数据集用在自己的项目里我建议拿到先别急着训自己抽20张看一眼标注框和原图是否匹配。标注质量是训练的上限。后面我会专门讲怎么快速检查标签。2. 标注环节VOC转YOLO格式的那些细节这个数据集最开始的原始标注文件并不是YOLO格式而是从多个地方聚合来的。有的是VOC的XML有的是简单的坐标CSV还有一小部分是框选之后导出的JSON。为了保证训练链路干净我把所有标注统一成了YOLO格式。这一步看起来简单实际坑特别多。2.1 目录结构与标签映射最终目录结构长这样dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── data.yaml └── README.mdimages和labels的子目录一一对应图片名和标签名也必须完全一致只是后缀不同。例如dog_00123.jpg对应dog_00123.txt。每张图片如果没有标注框那就不要生成对应的txt文件或者在txt里留空。我这里统一采用了“有目标才建文件”的做法因为训练脚本在找label时如果发现图片没有对应txt会自动视为背景图这对负样本有帮助。标签文件每一行表示一个目标格式是class_id x_center y_center width height注意这里全部是归一化到0到1之间的比例值不是像素绝对值。x_center和y_center是框中心点的相对坐标width和height是框的相对宽高。很多新手第一次转格式容易把像素坐标直接填进去训练出来的loss看着正常但推理结果框的位置全偏了。2.2 一个稳妥的转换脚本如果你手里的原始数据是VOC格式可以用下面这版Python脚本转换。我写的时候特意加了几层防御防止除零和越界import os import glob import xml.etree.ElementTree as ET CLASSES [cat, dog] # 这个顺序就是最终的类别ID顺序必须全局统一 def voc_to_yolo(xml_path, out_path): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) if img_w 0 or img_h 0: return False lines [] for obj in root.findall(object): name obj.find(name).text.strip().lower() if name not in CLASSES: continue cls_id CLASSES.index(name) box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) x1 max(0, min(x1, img_w)) y1 max(0, min(y1, img_h)) x2 max(0, min(x2, img_w)) y2 max(0, min(y2, img_h)) if x2 x1 or y2 y1: continue x_center (x1 x2) / 2.0 / img_w y_center (y1 y2) / 2.0 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) if len(lines) 0: return False with open(out_path, w, encodingutf-8) as f: f.write(\n.join(lines)) return True xml_files glob.glob(xmls/*.xml) for xml_path in xml_files: out_path os.path.join(labels, os.path.splitext(os.path.basename(xml_path))[0] .txt) voc_to_yolo(xml_path, out_path)这段脚本的核心思路是先读宽高再把像素坐标归一化。里面有两个容易被忽略的点一是类别顺序。如果不同批数据里CLASSES顺序不一样那生成的类别ID会错位比如把猫当成狗。处理多来源数据之前先统一个全局类别清单不要偷懒。二是越界裁剪。偶尔会有标注框超出图片边缘如果不处理训练时可能生成极端宽高比影响锚框匹配。2.3 标注质量检查的土办法转换完标签之后我强烈建议做一次可视化检查。每次为了省事跳过检查后面处理badcase都要花双倍时间。最直观的方式是写脚本把标注框画回图上我习惯用OpenCV做这事import cv2 img cv2.imread(dog_00123.jpg) with open(dog_00123.txt) as f: for line in f: cls_id, xc, yc, w, h line.strip().split() cls_id int(cls_id) xc, yc, w, h map(float, (xc, yc, w, h)) x1 int((xc - w / 2) * img.shape[1]) y1 int((yc - h / 2) * img.shape[0]) x2 int((xc w / 2) * img.shape[1]) y2 int((yc h / 2) * img.shape[0]) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(check.jpg, img)检查不是让你一张张看过去那会看瞎。正确做法是随机抽50张重点看那些“小目标”和“多目标重叠”的图。如果抽查里出现超过5%的框位置偏移那说明整个批次的标注质量存疑最好重新过一遍。3. 训练前的准备数据划分、超参数和Baseline选择数据集准备好之后不要急着甩命令。YOLO训练这个环节参数调得好不好直接决定你后面是省心还是天天盯着loss发呆。我的经验是先用默认参数跑通一个最简版本再考虑优化。3.1 data.yaml怎么写YOLOv8和YOLOv5都认data.yaml内容如下path: /绝对路径/dataset train: images/train val: images/val test: images/test nc: 2 names: 0: cat 1: dog有一个细节容易被忽略label文件里写的是类别IDdata.yaml里names顺序必须和ID一致。假如你之前转换脚本CLASSES是[dog, cat]但这里names从cat开始训练出来的模型狗猫就反了。我见过不少朋友最后推理时发现“猫的框信誓旦旦打在狗身上”排查半天才发现是names顺序错了。另外path建议写绝对路径不要写相对路径。因为YOLO脚本在某些版本里会基于当前工作目录拼接相对路径一旦你在不同目录下启动训练路径就会找不到报错信息还不直观。3.2 初始权重与模型规模选择模型规模的选择我最建议从YOLOv8n开始。这是目前性价比很高的起点体积小、训练快、推理快而且从COCO预训练权重微调猫狗这类二分类任务并不需要特别深的模型。你拿4300张图去训一个YOLOv8x大概率会过拟合而且显存和训练时间成倍增长。训练命令可以这样写yolo detect train datadata.yaml modelyolov8n.pt epochs100 imgsz640 batch16 device0如果你更熟悉YOLOv5也可以用python train.py --data data.yaml --weights yolov5s.pt --img 640 --epochs 100 --batch 16我在实际对比里发现YOLOv8n和YOLOv5s在这个数据集上mAP差距不大但YOLOv8n的推理速度更快部署时省心。如果你想从原理上理解YOLO最近几个版本的变化可以找“efficient head yolo”相关的文章看重点对比检测头的设计差异这会直接影响你选模型。3.3 超参数里最容易翻车的三个第一是batch size。显存不够就调小但不要无脑调小。YOLO训练中常见的一个坑叫“BN崩溃”后面我详细说这里先说结论batch size尽量保持在16以上如果只有8建议用YOLOv8n而不是YOLOv8m否则BatchNorm统计量容易飘。第二是imgsz。我固定用640。宠物检测的目标通常不会特别小640已经能压住大部分情况。如果你部署的是超广角摄像头宠物在画面里很小建议用到768或960但推理速度会降。不要为了刷FPS强行用416小目标漏检会让你后续处理更痛苦。第三是epochs。100轮是个相对安全的起点。我试过150轮发现从80轮之后val loss基本不再下降再跑只是浪费时间。建议开启早停机制YOLOv8默认有patience参数或者自己盯val loss曲线。3.4 数据增强的取舍YOLO自带的Mosaic增强对宠物检测很有帮助它会把四张图拼成一张变相增加小目标数量。但也要注意Mosaic在训练后期可能让模型适应了“拼贴图”部署在真实单图上有微小精度损失。我习惯前30轮开Mosaic后20轮关掉或者把Mosaic概率从1.0调到0.5让模型多适应自然单图。另外不要对宠物图做强旋转和上下翻转。狗和猫虽然姿态多但倒着的宠物在真实相机画面里很少见。我试过加入90度旋转增强结果模型对正常站立的狗产生了一些奇怪的漏检可能把狗的头部特征和旋转后的“倒置感”混在了一起。左右翻转和轻微HSV扰动就足够了。4. 训练过程实录从loss曲线到常见翻车现场训练别只看最终mAP过程里有很多信息。我把自己训练这套数据集时遇到的情况摊开讲你对照着看能少踩不少坑。4.1 正常训练长什么样用YOLOv8默认配置训练日志里会有box_loss、cls_loss、dfl_loss三类loss曲线。其中dfl_loss对应的是Distribution Focal Loss它和回归分支的分布建模有关。很多人只看总的train loss觉得它在下降就放心了这不够。更好的习惯是同时盯val loss尤其是val/box_loss。正常的情况下前5到10轮loss下降非常快因为预训练权重已经具备了通用特征提取能力微调只是在学“猫狗这两个新类别”的分类器。到了20轮之后下降速度会放缓loss曲线会出现小幅度抖动这是正常的。如果看到train loss持续下降但val loss从某一点开始不掉甚至上升那基本就是过拟合了。解决过拟合我常用的招数按优先级排序降低模型规模从m降到n或s。增加数据增强强度特别是Scale和Translate。提前停止在val loss最低点保存模型不要傻等epochs跑完。冻结backbone前10层做5轮预热如果再不行就调小学习率。4.2 关于混淆矩阵“总和为什么不是1”训练结束后YOLO会生成混淆矩阵。很多朋友会问为什么矩阵里每一行加起来不是1或者总觉得数值对不上。这里有个容易混淆的点Ultralytics的混淆矩阵默认是按实例数归一化的但背景类也参与计算而且预测框和真实框的匹配还会受IoU阈值影响。矩阵里的数值代表的是“该真实类别的目标被预测成哪些类别的比例”但因为存在背景、漏检、重复检测行和列不一定精确等于1尤其是你看到对角线数值只有0.8几剩下的是漏检和误检都在背景那一列里。所以看混淆矩阵不要纠结数字加不加得到1重点看两点一是猫狗互相混淆的比例高不高如果猫被识别成狗的比例超过5%说明两类之间存在特征重叠可能需要增加“侧身、背影”这类难样本二是末端的背景行里有没有大量预测框集中在某个类别这往往是误检信号需要去查是不是把玩偶、地毯当成了宠物。4.3 YOLO训练中的BN崩溃前面提到“BN崩溃”这个词最近讨论度很高。本质原因是训练时batch size太小BatchNorm统计量在batch之间剧烈波动导致模型权重更新方向不稳定loss出现剧烈震荡甚至NaN。我在这个数据集上做过一次对比实验用batch8去训YOLOv8m前20轮看着正常第25轮开始loss突然跳高随后整个模型退化最后mAP只有0.5。解决BN崩溃的办法有两个方向把batch size加到16以上。如果你的显卡只有8G显存就用YOLOv8n并且把imgsz降到544这样batch size能翻倍。如果batch size实在提不上去就在ultralytics/cfg/default.yaml里把bn_momentum调低一点让BatchNorm的统计量更新更平滑。不过这个是治标不治本最好还是换小模型。还有一个小技巧训练启动后观察前几个batch的loss。正常情况loss会快速下降如果第一个batch的loss就是NaN或者直接不下降不要浪费时间去等立刻停下来检查数据、标签、学习率大概率是标签中有NaN坐标或者类别ID越界。4.4 我在这个数据集上的训练结果最后用YOLOv8nimgsz640batch32训练120轮加早停实际跑到90多轮就停了。测试集结果大致如下指标数值mAP500.941mAP50-950.712猫的精确率0.93狗的召回率0.95狗和猫两个类别没有特别悬殊。主要原因是猫和狗的整体外观差异较大不像工业检测里某些缺陷看得人脑壳疼。但这并不代表项目结束真正难的是部署环境里的各种“狗东西”。5. 推理与落地阈值、NMS和实际部署的坑训练出best.pt只是完成了第一步后面从模型文件到实际应用还有不少细节。如果你只是想在本地玩玩那开个摄像头跑YOLO demo就行。但要做成宠物喂食器、猫门禁或者自动拍照设备就得认真考虑导出和推理的逻辑。5.1 导出成ONNX还是TensorRT我一般先把模型导出成ONNX再根据部署平台决定下一步yolo export modelruns/detect/train/weights/best.pt formatonnx opset12导出后用一个简单脚本验证output shape是否正常。YOLOv8的ONNX输出通常是一个[1, 84, 8400]的张量84 4个框坐标 80个COCO类别。当你的项目只有2类时输出是[1, 6, 8400]前4个是cxcywh后面2个是类别概率。这里要注意不同YOLO版本输出形式不一样YOLOv5的推理前要自己加NMSYOLOv8则把NMS也封装进去了导出时需要决定是否包含NMS。如果你部署在Jetson或者PC的NVIDIA显卡上TensorRT会比ONNX Runtime快很多。我的实测是同一份best.ptONNX Runtime跑640输入大约40ms一帧TensorRT FP16能压到15ms一帧左右。对于摄像头场景15ms意味着可以跑到60FPS足够实时。5.2 confidence和NMS怎么调很多刚上手的同学喜欢把confidence阈值设成0.5觉得这样能减少误检。但宠物检测不一定适合。宠物会频繁转头、跑动出现运动模糊和部分遮挡阈值太高容易漏检。我自己用下来在室内摄像头场景confidence取0.25到0.3之间比较平衡。NMS的关键参数是IoU阈值默认0.45。如果画面里猫狗经常叠在一起比如狗追猫、猫从狗身边跑过去建议把IoU阈值降到0.4减少两个目标框被合并的概率。但也要注意降太低会让同一只宠物被重复检测出好几个框需要配合confidence阈值一起调。没有万能参数组合建议在验证集上跑一段grid search比肉眼感觉靠谱得多。5.3 部署后的经典误检和对应解法我在实际部署中遇到的误检按频率排序宠物玩具尤其绒毛狗、猫形抱枕模型会以高置信度识别为真实宠物。深色皮沙发、椅背顶部的圆弧结构容易被识别成猫的轮廓。窗外路过的其他动物或者影子会被当成狗。宠物用品店里常见的服装模特图案上有夸张的猫头。这些误检的根源大多是训练数据里缺少“假宠物”负样本。解决方案是硬负样本挖掘把部署环境里误检成猫狗的截图收集起来作为负样本加入数据集同时把它们原本周围包含真实宠物的区域标注为正样本。我加了大概400张硬负样本之后误检率肉眼可见下降。另一个通用的优化思路是给检测结果加时间维度。摄像头场景下不需要每帧都输出最终决策连续10帧里出现5次以上同一位置的检测结果再触发动作可以滤掉单帧闪烁带来的误检。这个后处理逻辑虽然土但对真实场景非常有效。6. 下一步迭代数据扩展与二次标注如果你准备把这套数据集长期用在产品里不要指望一次训练吃到老。我自己的经验是数据迭代会持续很长时间而且每次新增数据之后模型都会暴露出新的问题。6.1 当前数据集的短板这套4300张数据集有明显偏向。首先是视角超过六成图片都是人眼平视或略微俯拍摄像头如果是顶视角度比如安装在猫爬架顶部或者天花板角落模型表现会下降。其次是环境室内白天图片偏多夜间红外光或者造微弱光下图像是灰度或偏绿的模型会误以为颜色信息缺失导致检测能力打折。如果你要做夜间监控我建议补充至少200张夜晚环境图并且训练时不要只用RGB也可以考虑把图转成YOLO能接受的形式同时在预处理里加亮度抖动的增强。单纯的“调大亮度再检测”往往不如直接让模型见过夜视图。6.2 主动学习和半自动标注到了数据迭代阶段纯手工标注太慢。我的做法是先用当前模型对大量新图片做预标注预览时只修正置信度较低或者多个候选框重叠的图然后把这些修正后的结果加入训练集。这个过程在业内叫“模型辅助标注”本质上就是主动学习里的不确定性采样。具体操作可以这样把新图片丢给模型对每张图取所有检测框置信度的平均值排序后选取最低的500张给人看。这500张通常都是模型最“没把握”的也最容易给训练带来增益。如果新场景中模型把大量背景误检成宠物那这些图片也会出现在低置信度列表里正好一并修正。6.3 模型结构上的扩展空间如果你的设备算力不是问题可以考虑在YOLOv8基础上加注意力机制比如在backbone的C2f模块里嵌入轻量级SE或CBAM对宠物这种“中大型目标”通常能有1到2个点的提升。不过要小心改模型结构之后预训练权重可能不能完整加载需要跑更多epochs才能稳定。网上也有把YOLO和Transformer结合的做法把C2f换成Transformer块但参数量和计算量会上去。我个人在移动端部署还是更推荐保持纯CNN结构先把数据做扎实。模型改结构这件事等你的数据迭代到瓶颈期再考虑也不迟。最后再分享一个我自己的小技巧训练出来的模型不要只保留best.pt把每次训练对应的badcase截图也保存下来按日期建文件夹。这样每次迭代之后你都能回头看看模型把哪些“曾经的难题”解决了哪些问题还顽固存在。数据迭代这件事最怕的就是没有复盘光看mAP涨跌其实你并不清楚模型到底在哪些地方变好了又在哪些地方可能悄悄退化了。养成记badcase的习惯之后你会发现自己对模型行为会越来越有底。
返回列表