ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

猫情绪检测数据集全流程实践:3200张图YOLOv8训练与部署的坑与解法

猫情绪检测数据集全流程实践:3200张图YOLOv8训练与部署的坑与解法 先直接说结论这个项目——猫情绪检测数据集3200张图配YOLO训练——不是那种下载个公开数据集、跑通个模型就完事的 demo。真正动手做一遍你会发现难点根本不在训练那几行代码而是情绪本身是一个极度模糊的标注目标。猫不像狗狗开心会摇尾巴愤怒会龇牙动作幅度大、特征清楚猫的情绪表达极其含蓄耳朵转个角度、瞳孔缩一下、尾巴尖微微抖动全是信号。3200张图看着不多但只要能保证每一张的标注质量过硬足够让一个轻量级 YOLO 模型在宠物行为分析这条赛道上跑出可用的效果。我这次做的是四类情绪识别放松relaxed、警觉alert、烦躁agitated、恐惧fearful。用 YOLOv8 训练最终验证集 mAP50 大约在 0.86 左右单张 640×640 推理速度在 NVIDIA 3060 上不到 8ms换成边缘设备也能勉强跑实时。这个结果不算惊艳但在宠物场景里已经属于能落地的水平。这篇博文会完整记录我从数据采集、筛选、标注、格式转换到训练调参、评估迭代的全过程重点写那些文档里查不到的坑给正在做或者打算做宠物行为识别的朋友一个参考。1. 为什么猫情绪检测比想象中难问题和数据一起卡脖子1.1 猫的情绪表达和狗完全是两套逻辑我最早以为猫情绪检测就是个普通的图像分类问题后来发现天真了。狗的情绪信号大部分挂在嘴巴、尾巴和身体动作上一张侧面照就能判断个大概猫不一样它的核心情绪窗口是耳朵、瞳孔、胡须和尾巴尖而且变化非常细微。举个例子一只猫警觉和烦躁的区分点往往是耳朵从正前方转到侧后方那十几度的角度差加上瞳孔从圆形变成竖线。人类看多了猫片都容易误判更别说让模型学。我在采集阶段就发现网上大量所谓猫生气的照片实际是猫在玩耍或者高度专注情绪根本不是人类理解的那种烦躁。这就给数据集的标签体系提出了一个硬要求必须建立一套可执行的、基于猫行为学信号的标注规范而不是凭人类直觉去贴标签。1.2 情绪检测本质上是在检测行为线索的组合模式很多人会问情绪是内在心理状态一张静态图片怎么可能测出来我的理解是我们能检测的其实不是情绪本身而是情绪在当下时刻的外在行为表现组合。比如恐惧的猫往往同时出现飞机耳耳朵向两侧压低、瞳孔放大、身体伏低、尾巴夹紧或炸毛这几个信号同时出现的概率极高。YOLO 这类目标检测模型做的就是从像素里学出这些信号组合的模式。这也是我选择 YOLO 而不是纯分类模型的重要原因目标检测输出的是位置框 类别它天然适合一张图里有多只猫、或者同框出现不同行为片段的场景。我用分类模型试过一张图里两只猫一只放松一只警觉分类模型的输出基本就是灾难因为它默认整张图只有一个语义主体。YOLO 的边界框框住的是某只猫的那一片区域模型学的是这片区域内的情绪特征这更贴近实际应用。1.3 3200张图到底够不够用先说结论对于四类情绪、单一物种猫、不算太复杂的背景条件3200张是一个能跑但不宽裕的量级。如果你的场景是固定摄像头、固定角度比如智能猫厕所、宠物摄像头3200张完全够用如果是全网任意图片、任意角度、任意光线3200张就明显不够至少得上万。我在实际训练中的体会是数据量不够的时候模型学到的往往是背景先验而不是猫的情绪先验。比如训练集里大量放松的猫都趴在沙发上模型就会把沙发猫当成放松的模板换了地板背景就失效。所以我在筛选数据时特意做了场景多样性控制让各种情绪都覆盖至少三到四种不同的背景环境。这一点后面会详细展开。2. 数据从哪里来3200张图的采集、筛选与清洗全链路2.1 采集渠道组合自己拍、合作方提供、公开数据筛选我不会建议你只从一个渠道拿图。单一渠道带来的风格一致性会严重损害模型的泛化能力。我这次用了三个渠道自拍部分约800张用手机和微单在不同光线、不同时间段拍家里的猫和朋友的猫。这部分质量最高因为版权清晰、场景可控还能主动制造情绪变化比如用逗猫棒制造警觉、用吸尘器声音制造恐惧。合作渠道约1200张联系了本地两家猫舍他们日常有大量猫咪活动抓拍。这部分数据最大的价值是场景真实——猫舍里有笼子、玩具、其他猫干扰模型在这种环境里学到的特征更鲁棒。公开数据筛选约1200张从开源宠物数据集、免费图库CC0协议里按关键词筛猫图再人工挑出符合情绪标注要求的。这里一定要确认授权协议商业项目更要注意我只能提醒到这里。2.2 用脚本先做一轮机械筛选3200张图不是拿来就标我会先跑一轮脚本做机械筛选。核心是三个指标清晰度Laplacian方差、人脸/猫脸占比、尺寸门槛。模糊图直接淘汰因为情绪特征的细节瞳孔形状、耳朵角度都糊掉了标了也是给模型添乱。import cv2 import numpy as np def is_blurry(image_path, threshold80.0): img cv2.imread(image_path) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) laplacian_var cv2.Laplacian(gray, cv2.CV_64F).var() return laplacian_var threshold, laplacian_var这个阈值不是固定的我试过在室内暖光下拍的图 Laplacian 方差普遍偏低因为光照均匀导致纹理对比小。建议每个批次单独算一下分布取后10%左右人工再确认一次不要一刀切。尺寸门槛方面我要求单张图最短边不小于800像素因为后面要统一缩放到640训练源图太小的话猫脸区域可能只剩十几个像素再好的增强都救不回来。2.3 清洗环节去掉会让模型学歪的图机械筛选之后是人工清洗。这一层我花了两天时间主要干掉这么几类图带水印、带贴纸、带文字遮挡的。YOLO 的卷积核在浅层学会的是边缘纹理水印这种高频文本图案很容易被错误关联到某个情绪类别上。夸张滤镜和严重美颜的。有些公开图库的猫图用了色调分离、卡通化处理这不是自然图像分布会让模型学到假的颜色纹理。主体占比过小或者被大面积遮挡的。猫只露个尾巴尖、或者被毯子盖住大半身体这种图没法判断情绪标注的人也只能瞎猜。同内容重复图。网图下载经常出现同图不同尺寸、不同水印的变体我用imagehash的感知哈希做过一轮去重再从每个相似簇里保留一张清晰度最高的。清洗完3200张听起来是目标实际我筛完只剩2800多张后期又补拍了几百张才凑够。这里说句实在话数据集宁可少也不能脏。脏图对训练的破坏是指数级的一张错误标注的图可能比十张正确标注的图危害更大因为模型会努力去学那个错误的模式。3. 情绪类别怎么定标注规范与边界争议的落地处理3.1 从五类砍到四类的决策过程最初我设计了五个标签放松、警觉、烦躁、恐惧、好奇。标注到一半我就发现好奇和警觉在静态图片上的区分度太低——猫好奇的时候耳朵也是竖起来、瞳孔也是稍微放大和警觉差别很小。于是我果断砍掉了好奇把这类图归入警觉。这里给所有人一个建议情绪分类的类别数不是越多越好每多一类你需要的数据量和标注成本几乎是指数增长的。四类情绪已经够养活一个实用模型了先跑通再谈细化。3.2 标注规范把模糊的判断变成可执行的清单标注之前我整理了一份标注规范文档核心内容是一个行为信号-情绪对照表。每个标注员判断情绪时必须依据这张表不能凭感觉。情绪类别耳朵状态瞳孔状态身体姿态尾巴状态放松自然朝前或微向外正常大小圆形偏多舒展卧姿或缓行自然垂下或缓慢摆动警觉竖直向前微微转动略放大开始变竖线蹲坐或静止前爪微缩静止或尾尖微动烦躁向后压但未贴平明显竖线收缩身体僵硬耳朵伴随抖动快速抽打或大幅度摆动恐惧飞机耳向两侧压低异常放大或异常收缩伏低贴地躲避姿态夹紧贴近身体或炸毛同时我在规范里加了二选一原则一张图如果同时出现多个情绪信号比如飞机耳但尾巴放松取更严重的那个。这个原则很重要它避免了不同标注员对同一张图给出不同标签的混乱。3.3 多人标注的一致性控制我找了3个人一起标注分两轮进行。第一轮各标各的统计两两之间的一致性Cohens Kappa我记得结果是0.72左右属于中度到高度一致但离我的预期还差一点。把分歧样本挑出来开会讨论后发现分歧主要集中在警觉和烦躁的边界以及放松和警觉的低唤醒状态。讨论之后我更新了规范给放松加了一个硬性条件——耳朵角度必须低于某个阈值目测耳朵与头骨夹角不超过45度给烦躁加了一个排除条件——如果猫同时在做进食、理毛、玩耍等明显行为不得标为烦躁。第二轮一致性提升到了0.85以上这个质量水平我觉得可以接受了。标注工具的选用上我首先用 LabelImg 快速试了一批但矩形框必须完全是四边形遇到猫蜷成一团的图很难框。后来换成了 CVAT它的智能标注自动分割辅助能大幅减少框选工作量。最终导出格式我选的是 YOLO 格式也就是每个图片对应一个 txt每行是类别ID x_center y_center width height坐标是归一化的相对值。4. YOLO训练前的数据准备格式转换、数据集划分与增强策略4.1 手工标注转YOLO格式的细节CVAT 导出 YOLO 格式时有个坑它导出的类别是从0开始连续的整数ID你必须保证 data.yaml 里的类别顺序和导出时的顺序完全一致。我自己第一次就翻过车data.yaml 里写的是 relaxed、alert、agitated、fearful但 CVAT 导出时标签顺序是 alphabetical 的导致模型训练时标签全部错位。你们在转换完以后一定要做一次随机抽样验证抽个几十张图把 txt 里的坐标画回原图上人工确认框的位置和类别都对得上。这一步不花多少时间但能救命。import cv2 def verify_yolo_annotation(image_path, txt_path, class_names): img cv2.imread(image_path) h, w, _ img.shape with open(txt_path, r) as f: for line in f: parts line.strip().split() cls_id int(parts[0]) x_c, y_c, bw, bh map(float, parts[1:]) x1 int((x_c - bw / 2) * w) y1 int((y_c - bh / 2) * h) x2 int((x_c bw / 2) * w) y2 int((y_c bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, class_names[cls_id], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow(verify, img) cv2.waitKey(0)4.2 数据集划分按来源而不是按文件随机分这是这次项目里我最想强调的一个细节。很多人做划分的时候直接random_split结果训练集和验证集里可能混着同一段视频的相邻帧或者同一个猫舍同一天拍的非常相似的图。这种情况下的验证集指标会虚高模型真正面对新数据时直接拉胯。我的做法是按数据来源划分自拍的800张放在一起、猫舍的1200张放在一起、公开数据1200张放在一起然后按 8:1:1 分到训练、验证、测试。保证同一来源的数据尽量只出现在一个集合里。划分之后我又手动检查了一遍验证集确保它里面的样本在姿态、光线、背景上和训练集有可见差异。最终数据分布大概是训练集 2560 张左右验证集 320 张测试集 320 张。各类别数量我做了统计放松最多约1200张恐惧最少约500张存在明显的长尾。4.3 数据增强不是越多越好要符合猫的生理规律YOLOv8 默认开了一堆增强mosaic、随机翻转、HSV扰动、随机缩放裁剪。我的建议是默认增强可以先用但要关掉两个东西第一是垂直翻转。猫的耳朵、眼睛、胡须在垂直方向上没有对称性垂直翻转会产生现实中几乎不存在的倒立猫样本模型学到的是假特征。水平翻转没问题猫左右脸基本对称可以放心开。第二是mosaic 的强度要控制。mosaic 会把四张图拼在一起猫的边界框被截断的概率很大。情绪特征主要集中在头部区域如果头被切掉一半标注框里实际只剩身体这个样本对模型学习情绪特征几乎没帮助。我把 mosaic 概率从默认的1.0调到了0.6左右。HSV 增强我会保留而且把色调扰动的幅度稍微调大了一点。原因是实际部署时光线变化很剧烈暖光、冷光、阴影下的猫颜色差异很大适当增加颜色扰动能让模型更关注形状特征而不是颜色先验。4.4 类别不平衡的两种处理方式恐惧类只有500张比放松类少了一半多。我同时用了两个策略过采样在训练集里对恐惧类图片做重复采样一轮 epoch 中恐惧类图片实际出现次数和其他类接近。损失函数加权YOLOv8 的cls损失权重可以调我把恐惧类的分类损失权重乘上一个系数约1.3让模型在类别判断上更偏向少数类。这里要提醒的是过采样不能解决标注本身的质量问题如果恐惧类数据里混了不准确的标注过采样只会放大错误。所以我是先做完前面说的一致性审核才开始做数据增强和训练。5. YOLO训练配置与实战调参一次从头到尾的踩坑记录5.1 基础配置从nano起步先用小模型跑通流程我的硬件是 NVIDIA RTX 3060 12GB这是很主流的大众配置。我第一轮用的是yolov8nnano版本输入尺寸640batch size 16epochs 200。第一轮的目标不是拿到最好精度而是快速验证数据管线有问题没有——比如标签有没有错位、类别的特征能不能被学到。为什么选 nano 起步因为小模型训练一轮只要20分钟左右方便快速迭代。等管线验证通过了我再用yolov8s甚至yolov8m去冲精度。很多新手一上来就上yolov8x一张卡根本跑不动batch只能设4BN层统计量不稳定反而容易训崩。训练命令大概是这样的yolo train datadata.yaml modelyolov8n.pt epochs200 imgsz640 batch16 device0 workers4yolov8n.pt是官方在 COCO 上的预训练权重用预训练权重做初始化比随机初始化快很多而且泛化能力明显更好。我试过不用预训练权重从头训mAP50 低了大约15个百分点所以这一步别省。5.2 冻结训练先用COCO特征的通用能力稳住底层我在第一轮直接把全部层都解冻训练了。后来换yolov8s的时候想到了一个更稳的方法冻结backbone的前几层只训练靠后的层和检测头。原理很简单COCO预训练模型的前几层学到的是边缘、颜色、纹理这些最底层的视觉特征这些特征对不同任务来说是通用的不需要为猫情绪任务重新学习。如果一开始就全解冻梯度会同时更新所有层新手随便调个学习率就可能把底层已经学好的好特征冲掉了。冻结训练5000步左右等损失曲线平稳了再解冻全部层用更低的学习率微调。这个方法让我最终模型的精度比全程全解冻高了约3个点而且收敛更快。我在 YOLOv8 里用freeze10表示冻结前10层包括 backbone 的大部分卷积层。这个数字不是固定的你可以通过打印模型结构自己看层数来判断要冻结哪些。5.3 损失函数里的门道loss加权和超参数YOLOv8 的损失由三部分组成box loss框回归损失、cls loss分类损失、dfl loss分布焦点损失。训练日志里你会看到box_loss、cls_loss、dfl_loss三个指标。很多人只看总损失下降就完事其实应该分别看。我在第一轮训练到60轮的时候发现cls_loss下降变得非常慢但box_loss还在正常下降。这说明分类分支已经遇到瓶颈了。排查下来有两个原因一是少数类样本不足二是默认的损失权重占比对分类不够友好。我把cls相关的权重调高了一点同时给恐惧类加了过采样cls_loss明显改善。还有一个容易被忽略的参数是anchor。YOLOv8 用的是 anchor-free 机制它不再需要手动设定先验框的尺寸这省了很多麻烦。但如果你用的是 YOLOv5那就要注意根据猫的尺寸重新聚类 anchor。我的数据集里猫框的长宽比集中在 0.8~1.6 之间和 COCO 默认 anchor 差异不小不调整的话早期收敛会非常慢。5.4 训练中的崩溃现场BN层炸了我训练过程中遇到了一个非常典型的崩溃到第50个epoch左右损失突然飙升到无穷大然后 training 直接中断。日志里显示RuntimeError: CUDA error: CUDNN_STATUS_INTERNAL_ERROR。这个坑主要是几个原因叠加引起的batch size 太大导致显存不足cudnn 初始化失败输入尺寸变化太大——我用 640 但开了 mosaic拼接出来的图尺寸不一致显卡驱动和 cudnn 版本不匹配。解决办法是把 batch size 从16降到12关闭 mosaic 做两轮正常训练让 BN 统计量重新稳定然后更新显卡驱动。重启之后训练恢复正常。如果你们也遇到 BN 相关的问题loss 里出现nan或者 tensor 里出现inf优先排查学习率是不是太高超过0.01危险、batch size 是不是太小低于8危险、增强是不是太猛。训练完成之后我习惯性地做了一件事用验证集最好的权重去做一次测试集推理把预测错误的图全部导出来专门放一个文件夹。这一步是我整个项目里认为最值钱的做法它直接让我发现了数据集的真实问题而不是只盯着一堆指标数字。6. 评估不只是看 mAP混淆矩阵和典型误判案例的价值6.1 指标怎么解读才不算白看训练结束后results.csv里记录了每轮验证的 precision、recall、mAP50、mAP50-95。我见过很多人只盯 mAP50这个习惯不算错但对于情绪识别这种类别间高度相似的场景远远不够。我关注的顺序是先看混淆矩阵再看各类别的 precision/recall最后才看 mAP。YOLOv8 训练完后会在runs/train/exp/下生成confusion_matrix.png这个图直接告诉你哪两类最容易互相搞混。我这轮的混淆矩阵显示警觉和烦躁之间有个明显的互混区大约12%的烦躁样本被预测成警觉恐惧的 recall 只有0.78明显低于其他类。这说明模型在恐惧类上学到的特征还不够典型部分因为恐惧样本本身形态多样——有的猫是静止伏低有的猫是炸毛逃窜这两种形态差异比类和类之间的差异还大。6.2 典型bad case拆解一张图片暴露一个数据缺口我挑两个典型的误判案例出来说。第一个是逆光窗台上的猫。这张图里的猫实际是放松状态但耳朵因为逆光形成了明显的深色剪影模型把它判断成了警觉。原因很直接放松类训练数据里几乎没有逆光样本而警觉类数据里有很多耳朵轮廓锐利的样本。模型学到的是耳朵轮廓锐利警觉而不是耳朵朝向瞳孔状态警觉。第二个是多猫重叠场景。两只猫一只睡觉一只炸毛标注的时候两框重叠区域很大。模型预测时把炸毛猫的框几乎全压在睡觉猫身上导致原类别的精度被拉低。这暴露的是标注框质量问题——我当时为了赶进度重叠场景的框没有做细致调整让大框盖住了重要特征。6.3 迭代修正闭环把预测错误变成新的标注素材我处理这些 bad case 的方法不是简单加数据而是形成了一个迭代闭环测试集推理导出所有预测错误的图。重新看原图判断是标注错误还是模型错误。如果是标注错误立即修正标注加入训练集。如果是模型错误分析共性原因逆光角度罕见针对性地补充数据或调整标注规范。这个闭环做两轮之后我的 mAP50 从0.81提升到了0.86而且提升主要来自 hard case而不是简单重复的样本。我强烈建议每个人都跑一下这个loop它相当于拿着检测结果反过来审查数据集质量比盲目堆数据高效得多。6.4 关于置信度阈值的选择YOLO 模型输出的置信度是一个连续值你可以通过调整置信度阈值在 precision 和 recall 之间做取舍。情绪检测场景我建议阈值调低一点0.25~0.3 之间因为漏检一个恐惧的猫可能比误检一次更麻烦。尤其是做宠物健康监测之类的应用宁可多报不可漏报。这个阈值在部署阶段再调训练阶段就用默认的0.25评估。7. 部署时的实际体验和后续扩展的真实想法7.1 ONNX导出从PyTorch到边缘设备的最后一公里训练完的权重文件是.pt直接拿去部署不太现实我一般会转成 ONNX 再走 TensorRT 加速。YOLOv8 转 ONNX 很简单yolo export modelbest.pt formatonnx imgsz640 opset12导出后我用 ONNX Runtime 做了精度对比那个好的权重转出来的输出和 PyTorch 原版基本一致差异在1e-4级别可以放心用。在边缘设备我用过 Jetson Nano上跑的时候建议把输入分辨率降到 416 或者 320速度会快很多精度损失大约3个点。如果部署环境是手机 App还可以把输入进一步裁剪到猫脸区域——先跑一个轻量级猫检测器把框出来再在框内做情绪识别这样既能保证小目标检测精度又能降低计算量。7.2 后续扩展从单帧情绪到行为序列单帧图片的情绪检测只是第一步实际产品里更需要的是视频流里的行为趋势分析。一只猫可能在某几帧里因为一个突然的响动进入警觉状态几秒后又恢复放松如果我们只看单帧很容易被瞬时信号误导。我现在在尝试的方向是把 YOLO 的输出接一个时序模型比如简单的 LSTM 或者滑窗投票机制用最近2秒的情绪类别投票来做最终判断。这个方案实现成本很低但效果立竿见影——恐惧类的单帧误报率能降一半。另一个值得做的方向是个体识别情绪关联。多猫家庭场景里摄像头拍到的画面经常是两只猫同时出现如果能先做个体识别猫脸 ReID再把情绪检测结果挂到具体个体上产品价值会高很多。这个方向也是我下一阶段想做的。最后说一点个人体会。这个数据集项目做下来我最深的感触是在计算机视觉项目里数据工作量的占比永远是第一位的。训练模型的时间可能只占整个项目的20%剩下的都是采集、清洗、标注、审查、修正的循环。3200张图看起来是个小数目但它背后是连续三周每天两小时的标注、是几十次分歧讨论后的规范迭代、是后来补拍补标才达到的质量水平。数据集的真正价值从来不是数字而是数字背后的每一张图都经得起推敲。希望这篇记录能帮后来的人少走一些弯路也欢迎正在做类似项目的朋友一起交流思路。
返回列表