news 2026/9/12 1:55:39

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于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.yamlyolov5m.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的PANet(Path 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.18,PyTorch 1.13或2.0以上都行。如果你用的是NVIDIA显卡,第一步不是急着克隆仓库,而是先装好CUDA版的PyTorch。这个顺序反了会让你在后面被各种No module named 'torch'或者AssertionError: CUDA unavailable折腾半天。

conda create -n fer python=3.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.001~0.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 640,batch上限是24左右;8GB显存就老老实实用16。如果训练中途报CUDA out of memory,优先把batch降到8,而不是改img,因为img影响所有batch,更容易破坏训练稳定性。

4. 训练完成后:做检测识别、实时视频推理和模型导出

4.1 静态图片与视频推理的detect.py参数

训练结束会得到runs/train/face_exp/v1/weights/best.ptlast.ptbest.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的输出格式会改,后续代码也要跟着改。我平时部署只用onnxonnxruntime-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领域(面部表情识别)惯用的是加权平均召回率WES(Weighted Expression Score),因为表情类别天然不均衡,中性样本往往比厌恶样本多十倍以上,mAP会被多数类抬高。自己去算WES并不复杂,先拿验证集推理出一份results.txt,里面是每张图的真实类别和预测类别,然后按真实类别的数量加权计算召回率。YOLOV5在训练过程中自动生成的confusion_matrix.png就是混淆矩阵的可视化,存在runs/train/face_exp/v1/confusion_matrix.png里。我每次训练完第一件事就是这个文件——它能瞬间告诉你哪些表情互相混淆。最常见的混淆对是“恐惧”和“惊讶”,因为这两种表情都伴随眼睛睁大、嘴巴张开。另一个高混淆对是“厌恶”和“生气”,表现在眉毛下压和嘴唇紧闭上极其相似。

针对混淆,最好的修正手段不只是加数据,而是加“边界样本”。去找面部动作编码系统(FACS)中对每个表情的AU(Action Unit)定义,比如“惊讶”的AU1+AU2+AU5+AU26,“恐惧”是AU1+AU2+AU4+AU7+AU20+AU26。然后从现有数据里把同时满足这两组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张图,计算标注一致性(Cohen's Kappa),低于0.6的样本直接丢弃。这个操作比任何超参数调整都立竿见影——模型在一个“标准不统一”的数据集上,无论怎么调权重都学不出稳定边界。清洗之后重新评估WES,通常能一次性提升5个百分点以上。再继续碰混淆样本的标注和AUC分析,而不是盲目堆epoch。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 1:51:48

Debian环境变量配置与管理全指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 1:50:11

MATLAB贝叶斯优化LSTM时间序列预测系统

简介:本资源是一份面向MATLAB初学者与时间序列建模进阶学习者的完整实践方案,聚焦贝叶斯优化与LSTM协同建模这一前沿技术组合,解决金融、电力、气象等领域的高精度时序预测问题。压缩包共5个文件,含2个说明类txt文档(含…

作者头像 李华
网站建设 2026/9/12 1:49:39

AI建站之后,小生意如何真正增长?

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 1:49:38

PIC18F46K40控制AD5293数字电位计:工业校准中的SPI与固件实现

在工业校准设备上,我做过最烦的一件事就是拿螺丝刀去调一颗三毫米的机械电位计。手一抖,输出偏了,重调;环境温度一变,阻值又漂了;设备往现场一放,运输振动可能让旧电位计的位置彻底移位。后来我…

作者头像 李华
网站建设 2026/9/12 1:48:00

制造业数字化转型:业务架构与流程优化实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华