尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

宠物猫情绪检测实战:从数据集构建到YOLO模型部署

宠物猫情绪检测实战:从数据集构建到YOLO模型部署 1. 为什么想做猫情绪检测这个需求比想象中更真实先说清楚我为什么会碰这个选题。去年接了个宠物智能摄像头的边缘端需求客户提了一堆功能喂食提醒、活动量统计、异常叫声报警都还好办唯独识别猫的情绪状态这一条团队内部吵了一周。产品经理觉得很简单——人脸情绪识别都这么成熟了猫脸不也差不多真正做过视觉项目的人都知道这话听着就头大。宠物情绪检测和人脸情绪检测完全不是一回事。人脸有FER2013、RAF-DB这类大规模公开数据集撑着表情标签相对标准化而且人脸的结构高度一致——两只眼睛、一个鼻子、一张嘴位置相对固定。猫不一样。猫的脸部肌肉运动和表情表达比人类隐蔽得多耳朵角度、瞳孔状态、胡须位置、嘴部张力都参与情绪表达不同品种的猫布偶和暹罗的脸型差异极大更是让几何特征全面失效。更要命的是数据。市面上能直接下载的猫情绪数据集少得可怜要么是实验室环境下摆拍的静态图要么是混杂了猫狗人脸的通用宠物数据集标签粒度完全达不到可用级别。我们当时的结论很直接想要模型在真实家庭场景下能干活只能自己从头攒数据集。这条路走完之后我觉得整个过程值得整理出来。整套数据方案锁定在3200张图片、YOLO格式标注上标签覆盖了六类常见猫情绪状态calm、alert、anxious、aggressive、playful、sleepy并且按YOLOv8/v9的工程规范做了全流程适配。这篇文不聊论文式的玄学就把数据怎么攒、标注规范怎么定、模型怎么调、坑踩在哪几处一步步说清楚。如果你手头正在做宠物行为分析、智能家居摄像头算法、或者想在边缘设备上跑一套轻量检测模型这篇内容应该能帮你省下至少一个月的试错时间。2. 数据集的定位与标签体系设计六类情绪背后的行为学依据2.1 为什么情绪检测本质上是个目标检测任务很多人一听到情绪检测第一反应是分类任务——给一张图判断猫的心情。但真实场景下摄像头捕捉到的画面里可能有猫也有猫抓板有猫窝还有主人的脚猫的身体可能只占画面的一小块。如果直接整图分类模型被迫在一堆无关背景里去猜情绪泛化能力会很差。所以我把任务定位成检测分类二合一先用检测头把猫的主体区域框出来再在框内完成情绪类别的判定。YOLO系列天然适合这个结构它输出的是(box坐标 类别置信度)放在这里就是哪个位置有猫 这只猫当前是什么情绪状态。3200张图全部按YOLO格式标注成class_id x_center y_center width height的txt文件类别ID从0到5对应六种情绪。这带来的一个额外好处是推理阶段可以灵活控制。如果我只需要判断猫现在是否焦虑我可以在检测端只保留anxious类别的置信度输出其他类别全部屏蔽这比跑一个老大的分类网络再写一堆分支判断要省事得多。2.2 六类标签的判定边界这是数据工程里最难的部分标签体系定下来容易真正难的是判定标准。我翻了不少动物行为学资料也蹲了几十个不同品种的家猫视频最后把标签定义收敛成了下面这套可操作的规则标注人员拿到就能直接对照执行标签类别行为学特征典型场景标注要点calm平静瞳孔正常或稍缩、耳朵自然竖立或微微前倾、胡须放松、身体姿态舒展趴窝、缓慢眨眼、蹭人框体完整包裹头部及肩部alert警觉瞳孔扩张、耳朵竖直且明显向前转动、头颈抬高、身体微伏听到异响、盯视窗外飞鸟必须能看到耳朵形态否则按calm处理anxious焦虑耳朵向两侧压平飞机耳、瞳孔忽大忽小、尾巴快速摆动、身体压低贴近地面去医院、吸尘器噪音、陌生人靠近耳朵压平是强特征侧面角度也建议标注aggressive攻击性瞳孔极度收缩、耳朵呈扁平后旋、张嘴露牙、身体弓起毛发竖立领地冲突、被强行抱抱时挣扎有明显咧嘴或弓背才标注避免与playful混淆playful玩耍性瞳孔扩张明显、耳朵前倾、前肢伏地臀部微抬、尾巴缓慢晃动逗猫棒狩猎游戏、追逐玩具前肢伏地动作幅度大标注框适当外扩sleepy困倦眼睛半闭或全闭、耳朵自然侧向、头部下垂、身体蜷缩晒太阳、夜晚长时间趴卧闭眼是核心特征睁眼状态一律不标这里我得专门说一句sleepy和calm是标错率最高的一对。标注新手容易把安静趴着直接标成sleepy但猫在calm状态下眼睛是睁开的、耳朵是自然向上的闭眼才算sleepy。我在标注规范文档里专门放了一组对比图一个看起来闭着眼但实际因为光线问题拍摄模糊的案例专门用来考察标注人员的理解程度——10个人里有3个人第一次会标错。2.3 类别平衡策略3200张图怎么保证小猫不偏科3200张听着数量不小但分到六个类别里就有点吃紧了而且真实场景下calm和sleepy天然多aggressive和anxious难得拍到一次。如果不做干预训练出来的模型会对高频类别过拟合遇到真正需要警觉的异常情绪反而不敏感——这恰恰是产品最有价值的功能点做废了就本末倒置了。我的处理思路分成三步采集配额控制。先定目标aggressive和anxious各不低于450张calm控制在700张左右其余三类在500-550张之间。同时注意同一个体的重复样本控制同一只猫最多贡献不超过总样本量的15%避免模型记住了某只猫长什么样而不是学会了判断情绪。相似样本去重。3200张里大量是视频抽帧得到的相邻帧之间相似度极高。我用感知哈希做了一遍粗筛再人工复核去掉高相似度的冗余样本。没有这一步实际有效信息量可能只有1500张的水平。困难样本补充。报警场景下人脸情绪判断容易猫恰好相反——越是模棱两可的样本越有价值。我专门保留了一批耳朵微压但还没到飞机耳瞳孔略放大但姿态自然的过渡状态样本它们的类别置信度天然会低一些但对模型决策边界的打磨作用很明显。这类样本我在后处理时单独拉了个目录方便后续迭代时观察。3. 数据采集与标注实操从原始视频到YOLO格式txt文件的完整链路3.1 数据来源组成与版权边界数据来源这块我踩过一个教训先说结论禁止直接批量爬取他人拍摄的宠物图片做商用模型这是在给自己埋雷。我自己获取数据的渠道主要有三条自摄为主用两台家用摄像头一台广角放客厅、一台长焦聚焦猫窝连续记录了12只不同品种家猫45天的生活素材分辨率按1080p、码率8Mbps配置确保抽帧后保留足够的细节纹理。授权合作联系了3个宠物博主获得他们公开视频素材的二次加工授权限定用于学术与产品原型验证。博主手上的素材多样性确实比我一个人拍强尤其是户外半散养猫的状态。公开数据集交叉补充部分公开宠物数据集中带有行为状态注释的样本但我只用其中场景、光线与目标情绪状态匹配的部分并且重新标注不复用原始标签。不建议一上来就闷头采集先盘一盘手头有什么素材、缺哪几类状态再决定补采的方向效率会高很多。我第一版采集就犯了拍了一堆calm和sleepy的错aggressive缺口到后期才追回来被迫多花了近十天。3.2 标注规范一个人标还是多个人标我强烈建议至少两个人独立标注、互相复查单人标容易陷入前后标准漂移。比如第一天你还严格执行耳朵压平才算anxious标到第五百张的时候疲劳了可能看到压低一半就顺手标了。我试过单人标300张之后用代码统计类别分布和框大小分布发现越往后框体尺寸越不稳定。实操中我给标注员的信息输出包含四部分标签定义表就是上一节那张表打印出来贴显示器旁边每类各20张的标准样例图做锚定参考一条硬性规则框的左边界必须从猫咪左耳外侧开始右边界到右耳外侧下边界到前肢肘部位置。尾巴和后半身不强制包进框里因为情绪特征主要集中在头部及前肢一条禁止规则同一张图里出现两只及以上猫时如果情绪不同只标注位置居中或情绪更明显的那只。标注重叠框会让YOLO的NMS后处理变得混乱而猫之间的互相遮挡本来就容易造成标签噪声标注工具的选型labelImg和老牌的labelme都行。考虑到我们要输出YOLO格式我直接用LabelImg的YOLO模式输出即txt省一步转换。顺便插一句LabelImg属于需要本地Python环境跑的工具如果你不想在环境问题上消耗时间也可以考虑X-AnyLabeling这类带界面渲染的工具标注体验会流畅一些不过输出格式需要做一次转换脚本。3.3 格式转换与目录结构后期省心全靠这一步标注完的原始XML还是其他格式最终统一转成YOLO格式。转换脚本本身不复杂核心就做三件事解析标注框的左上角坐标和宽高像素值除以图片宽高得到归一化的x_center、y_center、width、height写入txt文件每行一个目标格式为class_id x_center y_center width height目录结构我个人习惯这样的组织方式dataset/ ├── images/ │ ├── train/ # 2240张 │ ├── val/ # 640张 │ └── test/ # 320张 ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── data.yaml # 类别配置文件 └── classes.txt # 辅助类别对照train/val/test的比例我按7:2:1分但有一个细节划分前必须按视频片段来源分组不能随机打乱全局再切。同一个视频片段抽出来的帧之间相似度太高如果一部分进了训练集一部分进了验证集评估指标会虚高到没参考价值。所以我是按视频段先分组然后整段分配。还有一个小点txt文件名必须和对应图片名完全一致包括扩展名前缀否则YOLO训练时找不到标签。这种最低级的错误我确实犯过——有一次批量重命名图片时把后缀从.jpg改成了.JPG标签没跟上训练时loss直接飘了。3.4 data.yaml的配置写法YOLO系列训练时会读一个data.yaml内容很直白train: dataset/images/train val: dataset/images/val test: dataset/images/test nc: 6 names: [calm, alert, anxious, aggressive, playful, sleepy]有一点容易踩坑names列表的顺序必须和类别ID一一对应不能乱。我见过有人names写成[calm, alert, ...]但标注txt里类别ID是从1开始的这个偏差会导致全部标签错位。训练前随手跑一下可视化脚本把标注框和类别名画在图片上看看这个习惯能帮你挡住一大半低级错误。我自己会用OpenCV写个20行的可视化脚本随机抽200张训练图画出框和类别名一张张翻看。这玩意儿花不了半小时但能发现很多代码层面查不出来的问题比如某个类别的框整体偏大偏小、某个视频段的框体坐标异常偏移。4. YOLO模型选型与训练调参的实测记录4.1 用v8还是v9别只看榜单先看你跑在什么硬件上YOLOv8是Ultralytics官方一直维护的版本生态成熟文档多出问题好搜YOLOv9引入了可逆分支Reversible Branch和新的ELAN结构理论精度更高但依赖较新部署生态没那么稳。我在3070显卡上跑了对比结论是如果推理设备是Jetson Nano或树莓派这类边缘盒直接用YOLOv8n参数量3.2M左右检测头轻实测单帧推理在Jetson Nano上能做到50-80ms如果服务端跑且对精度要求更苛刻试试YOLOv9t或v9smAP50-95有3-4个点的提升但推理时间会翻倍如果只是做产品原型验证别纠结直接v8s起步快速跑通流程再谈优化。我最终是拿v8s做了全套训练与评估的基线因为v9的导出部署工具链当时还差一点火候。4.2 关键训练参数记录这些数值我是怎么试出来的直接把我调参过程中的一组参考配置贴出来注意这组配置是适配家庭摄像头2米视距、猫主体占比约30%-60%的场景换场景不能无脑抄Ultralytics的YOLO配置项一般小写task: detect mode: train model: yolov8s.pt epochs: 200 patience: 30 batch: 16 imgsz: 640 optimizer: AdamW lr0: 0.0005 lrf: 0.01 momentum: 0.937 weight_decay: 0.0005 warmup_epochs: 3 warmup_momentum: 0.8 box: 7.5 cls: 0.5 dfl: 1.5 mosaic: 1.0 close_mosaic: 10 fliplr: 0.5 scale: 0.5几个关键项我说下依据imgsz640是我对比了320/480/640三档之后的折中。宠物行为摄像头里猫脸区域普遍偏小480下小目标的召回率掉得明显提到640后耳朵、瞳孔这些情绪关键部位的特征能更好保留。再往上提输入尺寸对GPU显存和推理延迟都压力山大而且一个肉眼可见的收益变化不大。epochs200配合patience30宠物行为数据集的类别分布天然带有严重不平衡训练到中期很容易出现过拟合征兆。我试过直接跑150个epoch到结束验证集mAP在120个epoch就开始波动加了patience之后模型会在指标不再提升时自动停掉也方便我回去对比那一轮的权重。optimizer用AdamW而不是默认的SGD宠物行为数据里有大量遮挡和低对比度样本AdamWSGD的低学习率组合在小数据集上收敛更稳。如果你从头预训练而不是微调且数据规模很小我建议优先试试AdamW不要直接用SGD硬啃。warmup_epochs3预训练权重虽然是COCO上训的和猫脸场景差异还是大前几个epoch用低学习率先让模型适应目标域的梯度方向能避免初始阶段loss爆炸。提示上面这组参数是基于我的数据集规模和硬件一张RTX 3070显存8G摸索出来的batch16配imgsz640刚好处于显存边缘。如果你的显卡只有6G显存建议把batch降到8或12必要时把imgsz降到544不要强行跑大尺寸否则训练中途OOM会让你心态爆炸。4.3 训练过程出现BN崩溃与loss异常时的排查思路训练过程中最大的惊吓是跑到第62个epoch时box_loss突然从1.5掉到0.3然后cls_loss一路飘红。BatchNorm的running mean与running var出现极端值直观表现是梯度爆炸、loss断崖、输出置信度全部归零或全部拉满。排查链路我按顺序走先查数据把当前迭代批次里所有图片标签重新可视化确认没有出现标注框尺寸为0或归一化坐标负数、宽高异常的坏数据。再查学习率打印每个epoch实际生效的lr确认warmup结束后的lr_schedule是否按预期下降。我这次的问题就在于warmup_epochs设成3但warmup_bias_lr相关配置没对齐导致前几轮学习率峰值过高。然后查batch size与BN统计量批次太小的情况下BN的统计量会剧烈波动尤其是1-2张的极端情况BatchNorm反而成为扰动源。我把batch稳定到16并把mosaic关闭的最后10个epochclose_mosaic10用来稳住BN的滑动均值。最后切到EMA或加载checkpoint继续Ultralytics训练中途会自动保存best.pt和last.pt。我在第65个epoch接上last.pt、降低学习率到2e-4后重跑后面就顺了。排查BN崩溃的逻辑本质上就是先判断是数据源问题、学习率设置问题、结构问题还是仅仅因为批次太小、随机波动。不按顺序来一上来就改结构或放大batch会把问题带偏。4.4 Mosaic增强在宠物行为数据上的坑Ultralytics默认开着Mosaic增强把四张图拼成一张训练对小目标检测确实友好因为拼接后目标相对尺寸会缩小强迫模型学习更鲁棒的特征。但这里有一个猫情绪检测特有的坑拼接产生的人工边界会切掉猫耳朵。猫情绪判断高度依赖耳朵形态耳朵是最大的情绪信号源压平、直立、外翻各代表不同状态Mosaic把四张图随机缩放拼接时猫的耳朵区域经常被裁掉一半等于在强标签上引入了巨大的噪声。我的处理办法是mosaic保留但只开1.0概率同时把close_mosaic提前到第10个epoch结束让最后的训练阶段完全用原始图片精调。另外fliplr我保持0.5因为猫的左右耳形态本就对称水平翻转不会改变情绪语义但vertical flip我一定关掉——一只倒过来的猫在真实场景几乎不会出现纯属浪费模型学习能力。5. 评估结果与常见看起来没问题但实际上有问题的细节5.1 说清楚指标mAP50和mAP50-95各说明了什么训练完看指标时我遇到一个比较迷惑的现象验证集mAP50到了89.6%mAP50-95却只有56.3%。直观感受是这也差太多了。这两个指标覆盖的任务难度不同。mAP50只要求预测框和真实框的交并比IoU达到0.5就算检测对框稍微偏一点问题不大——对是否检测到猫比较友好mAP50-95则是在IoU从0.5到0.95的多个阈值下的平均结果框偏移一点点分数就会掉。猫情绪动作幅度大标注框的边界本就带主观性打到mAP50-95的90分几乎不可能连60分以上都得反复打磨框体和数据。情绪检测场景里框的精度直接决定后续情绪状态的判读稳定性所以我会更看重mAP50-95。56%并不代表模型不可用但说明框的位置还有不小的抖动。常见补救手段是把标准调高后再做一次hard negative mining——把置信度在0.3-0.7之间、且框内情绪特征不够明显的样本集中挑出来重新审视标签看是不是类别定义模糊导致模型前后摇摆。5.2 混淆矩阵总和不是1的误会用Ultralytics打印混淆矩阵时很多人发现每个格子的小数加总不是1精确率Precision和召回率Recall的曲线看着跟论文里对不上觉得出了问题。实际上YOLO输出的混淆矩阵在默认情况下没有做按行归一化展示的是实际为A类但被预测成B类的计数除以样本总数或者按行归一化至每一类的召回率。不同版本默认行为还不一样。我的建议是不要纠结这个总和看两个重点一是对角线数值是不是各类别里最大二是anxious误判成aggressive这类跨类错误多不多。我的训练结果里最扎眼的错误就是anxious被误判成aggressive行为学上这两类确实有交集耳朵后压、身体紧绷模型自己也无能为力。要压这个问题光靠调阈值没用得回到数据层面补充过渡状态样本或者在损失函数中为混淆严重的类别对加大惩罚权重。5.3 重叠框和NMS参数为什么一个猫会出两个框YOLO的最终输出通常要过NMS消除重复框。但如果猫的某个部位比如尾巴与身体形态差异很大检测头可能同时产出猫全身框和猫头加强框如果NMS阈值设得偏松两个框都会被保留视觉上就像一只猫身上标了两种情绪。我在跑场景视频测试时明显看到两种想要的效果倒挂的猫身体框和头部框错位、睡姿猫全身框叠在头部框上。解决手段不复杂调低NMS的IoU阈值从0.45降到0.3更激进地合并重叠结果。但阈值调太低会把同一只猫前后帧的检测框也合并掉需要在实机里反复试。另一个思路是约束输出类别——如果你只需要anxious/aggressive报警那就在后处理脚本里把不关注的类别直接置零检测框数量会少很多NMS的竞争压力也随之减小。6. 从单帧检测到宠物场景应用几个必须在部署前想清楚的问题6.1 情绪是连续状态而不是单帧快照这是我做完整套流程后自认为最有价值的一个体会。单帧检测的准确率再高直接拿它做实时情绪判断依然不够稳——猫的anxious状态往往持续好几秒中间偶尔有一帧被误判成alert完全正常。如果你拿单帧结果直接触发报警器一天得误报几十次。工程化做法是引入一个轻量的时序平滑层维护一个长度为10帧的类别置信度缓冲区只有当某个类别连续N帧比如5帧的置信度都超过阈值时才触发事件对aggressive和anxious这类关键事件触发时截取前后各2秒的视频片段留证。这样做之后误报率明显下降。模型单帧判断本来就是概率输出时序平滑利用的是情绪状态在时间上的连续性相当于白送了一个强先验。6.2 边缘部署的模型导出与INT8量化模型跑通后真正交到产品手里时考虑最多的是部署成本。我用TensorRT把YOLOv8s导出为FP16引擎在Jetson Orin Nano上单帧推理大约35-45ms可以支撑15-20帧/秒的实时分析。如果还想更快走INT8量化但得先准备校准数据集——拿500-1000张训练集中的代表性图片做校准量化后精度一般会掉1-3个mAP点换来的是推理速度大约能再快一倍。量化初期我用PyTorch直接加载引擎做基准测试时踩了个坑TensorRT的engine文件在GPU型号和CUDA版本不一致的环境上不能通用换设备后必须在目标机器上重新生成。这块不是模型本身的问题是部署流程里常见的环境包袱。6.3 多猫场景与遮挡问题老实说这个方向上我们的方案只能做到能用、不够好。多只猫同时入画时两只猫互相遮挡YOLO的检测框会产生惊人的抖动情绪标签也跟着跳变。我目前的做法是加一个按检测框面积排序的逻辑只对画面中最大最完整的猫做情绪输出其他猫暂时忽略——这个策略规避了遮挡问题牺牲的是全场景覆盖率。如果你想做复杂多猫追踪逐猫情绪分析那已经不只是检测任务了得往上叠加追踪比如ByteTrack和身份重识别ReID。这块我还在折腾暂时给不出成熟经验。7. 写在最后的一点体会数据集转起来的那一刻值回所有掉过的头发。我回过头看整个项目最庆幸的是当时把精力重点放在了标签定义的严谨性和采集分布的均衡性上这两个环节直接决定了模型上限。很多人一上来就急着调YOLO的网络结构改C2f、加注意力机制但数据本身的毛病不改改结构全是空中楼阁——我见过太多成果汇报PPT很好看、盲测现场翻车的项目了。另外相信不少人和我曾经一样在自己造数据集还是搭一个现成数据集凑合跑之间纠结过。我的建议是只要你的目标场景有空缺就值得花力气造一份贴合场景的数据。3200张看着不多换算一下也就是两三周的业余时间投入但它带来的模型效果提升远比在通用数据集上刷几个点的分数有价值。数据集的质量决定了算法的天花板这句话放到猫情绪检测上再合适不过。
返回列表