ARTICLE DETAIL

资讯详情

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

YOLO犬类情绪识别实战:从数据标注到实时部署全流程解析

YOLO犬类情绪识别实战:从数据标注到实时部署全流程解析 简介一套完整的基于YOLO的犬类情绪识别项目包主要面向深度学习初学者和图像分类实战开发者尤其适合作为毕业设计、课程设计或期末大作业的参考项目。项目覆盖从数据准备、模型训练到测试评估与结果可视化的全流程能帮助使用者理解目标检测与动物情绪分类的落地方式适用于教学演示、项目汇报和算法对比实验。压缩包共72个文件约60.25MB其中以jpg/jpeg/png图像样本和结果图为多数配合py训练与测试脚本、md说明文档、依赖文件及gif演示结果目录中另有模型目录、输出结果目录和评估图表包含P曲线、R曲线、PR曲线、F1曲线、混淆矩阵等便于直接查看模型效果。已有56人浏览学习。通过源码、样例图片、训练后的模型与说明文档使用者可复现犬类情绪识别实验也可替换数据集进行迁移训练节省从零搭建代码与调参的时间是兼具教学参考和实际应用价值的完整资料。1. 犬类情绪识别一个 YOLO 就能带走的课程设计项目如果你正在为毕业设计或期末大作业发愁想找一个“深度学习 图像识别”方向、能落地演示又能写进简历的项目基于 YOLO 的犬类情绪识别是个很务实的选择。它能对狗的脸部表情和姿态做实时分类区分开心、焦虑、愤怒、悲伤、平静这几类典型情绪状态覆盖从数据标注、模型训练到摄像头推理的完整流程。这套资源最大的价值不是“识别有多准”而是把深度学习中目标检测落地的完整链路都串起来了数据怎么清洗、VOC 标注怎么转 YOLO 格式、训练参数怎么调、摄像头实时推理怎么接、模型怎么导出部署。对新手来说它是一份能跟到最后的完整代码包对已经有基础的人来说它也能当成一个快速出结果的基线工程省掉从零搭环境的时间。我拆这个项目时最直观的感受是真正的功夫不在训练那一步而在数据整理和参数调试上。下面按我自己的复现路径往下走每个环节都给到可直接抄的代码和参数。2. 数据先行整理、标注转换与五分类标签的落地方法2.1 数据从哪来构建与清洗犬类情绪识别本质上是一个目标检测任务输入是包含犬只的图片输出是检测框加上情绪类别。情绪识别比普通品种识别更难因为同类情绪在不同犬种上的表现差异很大哈士奇和柯基的“开心脸”完全是两个画风。因此数据集的构成直接影响模型能不能收敛。资源里自带的原始图片多为网图、公开宠物数据集和少量自拍素材的混合体第一件要做的事是清洗。常见做法是把这三类图片按“犬只朝向正面或侧面 45 度以内、脸部占比大于 1/4、单张图只有一只狗、无大面积遮挡”的标准筛一遍。我一般会用一个简单的脚本统计每张图的尺寸和通道把损坏的、灰度异常的、分辨率低于 300px 的图直接剔除。import os from PIL import Image src_dir raw_images valid_dir cleaned_images os.makedirs(valid_dir, exist_okTrue) min_side 300 removed [] for name in os.listdir(src_dir): path os.path.join(src_dir, name) try: img Image.open(path) img.verify() # 检查文件是否损坏 w, h img.size if w min_side or h min_side: removed.append((name, too small)) continue if img.mode L: removed.append((name, grayscale)) continue img Image.open(path).convert(RGB) img.save(os.path.join(valid_dir, name)) except Exception as e: removed.append((name, str(e))) print(fremoved {len(removed)} files: {removed[:10]})img.verify()只做完整性校验不开图速度很快img.mode L是灰度图过滤因为训练时颜色信息对情绪识别有贡献灰度图会引入分布偏移。这里把筛掉的图片名打印出来便于回头补数据时排查。清洗后大约能留下 70%80% 的图片。这一步不能省脏数据进训练集后面所有指标都会变得奇怪。2.2 标注格式转换VOC 转 YOLO清洗完成后要处理的是标注格式。资源包里原始标注是 XMLVOC 格式而 YOLO 训练需要的是每个图片对应一个 txt每行写“类别_id x_center y_center width height”坐标全部归一化到 0 到 1 之间。# convert_voc_to_yolo.py import xml.etree.ElementTree as ET import os # 类别顺序必须与 data.yaml 中完全一致 CLASSES [angry, anxious, calm, happy, sad] def voc_to_yolo(xml_path, out_txt_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.iter(object): cls_name obj.find(name).text.strip().lower() if cls_name not in CLASSES: continue box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) x_center (xmin xmax) / 2.0 / w y_center (ymin ymax) / 2.0 / h box_w (xmax - xmin) / w box_h (ymax - ymin) / h cls_id CLASSES.index(cls_name) lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) with open(out_txt_path, w, encodingutf-8) as f: f.write(\n.join(lines)) xml_dir annotations/voc txt_dir annotations/yolo os.makedirs(txt_dir, exist_okTrue) for xml_name in os.listdir(xml_dir): if not xml_name.endswith(.xml): continue stem os.path.splitext(xml_name)[0] voc_to_yolo(os.path.join(xml_dir, xml_name), os.path.join(txt_dir, stem .txt))宽度和高度必须从size标签里取不能自己用 PIL 再算一次。有些手机照片带 EXIF 旋转信息直接读图得到的宽高会被方向信息干扰标注会整体偏移。归一化坐标让不同分辨率的图片在训练时能统一到同一个尺度这也是 YOLO 系列一直用这种方式的原因。转换完成后抽查几份 txt如果出现 x_center 大于 1 或小于 0 的情况说明标注框越界需要回到原 XML 修正。2.3 数据划分与类别均衡分类项目的数据划分不能只用随机切分尤其是情绪识别这类天然不均衡的数据集。狗的表情里“开心”和“平静”的样本远多于“焦虑”和“愤怒”如果按原始比例切分验证集里愤怒样本可能只有个位数模型全猜平静也能拿到高准确率这会让指标失真。import os import random from collections import Counter from shutil import copy2 image_dir cleaned_images label_dir annotations/yolo train_dir dataset/images/train val_dir dataset/images/val train_label_dir dataset/labels/train val_label_dir dataset/labels/val for d in [train_dir, val_dir, train_label_dir, val_label_dir]: os.makedirs(d, exist_okTrue) label_files [f for f in os.listdir(label_dir) if f.endswith(.txt)] random.seed(42) # 统计每个类别的样本框数量按框数而不是图片数分配 cls_counter Counter() for lf in label_files: with open(os.path.join(label_dir, lf), r, encodingutf-8) as f: for line in f: cls_id int(line.split()[0]) cls_counter[cls_id] 1 print(class distribution:, dict(cls_counter)) train_ratio 0.85 train_files [] val_files [] # 分层采样保证每个类别在验证集中都有足够样本 for cls_id in range(len(CLASSES)): cls_files [ lf for lf in label_files if any(int(line.split()[0]) cls_id for line in open(os.path.join(label_dir, lf), r, encodingutf-8)) ] random.shuffle(cls_files) split_idx int(len(cls_files) * train_ratio) train_files.extend(cls_files[:split_idx]) val_files.extend(cls_files[split_idx:]) for f in train_files: copy2(os.path.join(image_dir, f.replace(.txt, .jpg)), train_dir) copy2(os.path.join(label_dir, f), train_label_dir) for f in val_files: copy2(os.path.join(image_dir, f.replace(.txt, .jpg)), val_dir) copy2(os.path.join(label_dir, f), val_label_dir)这里用了分层采样先把包含某个类别的图片单独拎出来再按比例抽取确保“愤怒”这类长尾类别不会在验证集里消失。训练集比例取 0.85如果数据总量少于 1000 张可以提高到 0.9。这里有一个常见的翻车点按图片数切分看似没问题但一张图里可能同时有两只狗、两个情绪框按图片数切分会让验证集和训练集出现内容重叠所以我按标签文件数来切并且只关心标签里出现过的类别。划分完成后数据集目录结构就是标准的 YOLO 布局dataset/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/最后写一个data.yaml这是训练时唯一要指定的数据集配置文件类别列表顺序必须与上一步的 CLASSES 完全一致。path: dataset train: images/train val: images/val names: 0: angry 1: anxious 2: calm 3: happy 4: sad3. 训练自己的识别模型环境、参数配置与损失函数解读3.1 环境搭建与预训练权重训练环境是第一个拦路虎网上搜“YOLO 环境配置”能看到的坑基本都集中在 CUDA 和 torch 版本不匹配上。资源包里的代码基于 YOLOv8ultralytics 框架Python 版本 3.8 到 3.10 都能跑我建议用 3.10。# 创建虚拟环境避免污染系统 Python conda create -n yolo python3.10 -y conda activate yolo # 安装 PyTorch先确认本机 CUDA 版本再选择对应命令 pip install torch2.1.0 torchvision0.16.0 --index-url https://download.pytorch.org/whl/cu118 # 安装 ultralytics pip install ultralytics8.0.100这里不要直接pip install ultralytics了事锁版本很有必要。YOLOv8 的 API 在 8.0.x 之后有过一些调整资源代码是在这个版本下调试过的升到 8.2 以上有些接口签名会变新手照抄代码可能跑不通。安装完成后用yolo predict sourcehttps://ultralytics.com/images/bus.jpg验证环境能出结果说明环境正常。预训练权重建议用yolov8s.pt而不是yolov8n.pt。虽然 nano 模型更小、训练更快但情绪特征嘴角弧度、耳朵位置非常细微nano 的浅层特征表达力不够最终 mAP 会比 s 低 5 到 8 个点。如果显卡显存低于 6G再退回到yolov8n.pt。3.2 训练命令与超参数设置训练命令看起来简单真正决定结果的是参数。下面是我在这套资源上调通的一组基线参数。yolo train \ modelyolov8s.pt \ datadog_emotion.yaml \ epochs100 \ imgsz640 \ batch16 \ device0 \ patience30 \ optimizerAdamW \ lr00.001 \ lrf0.01 \ mosaic1.0 \ fliplr0.5 \ scale0.3 \ projectruns/detect \ namedog_emotion_train逐个说参数含义。epochs100对于这个规模的数据集够用太多会过拟合太少不收敛imgsz640是训练输入分辨率如果显卡允许改成 768 对情绪识别有明显帮助因为脸部关键特征在低分辨率下容易糊掉batch16由显存决定显存 8G 用 1612G 以上可以到 32patience30表示连续 30 个 epoch 验证集指标没有提升就提前停止这是防止时间浪费的保险丝optimizerAdamW比默认的 SGD 收敛更快尤其适合迁移学习场景。mosaic1.0是马赛克增强把四张图拼成一张训练对小目标检测非常有效但如果训练集里单张图只有一只狗mosaic 过强会在早期造成“半个头”的幻觉训练初期 loss 可能不降反升。真遇到这种情况把mosaic0.5跑几个 epoch后期再调到 1.0 也行。训练过程中重点看两行输出box_loss和cls_loss。box_loss 是检测框回归误差cls_loss 是分类误差。情绪识别的难点在 cls_loss 上如果训练了十几轮 cls_loss 还在 1.5 以上先想到的是数据问题而不是模型问题。3.3 损失函数与训练曲线怎么看YOLOv8 的损失由三部分构成分类损失BCE 变体、框回归损失CIoU、分布式焦点损失DFL。DFL 是 YOLOv8 相对 v5 的新增项它把边界框的四个边当成分布来回归而不是直接预测数值这提升了小目标的定位精度代价是训练时对学习率更敏感。我习惯每 10 个 epoch 停下来看一下results.png里的曲线。有几个异常信号需要记住train/box_loss快速下降但val/box_loss震荡上升这是过拟合的早期信号立即加大weight_decay或降低epochscls_loss在训练初期就在 0.3 以下说明类别分布严重不均衡模型在走捷径优先学容易的类别训练开始就出现nanloss几乎可以肯定是学习率过大或某张图的标注框坐标异常比如宽高为 0去检查数据集比调参更有用。训练结束后在runs/detect/dog_emotion_train/目录下会生成best.pt和last.pt后续推理用best.pt。这个目录还包含confusion_matrix.png它比 loss 曲线更能说明问题——如果对角线很亮但某个类别的列全黑那就是典型的长尾漏检。4. 推理与部署从单张图片到实时摄像头识别4.1 单图推理脚本训练完模型后的第一件事是用它跑几张训练集之外的图片验证基本效果。下面这段脚本可以直接复制使用。# inference_image.py from ultralytics import YOLO model YOLO(runs/detect/dog_emotion_train/weights/best.pt) results model.predict( sourcetest_images, conf0.35, iou0.45, imgsz640, saveTrue, save_txtTrue, device0 ) for r in results: boxes r.boxes if boxes is not None: for box in boxes: cls_id int(box.cls[0].item()) conf float(box.conf[0].item()) print(fclass{model.names[cls_id]} confidence{conf:.3f})conf0.35是置信度阈值低于这个值的检测框会被丢弃。情绪识别的置信度普遍比普通目标检测低因为狗脸角度变化大设 0.35 到 0.4 是一个平衡点。iou0.45是 NMS 交并比阈值值越小抑制越强同一只狗被框两次的场景可以调到 0.5。打印类别名和置信度有助于快速定位哪些图片误检严重。4.2 摄像头实时检测课程设计答辩现场用摄像头实时演示效果远比静态图片震撼。这里给一个接 OpenCV 的实时推理版本。# inference_webcam.py import cv2 from ultralytics import YOLO model YOLO(runs/detect/dog_emotion_train/weights/best.pt) cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) while True: ret, frame cap.read() if not ret: break results model.predict(frame, imgsz640, conf0.4, device0, verboseFalse) annotated_frame results[0].plot() fps cap.get(cv2.CAP_PROP_FPS) cv2.putText(annotated_frame, fFPS: {fps:.0f}, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow(Dog Emotion Recognition, annotated_frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()实时推理时我把imgsz从 640 降到 416这不是为了精度而是为了帧率。摄像头场景下运动模糊已经让细小情绪特征不可靠640 的输入会拖慢推理速度反而导致画面卡顿。这里有个隐藏问题results[0].plot()会在原图上绘制检测框和标签但如果检测到多个框且类别置信度不高画面上的字会非常多答辩时反而显得 demo 杂乱可以只在画面上画置信度高于 0.5 的框。4.3 模型导出与边缘部署训练好的 PyTorch 权重不能直接跑在手机上或者 Jetson 这类边缘设备上需要导出成 ONNX 或 TensorRT 格式。yolo export modelruns/detect/dog_emotion_train/weights/best.pt formatonnx imgsz640 opset12 yolo export modelruns/detect/dog_emotion_train/weights/best.pt formatengine device0 halfTrueONNX 导出的关键是opset版本设成 12 兼容性最好新版 PyTorch 默认的 opset 17 在部分老设备推理引擎上会报不支持的算子。TensorRT 导出前先确认本机 CUDA 版本和 TensorRT 版本匹配否则引擎构建到一半会报显存问题。halfTrue开启 FP16 精度推理速度提升明显但精度有轻微损失情绪识别这个任务影响不大。如果在树莓派或者 RK3588 这类设备上部署ONNX 格式是更稳妥的选择配合 RKNN-Toolkit 转换工具可以做硬件加速。这方面的行话叫“边缘部署监控误检率高”核心原因是边缘设备上为了性能往往会降低输入分辨率而情绪特征恰恰依赖细节所以部署时的输入分辨率不要小于 512。5. 常见问题与避坑训练不收敛、BN 崩溃与误检排查5.1 训练阶段BN 崩溃、过拟合与损失异常现象一训练到第 20 个 epoch 左右loss 突然变成nan控制台开始刷红色警告模型权重无法保存。这大概率是训练中 BN 崩溃BatchNorm blowup。产生原因通常是学习率设置过高加上早期批次数据里某些标注框异常导致某一层的统计量爆炸。解决方法是先把lr0降到 0.0005同时把batch设成 8 或 16 的倍数确保每个批次里的样本数足够让 BN 统计量稳定。另一个常见做法是在yolo train命令里加cacheTrue把数据缓存到内存减少读取抖动带来的输入异常。现象二训练集精度升到 95% 以上验证集 mAP 却只有 50%而且还在往下掉。这是典型的过拟合在小数据集上非常普遍尤其是清洗后图片数少于 800 张时。对策不止一个我建议按顺序操作先加weight_decay0.0005再把mosaic关掉或降到 0.3然后尝试用yolov8s-cls.pt的分类预训练权重做迁移初始化。注意这不是玄学是超参数和数据规模不匹配的必然结果。现象三val/cls_loss曲线一路平坦怎么调参都下不去。先检查你的data.yaml里类别顺序是否和训练标签的 class_id 对应。我见过不止一次因为 CLASSES 列表顺序变了导致模型把“开心”学成了“焦虑”的惨案。模型没毛病是标签语义对不上。5.2 推理阶段误检、漏检与混淆矩阵异常现象四视频里对着墙壁或地板也会框出一个小方框置信度还不低打上了某个情绪类别。原因是背景过拟合。如果训练集里大量图片的背景都是室内地板或狗窝模型会把纹理特征当成判断依据。解决方式是做数据增强加hsv_h0.5 hsv_s0.5 hsv_v0.5随机改变色调饱和度降低颜色纹理对分类的依赖再用translate0.1让目标在画面里移动破坏固定的位置关联。推理时把conf调高到 0.5 也能过滤一部分背景误检。现象五confusion_matrix.png里所有类别的横排总和明显大于纵排总和甚至出现奇怪的对角线。搜过“YOLO 混淆矩阵总合不唯一”的应该都知道目标检测的混淆矩阵计算逻辑与图像分类不同它按预测框是否与真实框匹配来计数一个真实框可能被多个预测框命中一个预测框也可能对应多个类别得分。看混淆矩阵时不要追求行和列相等重点看每个类别是否在对角线上有足够高的值。某一行除对角格外还有高响应说明模型在类别间混淆优先补区分这两个类别的数据比调参更有效。6. 进阶注意力机制改进与 TensorBoard 验证习惯6.1 给 YOLOv8 加一个轻量注意力模块如果完成基础版本之后还有余力给模型加一个 SESqueeze-and-Excitation注意力模块是性价比很高的改进方向。犬类情绪识别依赖的耳朵位置、嘴角弧度这些细小特征注意力机制能让特征提取网络更关注这些区域同时计算开销非常小。# common.py 中添加的 SE 模块 import torch import torch.nn as nn class SEBlock(nn.Module): def __init__(self, channels, reduction16): super(SEBlock, self).__init__() self.avg_pool nn.AdaptiveAvgPool2d(1) self.fc nn.Sequential( nn.Linear(channels, channels // reduction, biasFalse), nn.ReLU(inplaceTrue), nn.Linear(channels // reduction, channels, biasFalse), nn.Sigmoid() ) def forward(self, x): b, c, _, _ x.size() y self.avg_pool(x).view(b, c) y self.fc(y).view(b, c, 1, 1) return x * y.expand_as(x)这个模块通过全局平均池化把每个通道压缩成一个值再用两层全连接学习通道间的关系最后用 Sigmoid 产生 0 到 1 的通道权重乘回原特征图。reduction16控制中间层压缩比值越大参数越少但信息损失也越多。修改完模型结构后训练命令不变只是在yaml配置里替换对应层的模块名。6.2 用 TensorBoard 对比改进前后的训练曲线改进是否有效不能靠感觉。ultralytics 框架自带 TensorBoard 支持但需要先安装依赖再启用。pip install tensorboard tensorboard --logdir runs/detect打开http://localhost:6006就能同时看到基线模型和注意力改进模型的 loss 曲线。我通常会对比两组指标val/cls_loss和mAP0.5:0.95。如果加了注意力模块后cls_loss更低但box_loss略高这是正常的注意力机制本质上是牺牲一部分定位精度换取分类精度在情绪识别这个任务上是划算的。对比训练时还有个习惯值得养成用固定的random_seed42让两次训练的数据增强顺序一致。如果不固定种子两次训练的提升可能是随机性造成的那就失去了对比的意义。这个资源我前后拆了三遍第一遍踩在数据清洗上第二遍死在 BN 崩溃第三遍才把训练跑顺。从那以后我每次做目标检测项目都会强制自己先写一个数据分布统计脚本再进训练环节。模型可以迭代数据问题不能靠模型硬扛。完整的标签转换脚本、分层采样代码和训练配置都在资源包里希望帮到你。本文还有配套的精品资源点击获取
返回列表