ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于YOLOv8的猫情绪检测数据集实践:从标注到部署的完整经验总结

基于YOLOv8的猫情绪检测数据集实践:从标注到部署的完整经验总结 这个数据集项目我关注挺久了标题是“猫情绪检测数据集 | 3200张YOLO宠物行为数据集”热词里全是YOLO相关的东西。乍一看只是“又一个目标检测数据集”但真正上手做一遍才发现猫情绪检测跟常规的“猫狗检测”完全是两码事——同一个“哈气”状态有的猫是张嘴露牙有的猫是嘴巴微张但耳朵已经压平还有的猫直接背弓起来毛炸成一圈。这些状态放在目标检测框里即便是经验丰富的人也要盯半天才能达成一致。这篇文章就把我这次做数据采集、清洗、标注、用YOLOv8训练、以及部署后遇到的实际问题完整梳理一遍。适合准备自己搞动物行为数据集、想用YOLO做细粒度状态识别、或者单纯想看看“情绪检测”到底能不能落地的朋友。我会把数据集的设计逻辑、标注规范、训练调参、混淆矩阵解读、以及部署后的翻车现场都讲清楚不整虚的。1. 为什么普通的猫脸检测数据集解决不了“情绪识别”1.1 行为状态检测和“检测猫”根本不是同一个任务大多数公开的宠物数据集做的是“猫狗二分类”或者“猫脸检测”模型学到的是“有猫”“猫在哪”相当于一个物体存在性判断。但情绪检测要回答的问题是“这只猫现在处于什么状态”本质上是细粒度的行为状态分类而且很多状态依赖的是局部特征和整体姿势的组合。举个例子猫的“飞机耳”状态耳朵向两侧压平如果只框住猫头模型看到的可能就是一团毛和一个疑似耳朵的轮廓如果框住整个猫身那模型又要同时处理“耳朵角度 身体姿态 尾巴摆向”多个特征难度直接翻倍。我在做标签设计的时候一开始还想用“愤怒、开心、恐惧”这种主观情绪词后来果断放弃——因为两个人看到同一张猫图对“开心”和“平静”的界定完全不一致标注一致性崩了模型训练就是空中楼阁。最后我定的方案是检测目标为“猫个体”标签类别为可观察的行为状态而不是推断猫的内心感受。这样既保留了检测框的通用性又把情绪状态落实成可标注、可复议的客观动作。1.2 3200张数据集里究竟该设哪些标签类别情绪状态的选取要遵循三条原则第一状态之间要有肉眼可区分的视觉差异第二每个状态要有足够的样本量支撑训练第三状态名称要中性能写进标注规范里让不同标注员得出相同结果。我最终敲定了8个类别这8个类别覆盖了猫日常行为的大部分场景类别名视觉判定要点主观情绪对应normal耳朵直立、眼睛正常睁开、身体放松平静/中性alert耳朵前倾、瞳孔略放大、身体微微紧绷警觉/好奇play身体前倾、尾巴高举或摆动、眼睛明亮玩耍/兴奋scared耳朵后压、瞳孔放大、身体蜷缩或后退恐惧/紧张aggression张嘴露牙、耳朵完全压平、背部弓起、有时伴随炸毛愤怒/攻击sleepy眼睛半闭或全闭、身体趴卧困倦/放松vocalize张嘴、嘴部有明显张合动作或胡须前伸叫声/喵叫grooming舌头外伸或舔舐身体某部位、前爪反复擦拭面部梳理/清洁你可能会问“炸毛”为什么不单列一类我测试过但炸毛状态在图片数据集里极度依赖拍摄角度和毛色短毛猫炸毛和长毛猫正常状态的视觉差异极小第一批标注复审时一致率不到50%所以最终把炸毛并入aggression类别作为辅助特征而不是独立状态。这也是做数据集时很容易踩的坑——类别划分不能光靠理论一定要先拿现实素材试标一遍再看一致率。1.3 从原始素材到“可训练数据”的筛选逻辑猫情绪的图片素材来源很杂我搜集了大概12000多张网络公开图片和自摄照片但经过三轮筛选才留下3200张。筛选标准不是只看图片是否清晰而是看“这张图是否能让标注员在一个目标框内明确判断出状态”。具体来说我淘汰了以下三类素材第一类是“多猫同框但状态矛盾”的图片一只猫在睡觉另一只猫在警戒这种图如果做单框标注标签语义就会被污染只能做多框分别标注处理成本太高直接弃掉第二类是“状态模糊”的图片比如猫正在从警戒切换到攻击的瞬间耳朵半压半立这种动态中的中间态连人都很难判断模型学了只会增加内部表征的混乱第三类是“面部被严重遮挡”的图片比如猫在舔爪子的时候半个脸被爪子挡住虽然能看到行为但无法确认表情状态情绪判定的关键线索缺失。3200张是权衡了状态覆盖度和训练时间后的结果。分类别来看normal和sleepy类样本在公开素材里最充裕各占约20%和18%alert、play、vocalize占15%左右scared和grooming这种“可遇不可求”的状态各占10%上下最稀缺的是aggression占比不到12%因为猫真正进入攻击状态时通常是高速动态静态图不容易拍清晰。这个长尾分布我后面专门做了处理放在第三节细说。2. 标注规范和YOLO格式转换这几步决定模型上限2.1 标注边界到底该怎么画做目标检测的人都纠结过“框该画多大”这个问题。猫情绪检测和车辆检测有个本质区别车辆检测框大一点小一点都不影响类别判断因为类别是由车辆外观本身决定的但猫情绪状态往往依赖“耳朵尖、嘴巴、尾巴”这些局部信息框的范围会直接影响模型能不能看到这些关键特征。我在标注规范里定了两个硬性要求。第一个要求是检测框必须完整包含“头 前肢根部”允许包含部分躯干但不强制覆盖尾巴。原因是猫的耳朵和嘴部是情绪判断的第一优先级特征前肢能辅助区分grooming和play而尾巴在静态图里经常被压身子底下框进去反而会产生无效背景干扰。第二个要求是框的边距尽量贴紧主体轮廓上下左右预留不超过主体宽度5%的边距。太紧了会裁断耳朵尖太松了会把沙发、床单、地板这些背景大片框进来。这里有个非常反直觉的体验刚开始我为了让模型“看到更多场景信息”把框有意放大了一些结果训练出来的模型在测试集上mAP掉了将近三个点。复盘后发现原因很简单——放大框等价于给模型增加了背景噪声而猫情绪状态的特征本身就很细微模型被背景里的条纹枕头、木质地板反复误导学了一堆不该学的背景纹理。后来把框收紧性能立刻就回来了。2.2 YOLO标签格式的换算细节YOLO要求的标注格式是归一化的类别ID、中心点x坐标、中心点y坐标、框宽、框高所有数值都在0到1之间。从原始标注工具导出时通常是Pascal VOC格式的XML或者COCO格式的JSON坐标值通常是绝对像素值所以需要做一个换算。换算公式本身很简单x_center (xmin xmax) / (2 * image_width)y_center (ymin ymax) / (2 * image_height)width (xmax - xmin) / image_widthheight (ymax - ymin) / image_height。但真正的坑在于每张图片的实际宽高不一定一致我手里的素材长宽比从4比3到16比9都有还有少数竖构图。如果换算时不管图片原始尺寸直接除以一个固定值那标注框位置就会偏移训练时模型会对空间位置产生错误先验。我在这上面栽过一次。第一版预处理脚本图省事默认所有图片都是1280乘720结果有将近四百张竖图全部标注错位训练出来的模型在竖图上检测框整体偏右下。后面改成读取每张图片的header信息获取真实宽高再计算这个低级错误才消除。建议做数据集的同学在处理任何图片格式时都先跑一个脚本批量统计所有图片的尺寸分布再写换算逻辑别偷这个懒。2.3 标注质量管控的实操手段标注质量是整个数据集项目的生命线3200张图如果错个3%就有约100张带噪样本这些噪声样本在训练中会不断放大模型的不确定性。我的质检流程分三层。第一层是“双人独立标注 差异仲裁”。每张图至少有两个人独立标注标注完跑一个比对脚本把两个标注框的IoU低于阈值或者类别标签不一致的样本全部抽出来由第三方仲裁员复看并给出最终判定。第二层是“类别混淆矩阵审查”。把标注结果按类别交叉对比找出“同一张图被标成A又标成B”的高频组合比如alert和scared、play和vocalize这两个组合在争议列表里占比最高说明这两个类别的判定规则在标注规范里描述得不够清晰需要修订规范。第三层是“动图逐帧抽检”。虽然最终数据集是静态图但有相当一部分素材来自视频抽帧同一段视频连续几帧的状态应该是平滑变化的如果某一帧的状态标签和前后帧差了十万八千里那这帧极有可能标错了。三轮质检走完3200张的最终结果里被我标记为“存疑待定”的用了差不多400张这些图我宁可丢掉也不彻底让它污染训练集。对于情绪这种微细粒度的识别任务干净的数据比多的数据更重要这是我做完这个项目后最强烈的一个感受。3. YOLO训练前的数据分配与增强策略直接影响mAP天花板3.1 数据划分必须“按猫个体”而不是“按图片”这是无数做动物数据集的人最容易忽略的一点。如果同一只猫的几十张图片既出现在训练集又出现在验证集模型其实是在“认猫”而不是在“认情绪状态”——它记住了这只猫的毛色、花纹、体型等个体特征然后在验证时看到同一只猫就“碰巧”猜对了状态。这样跑出来的验证指标极其虚高等你部署到新场景遇到一只没见过的猫效果立刻原形毕露。我的做法是在收集素材时就记录每只猫的身份ID按ID进行数据划分。3200张图归属于大约430只不同的猫其中个体种类覆盖了橘猫、奶牛猫、暹罗、英短、美短、布偶、狸花等常见品种还有一些混血流浪猫。按个体划分后训练集约2700张验证集约300张测试集约200张三者没有重叠的猫个体。测试集里的猫品种尽量是训练集里出现过的但保证具体个体完全没出现过。这样划分后最明显的变化是验证集的mAP从95%左右掉到了89%左右。这个掉点其实是好事——之前的95%是“记忆个体”的虚高现在的89%才是模型真正泛化到“陌生猫”上的能力底线。任何做行为识别数据集的博主我都建议你在数据划分上彻底隔离个体哪怕验证集指标难看一点那才是真实水平。3.2 针对猫情绪场景的数据增强参数取舍YOLOv8自带的增强管线默认参数很完善但在猫情绪检测这个场景下有几个参数需要特殊调整。第一个是hsv_h、hsv_s、hsv_v色彩抖动这三个参数控制HSV颜色空间里的色相、饱和度、明度随机变化。猫的情绪判断高度依赖毛色和耳朵颜色尤其是橘猫的耳朵和身体同色如果色相抖动太强橘猫的耳朵轮廓会被“染”成别的颜色模型就会错误学习颜色一致性我把色相抖动从默认的0.015降到0.005饱和度和明度保持默认。第二个是degrees旋转角度猫的图片里有很多侧躺、仰头、趴卧的姿势角度变化极大我把它从默认0加大到30但要注意大幅旋转会引入黑色填充边角需要配合scale参数控制在0.5到1.5之间否则旋转后的猫会变形得不像猫。第三个是mosaic马赛克增强这个参数能把四张图拼成一张增加背景多样性但在情绪检测里我强烈建议设置mosaic1.0而不要混合多次重复使用因为马赛克拼图时不同猫的状态混在一起如果模型在训练时反复看到“半个猫头 半条尾巴”拼接的合成样本对细粒度特征的提取反而有害。另外一个值得说的是fliplr左右翻转。猫的耳朵在左右翻转后仍然是猫的耳朵这样不破坏语义可以保持默认打开。但上下翻转我直接关了——因为真实的猫情绪图片几乎没有倒挂视角训练时引入倒挂的猫会让模型错误学习“头在下”这种不存在于现实分布的特征。3.3 类别不平衡的两种补偿手段前面提到aggression和grooming这些状态数量偏少直接训练的话模型会倾向于把多数类预测得更准少数类精度就会拉胯。我用了两层补偿。第一层是离线过采样把scared、aggression、grooming这三类样本做轻度复制复制比例控制在1.5到2倍反复试验后发现在不引入过拟合的前提下把grooming从约320张增到约600张后该类别的召回率从71%提升到了84%。第二层是在YOLOv8里设置每个类别的loss权重具体做法是在data.yaml外额外定义类别权重列表让模型对样本量不足的类别在计算分类损失时乘以更高的系数。实际操作时我会把cls_pw或者按类别权重手动加到损失函数的类别权重项里。但要注意过采样和类别权重不能同时给得太猛否则少数类会被模型“死记硬背”产生过拟合——我试过把grooming过采样到4倍同时权重拉到2.0结果验证集中grooming的mAP只有63%明显是学虚了。补偿后整个数据集在YOLOv8s上的验证集mAP50稳定在88%到90%之间mAP50-95在61%到63%之间。这个数字不算漂亮——毕竟情绪状态属于细粒度分类比常规的80类COCO检测难不少但对于一个3200张的小型专项目数据集来说已经是可以上线验证的程度了。4. 训练配置与超参调优我踩过的几个“玄学”坑4.1 模型选型和输入尺寸的纠结过程先谈模型选型。YOLOv8n、YOLOv8s、YOLOv8m、YOLOv8l这几个档位我都跑过对比在3200张这个数据量级下YOLOv8m是一个性价比非常尴尬的选择——它的参数量比YOLOv8s翻了将近三倍但验证集mAP只提升了约1.2个百分点。原因很直白数据量不够大的时候模型参数量膨胀带来的收益远小于过拟合风险大模型在小型数据集上就是费力不讨好。最终我选定YOLOv8s作为主力模型理由是它在单卡3080Ti上训练一个epoch只要不到两分钟推理一张图只需要15毫秒同时精度损失完全可以接受。输入尺寸我做了个对比实验。在YOLOv8s下640分辨率输入的训练速度大约是1280分辨率的2.3倍验证集的mAP50-95却只掉了不到1个百分点。但在这里我要强调一个特殊情况——猫的耳朵尖和眼睛在低分辨率下很容易被下采样弄丢。如果把输入分辨率降到416验证集的scared和aggression类别mAP会明显下降因为这两个状态极度依赖耳朵角度和牙齿细节。所以我的最终选择是640作为默认输入只在需要极高帧率部署时才考虑降到512不建议再低。4.2 从训练日志里读出“正在过拟合”的信号训练过程中要盯的输出不止是mAP曲线更要关注分类损失和定位损失之间的平衡。我在训练到第80个epoch左右时发现训练集分类损失还在稳步下降但验证集分类损失开始小幅回升这是过拟合的典型信号。于是我把epoch数从默认的300砍到150并在第120个epoch后开启早停最终模型在第135个epoch达到最佳权重。还有一个值得记录的现象是“mAP50在稳定上涨但mAP50-95纹丝不动”。这种情况说明模型对“宽松匹配”的检测框学得很好但对“精确框位置”和“细粒度类别边界”的学习停滞了。我的处理方法是在后30个epoch把iou损失的权重略微调高然后关掉mosaic增强再做最后的精调——这个过程在YOLO圈子里有个通俗说法叫“解冻精调”本质上就是前面阶段让模型快速收敛最后阶段用更贴近真实分布的样本打磨边界。如果你看到训练日志里的box_loss非常小、但cls_loss在最后几十个epoch里疯狂震荡那大概率是模型陷入了“置信度偏移”——它开始对每个框都预测一个高置信度但不真正区分类别。这时候直接把epoch数减半重训大概率比硬着头皮继续训效果要好。这个经验我在多个数据集上验证过情绪检测这种细粒度任务尤其明显。4.3 关于“BN崩溃”这个热词的亲身排查经历热词列表里有一条是“yolo训练中bn崩溃”这个词在当前YOLO社区里讨论度很高我也在训练中途真实遇到过。现象是训练到第30个epoch左右loss突然从1.2左右直接跳到6以上然后训练曲线变成了一团乱麻怎么调学习率都救不回来。排查过程是这样的。我第一反应是学习率太大把learning rate从0.01降到了0.005重开一轮训练结果在第40个epoch照崩不误。第二反应是数据有问题我随机抽查了最近一批增强后的图片发现mosaic拼接出来的样本里有四张图里猫的比例差异过大有的猫只占全图不到5%有的猫占到了80%以上这种极端尺度差异直接冲击了BatchNorm层的统计量——BN层在计算均值方差时会因为极端样本产生巨大波动一旦波动超出一个阈值后层激活值分布就彻底失衡这就是“BN崩溃”的直观原理。最终我用两个方法稳定了训练一个是在数据加载阶段做了“尺度归一化预过滤”把马赛克拼接时的随机尺度范围收紧从0.3到1.5改为0.5到1.3极端尺度样本明显减少另一个是把batch size从16降到8BN统计量对单batch内极端样本的敏感度减小了。调整之后同样的训练配置在之后一百多个epoch里再也没出现过崩溃。这里也给正在训YOLO的朋友一个排查顺序建议先看训练日志里loss跳变前有没有出现“NaN”“inf”之类的数值异常再看最近几个epoch的增强图有没有尺度极端差异最后才考虑调学习率。顺序反了容易走很多弯路。5. 混淆矩阵解读与“总合不唯一”问题我找到的根因5.1 为什么confusion_matrix里的数值总和不等于100%热词里有“yolo混淆矩阵总合不唯一”这确实是初学YOLO时最常见的一个疑问。正常直觉里每个类别行加起来应该等于该类别的总样本数但YOLO生成的混淆矩阵却常常“行和”不等于该类别的真实数量甚至列和也不等于全部预测数量。我排查后发现根因有两个。第一yolo的混淆矩阵统计的是“预测框和真实框在IoU阈值以上匹配后”的TP、FP、FN关系而一张图里如果同时存在多个目标框一个预测框可能匹配到多个不同类别的真实框这时候预测框会被重复计入多个类别。第二YOLO在验证时默认做NMS后处理NMS会抑制掉一部分重叠的预测框被抑制的预测框不会出现在矩阵里但它的“真实归属”仍然在真实框里这就导致落在“背景”列里的数字变多出现所谓的“总合不唯一”。举个例子我的验证集里有一只猫在aggression状态下同时张嘴露牙和耳朵后压标注时它被标成两个框——一个框以头部为主一个框以身体为主。NMS把这两个框合并成一个大框后这个“合并框”同时命中了一部分头部真实框和一部分身体真实框混淆矩阵里它就同时增加了aggression行和normal行的计数。所以总数字看起来“不唯一”其实是正常的关键要看的是“对角线上的数值占比”而不是绝对数字总和。5.2 类别之间的混淆规律与针对性修整混淆矩阵里最让我留意的是两对高频混淆第一对是alert和scared占所有错误匹配的35%左右第二对是play和vocalize占28%左右。这两对组合的混淆本质就是“视觉特征重合度太高”——alert状态的猫耳朵前倾、瞳孔微微放大scared状态的猫耳朵后压、瞳孔放大两者在低光照或模糊图片里几乎不可区分play状态下猫张嘴扑咬玩具和vocalize状态下张嘴叫唤如果截图瞬间恰好都在张嘴边也极容易混淆。针对第一对我调整了标注规则要求耳朵角度必须结合整个头部判断如果双耳的视觉信息在图中不够清晰这帧必须标成“模糊不可判”并从数据集里剔除。针对第二对我把play类别细化为“带逗猫棒、小球等道具”的上下文概念也就是说模型要见到的play不仅是张嘴还要看到前肢扑向某个物体或者尾巴大幅度摆动如果只是静静的张嘴那更偏向vocalize。经过这两轮修整alert和scared的混淆比例从35%降到了20%左右play和vocalize的混淆比例也从28%降到了16%。最终我把这两组混淆比例写进了项目总结里作为未来做“多模态情绪识别”时的优先改进方向——单纯靠静态图的区分能力已经到头了下一步必须引入时间序列信息来辅助判断。5.3 置信度阈值和NMS策略在细粒度任务中的调整思路YOLO默认的置信度阈值是0.25NMS的IoU阈值是0.7。这套参数在通用检测里表现稳健但用在猫情绪检测上不够理想。我在测试集上扫了一遍不同阈值组合发现把置信度阈值从0.25调整到0.4、NMS的IoU阈值从0.7调整到0.5之后mAP50虽然掉了0.8个百分点但误检率下降了将近22%。为什么这么调核心原因是情绪检测的类别之间视觉相似度高低置信度阈值会导致模型把“比较像scar但其实是alert”的检测结果全部放出来增加误报而高IoU阈值会让高度重叠的同类预测框合并到一起如果两个框分别预测了不同类别合并后就会生成一个“混合类别”的怪框。把IoU阈值降下来重叠框被抑制得更坚决最终输出的框更“纯粹”。在部署到实际项目时我的经验是宁可让模型少报几个框也不要让猫的情绪状态被误判——因为智能猫窝、宠物监测摄像头这类产品的用户收到一条“你的猫现在处于攻击状态”的推送如果十条有两条是误报用户大概率就把通知关掉了。精准比召回更重要这个决策逻辑在情感类检测任务里是必须反复强调的。6. 部署到实际场景后暴露出的问题以及下一步改进方向6.1 运动模糊和检测框抖动静态数据集的天花板我训练好的YOLOv8s模型拿到真实场景去测第一个明显的痛点就是“运动模糊导致状态误判”。猫在快速扑向玩具或者突然转身时单帧图像里的耳朵和嘴巴会因为曝光时间长而变模糊模型在这种模糊帧上经常把vocalize看成aggression或者把play看成alert。这个问题用纯静态图像数据集没法从根本上解决。为了缓解我在部署端加入了“多帧投票机制”连续取10帧的检测结果按类别做加权投票只有同一类别在10帧里出现6次以上才推送状态变化。这样一来瞬时误判被大幅抑制但代价是状态变化的响应延迟增加到了1秒左右。对于猫情绪检测这种“非生死攸关”的功能场景我觉得这个取舍是值得的。6.2 被毛颜色和光照变化对情绪判断的干扰另一个部署时才发现的问题是“同猫不同毛发颜色状态下的模型表现差异”。比如一只白色长毛猫在暖黄色灯光下耳朵边缘会被毛色和灯光染成偏金色模型对耳朵角度的判断就会受影响。更离谱的是同一品种的猫如果训练集里没出现过类似的毛色模型的置信度就会普遍偏低。我验证过一组对比在3200张训练集里白猫占比约10%橘猫占比约22%奶牛猫占比约15%而测试集里白猫的表现比橘猫平均低了4个百分点。这说明数据集的颜色多样性还有提升空间。后续如果想进一步做场景迁移至少要再补500张以上的白猫、灰猫、以及强逆光光照环境下的素材才能把颜色先验偏差拉平。6.3 从静态检测到“时序音频”融合的扩展思路做了这个数据集之后我越来越觉得纯静态单图的情绪识别在理论上有一个天花板——猫的情绪是一个时间连续变化的过程单帧能提供的信息毕竟是有限的。热词里有“yolo和transformer结合”和“mamba yolo复现”这类讨论这些其实都是在尝试用更强大的序列建模能力去捕抓时序上下文。我个人下一步的计划是在现有YOLO检测结果基础上把每个检测框对应的“耳朵角度、嘴巴开合度、瞳孔大小”等区域特征抽出来送入一个轻量级时序模型比如LSTM或者Transformer的Encoder做状态转移建模。同时结合麦克风采集的猫叫声频段特征在实时识别时把“张嘴”和“有叫声”两个信号做交叉验证从而把静态图片里无法解决的alert和scared持续混淆问题压缩到更低的水平。如果你也想复现这个数据集项目我的建议是不要一上来就追求模型结构创新先用YOLOv8s把一套干净的数据管线跑通把标注、质检、训练、部署这四个环节都走完一遍再谈引入时序模型。数据基础不牢模型结构再先进也是白搭。最后分享一个我在实际项目中反复体验到的小技巧训练完模型后不要只看验证集的指标一定要把测试集里所有“预测错”的图片单独导出来按类别排列成一张大图肉眼过一遍。你会发现错误样本的分布模式比任何mAP数字都更能告诉你数据集下一步该怎么补——比如当50%的错误样本都集中在光照不足的场景那你接下来补数据的优先级就很明确了。我做这3200张数据集最大的收获就是这个朴素的道理AI模型不会骗人它会诚实地暴露你数据里的每一个偏见。
返回列表