ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于YOLO的猫情绪检测数据集构建与训练实战

基于YOLO的猫情绪检测数据集构建与训练实战 1. 这个数据集的诞生逻辑猫情绪检测为什么难做1.1 为什么非要做一个专门的猫情绪数据集养过猫的人都知道读懂猫这件事本身就是一个门槛极高的技能。耳朵的角度、尾巴的摆动频率、瞳孔的收缩状态、胡须的走向这些细节组合在一起才构成一只猫真实的情绪表达。但人眼观察有个天然瓶颈主观、易疲劳、容易被环境和灯光干扰。放在宠物医疗、行为矫正、智能喂养等场景里这个主观性就变成了致命短板——同一个视频片段两个铲屎官可能给出完全相反的情绪判断。所以猫情绪检测这个方向本质上是想用计算机视觉替代人的主观判断把这只猫现在大概什么状态变成这只猫的焦虑概率是0.78、放松概率是0.52这样的量化输出。这个转变听着简单做起来却非常麻烦。原因在于猫的情绪不像人脸那样有标准化的表情组件可以对齐人脸的FACS系统里有明确的嘴角上扬眉毛上挑等编码单元猫压根没有这套体系。我做这个3200张的YOLO宠物行为数据集起因其实很朴素市面上的宠物数据集大多以猫狗分类品种识别为主真正针对猫情绪状态的检测数据少得可怜。偶尔找到几个学术数据集要么是单猫室内场景要么只标注了开心/生气两个粗粒度标签根本不够业务落地用。宠物摄像头、智能猫砂盆、自动喂食器这些产品要做的是在实际家居环境里实时判断猫的状态这需要大量多场景、多角度、多光线条件下的标注数据。于是自己动手造数据就成了唯一可行路径。1.2 3200张的规模是怎么定下来的很多初学者一上来就想集齐上万张图这个心态我特别理解但真没必要。选择3200张不是随手拍脑袋而是从模型复杂度、标注成本、场景覆盖三个维度算出来的。先说模型复杂度。YOLO系列尤其是YOLOv8这类anchor-free结构对数据量的需求比老一代YOLOv5要友好一些。单类别检测任务在3000张左右的数据量下配合合理的增强策略完全可以把mAP0.5做到0.9以上。如果是多类别细分任务比如同时检测紧张、放松、警戒、攻击四种情绪3200张也刚好能覆盖每个类别800张左右的底线。再少就容易欠拟合再多对单类检测来说边际收益就很低了。然后是标注成本。这个不能只看标注AI的费用还要算上自己团队的人工复核时间。3200张图平均每张包含1到3只猫每只猫要框出关键区域并打上情绪标签熟练标注员一小时能处理80到120张已经很不错了。算上质检、返工、边界情况讨论3200张刚好是一个一个人两周能搞定的规模。数据量太少模型学不到泛化特征太多又拖慢项目节奏3200张是我反复权衡后认为最适合起步的平衡点。最后是场景覆盖。我特意把数据拆成了室内主卧、客厅、阳台、猫笼、户外庭院五个子场景每个场景600到700张。再叠加白天自然光、夜晚补光、黄昏逆光三种光照条件形成了大约15个场景光照组合。每个组合下再收集不同花色、不同体型的猫保证模型不会只对某一种颜色的猫有效。维度数量说明总图片数3200约2000张直接拍摄1200张现有视频抽帧标注框数约5400单张最多标注4只猫子场景数5室内/客厅/阳台/猫笼/庭院光照条件3自然光/夜晚补光/黄昏逆光情绪标签4放松/警觉/紧张/攻击这个结构做完之后我自己拿未参与训练的200张图片做了一轮盲测发现模型在警觉和紧张这两个标签上特别容易混淆。后来拆了数据才发现问题出在警觉场景里猫的瞳孔状态和紧张高度相似只靠框选身体区域根本区分不开。这个坑在后面标注策略调整部分详细讲。1.3 整个流程拆开看从拍摄到出图打造一个能用的数据集流程上大致是素材采集 - 关键帧筛选 - 清洗去重 - 标注 - 质检 - 划分数据集 - 格式转换。每一步都有坑我一个个说。素材采集这块我建议不要只依赖相机连拍。猫的耐心非常有限你举着相机对着它它要么跑开要么盯着你看拍出来的全是警觉状态数据分布直接歪掉。我后来换了策略用三台固定机位在不同角落同时录制人不在现场让猫在自然环境下活动。录完的视频再通过ffmpeg按每秒5帧抽帧然后丢进自己写的一个小脚本里做质量过滤把模糊的、大动作残影的、重复率过高的帧直接剔掉。这一步能去掉大约70%的废帧保留下的都是相对清晰、有标注价值的画面。清洗去重这块容易被忽略。连续帧之间的相似度极高如果直接把每秒5帧全送进标注模型会被大量近似样本喂偏。我用的方法是基于感知哈希计算帧间相似度相似度超过0.92的只保留一张。这样实际进入标注流程的图片从9000多张压缩到了4200张左右再人工筛一遍留下3200张。整个过程有点像做菜前的食材处理——不好的菜叶子先摘掉看着相似的重复食材合并掉剩下才是真正的有效成分。为什么不在这一步直接把没有猫的图片删掉因为训练YOLO时适度加入一些负样本不包含目标物但场景相同的背景图可以显著降低误检率。所以我保留了大约150张空背景图作为负样本它们没有标注框但在训练时会被视为背景类帮助模型学会没有猫的时候不要瞎预测。2. 标注质量是数据集的命根子类别定义与边界规则2.1 情绪类别怎么划分才不算瞎分标注之前最难的一步其实是定义什么是情绪。人眼能看出猫好像不太高兴但这件事落到像素级标注上必须转化为可被边界框框选的、可被模型区分的视觉特征。我最终把情绪划分为四类放松身体舒展尾巴自然下垂或缓慢摆动瞳孔正常或略收缩耳朵直立微向前警觉身体静止但肌肉紧绷瞳孔扩张耳朵向前转尾巴僵直或慢慢扫动紧张耳朵向两侧压低或呈飞机耳瞳孔放大身体弓起或紧贴地面尾巴快速甩动攻击耳朵完全压平贴向后脑勺瞳孔极度收缩背部弓起有出爪或嘶吼动作这个分类有三个设计逻辑。第一它覆盖了猫在居家环境中绝大多数可见的行为状态宠物主人最常遇到的它到底怎么了基本都能归到其中一类。第二每一类都有至少两个以上的视觉特征可以互相印证比如紧张我要求同时看到耳朵压低和瞳孔变化只满足一个特征不标注。这就避免了模糊样本把模型带偏。第三我没有设置中立或未知类因为这类无意义标签会稀释模型的判别能力遇到实在判断不了的情绪状态就统一标记为废弃并从数据集中剔除。2.2 标注框的边界规则框多大、遮挡怎么办、耳朵算不算刚开始做标注的时候我犯过一个特别典型的错误让标注员把整只猫框进去。后来训练出来的模型出现了一个很有意思的现象——在攻击和紧张两个类别上精度尚可但放松类经常把猫睡觉时蜷缩的身体和紧张时弓起的身体搞混。原因是整只猫的边界框把大量背景和身体形变信息混在一起模型学到的不是情绪而是体型轮廓。排查之后我改用了部件级标注策略边界框只框头 前置躯干这部分区域。为什么这么定因为猫的情绪表达最集中的区域就是头部耳朵、瞳孔、胡须和前腿肌肉紧绷状态、准备攻击的姿态而后半身的尾巴虽然也有表达价值但YOLO这类单阶段检测器对细长形变目标的定位能力有限把尾巴并进框里反而会引入大量噪声。当然尾巴状态我没有完全丢弃在标签的附加属性里单独记录了一个尾巴状态字段竖直/下垂/快速甩动/缓慢摆动。这个字段在训练时用不上但在后续扩展更细粒度行为识别时可以直接当弱标签用。遮挡问题我定了一条规则目标可见面积大于50%才允许标注。比如猫钻进猫窝只露出一个头这种情况不标。因为这种被大面积遮挡的目标即使在训练集里教给模型模型也会学到头紧张这种荒谬的捷径。但遮挡在30%到50%之间的保留并且正常标注这能让模型在遇到部分遮挡的真实场景时依然保持鲁棒。耳朵算不算进框算。但前提是耳朵没有被完全压平到不可见。攻击状态里猫的耳朵会完全贴到脑后从正面视角几乎看不到这种样本我不会因为耳朵不可见就放弃标注因为瞳孔和背部弓起仍然是强特征。规则是这样定的耳朵只要可见就要求框的上边界紧贴耳尖不可见时上边界以头顶为限绝不为了凑耳朵把框拉高到吞进大量背景。2.3 质检与复核标注员从来不是全部标注这件事一个人做容易疲劳一群人做容易标准漂移。我这边是三人小组标注我作为项目负责人做全局质检。实际执行下来发现一个很有意思的现象三人对警觉和紧张的判断一致性只有71%而对放松的判断一致性高达95%。这个数据反映了一个本质问题——有人把眼睛睁大理解为警觉有人理解为紧张描述性定义在实操中撑不住。解决办法是把定义重新量化。我给每类情绪做了一个特征打分卡标注员必须逐项打分后汇总判定。特征项放松(1分)警觉(2分)紧张(3分)攻击(4分)耳朵位置直立/微前向前竖立两侧压低向后贴平瞳孔状态正常/微缩扩张可见明显放大极度收缩身体姿态舒展静止紧绷弓起/贴地弓背预备尾巴状态自然/慢摆僵直/慢扫快速甩动炸毛/竖直每张图的最终标签由三个维度的总分决定总分4到6分判为放松7到9分判为警觉10到12分判为紧张13分以上判为攻击。这套打分表上线之后三人间的一致性直接拉到了88%。分数落在相邻类别边界上比如总分9分同时符合警觉和紧张的特征时由我人工仲裁并回写标注规范。这时候数据集的规则才是活的而不是文档里躺着的死文字。2.4 数据增强与样本均衡原始数据分布永远不可能完美均匀。我这边放松类因为猫睡着的时候多样本量最大有将近1100张攻击类的样本最少只有400多张。如果不做均衡处理YOLO训练出来的模型会对放松极度偏科。均衡策略我分两级。第一级是训练时的在线增强对攻击和紧张这两类样本在Mosaic增强基础上额外叠加随机角度旋转±15度和随机透视变换把单类的有效训练样本量抬高。第二级是离线层面的针对性采集发现哪类少就额外去找哪类的场景视频抽帧。在这里我特别不建议对图像做简单的复制粘贴式过采样尤其是同一张图不同裁剪再标一遍这种做法对YOLO这种本身已带随机缩放的数据管线来说就是叠加了个寂寞模型权重更新的有效信息量没有任何增加。说到增强还有两个我自己踩过的坑。第一个是不要把HSV色域增强的幅度调得太大。猫毛发的颜色是识别情绪的一个重要辅助特征色相偏移超过±30度后模型会开始把橘猫的放松姿态和灰猫的紧张姿态混为一谈因为颜色变成了不可靠的线索。第二个是Mosaic增强在目标物体很小的场景下容易把目标切没。切没的框会被标注软件自动忽略于是这类样本就悄悄变成了背景图参与训练间接污染了负样本分布。后来我在拼接Mosaic时做了目标面积校验拼接后目标物体的像素面积小于原图的60%就重新拼确保每个目标在增强后的图上依然可辨认。3. YOLO实测用这个数据集训练猫情绪检测模型3.1 环境与预训练模型选择训练环境这块没什么悬念我用的是一张RTX 4090 24G显卡。这套配置对YOLOv8的常规训练完全够用甚至有些富裕。CUDA版本跟PyTorch的版本匹配要注意一下我用的是CUDA 12.1 PyTorch 2.1.1的组合实测下来最稳。做AI训练的人应该都有过这种经历环境装不好后面全是泪。预训练模型我选的是YOLOv8m没有选更大的YOLOv8x也没选最小的YOLOv8n。原因很简单这个数据集的任务是单阶段四分类情绪识别目标不算极小但类别间差异不算特别大需要一个中间规模的骨干网络来平衡速度和精度。YOLOv8m在COCO上的预训练权重里已经包含了对猫这个类别的部分特征响应迁移过来时会有一个很好的起点。YOLOv8x虽然精度可能再高一点但推理速度下降明显对后续要做实时宠物摄像头场景来说性价比不高。3.2 训练参数详解img、batch、epochs为什么要这样设训练参数的设置是最能体现实操经验的地方。我把我的参数列出来逐项解释为什么要这样设。# dataset.yaml path: ./cat_emotion_dataset train: images/train val: images/val test: images/test names: 0: relaxed 1: alert 2: tense 3: aggressive图片尺寸img640。这是一个经典的平衡点。猫的面部细节虽然重要但情绪判断依赖的是耳朵角度和身体姿态这类中尺度特征640分辨率下这些特征足够清晰。如果改成1280训练时间几乎翻倍但精度收益不到一个点不值。batch32是我在24G显存下的选择。YOLOv8默认的自动batch检测会给出一个更保守的值但手动调高能让梯度估计更稳。如果你显存不够可以降到16但千万不要低于8否则BN层的统计量会因为batch太小而出现剧烈震荡。关于BN崩溃的问题网上有大量讨论我在实践中的体感是batch小lr大是团队最容易撞上的两个坑。epochs200看着多但因为数据集只有3200张实际上一轮epoch跑完只需要两三分钟200个epoch总共也就5个小时左右。这里有个经验值当验证集mAP连续20个epoch不再上升时模型已经吃饱了不需要硬跑完全部epoch。我实际训练在第160个epoch左右就触发了早停。optimizerSGD而不是AdamW。做YOLO训练的朋友应该都知道YOLOv8官方给的ultralytics默认优化器是SGD配合特定的学习率策略和weight decay收敛效果在目标检测任务上通常比AdamW更稳。我自己做过对照组实验同样的数据、同样的epochSGD的最终mAP比AdamW高了约2.3个百分点。学习率lr00.01、lrf0.01也就是初始学习率0.01最后衰减到初始值的1%。3.3 训练日志与关键指标怎么看很多人训练完只看一个mAP就完事了这个习惯非常浪费。YOLOv8训练过程中输出的指标里我更看重下面这几个train/box_loss边界框回归损失这玩意稳不稳直接反映了标注框的质量。如果这个loss降不下去大概率是标注框本身有大量不一致。train/cls_loss分类损失这个直接反映模型对四个情绪类别的区分能力。它和val/cls_loss之间的差距如果超过30%说明过拟合已经开始了。metrics/mAP50(B)IoU阈值0.5下的平均精度对单类检测来说这个指标能到0.9以上就算优秀。metrics/mAP50-95(B)更严格的指标跨多个IoU阈值计算。这个指标在0.7以上意味着模型的框定位精度很好我的实测结果是0.751。val/d_box_loss验证集上的边界框损失。当它开始回升而训练集上的损失还在下降这就是经典的过拟合信号。我自己训练完的最终指标如下指标值mAP0.50.912mAP0.5:0.950.751Precision0.886Recall0.903单图推理耗时(RTX4090)6.2ms从指标上看警觉的Recall偏低只有0.86左右这说明有些警觉样本被当成了紧张。这个结果其实在数据层面就能找到原因警觉和紧张在特征打分卡上的总分经常相邻边界样本本来就多。后来我把alert类评判规则中尾巴僵直这一条改成了只参考但不作为关键判定项之后重新标注了一批边界样本再训练把警觉的Recall拉到了0.89。3.4 完整训练命令与一次跑通的实操记录训练命令本身不算复杂关键是要把参数都写在命令行里方便复现yolo detect train \ datacat_emotion_dataset/dataset.yaml \ modelyolov8m.pt \ imgsz640 \ batch32 \ epochs200 \ optimizerSGD \ lr00.01 \ lrf0.01 \ patience20 \ cacheTrue \ scale0.5 \ mosaic0.8 \ hsv_h0.015 \ hsv_s0.25 \ hsv_v0.25 \ seed42 \ project./cat_emotion_run \ nameexp01这里有几个参数我想单独强调一下。cacheTrue会把数据集预加载到内存里3200张图全部加载大约占用12G内存换来的是每个epoch的训练时间从3分钟降到1分半值。但如果你机器内存只有16G这个参数就别开了会直接OOM。scale0.5控制的是仿射增强的随机缩放范围这个值太大容易让猫的框在增强后变得过小。mosaic0.8表示80%的批次使用Mosaic拼接剩下的20%使用普通增强兼顾了增强强度和数据真实性。hsv_h0.015这一项特别提醒一下就是前面说的色调偏移不能设太大0.015是个安全值。训练结束之后用yolo detect val做一次完整的验证评估再把验证集上预测错误的图片单独导出来翻一遍。这一步我从1200多张验证图里翻出了37张错检图其中有12张确实是因为图片模糊导致的边界争议剩下25张是特征表达不够清晰的灰色地带。这些图片我没有简单丢弃而是单独归入了一个hard_examples文件夹留着做后续模型迭代时的专项测试集。这个做法帮我省了大量时间——下次改模型结构或标注规则时只需要跑一遍这个硬样本集就能快速判断新模型有没有真正变强。4. 常见问题与排查技巧实录4.1 训练集/验证集/测试集划分的坑数据集划分看似简单实际上最容易埋雷。按图片随机划分的问题在于同一个视频片段相邻帧非常相似随机划分会把高度相似的帧同时分进训练集和验证集导致验证集指标虚高实际部署时一测就崩。我采用的划分方法是按视频片段划分而不是按单张图片划分。具体来说把所有图片按来源视频ID分组整组划分到同一个子集保证同一个视频的帧永远不会同时出现在训练集和验证集中。最终划分比例是80%训练、10%验证、10%测试。测试集完全独立只在最后模型评估时才碰一次平时训练过程中绝不使用。经验划分前先按视频分组再随机分配组而不是直接洗牌图片测试集一定要保留独立样本不要用验证集凑数如果数据来自多个会话确保各会话分布均匀落在三个集合中4.2 类别不平衡引发的loss异常训练到第40个epoch时我注意到cls_loss突然出现了一次上调。排查下来发现是攻击类样本太少Mosaic增强的随机组合经常把一个batch里全是放松样本导致模型对整个攻击类几乎没有梯度更新。解法分两步。第一步是给dataset.yaml里每个类别设置weight参数用class_weight给少数类加权。计算公式很简单weight_i N_max / N_i即样本数量最大类的样本数除以当前类的样本数。这样攻击类400张的损失权重就是放松类(1100张)的2.75倍。第二步是自实现一个BalanceMosaic每次做Mosaic拼接时确保四张图里至少有一张来自少数类。实际操作中我只用了第一步就把攻击类的mAP从0.74拉到了0.85。第二步的BalanceMosaic实现起来要多写几十行代码在数据量更大的项目里非常有用3200张这个规模其实用简单的加权就足够了。4.3 小目标猫脸检测不到的排查模型在整体猫框上的检测效果很好但当一个场景里猫距离镜头非常远比如庭院场景中猫在5米开外时YOLO经常漏检。漏检的原因不是模型结构问题而是数据问题远距离样本中猫在640x640的图上只占约25x25像素太少。排查过程是这样的我先统计了验证集里所有标注框的面积分布发现小于32x32像素的框只占总数的3.7%而这3.7%的框恰好贡献了超过11%的漏检。模型根本没有在训练阶段见过足够多的小目标。对策我用了两条。一是把img调到960让远距离猫在图中占更多像素再训练一轮二是数据增强时对原图做0.5到0.8的随机下采样再贴回大图人为制造更多小目标样本。两条路都跑通后小目标Recall从0.63提升到了0.74。这里有个附加提醒调大img后batch要相应调小我当时从32降到16不然显存不够。4.4 数据集过拟合与泛化的经验3200张数据集在训练时最容易碰到的问题就是过拟合。我判断过拟合有一个自己的经验标准val/cls_loss和train/cls_loss的比例超过1.5倍就要警惕了。实际在训练到120个epoch左右验证集loss开始出现轻微反弹。对策有三个按优先级排序早停patience20连续20个epoch验证指标没提升就停我当时在160个epoch停了更强的正则把weight_decay从默认的0.0005调高到0.001实测能拉低过拟合程度更激进的数据增强这个要多试增大scale和mosaic的随机性值得强调的是正则化其实隐藏着一个取舍权重衰减太大模型容错能力会下降看到稍微有点模糊的猫就直接漏检太小则过拟合严重记住的都是训练集里的死特征。我最终停在0.0008这个值上既能压住过拟合又不会让模型变得太脆。从泛化角度说这个数据集最大的好处是场景多样性足够。我把训练好的模型拿去过摄像头场景实测室内环境的表现基本和验证集一致搬到朋友家完全不同装修风格的客厅里mAP下降了大约7个百分点但在可接受范围内。这种泛化损失主要来自墙体颜色和家具纹理这些背景差异后续如果要进一步提高可以在训练时加入更多背景随机的增强策略或者用更高比例的Mosaic把背景切成碎片让模型不那么依赖整体背景特征。5. 部署与落地从实验室到真实场景5.1 视频流检测的工程化模型训练好了只是第一步真正落地到宠物摄像头或智能家居设备上还隔着一层工程化的距离。首当其冲的是帧率问题。YOLOv8m在RTX 4090上单帧推理只要6毫秒但换到嵌入式设备如Jetson Orin Nano上单帧耗时直接飙到45毫秒左右。实时视频流如果每秒处理10帧CPU和NPU就接近满载了。实际落地我推荐三层策略。第一层是抽帧策略不逐帧检测而是每秒抽2到3帧做检测。猫的情绪变化本身是缓慢过程每秒抽3帧足够捕捉到放松变为警觉这类状态迁移。第二层是目标追踪用ByteTrack之类的轻量追踪器把同一只猫在多帧中的位置关联起来避免每帧重复检测产生大量冗余计算。第三层是时序平滑情绪判断不依赖单帧输出而是取最近5帧的平均置信度能显著抑制偶发性的误检抖动。部署格式上我习惯先把PyTorch权重转成ONNX再转TensorRT的engine文件。只转ONNX的话在Jetson上跑也就30多毫秒一帧转成TensorRT后能压到15毫秒左右。关键在于转ONNX时要固定模型的输入尺寸我固定为640x640并且把NMS的置信度阈值和IoU阈值在转换前就写进模型图里这样推理时少一层参数传递也少一道出错的可能。5.2 移动端轻量化的取舍如果目标是跑在手机App或入门级智能摄像头上YOLOv8m的体量就太沉了。这时候要把模型换成YOLOv8n或者干脆上YOLOv8n的剪枝量化版本。YOLOv8n在这个数据集上的实测精度比v8m低了大约5个点但模型体积只有v8m的1/4FP16量化后Flash推理能稳定跑在25帧以上。对萌宠类App来说这个精度损失通常可以接受因为用户要的是我的猫现在是开心还是紧张这种粗粒度反馈而不是科研级的情绪得分。如果你连FP16量化后的模型体积都嫌大可以走一轮8bit动态量化体积再砍一半精度大概再掉2到3个点。建议工程上预留一个精度实时切换的开关在弱网或低性能设备上自动降级到轻量模型在旗舰上使用全精度模型这样体验和性能都能兼顾。5.3 后续扩展方向与参考价值这个3200张的数据集只是一个起点不是终点。我现在已经在规划两个扩展方向。第一个方向是多猫场景的个体识别叠加情绪判断也就是先在画面里识别出这是大橘还是小黑再单独判断每只猫的情绪状态。这个需要给每只猫建立独立ID并跟踪其情绪变化曲线比单纯的帧级检测复杂得多。第二个方向是结合音频信号做多模态情绪识别猫的呼噜声、嘶吼声、短促喵叫在情绪判断上有很强的互补信息尤其是画面中猫背对镜头时音频几乎成了唯一线索。对打算做类似数据集的人来说我的核心建议只有一句话不要迷信数据量要把精力花在定义清晰、边界分明、场景覆盖合理的标注规范上。3200张图可以做出一个实用的模型前提是每一张图的标注都有明确依据每一个类别的边界都有可执行的判断标准。数据集的真正价值从来不在总数上而在每一张图的质量和每一个标签的可解释性上。另外发布数据集的时候建议连同标注规范和一份失败样本分析报告一起放出来。标注规范帮助别人理解你的标签定义失败样本分析则能帮后来者少走弯路。这两份文档的价值有时候比数据集本身还大因为它们承载的是你踩过的坑和总结出来的方法论。我自己的实操体会是做数据集像做一顿慢炖的汤原料简单但火候、时长、调味每一步都不能省。3200张图从采集到标注再到完成训练前后两周多的时间中间反复修改了三次标注规则才最终得到一个让自己满意的模型。节奏可以快但关键的质检步骤绝对不能跳过——那些看起来多余的一遍复核往往就是决定模型最终精度的分水岭。
返回列表