首页 / 资讯中心 / 文章详情

YOLOv8猫情绪识别实战:3200张数据集的构建与训练全流程

YOLOv8猫情绪识别实战:3200张数据集的构建与训练全流程 ★ FEATURED ARTICLE
做宠物行为识别的朋友应该都有同感猫的情绪判断远比狗要麻烦。狗开心会摇尾巴紧张会夹尾巴行为指向性非常强猫则完全是另一套逻辑生性谨慎很多情绪藏在细节里——耳朵的角度、瞳孔的扩张、尾巴摆动的节奏、胡须的方向甚至踏步方式都在传递信息。更要命的是猫在疼痛或紧张时还会刻意隐藏异常这是刻在基因里的生存本能。这就导致传统的人工观察判断误差大而纯养宠经验又很难量化。所以我做这个猫情绪检测数据集时身边不止一个人问3200张YOLO标注数据到底能训练出一个什么样的猫情绪识别模型这篇就把数据集的构建思路、标注细节、YOLO训练全流程和踩过的坑完整拆开讲。先说结论用3200张数据配合YOLOv8训练在小规模场景固定机位、室内环境下完全能跑到95%以上的mAP50情绪类别区分度好的能达到98%。但把这个模型放到陌生环境掉点也非常明显。问题不在模型在数据的设计思路。下面我从头拆解。1. 项目立项为什么用目标检测做猫情绪而不是分类网络1.1 猫情绪识别的现实痛点猫的情绪识别在宠物医疗、智能家居、动物行为学研究里都有明确需求。宠物医院里一只紧张到炸毛的猫在候诊区可能攻击兽医家庭环境里多猫共处的慢性应激很难被主人察觉等到出现乱尿、过度舔毛等行为时往往已经发展成行为问题。视觉AI在这里能做一个持续、客观的情绪观测工具不打扰猫也不用靠人的经验猜。但情绪识别和传统目标识别不同它本质上是一个时序空间特征的组合问题。单看一帧静态图放松蹲坐和轻度紧张的蹲坐姿态差异极小真正的区别在于耳朵朝向、瞳孔大小、尾巴肌肉张力这些细节。这也是我选择目标检测框架的原因——它能在定位猫身体区域的同时把每个部位的姿态特征也框出来作为类别判定的依据。1.2 为什么选YOLO而不是分类网络或关键点检测最直观的方案其实是训练一个图像分类器比如ResNet、EfficientNet直接对整张图判断情绪。但实际用下来有个致命问题分类网络对猫的位置不敏感。一张图里有两只猫一只是放松的一只是紧张的分类网络只能输出整图的概率分布没法告诉你是哪只猫在紧张。这在多猫家庭完全不可用。用关键点检测如HRNet、MediaPipe也可以能精确输出耳朵、尾巴、瞳孔的坐标但关键点方案需要更精确的标注3200张数据量根本不够打关键点而且部署复杂度直线上升。目标检测则卡在中间位置既给了目标的区域位置又不需要像素级的标注成本。YOLO在这里是最合适的——检测速度快、生态成熟、预训练权重丰富3200张数据配合COCO预训练模型起步收敛速度比从头训练快得多。2. 数据集构建3200张图片是怎么凑出来的2.1 数据来源的分布很多做数据集的人会忽略一个问题数据的场景多样性比总数更重要。3200张听起来不多但如果全部来自同一个直播间或同一个摄像头的连续帧模型学到的其实是这个房间的光线、这个摄像头的色调而不是真实的猫情绪特征。所以我严格控制了数据来源的构成。我手上的3200张实际分成了几类自家朋友家猫咪拍摄约1200张不同品种、不同花色、不同年龄段包含室内、阳台、猫爬架、纸箱等多种场景公开授权图片约800张从开放数据集和CC授权图库筛选优先选光线条件复杂、背景杂乱的真实照片宠物医院合作素材约600张候诊区、诊室里的真实应激状态这部分最宝贵因为猫真正紧张焦虑时普通人家很难拍到视频抽帧约600张从自己录制的视频中按关键行为节点抽帧保证动作过程中的连续状态也被捕捉这里要说明一下数据集的版权是红线。网上搜图、搬运管理不当很容易惹麻烦。我一开始也走过弯路用爬虫批量扒图结果很多带水印、分辨率乱七八糟还得重新清洗。后来老老实实和朋友合作购买授权虽然费时间但数据干净可靠能公开能商用这是做数据集的基本素养。2.2 标签体系设计情绪类别怎么定情绪分类不是拍脑袋定的。猫的每一种情绪都必须对应可观察、可标注的视觉行为锚点否则不同标注员对同一张图会给出完全不同的标签。我最终采用了7类别体系类别英文标签视觉锚点放松relaxed眼睛半闭、耳朵自然前竖、尾巴缓慢摆动或盘起、身体舒展警觉alert瞳孔中等扩张、耳朵竖直向前、身体紧绷但未弓背、尾巴轻微抽动紧张/焦虑anxious瞳孔扩张明显、耳朵呈飞机耳向两侧压平、身体低伏、尾巴紧贴身体或快速甩动攻击/防御aggressive瞳孔收缩、耳朵完全后压、弓背炸毛、发出哈声时的嘴部动作、尾巴炸开玩耍/兴奋playful瞳孔扩张、耳朵前竖、身体伏低呈捕猎姿势、尾巴高高翘起或快速摆动不适/疼痛pain眼睛微微眯起并眯成一线、耳朵轻微外翻、蜷缩不动、尾巴紧裹身体、呼吸急促可见睡眠/休息sleeping眼睛完全闭合、身体蜷缩或侧卧、耳朵自然放松下垂这个设计里有几个关键取舍第一把“睡眠”做成了一个独立类别。很多人会把睡眠归到放松里但实际训练时我发现睡眠和放松在目标框里的特征差异很大眼睛闭合、身体姿势完全不同混在一起会让模型的收敛边界变得模糊。分开反而提升了两个类别的精度。第二不适/疼痛这个类别是最难标注的因为猫的疼痛表现往往非常微妙而且很多疼痛状态的猫看起来像在休息。这一类的标注我全部人工复核了两遍请了兽医帮忙确认特征描述。3200张里不适/疼痛只占约240张7.5%比例不高但训练时这类样本的重要性远高于数量占比。2.3 标注格式与YOLO的适配标注格式直接用YOLO的txt格式每行一个目标格式为class_id cx cy w h其中cx、cy是框中心点的归一化坐标w、h是归一化宽高。0 0.5123 0.3842 0.3215 0.4478我之前也纠结过到底该框整个猫身还是框头部实验下来发现框整个身体在情绪识别的场景下效果更好。原因很简单猫的紧张、攻击等情绪往往通过弓背、炸毛、尾巴姿态表达只框头部会丢掉这些关键信息。而框全身的代价是不同图片里猫的占比差异很大有的猫离镜头近框很大有的很远框很小这需要在训练时配合mosaic和尺度增强来消解。如果是YOLOv8以上版本用ultralytics框架训练时数据目录结构直接按标准格式组织就行。这个我在第4节详细说。3. 标注实操工具、流程和质量控制3.1 标注工具选型与配置3200张图的标注量不算大但为了让两个标注员能高效协作我花了一些时间选工具。试过LabelImg、labelme、X-AnyLabeling最后的结论是LabelImg老牌轻量支持YOLO格式直接导出适合单人小项目但协作功能基本为零labelme支持多边形分割过于笨重做矩形框反而别扭X-AnyLabeling基于Qt的现代化工具内置了自动标注模型SAM可以先用模型预标注再人工修正3200张图里约800张是用这种方式批量过的实际用下来工具选型最重要的标准是导出格式是否直接支持YOLO的txt以及能否多人协同管理标注状态。X-AnyLabeling支持导出YOLO格式配合已有模型做半自动标注能省不少时间。但要注意SAM等模型预标注的框往往是物体级不是行为级修正量其实不小不能完全依赖。3.2 多人标注一致性控制两个标注员如果标准不一致训练出来的模型就会学到混乱的特征。我的做法是写了一份三页的《标注规范手册》核心内容包含每条类别视觉锚点的示例图、边界情况处理规则、常见歧义图的参考判断。边界情况处理规则举几个例子猫侧躺且眼睛半闭看起来像放松又像不适——这要以“耳朵是否外翻尾巴是否紧裹”为决定项都出现则标不适否则标放松弓背但无炸毛、无哈声——归为警觉而非攻击一张图里猫的表情不明确——宁可不标也不要硬标一个类别。3200张最终可用于训练的图不足3200就是因为剔掉了一些模糊图标注完成后我额外做了一轮交叉校验两个标注员互相盲评对方的标注结果以统一规则下的参考标准为基准。不一致的图片单独拉会逐张讨论。最终统计两个标注员的一致性Cohen‘s Kappa从第一轮的0.58提升到最终轮的0.83这个数值对于多维度的行为标注来说已经算可以接受。3.3 数据清洗与坑点清洗环节容易被低估。3200张图里我实际剔除掉的约130张理由包括分辨率低于640×640检测框小于20×20像素的这种情况即便标注了训练时也学不到有效特征严重运动模糊、无法准确判断瞳孔状态同一只猫的严重相似帧直接保留一帧背景过度杂乱、猫与背景颜色同化导致边界不清的这里有个经验剔除脏数据比盲目增加数据量更有价值。错误的标注对训练的负面影响呈非线性放大尤其是识别边界本就模糊的情绪类别时几条标注错误就会让模型学会错误模式。4. YOLOv8训练全流程从配置到调优4.1 环境部署细节我用的是YOLOv8n起步后续测试了s和m版本。硬件是单卡RTX 3090训练3200张完全够用batch size可以开到16。环境版本我固定好并导出了一遍防止以后复现出问题# 创建conda环境 conda create -n cat-emotion python3.10 -y conda activate cat-emotion pip install ultralytics8.2.0 pip install torch2.1.0 torchvision0.16.0 --index-url https://download.pytorch.org/whl/cu118这里强调一个容易翻车的地方别盲目装最新版ultralytics。我最初装了当时最新的8.3.x结果训练中loss曲线的输出格式有变化部分回调函数签名也变了害得我排查了半天。固定版本号是复现和排错的基本素养。4.2 数据组织与配置文件数据目录按YOLO标准组织cat_emotion_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── data.yaml └── README.md划分比例是train 2560张80%、val 320张10%、test 320张10%。需要特别强调划分要按照“来源分组”来分不能随机打乱。比如同一个家庭摄像头拍的照片要整体放进同一个集合否则验证集会泄漏训练集的场景信息评估结果虚高。data.yaml的核心内容path: /path/to/cat_emotion_dataset train: images/train val: images/val names: 0: relaxed 1: alert 2: anxious 3: aggressive 4: playful 5: pain 6: sleeping4.3 关键训练参数详解很多人训练YOLO习惯直接用默认参数但情绪检测任务有两个特殊性参数需要针对性调整。第一类别不平衡。前面说过pain类只有240张占比7.5%而relaxed类别约1000张占比31.2%。模型天然倾向预测样本多的类别导致pain的召回率很低。我的做法是设置类别权重加大少样本类别的loss贡献from ultralytics import YOLO model YOLO(yolov8m.pt) # 类别权重数量越多权重越低按 样本总数/(类别数*类别样本数) 估算 class_weights { 0: 0.6, # relaxed 数量多降低权重 1: 0.9, 2: 1.2, 3: 1.2, 4: 0.9, 5: 2.0, # pain 数量少大幅提高权重 6: 1.0 }第二数据增强参数的调整。情绪特征受图像色调影响非常大——一只在黄色灯光下的猫瞳孔看起来会偏小一只在冷白光下的猫瞳孔看起来会偏大。所以我把hsv_h、hsv_s这些色彩增强参数调大到接近上限强迫模型学会跨色调识别。同时由于室内场景的纹理同质化严重我把mosaic和mixup保留开启能在一定程度上缓解过拟合。最终核心训练命令如下yolo train \ data/path/to/cat_emotion_dataset/data.yaml \ modelyolov8m.pt \ imgsz640 \ epochs150 \ batch16 \ lr00.01 \ lrf0.01 \ optimizerSGD \ cos_lrTrue \ hsv_h0.02 \ hsv_s0.8 \ hsv_v0.6 \ mosaic1.0 \ mixup0.2 \ patience30 \ projectcat_emotion_runs \ nametrain_exp1 \ seed42这里说说为什么用SGD而不是AdamW。YOLOv8默认是SGD我在同样设置下对比过AdamWSGD的最终mAP约高1-2个点且收敛更稳。AdamW收敛快但容易落到尖锐极小值泛化略差。在数据量本身就有限的项目里泛化差距会被放大。用SGD余弦退火epochs拉长到150是更稳妥的组合。另外预训练模型的选择上我对比了yolov8n、s、m三个规模。n的速度最快但精度明显不足m比s的mAP约高3个点但有约两倍推理时间。考虑到后续要在边缘设备部署最终选择m作为基线后续再做量化。4.4 训练过程监控与判断训练启动后我习惯每10个epoch存一次权重并跑验证集。重点看三样东西一是train loss和val loss的距离。训练初期两者同步下降但在60-80个epoch附近val loss开始震荡上升train loss继续下降这是过拟合的典型信号。m模型约在90个epoch开始过拟合早停patience30在110个epoch附近触发。也就是说实际有价值的训练集中在110个epoch以内后面纯粹在浪费算力。二是每个类别的Precision/Recall曲线。整体mAP好看不代表每个类别都可用。我观察最典型的现象是pain类别的recall始终偏低约0.78而relaxed的precision高达0.98。这说明模型把大量pain预测成了relaxed或sleeping。按这个线索增加权重后pain的recall提升到了0.85虽然relaxed的precision略有下降从0.98降到0.97但综合F1分数更均衡了。三是confidence阈值的选择。训练后的模型默认conf0.25但对情绪识别来说每个目标最好都给出类别宁可不检测也不能频繁误检。我最后把conf提到0.35precision提升明显recall损失可接受。在部署脚本里设为conf0.35。5. 推理部署与关键踩坑记录5.1 导出与部署链路训练完成后模型导出为ONNX再转TensorRT推理耗时在Jetson Orin Nano上约12ms一帧640分辨率。导出命令yolo export modelbest.pt formatonnx imgsz640 opset12 dynamicTrue # TensorRT转换 trtexec --onnxbest.onnx --saveEnginebest.trt --fp16在Python脚本里通过ultralytics加载TensorRT引擎推理即可。要注意的是转TensorRT后类别输出顺序不变但评估指标需要重新跑一遍——FP16量化后mAP约掉0.8-1.5个点如果精度敏感可以考虑INT8量化加校准数据集但校准数据至少要200张覆盖所有类别的代表性图片否则掉点更严重。5.2 环境迁移的掉点现象这是我踩过最深的一个坑。模型在自有测试集上mAP50达到96.4%但在朋友家的客厅实测直接掉到82%。排查后发现原因有几层第一数据集的背景和光照偏差。自有数据以白墙、木地板和浅色沙发为主朋友家是深色地毯、暖黄灯光模型的背景特征先验完全被打破。第二摄像头视角差异。数据集中侧面视角居多但朋友家的监控摄像头是俯视角猫的背部轮廓和耳朵空间关系变化巨大。第三猫的品种偏差。训练集中的短毛猫约70%朋友家的布偶猫毛发纹理和轮廓特征完全不同。这个问题的根治方案是补充迁移数据重训而不是调模型。我追加拍摄了约500张覆盖新环境和新品种的图片标注后做了增量训练mAP恢复到92%。这个经验也说明做数据集时场景多样性投入产出比极高前期多折腾几个拍摄环境比后期反复补数据省力得多。5.3 连续帧抖动的后处理技巧实时推理时连续帧的情绪预测结果会出现抖动。比如一只猫明明放松地趴着连续几帧输出会是relaxed、relaxed、alert、relaxed。这在单帧检测里很难完全消除但可以通过时序平滑处理。我写了一个轻量的滑窗投票from collections import deque class EmotionSmoother: def __init__(self, window_size5): self.window deque(maxlenwindow_size) def push(self, detection): # detection: {box: (x1,y1,x2,y2), cls: relaxed, conf: 0.92} self.window.append(detection) def get_smoothed(self): if not self.window: return None # 按类别出现次数投票同时要求最低置信度 votes {} for d in self.window: if d[conf] 0.35: votes[d[cls]] votes.get(d[cls], 0) d[conf] if not votes: return None best_cls max(votes, keyvotes.get) return best_cls这个类输出的是过去5帧的置信度加权投票结果实测能把单帧抖动的误报率降低约60%。对于实时的设备端应用5帧窗口约250ms延迟人眼完全感知不到但输出情绪状态稳定很多。6. 常见问题与排查技巧实录6.1 类别混淆紧张和警觉总是分不清这是训练中最头痛的问题。警觉的猫和轻度紧张的猫单张静态图里的差异可能就是耳朵的角度差十几度、瞳孔扩张差几个毫米。人眼都容易看错模型犯错也正常。排查下来主要有两个解法。一是增加含“过渡状态”的样本。我专门从视频抽帧里选取警觉到紧张、放松到警觉的过渡瞬间这些样本能帮模型学到类别间的边界。二是给模型更多上下文信息。单帧不行就考虑双帧或多帧输入比如判断“这只猫的前一帧是放松这一帧突然耳朵压平”这是强得多的信号。我在实际项目中后来引入了一个简单的帧差特征送入模型输入紧张类别的F1提升了约4个点。6.2 小目标检测失效有些图片里猫在远处目标框可能只有50×40像素。YOLO m在小目标上的表现本就一般情绪特征在这么小的框里几乎不可辨。我试过提升输入分辨率到960mAP只提升了1个点左右但推理速度掉了接近一半性价比很低。最终解决方案是加一个检测层特征融合的微调用YOLOv8的P2层检测头yolov8-p2.yaml在训练时从更高分辨率特征中学习小目标推理时仍然用640输入。这个改动在val集中小目标子集上mAP提升约5个点算是性价比高的优化。6.3 训练中BN层崩溃这是YOLO训练中偶发的问题。在大batch size、预训练模型从其他领域迁移过来的场景中训练到某个epoch后总会出现loss突然飙升甚至NaN通常和BatchNorm的统计量失稳有关。这个问题的典型表现是前几个epoch正常到20-30个epoch时loss剧烈震荡。排查时我发现是batch size从32变化到8时BN的running statistics来不及适应。解决办法是降低初始学习率l r0从0.01降到0.005或者在预训练加载时冻结BN层前20个epoch加载参数时设freeze10。遇到过几次之后我现在习惯在微调任务里都设置freeze10即冻结前10层参数避免早期训练震荡。6.4 标注噪声对边界的干扰最后一个常见坑来自标注本身。即使做了交叉校验标注边界依然存在噪声。特别是“警觉”和“紧张”这两个类别的边界标注员的判断本身就带有主观性。我在训练时发现这个边界噪声会导致模型的输出在测试集上反复横跳。针对这个问题的补救措施是训练完成后把验证集预测中置信度介于0.4-0.6之间的样本全部拉出来人工复查如果标注确实模糊就修正标签并重新微调。别看这做法“土”它解决的问题恰恰是标注噪声对分类边界的负面影响。修正了约60张模糊样本后val集mAP又涨了约1.8个点。7. 应用场景与后续扩展7.1 当前可落地的三个场景这套模型在三个真实场景里表现出可用性宠物医院候诊区监测在候诊室顶部安装摄像头识别猫的情绪状态一旦出现攻击/防御或不适/疼痛标记医护能提前采取防护措施。实测中这个场景的优势在于视角固定、光线稳定模型精度保持较好。多猫家庭的慢性应激监测长期监测每只猫每天的情绪状态分布如果某只猫的紧张/焦虑时间占比持续上升可能是慢性应激信号提示主人排查环境因素。这个场景不需要很高帧率每5秒抽一帧足够对算力要求很低。智能宠物产品联动智能猫窝或喂食器检测到猫处于玩耍/兴奋状态时推送互动玩具检测到紧张状态时播放安抚音乐。这个场景需要尽可能本地推理TensorRT优化后的Jetson方案是可行的。7.2 数据的局限性坦诚说明坦率地说3200张数据集的模型离通用猫情绪识别还有距离。这个数据集的场景偏向室内家养环境室外、流浪猫、不同品种的极端外观差异覆盖不足。更关键的是情绪是一个时间连续过程单帧静态图的信息天花板有限。真正可靠的猫情绪判断需要结合时序信息尾巴摆动的节奏变化、耳朵朝向的突变、声学信息呼噜声的频率特征、哈声甚至心率。视觉模型能解决的是其中一大块但不是全部。7.3 后续扩展计划我的计划分三个方向。一是补充关键点检测模块输出耳朵、尾巴、瞳孔位置为情绪判断提供结构化的时序特征。二是增加声音模态收集呼噜声、哈声、喵叫的音频样本做多模态融合。三是针对不同品种做领域适配微调数据集。在实操层面数据集的持续迭代比模型结构更重要。每新收集一个场景的数据我都会先跑一遍现有模型的测试集把失败样本集中补充进训练集。这比每次都从头调模型结构有效得多。最终这个数据集会保持“3200张基础集场景增量集”的模式来演进。做这个项目最大的体会是情绪识别不像普通目标检测它的难点不是“找到目标”而是“理解上下文”。数据集的每张图、每个标注框背后都是对猫行为语言的拆解。3200张不多但如果每张图的质量和标注一致性都把控到位配合YOLO成熟生态做一个限定场景下的可靠模型是完全可以做到的。后面做增量数据时我也会继续把标注规范、清洗流程和场景补充记录下来把整套数据生产链路沉淀成可复用的方案。
阅读完成 · 觉得有帮助?
咨询建站