ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于YOLOV5的面部情感表情检测实践指南

2026/9/12 1:56:07 拓冰建站 浏览量
基于YOLOV5的面部情感表情检测实践指南 简介基于YOLOV5的面部情感表情检测识别Python源码面向计算机视觉学习者、人工智能课程设计与毕业设计学生可用于快速实现人脸表情如喜怒哀乐等的检测与识别。项目包含84个文件整体体积约1.06MB核心部分以Python脚本为主涵盖模型训练、图片/摄像头检测等配合YAML配置模型结构、训练超参数、Shell权重下载脚本、Dockerfile环境部署文件以及少量示例图片构成一套可直接运行的完整工程。源码来自个人大作业项目评审分达到95分以上经过本地调试确保可运行目录按YOLOv5官方工程结构组织并带有weights下载脚本、runs检测结果输出等模块适合对照模型配置文件与Python脚本理解目标检测、特征提取与情感分类流程。目前已有185人学习项目难度适中尤其适合需要将YOLOv5迁移到面部表情识别场景、或希望参考高分作业源码快速上手的研究者与在校生。1. 拿YOLOV5做面部情感表情检测为什么不是换个分类网络那么简单你在GitHub或毕设交易网站上看到这个标题时第一反应可能是表情识别不是用ResNet或MobileNet跑个分类就完事了吗为什么非要套上YOLOV5我一开始也这么想直到自己动手做才发现标题里的“检测识别”四个字意味着任务根本不一样——分类网络只能告诉你“整张图里有什么表情”而YOLOV5要回答的是“这张图里哪几个人分别是什么表情”。在课堂、会议室、司机状态监控这类多目标场景下人脸定位和表情分类必须同时完成。这个项目形态最典型的落地方式就是用YOLOV5检测人脸框同时把边框分类为生气、厌恶、恐惧、开心、悲伤、惊讶、中性七类情感表情。适合的人群很明确正在做毕设、课程设计或者想快速拿到一套可运行、可改参数的Python源码的工程师。你不需要从零写模型但要能改数据、调参、换结构、修Bug。2. YOLOV5网络结构里表情检测改的是哪几个地方2.1 从coco80到fer7修改nc和yaml的连锁反应YOLOV5的源码目录里models/下有一组yolov5s.yaml、yolov5m.yaml之类的配置文件。这些YAML文件定义了网络的深度、宽度和类别数。以最常见的yolov5s.yaml为例末尾关键行是nc: 80 # number of classes训练COCO数据集时是80类里面有人、车、猫、狗但没有“开心”和“愤怒”。做面部情感表情检测第一件事就是把类别数改成7或你实际定义的类别数常见是七分类也有包含轻蔑的八分类。改这个数字会引发连锁反应模型的检测头最后的卷积层输出维度是(5 nc) * 3其中5是边界框的四个坐标加置信度3是每个特征层上的三个anchor。nc从80改成7输出维度从255变成36。这个改动不需要你手动改网络代码YOLOV5的训练脚本会读取yaml再通过parse_model函数动态构建网络。改动之后必须同步修改数据配置文件。YOLOV5约定每个数据集都有一个.yaml文件里面至少包含三部分train: ./face_expression/train/images val: ./face_expression/val/images nc: 7 names: [angry, disgust, fear, happy, sad, surprise, neutral]names的顺序就是训练时类别的索引顺序从0开始。这里有一个新手极易踩的坑训练时损失函数会和这个索引顺序绑定而LabelImg标注时下拉框里显示的也是这个顺序两边一旦不一致模型训练出来就是“张冠李戴”。我在第一次训练时就吃过这个亏当时把happy放在第4位标注软件里也按这个顺序选的没问题后来为了可视化方便改了names顺序忘了同步标注文件结果跑了30个epoch才发现验证集的混淆矩阵完全乱掉。2.2 为什么保留YOLOV5的检测头而不是换成纯分类头表情识别领域很多人直接用图像分类模型输入一整张图输出一个表情标签。但YOLOV5项目的核心价值在于“检测分类”联合输出。它的Head部分是三个不同尺度的特征图分别负责小、中、大目标。人脸在画面里往往只占很小一块尤其在教室、会议室这种多人场景小目标人脸非常多。YOLOV5的PANetPath Aggregation Network会把高层语义信息回传到低层特征图帮助小目标检测。如果你把检测头换成Global Average Pooling加全连接层那这个模型就退化成了分类器等于扔掉了一半优势。这里我建议你直接保留YOLOV5s的完整结构不要轻易改动Backbone或Neck。对于面部表情检测这个任务YOLOV5s的参数量约700万已经足够。如果你用YOLOV5s在GTX 1660上训练batch size设置16输入尺寸640显存占用大约6GB训练速度也能接受。换更大或更小的版本取决于你手头的GPU资源——后文我会给出一张资源匹配表。2.3 先跑通官方预训练权重再谈自己的数据不建议完全从零开始训练。表情数据集的公开规模通常只有几万张和COCO的33万张图像比差一个数量级。YOLOV5官方仓库提供了在COCO上预训练好的权重文件yolov5s.pt虽然它没有表情类别但Backbone已经学到了丰富的纹理、边缘、形状特征。人脸的眼角、嘴角、眉毛纹理本质上也是纹理特征。迁移学习的常规操作是保留Backbone和Neck的全部参数只随机初始化检测头的最后一层。YOLOV5的train.py在加载预训练权重时会自动跳过形状不匹配的层因为nc从80改成7后最后一层卷积的shape对不上这一层会自动重新初始化。你不需要手动做任何事只需要在训练命令里加上--weights yolov5s.pt。3. Python环境搭建与YOLOV5训练表情数据集的标准流程3.1 环境配置Python版本和依赖锁死YOLOV5对Python版本并不苛刻3.8到3.10都能跑。我自己长期用的是3.9.18PyTorch 1.13或2.0以上都行。如果你用的是NVIDIA显卡第一步不是急着克隆仓库而是先装好CUDA版的PyTorch。这个顺序反了会让你在后面被各种No module named torch或者AssertionError: CUDA unavailable折腾半天。conda create -n fer python3.9 conda activate fer # 安装CUDA版PyTorch根据你的CUDA版本选择对应的索引 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt安装完成后先别急着训练用一条命令验证环境python detect.py --source data/images/bus.jpg --weights yolov5s.pt这条命令能跑通说明PyTorch、YOLOV5依赖、权重下载链路都没问题。注意detect.py的默认参数是COCO的80类它会把bus里的行人、公交车框出来。这一步的意义是确保你的Python环境没有任何隐藏问题之后再改自己的数据和配置文件。3.2 用LabelImg标注自己的面部表情图片并转成YOLO格式面部表情数据集有很多现成的比如FER2013、CK、RAF-DB但它们大多是单张人脸居中的图像缺少“多人同框各自表情”的检测场景。自己做检测项目最省力的组合是从WIDER FACE等公开人脸数据集中裁图再人工标注表情类别或者直接用手机拍一段多人对话视频抽帧。标注工具我推荐LabelImg注意是tzutalin版不是Label Studio后者做检测标注也能用但导出YOLO格式稍绕。LabelImg标注后保存的格式是Pascal VOC的XML而YOLOV5需要的是txt文件每行一个目标格式为class_id x_center y_center width height这四个坐标值都除以了图片宽度和高度归一化到0-1之间。你需要一个转换脚本常见写法如下import xml.etree.ElementTree as ET import os def convert_label(xml_file, out_dir, classes): tree ET.parse(xml_file) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) out_lines [] for obj in root.iter(object): cls obj.find(name).text if cls not in classes: continue cls_id classes.index(cls) box obj.find(bndbox) x1 int(box.find(xmin).text) y1 int(box.find(ymin).text) x2 int(box.find(xmax).text) y2 int(box.find(ymax).text) x_center (x1 x2) / 2.0 / img_w y_center (y1 y2) / 2.0 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h out_lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) out_file os.path.join(out_dir, os.path.basename(xml_file).replace(.xml, .txt)) with open(out_file, w) as f: f.write(\n.join(out_lines))这段代码的关键逻辑是遍历XML里的每一个object节点取出标注框的四个像素坐标然后除以图片宽高归一化。注意classes列表的顺序必须和face_expression.yaml里的names顺序完全一致最好是同一个变量来源避免手动维护两份东西。我习惯把这个列表放在一个单独的classes.txt里yaml和转换脚本都读它这样只改一处。标注完成后你的目录结构应该是这样的datasets/ └── face_expression/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── face_expression.yaml注意图片和标签的文件名必须一一对应同一个base name后缀不同。YOLOV5在训练时会把同一张图片和它对应的txt文件配对如果找不到标签文件训练会告警并跳过那张图。val集的划分我一般按0.9比0.1随机抽肉眼确认每一类在val集里至少出现10个实例。表情类别的天然不均衡比如“厌恶”样本通常很少会在这个阶段暴露出来后面我会讲怎么处理。3.3 超参数设置一张表说清该动谁、不该动谁YOLOV5默认的hyp.scratch-low.yaml已经适配大多数目标检测任务但对表情检测这种小目标多、类别内方差大的任务有三个参数值得手动调。超参数默认值表情检测建议原因lr00.010.0010.01表情数据集通常比COCO小学习率太大会过拟合或震荡lrf0.010.001低至最终学习率的衰减系数余弦衰减末段需要更小步长去收敛warmup_epochs3.05.0让预训练Backbone先适应新检测头避免早期梯度冲太猛momentum0.9370.937动量不用动默认值在各种任务上都稳weight_decay0.00050.0005大于0.001会严重欠拟合小数据集hsv_h / hsv_s±0.015 / ±0.7调低到0.01和0.5表情颜色信息脸红、苍白是辅助特征但过度色彩增强会抹掉肤色细节设置超参数有两种方式直接编辑data/hyps/hyp.scratch-low.yaml或者在训练命令里用--hyp指定一个自定义yaml。我更推荐后者因为可以把不同实验的参数版本提交到Git里追踪。训练命令的基准是这样python train.py \ --data datasets/face_expression/face_expression.yaml \ --cfg models/yolov5s.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --hyp data/hyps/hyp.scratch-low.yaml \ --project runs/train/face_exp \ --name v1--img 640表示输入分辨率。如果你的场景是多人脸小目标建议用640而不是416。表情的细微差别比如“惊讶”和“恐惧”都表现为嘴张开、眼瞪大在高分辨率下更容易区分代价是显存和训练时间几乎翻倍。--batch的大小取决于显存12GB显存跑yolov5s和--img 640batch上限是24左右8GB显存就老老实实用16。如果训练中途报CUDA out of memory优先把batch降到8而不是改img因为img影响所有batch更容易破坏训练稳定性。4. 训练完成后做检测识别、实时视频推理和模型导出4.1 静态图片与视频推理的detect.py参数训练结束会得到runs/train/face_exp/v1/weights/best.pt和last.pt。best.pt是验证集上mAP最高的模型last.pt是最后一个epoch的模型。推理时统一用best.pt。python detect.py \ --weights runs/train/face_exp/v1/weights/best.pt \ --source ./test_images/group_photo.jpg \ --conf 0.4 \ --iou 0.5 \ --save-txt \ --project runs/detect \ --name group_photo--conf是置信度阈值表情检测比通用物体检测更模糊建议0.3到0.4之间。设0.25会漏掉一些较难的表情样本实际上刚好相反conf越低会框出的候补越多但把“中性”误判成“悲伤”的假阳也会增加。--iou是NMS的IoU阈值0.5是常规值人脸之间重叠很少不需要动。--save-txt会输出一个和图片同名的txt文件每行是class_id x_center y_center width height conf。这个文件可以直接喂给下游数据统计模块比如统计一班学生里哪些人在走神。视频推流同理--source换成视频文件路径--save-txt对视频无效但--save-crop可以把被检测到的每张人脸裁出来单独保存这个参数在后期做错误样本细看时非常有用。4.2 用摄像头实时跑面部情感识别实时场景是硬件考验。模型推理本身速度不是瓶颈YOLOV5s在RTX 3060上跑640分辨率大约7毫秒一次前向瓶颈反而是摄像头读取和图像预处理。YOLOV5的detect.py支持摄像头直接作为源python detect.py --weights runs/train/face_exp/v1/weights/best.pt --source 0 --conf 0.35--source 0是默认摄像头设备号。如果你的电脑没有显示器纯服务器环境可以加--nosave不保存结果视频但依然会打印每帧的检测日志。真正做实时系统不建议用detect.py直接跑因为它是逐帧处理CPU处理和GPU之间有同步等待。更常见的做法是写一个Python脚本把视频帧放到队列里GPU推理线程只负责读队列算结果最后在另一线程画框显示。这个模式对毕设答辩演示来说稳定性比花哨的Web界面更重要。4.3 把表情模型导出为ONNX和TensorRT部署到嵌入式设备或使用OpenCV的DNN模块推理需要把PyTorch权重导出成通用格式。YOLOV5内置导出脚本python export.py \ --weights runs/train/face_exp/v1/weights/best.pt \ --include onnx engine \ --imgsz 640 \ --device 0导出ONNX后你可以用onnxruntime跑推理代码量比PyTorch原生更少而且不需要安装整个PyTorch。TensorRT的engine格式导出只对NVIDIA卡有效导出过程会做层融合和显存优化推理速度能在ONNX基础上再提升2到3倍。注意--grid这个参数在新版YOLOV5里控制是否保留原始推理头如果你要在TensorRT里做端到端需要带--end2end导出但那样NMS的输出格式会改后续代码也要跟着改。我平时部署只用onnx加onnxruntime-gpu这样在嵌入式平台和服务器上都能跑。4.4 训练和推理中最常见的四个坑第一训练时报Dataset not found多半是yaml里的路径是相对路径而当前工作目录不在项目根目录下。解决办法是用绝对路径或者始终在yolov5目录下执行命令。第二报AssertionError: train: No labels in .../labels/train意思是你的标签txt文件全空或者路径不对。用脚本统计一下标签文件的行数每行至少要有7个数字。第三推理时一张人脸都没框出来优先看是不是模型根本没有收敛——用last.pt试试如果也只有一两个框那就是训练数据量太少或者标注框和图片尺寸不匹配。第四CUDA与PyTorch版本不一致导致Driver/library version mismatch卸载后用pip uninstall torch torchvision重新安装对应CUDA版。5. 用WES、混淆矩阵和类别权重把表情识别做细评价表情模型光看mAP不够。FER领域面部表情识别惯用的是加权平均召回率WESWeighted Expression Score因为表情类别天然不均衡中性样本往往比厌恶样本多十倍以上mAP会被多数类抬高。自己去算WES并不复杂先拿验证集推理出一份results.txt里面是每张图的真实类别和预测类别然后按真实类别的数量加权计算召回率。YOLOV5在训练过程中自动生成的confusion_matrix.png就是混淆矩阵的可视化存在runs/train/face_exp/v1/confusion_matrix.png里。我每次训练完第一件事就是这个文件——它能瞬间告诉你哪些表情互相混淆。最常见的混淆对是“恐惧”和“惊讶”因为这两种表情都伴随眼睛睁大、嘴巴张开。另一个高混淆对是“厌恶”和“生气”表现在眉毛下压和嘴唇紧闭上极其相似。针对混淆最好的修正手段不只是加数据而是加“边界样本”。去找面部动作编码系统FACS中对每个表情的AUAction Unit定义比如“惊讶”的AU1AU2AU5AU26“恐惧”是AU1AU2AU4AU7AU20AU26。然后从现有数据里把同时满足这两组AU的样本挑出来单独用它们微调模型10到20个epoch比盲目把全体数据翻倍更有效。类别不均衡还可以从损失函数层面下手。YOLOV5默认的class loss是BCEWithLogitsLoss它支持--cls_pw参数设置类别权重。计算方式统计训练集中每类目标的实例数取倒数归一化。比如生气4000个样本厌恶只有600个那么厌恶的权重约是生气的6.7倍。你在数据配置yaml里不能直接给权重而是要在训练命令里手动指定一个权重列表python train.py ... --cls_pw 0.5 1.0 6.7 0.8 0.9 1.2 0.3这个列表长度必须和nc一致。加了权重之后模型会更关注少数类样本的梯度但代价是多数类比如中性的精度可能下降。所以实际调参时我通常先用1.0权重跑一轮看一眼混淆矩阵再决定要不要加。最后一个可落地的技巧是手动清洗测试集边界。表情识别比物体识别更依赖标注者主观判断同一个人的同一张脸不同标注者给出的标签可能不同。我把标注指南写清楚后让两名同学独立标注100张图计算标注一致性Cohens Kappa低于0.6的样本直接丢弃。这个操作比任何超参数调整都立竿见影——模型在一个“标准不统一”的数据集上无论怎么调权重都学不出稳定边界。清洗之后重新评估WES通常能一次性提升5个百分点以上。再继续碰混淆样本的标注和AUC分析而不是盲目堆epoch。本文还有配套的精品资源点击获取