☰
猫情绪检测实战:3200张数据集与YOLOv8训练完整方案
2026/9/30 5:46:21 网站建设 项目流程

刚做完一个猫情绪检测的项目,用的就是3200张自建数据集加YOLOv8训练,整体效果比我预想的好不少。这套数据集的定位很明确:不搞实验室级别的多模态分析,只做真实场景下的宠物行为识别。它能解决的核心问题有两个:一是帮助宠物主、宠物医院、智能喂食器厂商快速判断猫的当前情绪状态;二是给做YOLO目标检测的开发者提供一套现成的、标注规范的宠物行为数据集,省掉从零采集和标注的苦功夫。

文章会从数据集的构建思路讲起,再到标注规范、YOLO训练细节和踩坑记录,尽量把能复现的部分都写透,适合两类读者:想拿现成数据集跑通YOLO训练流程的初学者,以及准备自己采集宠物数据集、做行为识别的进阶玩家。

1. 数据设计的核心思路:为什么是3200张,为什么是YOLO

1.1 猫情绪检测到底要检测什么

很多人一听"猫情绪检测",第一反应是给猫脸拍张照,然后输出"开心""生气""焦虑"这种标签。这做法不是不行,但落地效果很差。猫的情绪表达和狗不太一样,狗靠尾巴和嘴巴,猫更多靠耳朵角度、瞳孔大小、胡须位置、身体姿态和尾巴形态的综合信号。单看脸,经常翻车——猫眯着眼睛可能是在放松,也可能是眼睛不适;耳朵压平可能是害怕,也可能是准备攻击。

所以这个数据集在设计之初就定了一个原则:按行为姿态分帧标注,用目标检测框出猫的各个部位,再由部位组合推断情绪状态。也就是说,YOLO模型输出的是"耳朵压平""瞳孔放大""尾巴炸毛""身体匍匐"这类细粒度行为特征,而不是直接输出情绪结论。情绪判断是在检测结果之上做一层逻辑规则,比如"耳朵压平+身体下压+瞳孔放大"组合成"恐惧/防御状态"。这样做的好处是模型任务更简单、数据标注更客观、误判率更低。

数据集里的3200张图,全部是真实拍摄和公开数据集筛选后的图像,覆盖了室内外不同光线、不同品种、不同年龄段的猫,每张图平均标注1到2个目标框。目标类别一共设计了8个,分别是:耳朵直立、耳朵压平、瞳孔正常、瞳孔放大、尾巴竖起、尾巴炸毛、身体放松、身体警觉。这些类别之间允许共存,一张图里可以同时出现"瞳孔放大"和"身体警觉"两个框,这符合猫情绪表达的复合性特征。

1.2 为什么选YOLO而不是纯分类模型

如果只是判断"这张图里猫的情绪状态",用ResNet或者ViT做图像分类就够了,根本用不到目标检测。但实际场景里有一个致命问题:猫不会乖乖待在画面正中央。尤其智能喂食器、猫窝摄像头这类设备,拍到的是整个房间,猫可能只占据画面的一小块,背景里还有沙发、玩具、其他宠物。分类模型面对这种场景,只能先把整张图丢进去,结果背景噪声直接干扰判断,准确率断崖式下跌。

YOLO这类目标检测模型天然适合这个任务。它先定位"猫在哪里",再在定位区域内识别"情绪特征是什么",两步合一,既能拿到位置信息,又能拿到行为特征。而且YOLO的推理速度非常快,在Jetson Nano或者手机端都能跑到实时帧率,这对监控类应用来说几乎是刚需。

另外还有一个很实际的原因:YOLO的生态太成熟了。Ultralytics的YOLOv8/v11系列自带数据增强、训练调参、导出部署的完整链路,一个小小的宠物行为识别项目,完全不需要从零搭建训练框架。我见过不少做智能宠物产品的团队,模型选型绕了一圈又回到YOLO,原因很简单——不是YOLO效果最好,而是它最省心。

1.3 3200张图的规模到底够不够

这个数据集叫"3200张",很多朋友第一反应是"这么少能训练出来吗"。这里要说清楚一个概念:目标检测任务的有效数据量,不是按"张"算的,而是按"标注框数量"算的。3200张图里,我筛掉模糊的、目标过小的、严重遮挡的之后,保留了约2400张有效图,其中每张图平均1.7个标注框,总共约4000个目标实例。而这些目标类别又集中在4个主类上(耳朵、瞳孔、尾巴、身体),每个主类下两个状态各占约500个实例。这个数量级训练YOLOv8s这种小模型,是够用的,实测mAP50能到0.86左右。

当然,如果目标是做多相机视角、多品种全覆盖的通用猫情绪识别,3200张肯定不够,至少得再翻一倍加上更细的类别。但作为V1版本数据集,它的定位是"把训练流程跑通,把识别逻辑验证住",这个规模性价比是最高的。数据集的扩展空间我也在设计时留好了,后续只要按同样的标注规范补数据,增量训练就行,不需要返工。

2. 数据采集与标注规范:全流程实录

2.1 采集渠道与筛选标准

采集这部分我折腾了最久,因为数据质量直接决定模型上限。最终用的三个渠道:自建拍摄、公开数据集筛选、视频抽帧。

自建拍摄主要是用手机和运动相机,在不同时段拍家里的和借来的猫,包括玩耍、进食、被惊吓、陌生人靠近、睡觉等场景。这里有个小技巧:不要只拍高清静态图,要多拍视频再抽帧。猫的表情和姿态变化是动态的,视频抽帧能自然获得同一只猫在不同姿态下的多种样本,比摆拍高效得多。我拍了一周视频,按3帧/秒抽帧,再人工筛掉模糊和重复的,得到900多张有效图。

公开数据集筛选这块,主要从Open Images和COCO里找猫的图片,再裁剪出猫的区域做二次标注。Open Images的标注类别里没有情绪维度,但没关系,我只需要原始的猫图,情绪标签自己标注就行。这个渠道补充了大概800张,特别解决了品种多样性问题——自建拍摄基本都是中华田园猫和英短,公开数据集里能找到布偶、缅因、暹罗等更多品种。

视频抽帧是另一个重要来源,主要是从YouTube等平台找宠物行为观察类的视频,按2秒间隔抽帧。这个方式最省事,但有两个坑必须注意:一是版权问题,只做个人学习和研究没问题,商用必须换用有授权的素材;二是视频画质参差不齐,很多视频压缩严重,抽帧出来根本没法标注。

筛选标准我定得很死,不满足任何一条直接淘汰:

  • 图像分辨率小于640x640的不用,因为YOLO输入尺寸通常640,原始图太小会损失细节;
  • 猫被遮挡超过三分之一的不用,标注出来的框语义不完整;
  • 严重运动模糊的不用,特征点根本无法辨认;
  • 同一只猫在不同图片中高度相似的,按构图角度差异做去重,防止数据冗余。

统计下来,采集阶段拿到约4200张原始图,过了筛选还剩约3200张,最终标注完成后保留约2400张有效训练图。这个淘汰率很正常,实际项目里能留下来一半就算不错了。

2.2 类别体系设计:状态组合而非单一标签

之前说过,这个数据集不做"直接输出情绪"的类别设计,而是做"行为特征组合推断情绪"的类别设计。8个类别归为4组:

部位状态A状态B判断要点
耳朵ear_normal(正常)ear_back(压平)压平指耳廓明显向后倒,贴向头部
瞳孔pupil_normal(正常)pupil_dilated(放大)放大指瞳孔面积占虹膜70%以上
尾巴tail_up(竖起)tail_fluffed(炸毛)炸毛指尾毛直立蓬松,体积明显增大
身体body_relaxed(放松)body_tense(警觉)警觉指四肢紧撑、背弓起或身体低伏

每组二选一,一张图里同时标注多个组。这样单个框的语义非常明确,标注人员之间的一致性很高,模型学起来也轻松。判断情绪时再组合:比如"ear_back + pupil_dilated + body_tense"是恐惧/防御,"tail_up + body_relaxed"是自信/兴奋,"pupil_normal + body_relaxed + ear_normal"是平静。

为什么不直接把"恐惧""兴奋"这类情绪直接作为标签?原因有三个。一是情绪是主观推断,两个标注人员对"这张猫是否恐惧"容易产生分歧,标注一致性差会让模型训练不稳定;二是行为特征是可观测的,情绪是不可直接观测的,模型只能学可观测的东西;三是一张图中猫可能同时混杂多种情绪,比如又好奇又警惕,这时候情绪标签没法标,但行为特征可以同时标。

2.3 标注工具与质量控制

标注工具我用的是LabelImg和X-AnyLabeling,这两个都是开源免费的。LabelImg老牌稳定,支持YOLO格式导出;X-AnyLabeling带SAM分割辅助,可以半自动生成初始框,极大提升效率。流程是这样:先用X-AnyLabeling的自动化模型(装在CPU上跑,用的是Grounding-DINO,效果不错)对图生成初步的猫体外框,再人工微调框位置,然后放大局部图像按部位标注细粒度标签。实测下来,纯手工标注8个类别,一张图平均需要47秒;用半自动辅助后,一张图只要25秒左右。

质量控制这一环很多人忽略,但它对数据集价值的影响是决定性的。我做了两层质检:第一层是标注人员自检,每标完50张图导出一次,和标注规范对照检查;第二层是我自己按10%比例抽检,重点看两类错误——框不贴合目标(边框超出或遗漏部位),以及标签语义错误(比如耳朵压平标成正常)。抽检不合格超过3%就全量返工。

标注规范里还有一个约定,很关键:同一张图内,同一部位的多个实例如果靠得很近,只标置信度最高、最完整的那一个。比如两只猫紧挨着,都露出耳朵,如果两个耳朵框会重叠超过50%,就只标前面那一只。这么做是为了避免模型在推理阶段产生大量重复检测框。

2.4 数据集格式与目录结构

最终交付的数据集是YOLO格式,目录结构如下:

cat_emotion_dataset/ ├── images/ │ ├── train/ # 约1900张 │ └── val/ # 约500张 ├── labels/ │ ├── train/ # 与images/train对应同名txt │ └── val/ # 与images/val对应同名txt ├── cat_emotion.yaml # 类别配置文件 └── README.md # 数据集说明文档

每个label txt文件的内容长这样:

3 0.5234 0.3567 0.1123 0.0965 0 0.4876 0.4301 0.1034 0.0878

每一行是"类别ID、中心点x、中心点y、宽度、高度",坐标都是归一化到0到1之间的相对值。这个格式是YOLO的标配,用Ultralytics训练时不用做任何转换,直接把路径配置到yaml文件里就能开跑。

分割训练验证集这里有个容易踩的坑:一定要按视频来源分组,而不是按单张图随机分割。如果同一个视频抽帧出来的连续图片被分到train和val,验证集会被污染,mAP虚高。我按"视频片段"为单位整体分配,保证train和val里的猫图像没有时间连续性上的重叠。

3. YOLO训练实战:从配置文件到调参心得

3.1 环境准备与模型选型

训练环境我用的是一张RTX 4060 Ti 16G显卡,显存不算大,但跑YOLOv8s绰绰有余。软件环境是Ubuntu 22.04 + Python 3.10 + PyTorch 2.1 + Ultralytics 8.0.190,全部用conda虚拟环境隔离。

conda create -n yolo python=3.10 conda activate yolo pip install ultralytics torch torchvision --index-url https://download.pytorch.org/whl/cu121

模型选型我测试过n、s、m三个档位。YOLOv8n训练速度最快,mAP50大概0.81;YOLOv8s速度和精度比较平衡,mAP50能到0.86;YOLOv8m精度最高,mAP50接近0.89,但推理速度慢了一半,而且16G显存下训练batch size被压到16,反而拖慢收敛。最终选了YOLOv8s,理由很简单:在Jetson Orin Nano这种低功耗设备上,YOLOv8s推理速度可以跑到30FPS以上,精度损失只有0.03左右,性价比最高。

3.2 训练参数配置:不是说默认参数就能用

Ultralytics的默认参数是很省心的,但对这个小数据集来说,有几个参数必须改,否则效果会差一个档次。

# cat_emotion_training.yaml path: /data/cat_emotion_dataset train: images/train val: images/val train_args: model: yolov8s.pt epochs: 150 imgsz: 640 batch: 32 patience: 20 lr0: 0.001 lrf: 0.01 warmup_epochs: 3 augment: true mosaic: 0.5 fliplr: 0.5 scale: 0.3 translate: 0.1 mixup: 0.2 hsv_h: 0.015 hsv_s: 0.5 hsv_v: 0.3 close_mosaic: 10

逐个说下调整的原因。

epochs=150:默认是100,但这个数据集小且类别间差异细,需要更多轮次让模型收敛扎实。150轮在RTX 4060 Ti上大概耗时45分钟,完全可接受。

batch=32:显存16G,YOLOv8s在640分辨率下,batch=32刚好能塞进去。batch越大,BN统计越稳定,训练越稳,梯度噪声越小。

lr0=0.001,lrf=0.01:这是迁移学习场景下的保守学习率。用了预训练权重(yolov8s.pt)时,前几层已经学好了基础特征,学习率太大会破坏这些特征,导致loss震荡。lrf=0.01表示学习率最后衰减到初始的1%,保证后期能做精细收敛。

mosaic=0.5,close_mosaic=10:Mosaic增强是YOLOv8训练的关键,它把4张图拼成一张,能极大缓解小目标检测问题。但对这个数据集来说,mixup和mosaic后期会引入大量"假样本"——猫的耳朵被拼到身体上,活塞混淆严重。所以做了个折中:mosaic概率降到0.5,并且最后10轮关闭,让模型在接近真实分布的数据上做最终精调。

这几个参数的经验是:小目标数据集,增强不能拉太满,否则训练loss好看,验证mAP拉胯。我一开始用了全默认的mosaic=1.0,效果反而变差,这类细粒度行为特征非常依赖原始空间结构,过度扭曲的图像增样只会教会模型"记位置",而不是"认特征"。

3.3 训练曲线分析与调参心法

训练过程我盯着三个指标:train/loss曲线、val/box_loss曲线、mAP50曲线。

正常的收敛过程是这样的:train/loss稳步下降,没有明显的突然跳变;val/box_loss在前30轮快速下降,30到80轮缓慢震荡下行;mAP50从第20轮开始快速爬升,到80轮后进入平台期,最终收敛在0.85左右。

我这次遇到一个有意思的现象。前50轮的训练loss降不下去,在1.2左右震荡了20多轮才继续下行。查了一下原因,发现是warmup_epochs设得太短,默认3轮对这个小数据集来说,学习率上升太快,导致前期不稳定。把warmup_epochs从3改成5后,loss曲线在第30轮左右就开始正常下降了。一个小参数调整,前期收敛速度提升明显,这就是盯曲线的价值。

另一个调整是anchor设置。YOLOv8是anchor-free的设计,理论上不依赖预设anchor大小,但在实际训练中,如果目标框普遍比较小(猫的耳朵框经常只有30x30像素),用auto_anchor可以自动根据数据集分布重新计算anchor尺寸。我执行了一轮训练后,用ultralytics的自动锚框检测看一眼匹配度,发现自适应anchor确实比默认的在低IoU阈值下有更高的召回率,实测提升了2个百分点左右。跑自动anchor可以用这行命令:

yolo detect train data=cat_emotion.yaml model=yolov8s.pt auto_anchor=true epochs=1

跑完会自动替换模型anchors,之后的正式训练就直接受益。

3.4 评估指标解读

模型训练完,用验证集评估,输出大概长这样:

Class Images Instances Box(P) R mAP50 mAP50-95 all 500 823 0.881 0.834 0.861 0.623 ear_normal 500 187 0.893 0.872 0.914 0.668 ear_back 500 106 0.821 0.764 0.839 0.571 pupil_normal 500 201 0.865 0.871 0.874 0.645 pupil_dilated 500 94 0.782 0.689 0.759 0.497 tail_up 500 62 0.902 0.857 0.885 0.642 tail_fluffed 500 53 0.876 0.694 0.819 0.542 body_relaxed 500 183 0.891 0.874 0.900 0.651 body_tense 500 77 0.845 0.721 0.793 0.555

从表格能看出来,mAP50-95整体在0.62左右,mAP50在0.86左右。这个差距是正常的,因为猫的很多行为特征框很小且边界模糊(尤其是耳朵压平和身体警觉),IoU阈值的提高会让模型精确定位变得困难。

大类里最弱的是pupil_dilated,mAP50只有0.759,原因有两个:一是瞳孔放大的样本数量只有94个,是所有类别里最少的;二是瞳孔放大在低光照或高光条件下视觉表现差异巨大,模型容易混淆。这类问题后面单独讲怎么补。

训练完模型,导出部署到边缘设备上用TensorRT做加速,这一步我写在第4章的常见问题里一并说明。

4. 常见问题与排查技巧实录

4.1 标注数据不均衡怎么办

刚才说的pupil_dilated就是典型的样本不均衡问题。这个数据集里,猫的"放松"姿态远多过"警觉""炸毛"姿态——真实生活里猫大部分时间是闲适的,这从逻辑上也说得通。但模型学的时候,会偏向预测多数类,少数类的precision还行(因为模型只在高置信时才敢判断),但recall率明显偏低,也就是说"少判"不肯"多判"。

解决办法分两步:过采样少数类 + 损失函数调整。过采样的意思是,训练时手动增加少数类图片的采样权重,本质上是对多数类欠采样。我在数据加载阶段写了简单的class_weight逻辑,给样本少的pupil_dilated、tail_fluffed、body_tense分别乘了1.5倍、1.3倍、1.2倍权重。实测下来,pupil_dilated的recall从0.689提升到了0.76左右,代价是老师说过度聚焦少数类会掉一点多数类的精度,大约掉0.02,总体mAP50反而涨了。

还有个技巧是用Ultralytics自带的class_weight训练参数:

yolo detect train data=cat_emotion.yaml model=yolov8s.pt class_weight=1.2

但这个只能对损失函数做统一权重,细粒度的自定义过采样推荐我前面说的数据采样层方案,灵活性更高。

4.2 训练过程中loss无法收敛

有朋友拿这个数据集自己训练,反馈说100轮跑下来train/loss一直在1.0到1.5之间震荡,完全下不去。排查思路按优先级排:

  • 先看学习率:用lr0=0.01默认值会导致震荡,这个数据集迁移学习建议0.001附近。如果学习率调低后loss有明显下降趋势,说明就是lr的问题。
  • 再看batch size:如果显存不够,把batch压到8以下,BN的统计量不够稳定,loss容易震荡。我建议batch最小不能小于16,不行就换YOLOv8n。
  • 检查标签是否错乱:labels/train里有没有txt文件和图片对不上的情况?比如一个图片文件里确实有3只猫,但label只有1行,模型就会在无监督区域瞎猜,loss掉不下去。写个脚本核对一下每个txt的行数和图片里手动数出来的目标数是否一致。
  • 最后才怀疑模型容量:小数据集加小模型,一般情况下不会存在"模型能力不足"的问题。我这次调整完基本一步到位,这个数据集难度没那么大。

4.3 推理阶段误检漏检问题

训练完成后,用webcam实时测试时发现两个场景翻车:

  1. 双猫场景漏检:两只猫颜色相近挨在一起时,后边那只的尾部框经常漏掉,或者被错标成前边那只的一截。这个问题的根源是NMS(非极大值抑制)会把IoU高度重叠的同类框合并掉。解决办法是调低NMS阈值,从默认的0.45降到0.4,并且适当提升conf_thres,从0.25提到0.3,保留更多低置信的干净框。
  2. 室外逆光误检:逆光下猫的眼部细节模糊,瞳孔放大被误检成瞳孔正常,甚至耳朵压平被误标成耳朵正常。这个没法靠调参解决,必须补数据:我专门挑晴天中午、逆光角度的视频做抽帧,增加了约180张逆光样本加入训练集,增量训练一轮后,逆光场景的误检率降低了约40%。

补充一个实际部署提示:如果部署在Jetson设备上,建议用TensorRT做推理加速。流程很简单:

yolo export model=best.pt format=engine device=0 half=True

导出后用TensorRT的batch=1推理,YOLOv8s的延迟能从20ms降到8ms左右。但注意TensorRT的engine文件和显卡架构绑定,不能迁移到不同算力的设备上。

4.4 数据集扩展与持续迭代

很多朋友会问"拿到这个数据集之后能不能一直用下去",我的建议是:把它当成基线,不断补新场景数据。这套数据集目前的短板很明显:猫的品种覆盖不均衡,中华田园猫和英短占比过高;逆光、夜间环境样本不足;幼猫和老年猫的行为特征与成年猫有差异,但数据量不够。解决方向是在部署过程中持续采集各场景视频回标,每两周做一次增量训练,模型会越用越准。

另外,目前这套体系标注的8个类别偏向"猫的静态姿态情绪",还没覆盖发声、舔毛、抓挠等动态行为。后续扩展可以考虑加入"猫脸关键点检测"分支,用关键点辅助判断更精细的情绪(比如胡须角度变化),YOLOv8-pose原生支持这类功能,可以为下一版数据集做准备。

跟数据打交道,最深的体会是:模型的准确率和数据集质量几乎线性挂钩。你多花50%的精力在数据标注和样本均衡上,往往能收获翻倍的性能提升。3200张图不算大,但它把一套完整的采集、标注、训练、部署流程跑通了。之后每一次迭代,都不过是在这个底子上添砖加瓦。如果你也想做一个宠物行为识别系统,拿着这套数据和训练方案去试,把场景换成狗、兔子甚至鸟,思路完全通用——先定细粒度行为特征,再组合成宏观判断,这条路我走下来是靠谱的,希望对你也有帮助。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询