最近我整理了一份猫情绪检测数据集,一共3200张图,全部转成了YOLO格式的txt标注。这份数据不是网上随手爬下来的图片堆,而是按真实可训练的标准重新过了一遍:每张图都有人工核过的边界框,框里的猫被标成对应情绪状态。它的用途很直接——拿去做目标检测模型,让程序在一张画面里同时回答“猫在哪”和“猫现在是什么状态”这两个问题。
如果你在做宠物智能硬件,比如自动猫门、宠物摄像头;或者手头有一个YOLO项目,想找个有标注的真实数据集练手跑通全流程;又或者搞动物行为学,需要自动化的行为标注工具,这份数据都可以直接用。我自己用它训练了一个YOLOv8模型,从数据整理到训练后验证,整个流程大约花了两天时间,定位精度和状态分类准确率都到了可用的水平。
这篇文章不是数据集说明书,而是记录我踩坑的过程:数据怎么筛、情绪标签怎么定、训练参数怎么设、遇到BN崩溃怎么救,以及模型怎么落地部署。下面直接按流程来聊。
1. 项目整体设计思路与方案选型
1.1 为什么从猫情绪检测切入
先聊聊为什么选猫情绪而不是猫品种、猫姿势这类方向。原因很实际:猫的情绪识别在宠物行业里有明确需求。现在的智能宠物设备大多只做“物体检测加运动检测”,能识别猫来了、猫在动,但识别不了猫的状态。而猫的情绪状态直接关联行为:焦虑的猫容易抓挠家具,攻击状态的猫可能伤人,放松的猫适合互动。如果设备能识别这些状态,就可以做出自动避让、安抚提醒、异常报警等功能。
同时猫情绪检测是一个典型的“细粒度目标检测”问题。猫不像人,表情上没有明显的“笑”和“哭”,它的情绪更多表现在耳朵角度、瞳孔大小、尾巴姿态和身体紧绷程度上。这些线索分布在整个身体上,所以用一个包含全猫的边界框去表达状态更合理,这也是选目标检测而不是图像分类的原因之一。
这个方向还带有一个现实挑战:情绪判定的主观性很强。同一张图,不同人可能给出不一样的情绪标签。所以做数据集的时候最难的其实不是画框,而是把“主观判断”统一成一批人都能遵守的规则。后面我单独讲标注规范。
1.2 为什么选YOLO而不是分类模型或关键点检测
有人可能会问:既然要判断情绪状态,为什么不用分类模型,比如对整张图直接输出“开心/焦虑/攻击”?原因在于实际场景里画面往往不止一只猫,或者猫只占画面的一部分。分类模型会对整张图做全局判断,一旦背景里出现两只猫、或者猫的位置很偏,分类结果几乎没有参考价值。
那为什么不用关键点检测?关键点方案确实能更精细地捕捉耳朵位置、瞳孔状态,但标注成本非常高。单猫的耳朵、眼睛、尾巴尖、四肢关节这些关键点,一张图可能要标十几个点,如果按3200张图来算,工作量会翻好几倍。而且关键点模型对光照、遮挡非常敏感,一旦猫侧身或躲在沙发后面,关键点预测会大量丢失。
我实测下来,YOLO方案还有一个额外优势:它天然支持多猫场景。家庭养猫不止一只,摄像头画面里同时出现两只猫、甚至三只猫的情况很常见。YOLO的检测头会输出多个候选框,每个框独立分配一个情绪类别,这就够了。从工程角度看,YOLO的生态也最成熟,v5、v8甚至更新版本的导出、量化、部署链路齐全,省去了自己搭模型工具的麻烦。
1.3 3200张数据集够不够用
说实话,3200张图放在今天的深度学习标准下不算大。常见的目标检测数据集比如COCO有超过12万张图像,遥感领域常用的HRSC2016也有数千张图像。那3200张够用吗?我的判断是:对于“单目标、多状态”的任务,基本够起步,但要想效果好,要配合预训练权重和合理的数据增强,不能裸训。
从训练角度看,YOLOv8这类模型通常先用ImageNet或COCO预训练权重初始化,模型已经具备丰富的通用视觉特征。猫的轮廓、纹理、背景分割这些低级特征不需要从零学,模型只需要在已有特征上“微调”猫情绪状态的判别逻辑。3200张足够让模型在常见场景里收敛,但极端角度、弱光、剧烈运动这些情况还需要扩充。
如果觉得数据不够,我推荐两个最低成本的扩充方向:一是用视频抽帧,一段10分钟的监控视频按每秒2帧抽,能得到约1200帧画面,再按相邻帧相似度去掉重复帧;二是找公开数据集做预训练迁移,比如先在相似的行为检测数据集上训练出特征提取头,再迁移到猫情绪上。这两个方法都不需要额外标注,见效很快。
2. 数据采集与标注:把情绪“翻译”成边界框
2.1 数据来源、采集与初筛
数据来源上我用了三种方式组合:自己拍的猫视频抽帧、朋友提供的养猫监控片段、以及从开放图库筛选的宠物图片。不建议直接去搜索引擎批量爬图,虽然快,但版权和重复内容问题会让人头疼。自行拍摄的素材在光线、场景上可能单一,所以一定要混合三到四种不同来源,让模型见到更多样的环境。
采集时要注意画质统一。模型训练最怕什么?图片格式混乱、分辨率差距过大。我处理时把所有图片统一压到最长边不超过1280像素,同时检查是否有严重模糊和运动拖影。猫的动作很快,很多抽帧画面是糊的,这类图需要做清晰度筛选。一个简单经验是:模糊到人眼都看不懂猫的姿态时,就不要放进数据集,它只会给模型灌输噪声。
还要处理重复帧。视频抽帧最容易出连续几十帧几乎相同的画面,模型反复看到相近样本,会倾向于把样本特征背下来,而不是学到泛化规则。我去重的方式是用相邻帧的感知哈希对比,设定阈值后把重复的全删了。最终保留的3200张图里,真正有信息量的样本才算数。
2.2 情绪类别定义与标注规范
这份数据集里我把猫的情绪状态分成了四类:relaxed(放松)、alert(警惕)、anxious(紧张)、aggressive(攻击/防御)。一开始我也想过加“happy”“sad”这类情绪,但后来全部砍掉了,因为猫的“高兴”和“专心看窗外”在视觉上几乎没有可标注的区分点。情绪检测模型能学到的,是猫的“可观察行为状态”,而不是内在情感。这个认知一定要提前建立。
为了让不同标注员保持一致,我定了一套可量化的标准:
| 类别 | 英文标签 | 典型行为信号 |
|---|---|---|
| 放松 | relaxed | 耳朵自然前伸或略侧,瞳孔正常或偏小,身体舒展,可能卧着、缓慢眨眼 |
| 警惕 | alert | 耳朵竖直朝向前方,瞳孔扩大,身体静止但肌肉紧绷,眼睛紧盯某个方向 |
| 紧张 | anxious | 耳朵压平或朝两侧,尾巴夹紧或快速摆动,身体低伏,频繁舔鼻 |
| 攻击/防御 | aggressive | 炸毛、弓背、耳朵完全后压,瞳孔极缩或极放大,伴随呲牙或嘶吼姿势 |
这些行为信号全部集合到一起后,让标注员围绕“整体姿势”做判断。边界框的规则也相当重要:框必须完整包住猫主体,允许包含尾巴尖;不要只框头,因为情绪判断依赖全身姿态。如果猫被遮挡超过三分之一,比如只露个头、身体完全在沙发后面,宁可删掉也不要勉强标“放松”,那会导致模型学到一个没有上下文的错误绑定。
2.3 标注工具选型与质量校验
我测试了LabelImg、Label Studio、X-AnyLabeling三个工具。LabelImg最轻量,适合单人快速画框;Label Studio适合多人协作,能设审核流;X-AnyLabeling内置了SAM,可以用提示词先做自动分割再转成外接框,效率最高。如果你只有一两百张图,LabelImg足够;如果要常态化维护数据,建议直接用Label Studio做团队协作,把标注和审核拆成两个角色。
标注完成后一定要做质量校验,这一步不能省。我做了三层校验:
- 第一层检查标签格式。打开txt文件,确认每一行是“class x_center y_center width height”且坐标在0到1之间;
- 第二层检查类别分布。统计四类样本数量,如果“relaxed”有1800张、“aggressive”只有150张,就需要补充攻击姿态的数据,否则模型会倾向把所有猫都预测成放松;
- 第三层是可视化审核。把标注框画回原图上,随机抽200张,人眼看一遍框是否贴合、类别是否合适。这一步能在进训练之前拦截掉大量低级错误。
从VOC或COCO格式转YOLO格式时,有一个常见坑是坐标中心化计算。VOC是左上角和右下角坐标,转成YOLO的xywh时必须除以图片宽高得到相对坐标,并且center_x要和宽的单位一致。我自己写过一次转换脚本,因为忘了归一化,导致模型训练时loss直接变成nan,后面会专门讲这个问题。
3. 用YOLOv8训练猫情绪检测模型
3.1 数据集目录与yaml配置
训练前先把数据整理成YOLO标准目录结构。我习惯这样组织:
datasets/cat_emotion/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/每个txt文件和对应图片保持同名,目录也一一对应。划分时用8:1:1的比例,并且按类别数量做分层抽样,保证每一类在所有集合中的占比基本一致。之前有人直接把数据放到一个文件夹里随机切,结果测试集里“aggressive”一张都没有,mAP看起来很高,但模型一遇到攻击状态的猫就漏检。
数据配置文件写起来很简单,就是一个yaml:
path: /path/to/datasets/cat_emotion train: images/train val: images/val test: images/test names: 0: relaxed 1: alert 2: anxious 3: aggressive需要注意两点:一是path要用绝对路径,或保证在项目目录下运行时相对路径正确;二是names里的顺序必须和标注txt里的类别编号一致。我见过不少翻车案例,txt里标记是“2 anxious”,yaml里却把2写成了aggressive,模型训练出来精度惨不忍睹,排查半天才发现是配置文件错位。
3.2 训练参数设置与背后逻辑
我用的版本是YOLOv8,起步模型是yolov8n.pt预训练权重。训练命令大概是:
yolo detect train \ data=cat_emotion.yaml \ model=yolov8n.pt \ imgsz=640 \ batch=16 \ epochs=200 \ lr0=0.01 \ optimizer=AdamW \ patience=30 \ project=runs/cat_emotion逐个说下参数理由。imgsz设640是YOLOv8的默认值,对猫这种中等尺寸目标很合适。如果画面里的猫特别小,比如摄像头装在3米高处往下拍,建议把imgsz提高到960甚至1280,但显存占用会明显上升。batch=16是在8GB显存下的稳妥值,如果显存更大可以开到32或64,更大的batch一般能带来更平滑的梯度更新。epochs=200配合patience=30,意思是连续30轮验证集mAP不提升就早停,避免无效训练浪费时间。优化器用AdamW而不是SGD,对小数据集收敛更稳定。
数据增强方面,我保留了YOLO默认的mosaic、flip和hsv增强,关闭了mixup。原因是mixup会把两只猫叠在一起,生成很奇怪的混合图,对情绪检测这种细粒度任务不太友好。如果训练过程中发现个别类别严重欠拟合,我会单独调整增强策略,比如对样本数量少的类别做更保守的翻转和裁剪,避免把关键行为特征破坏掉。
3.3 训练流程与结果监控
训练过程中要盯三个指标:训练loss、验证集mAP@0.5、mAP@0.5:0.95。mAP@0.5是IoU阈值0.5下的平均精度,主要反映“框得准不准”、类别判得对不对;mAP@0.5:0.95是在0.5到0.95多个阈值下的平均,要求更严,也更贴近真实部署。
我跑完200轮后,mAP@0.5在0.87左右,mAP@0.5:0.95在0.63左右。这个成绩对3200张的细粒度数据集来说是可以用的。测试集上专门看混淆矩阵发现,最容易混淆的是alert和anxious,因为两种状态下猫的耳朵都可能有不同程度的竖直或后压,仅在尾巴和身体姿态上有微妙差异。这个现象和标注阶段遇到的判断难点是一致的。
如果验证集的loss不断下降但mAP一直不涨,一般不是参数问题,而是标签噪声太大:相同的视觉特征被贴了不同的标签,模型无法形成稳定决策边界。此时最有效的动作是回到标注阶段,把混淆集中的图像抽出来重新投票确认标签,而不是盲目调参数。
4. 训练中的坑:从标签噪声到模型崩溃的排查
4.1 标签噪声和类别混淆还是头疼
先说标签噪声的影响。我一开始让两个人分别标注,没有做统一核对,结果训练到第50轮时发现“anxious”的召回率只有0.3。抽图回看,发现被标成anxious的样本中有将近三分之一,按规范应该算alert。这类噪声会让模型学到的是“反正这两个类差不多”,最后只能瞎猜。
解决办法就是规范化审核流程。我重新把两类图像并排对比,整理出一份带示例图的标注手册,标清楚耳朵角度在什么区间算“竖直”,瞳孔扩大到什么程度算“扩大”。之后重新过了一遍数据,把拿不准的样本全部删掉,不凑数。数据量从3200张掉到2850张,但第二轮训练mAP涨了接近5个点。少而精的原则在这类任务里非常适用。
还需要说一下类别不平衡。如果四类样本数量差距过大,模型会偏向高频类别。我的建议有两条:一是收集时定向补拍低频状态,比如故意逗猫触发攻击姿态;二是训练时用类别加权损失。YOLO在类别损失上支持设置权重,把低频类的权重调高一些即可,但注意别调太高,否则会过拟合到少数样本的特征上。
4.2 BN崩溃与loss异常处理
训练时最容易遇到的崩溃是BN层统计量失稳,表现是loss在某轮后变成nan,或者验证mAP直接归零。我遇到过两次,一次是因为标注坐标出现负数,另一次是学习率设太大。
排查顺序我一般是这样:先检查标签文件里有没有坐标越界或负值,最直接的方法是写脚本遍历所有txt,看一眼坐标是否在0到1区间;再检查yaml的names配置和txt类别编号是否对应;最后看学习率。yolov8n这种小模型,初始学习率0.01很正常,但如果你反复中断续训,warmup逻辑会干扰先前的优化状态,我建议重新从头训而不是续训。
如果你加了自定义增强,也需要排查数据管线。我第二次BN崩溃就是自己在预处理里加了随机裁剪,但裁剪后没有重新归一化到640x640,导致模型输入尺寸混乱,BN的统计量被带偏。YOLO的输入尺寸必须保持统一,想用多尺度训练,就让框架内部的scale参数去做,不要在外面破坏尺寸一致性。
4.3 小目标、运动模糊与识别率下降
猫在快速转头、跳跃时,画面里的目标会出现运动模糊,边界框会变得不稳定。还有环境光照不足时,图像噪声增大,模型会把噪声当成纹理信息。针对这两个问题,我做了三件事:
- 把输入分辨率从640提升到960,小目标召回率提升明显;
- 数据增强里把“模糊”“噪声”单独做成增强项,模拟运动模糊和传感器噪声,模型对低质量帧的抗性增强;
- 推理时加帧间平滑,对同一个目标在连续帧上的类别做投票或EMA,单帧误报被抑制。
另外,有很多做目标检测的朋友会讨论用NWD替换IoU损失来改进小目标检测。NWD,即Normalized Wasserstein Distance,是把边界框建模成二维高斯分布来计算距离的损失,对微小框的偏移不像IoU那样敏感。如果你的监控画面里猫在很远的位置,框只有十几个像素,用NWD替换CIoU损失确实能提升召回。我们这批数据以中等目标为主,所以没有大规模引入,但如果你的场景是“全景摄像头拍十几只猫”,值得一试。
5. 从验证集到落地:导出与场景扩展
5.1 模型导出与TensorRT加速
训练完成后需要部署到实际场景。YOLOv8导出命令很简单:
yolo export model=weights/best.pt format=onnx导出的ONNX可以直接用ONNX Runtime推理,但要达到视频流级别的速度,我会再转成TensorRT引擎,在Jetson等设备上做FP16量化。一个踩过的坑是:TensorRT和YOLO的解码层版本必须匹配。Ultralytics在导出时提供simplify选项,能去掉一些冗余算子,但不要为了省事把一些必要的解码层删了,否则检测结果的坐标会完全偏移。
在8GB显存的Jetson Orin上,我实测用TensorRT FP16跑640分辨率的YOLOv8n,单路推理大概在15到25毫秒一帧,支持7到8路1080p25帧的视频流检测。这个水平已经足够覆盖家庭监控场景。如果还想压帧率,可以降成432p输入,或者把batch动态化来分摊固定开销,但要注意小目标在低分辨率下丢失会比较严重。
5.2 落地场景与后续升级路径
这个模型最典型的落地是宠物摄像头。场景可以这样设定:画面里检测到猫处于aggressive状态,设备自动推送“猫咪正在攻击”提醒;连续长时间anxious则提醒主人“家里的猫状态不好,注意加环境安抚”。自动猫门也可以做一个联动策略:只有relaxed状态才放行,攻击状态时关闭出口,减少多猫家庭的冲突。
如果想继续提升,我建议往三个方向扩展。第一是加入时序维度,用连续帧序列做状态判断,因为单帧里“伸懒腰”和“攻击前兆”可能相似,但动作变化趋势不同;可以用YOLO检测加特征序列输入轻量时序模型来解决。第二是加个体识别,把猫的身份和状态一起输出,这样才能长期追踪某只猫的焦虑频次、进食前后的状态变化。第三是结合行为学知识体系,把检测到的状态对齐到标准行为编码,输出可报告的结构化结果,这对宠物医生和行为咨询师非常有价值。
从数据整理到模型部署,这一整套流程我已经完整跑通了。你可能会发现,我花在调整模型参数上的时间其实不多,大部分精力都花在了数据标注规范和标签质量上。同样的3200张YOLO格式数据,标签整不整齐,直接决定了模型水平的上下限。
最后再分享一点个人体会:情绪检测模型的输出,不要当成猫的“内心真实感受”来解读。我们标注的其实是猫的可观察行为状态,模型学到的也是这个。把类别名定为“警觉行为”“防御行为”,而不是“害怕”“生气”,会让后续使用这套模型的人更清楚它的边界,也更难被误用。这个细节,从数据标注到产品文档,都应该保持一致。