
简介这份资源面向计算机相关专业的毕业设计、课程设计需求者提供一套基于Yolov5与Python实现的人脸识别及人脸表情识别完整项目源码适合具备一定深度学习与Python基础、需要快速搭建可运行系统的学生参考使用。压缩包共收录约2000个文件整体大小167.89MB其中以txt文本数据与说明为主配合39个py源码文件、20个yaml配置、6个sh脚本及若干md、json文件覆盖模型训练、数据加载、导出推理等环节目录结构清晰便于按模块查阅。目前已有372人学习关注。源码经过严格测试可直接运行读者可据此理解Yolov5在人脸检测与表情分类任务中的落地方式掌握训练配置、数据组织与推理流程并在此基础上完成功能扩展或论文撰写为毕业设计提供可复用的工程参考。1. 从一份毕业设计源码说起Yolov5 怎么做人脸识别和表情识别很多计算机毕业设计的选题都绕不开人脸方向但真正动手时才发现单纯调一个face_recognition库只能算「跑通 demo」答辩时老师一句「你的检测网络是什么结构、表情分类头怎么接的」就能把人问住。这份标题里的方案走的是另一条路用 Yolov5 做目标检测主干把人脸框出来再在裁剪出的人脸区域上接一个表情分类网络形成「检测 分类」的两级流水线。它解决的核心问题是既要定位人脸又要判断这张脸是高兴、生气还是中性而且整套流程用 Python 串起来方便在普通带显卡的机器上训练和推理。适合谁看如果你正在做计算机毕业设计选题涉及人脸识别门禁机、课堂表情分析、驾驶员疲劳监测这类场景或者你手头已经有一份 Yolov5 的源码但不知道怎么把表情识别接进去这篇笔记会从数据准备、模型改造、训练参数到部署推理一步步拆开讲。需要提前说明的是Yolov5 本身是检测框架它不直接输出「这是谁」或「这是什么表情」身份识别要靠 ArcFace 这类度量学习方案表情识别要靠额外的分类头标题里的「人脸识别」和「人脸表情识别」是两条并行的任务线下面会分别说清楚。2. 环境搭建与 Yolov5 源码跑通Python 版本、依赖和第一张推理图2.1 Python 环境与依赖安装的版本选择Yolov5 对 Python 版本比较敏感我一般用 Python 3.8 到 3.9太新的版本在装torch和opencv-python时容易碰到 wheel 不匹配的问题。如果你之前只装过 Python 但没配过虚拟环境建议先建一个独立的 venv避免和系统里的 numpy、cv2 打架。下面这套命令在 Windows 和 Linux 上都验证过CUDA 版本按你显卡驱动来选没有显卡就用 CPU 版 torch训练会慢但推理能跑。# 创建虚拟环境Python 3.8 兼容性最稳 python -m venv yolov5_env # Windows 激活 yolov5_env\Scripts\activate # Linux / macOS 激活 source yolov5_env/bin/activate # 安装 PyTorch以 CUDA 11.8 为例CPU 版去掉 --index-url 那行 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装 Yolov5 依赖requirements.txt 在源码根目录 pip install -r requirements.txt # 单独确认 opencv 和 numpy 能导入 python -c import cv2, numpy; print(cv2.__version__, numpy.__version__)这段命令的逻辑是先隔离环境再装深度学习框架最后补 Yolov5 的周边依赖。参数上唯一要留意的是--index-url后面的 CUDA 版本号它必须和你nvidia-smi里显示的驱动支持版本对应否则torch.cuda.is_available()会返回 False。装完以后跑一句python -c import torch; print(torch.cuda.is_available())输出 True 才算 GPU 可用。如果卡在pip install下载慢换国内镜像源即可但不要混用多个源容易把依赖版本搞乱。2.2 用预训练权重跑通第一张检测图环境好了之后先别急着训练自己的数据用官方预训练权重yolov5s.pt跑一张图确认整条推理链路是通的。这一步能帮你排除掉「代码没问题但环境有问题」的玄学情况。# 下载 yolov5s 预训练权重后对单张图片做推理 python detect.py --weights yolov5s.pt --source data/images/bus.jpg --img-size 640 --conf-thres 0.25 # 推理结果默认保存在 runs/detect/exp 目录下 # 如果想用摄像头实时看效果 python detect.py --weights yolov5s.pt --source 0 --img-size 640--weights指定权重文件--source可以是图片、视频目录或摄像头编号--img-size是推理分辨率--conf-thres是置信度阈值。跑通后你会看到runs/detect/exp里有一张画了框的图说明检测部分没问题。这里有个常见坑--img-size必须是 32 的倍数写 650 会报错写 640 或 672 都行。另外摄像头推理时如果画面卡顿把--img-size降到 416 能明显提速代价是小人脸漏检率上升。2.3 人脸检测数据集的准备与 YOLO 格式转换Yolov5 训练需要 YOLO 格式的标注每张图对应一个 txt每行是类别 x_center y_center width height坐标都归一化到 0 到 1。人脸检测通常只有一个类别所以类别 id 全是 0。如果你手头是 VOC 的 xml 或者 LabelImg 存的 xml需要转一道。import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_dir, out_dir, img_w, img_h): # 遍历 VOC 格式的 xml转成 YOLO 的 txt for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() lines [] for obj in root.iter(object): # 人脸检测只有一类类别 id 固定为 0 cls_id 0 bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 归一化并转成中心点加宽高 x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(os.path.join(out_dir, xml_file.replace(.xml, .txt)), w) as f: f.write(\n.join(lines)) # 调用时传入你的 xml 目录、输出目录和图片宽高 voc_to_yolo(annotations/, labels/, 640, 640)这段脚本的关键点是归一化分母必须用图片真实宽高而不是你训练时设置的--img-size。很多人在这里翻车标注时图片是 1920×1080转换时却除了 640导致框全部缩到左上角。转换完还要检查有没有宽高为 0 的脏框Yolov5 训练时遇到这种框会直接报NaN损失。另外数据集目录要按images/train、images/val、labels/train、labels/val组织再写一个 data.yaml 指向这些路径。3. 表情识别分类头的设计与训练从人脸裁剪到七类情绪3.1 为什么表情识别不能直接塞进 Yolov5 的检测头一个常见的误解是把「高兴」「生气」当成检测类别让 Yolov5 直接输出带表情标签的人脸框。这样做理论上可行但实际效果很差。原因是表情是整个人脸区域的全局属性不是局部纹理检测头依赖 anchor 回归局部特征对「嘴角上扬」这种细微变化不敏感。更稳的做法是两级Yolov5 只负责人脸定位裁出人脸区域后送进一个独立的分类网络比如 ResNet18 或 MobileNetV3输出七类表情概率。这样两个任务解耦表情分类网络可以单独在 FER2013 或 RAF-DB 上预训练再迁移到你自己的数据上。3.2 用 Yolov5 裁剪人脸并构建表情分类数据集训练表情分类器之前先用已经跑通的人脸检测模型把每张图里的人脸裁出来保存成小图再按表情类别分文件夹。这一步是数据流水线的核心裁得好不好直接决定分类上限。import cv2 import os import torch # 加载训练好的人脸检测模型 model torch.hub.load(ultralytics/yolov5, custom, pathbest_face.pt) model.conf 0.5 # 置信度阈值低于这个值的人脸不要 def crop_faces(img_dir, out_dir): # 遍历图片检测人脸并裁剪保存 for img_name in os.listdir(img_dir): img_path os.path.join(img_dir, img_name) img cv2.imread(img_path) if img is None: continue results model(img) # 取出检测框xyxy 格式 boxes results.xyxy[0].cpu().numpy() for i, box in enumerate(boxes): x1, y1, x2, y2 map(int, box[:4]) # 适当外扩避免裁掉下巴和额头 pad int(0.1 * (y2 - y1)) x1 max(0, x1 - pad) y1 max(0, y1 - pad) x2 min(img.shape[1], x2 pad) y2 min(img.shape[0], y2 pad) face img[y1:y2, x1:x2] # 统一缩放到 224x224方便后续分类网络输入 face cv2.resize(face, (224, 224)) save_path os.path.join(out_dir, f{img_name}_{i}.jpg) cv2.imwrite(save_path, face) crop_faces(raw_images/, faces_cropped/)这段代码里model.conf 0.5是置信度过滤太低会裁到背景太高会漏掉侧脸。外扩pad取人脸高度的 10% 左右这是血泪经验不扩的话嘴角和眉毛容易被切掉而这两个区域恰恰是表情分类最依赖的。裁完之后你要人工把faces_cropped里的图按表情分到happy、sad、angry、surprise、fear、disgust、neutral七个文件夹或者用已有的 FER2013 标签做映射。分类数据集不需要标注框只要文件夹名就是类别名。3.3 表情分类网络的训练参数与迁移学习策略分类网络我一般选 ResNet18参数量小在几千张人脸上不容易过拟合。用 ImageNet 预训练权重初始化再把最后一层全连接改成七输出。训练时学习率用 1e-3配合余弦退火batch size 设 32 或 64看显存。import torch import torch.nn as nn from torchvision import models, transforms, datasets # 数据增强训练集做随机翻转和颜色抖动验证集只做缩放 train_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness0.2, contrast0.2), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) val_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) train_ds datasets.ImageFolder(faces_train/, transformtrain_tf) val_ds datasets.ImageFolder(faces_val/, transformval_tf) # 加载 ResNet18 并替换分类头 model models.resnet18(pretrainedTrue) model.fc nn.Linear(model.fc.in_features, 7) model model.cuda() criterion nn.CrossEntropyLoss() # 只训练分类头时学习率可以大一点微调主干时降到 1e-4 optimizer torch.optim.Adam(model.parameters(), lr1e-3) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max20)参数说明RandomHorizontalFlip对表情分类是安全的因为左右脸表情对称但RandomRotation要慎用旋转超过 15 度会让表情失真。Normalize的均值和方差必须用 ImageNet 的因为预训练权重是在那个分布上学的。训练轮数一般 20 到 30 轮就收敛如果验证集准确率卡在 50% 上不去先检查裁剪的人脸是不是对齐的歪脸会让分类器学不到稳定特征。另外类别不平衡很常见中性脸往往最多可以在CrossEntropyLoss里加weight参数把少数类的权重调高。4. 人脸识别与表情识别的串联推理一条流水线跑通两个任务4.1 用 ArcFace 做人脸身份识别的接入方式标题里的「人脸识别」如果指的是「认出这是谁」那就不能只靠 Yolov5。常见做法是 Yolov5 检测人脸后用 ArcFace 或 FaceNet 提取 512 维特征向量再和底库里的特征做余弦相似度比对。ArcFace 的训练需要大量身份标注数据毕业设计里通常直接用预训练模型做推理不自己训练。import cv2 import numpy as np import torch from arcface_model import ArcFace # 假设你用的是某个开源 ArcFace 实现 # 初始化人脸检测和特征提取两个模型 detector torch.hub.load(ultralytics/yolov5, custom, pathbest_face.pt) embedder ArcFace().cuda().eval() def get_embedding(face_img): # 人脸图缩放到 112x112ArcFace 的标准输入 face cv2.resize(face_img, (112, 112)) face face[:, :, ::-1] # BGR 转 RGB face (face / 255.0 - 0.5) / 0.5 tensor torch.from_numpy(face).permute(2, 0, 1).float().unsqueeze(0).cuda() with torch.no_grad(): emb embedder(tensor) return emb.cpu().numpy().flatten() def recognize(img, gallery, threshold0.6): # gallery 是底库格式 {姓名: 特征向量} results detector(img) boxes results.xyxy[0].cpu().numpy() for box in boxes: x1, y1, x2, y2 map(int, box[:4]) face img[y1:y2, x1:x2] emb get_embedding(face) best_name, best_score unknown, 0 for name, ref_emb in gallery.items(): score np.dot(emb, ref_emb) / (np.linalg.norm(emb) * np.linalg.norm(ref_emb)) if score best_score: best_name, best_score name, score if best_score threshold: best_name unknown cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, best_name, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) return imgthreshold是判定阈值设 0.6 比较保守宁可拒识也不误识。ArcFace 的输入必须是 112×112 且做过归一化直接塞原始人脸图进去特征会完全不对。底库特征要提前算好存成 npy不要每次推理都重新算否则摄像头场景下帧率会掉到个位数。4.2 检测加分类的完整推理脚本与性能取舍把检测、识别、表情分类串起来就是一份完整的毕业设计演示脚本。这里要注意顺序先检测人脸再对每个人脸分别做身份比对和表情分类两个任务共享同一次裁剪避免重复计算。def full_pipeline(img, gallery, expr_model): results detector(img) boxes results.xyxy[0].cpu().numpy() for box in boxes: x1, y1, x2, y2 map(int, box[:4]) face img[y1:y2, x1:x2] # 身份识别 emb get_embedding(face) name, score match_gallery(emb, gallery) # 表情分类 expr classify_expression(face, expr_model) label f{name} | {expr} cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) return img性能上Yolov5s 在 640 分辨率下单帧 GPU 推理约 10 到 15 毫秒ArcFace 和 ResNet18 各约 5 毫秒整条流水线在 RTX 3060 上能跑到 30 FPS 以上。如果换到树莓派或 RK3568 这类边缘设备要把 Yolov5 换成 yolov5n并做量化否则帧率会掉到 2 到 3 FPS。取舍点在于检测分辨率降到 416 能提速一倍但小于 40×40 像素的人脸会漏检门禁场景够用课堂后排人脸就不行了。5. 避坑与排查训练不收敛、漏检和部署翻车的常见原因5.1 损失变成 NaN 或一直不下降现象训练头几个 epoch 损失就变成 NaN或者损失在 4.0 附近震荡不降。原因通常是标注框坐标越界或宽高为 0Yolov5 在计算 CIoU 损失时对非法框没有保护。解决方法是训练前跑一遍校验脚本检查每个 txt 里的数值是否都在 0 到 1 之间宽高是否大于 0。另外学习率设太大也会导致 NaNYolov5 默认 lr0 是 0.01小数据集上建议降到 0.001。5.2 验证集 mAP 很高但实际推理漏检严重现象训练日志里 mAP0.5 到了 0.9但拿手机拍的照片去推理人脸框不出来。原因是训练集和实际场景分布不一致比如训练集全是正脸、光照均匀实际场景有侧脸、逆光。解决办法是在数据增强里加mosaic和mixupYolov5 默认开启但小数据集上 mosaic 概率可以调到 0.5 而不是 1.0避免过度拼接导致小脸更小。另外推理时的--conf-thres从 0.25 降到 0.1 试试有时候只是阈值卡太高。5.3 表情分类总是偏向某一类现象不管输入什么脸分类器都输出「中性」或「高兴」。原因是类别不平衡加上交叉熵损失没有加权。解决方法是统计每个类别的样本数在CrossEntropyLoss里传weight1/class_count的倒数或者对少数类做重采样。另一个原因是裁剪的人脸没有对齐眼睛不在同一水平线上分类器学到的都是位置偏差而不是表情特征可以用人脸关键点做仿射对齐后再送分类网络。5.4 部署到边缘设备后帧率骤降现象在 PC 上跑得好好的部署到树莓派或 RK3568 上只有几帧。原因是模型没做量化且 Python 推理框架有额外开销。解决方法是把 Yolov5 导出成 ONNX 或 RKNN 格式用 C 推理引擎替代 Python 循环。导出 ONNX 时注意--img-size要和推理时一致动态轴设 batch 维度否则固定 batch 会导致单张推理也要凑批。量化到 INT8 后精度可能掉 2 到 3 个点用人脸数据做一遍校准能补回来。5.5 摄像头推理画面卡顿或延迟累积现象用 OpenCV 读摄像头画面越跑越慢延迟从几十毫秒涨到几秒。原因是cap.read()在主循环里阻塞而推理耗时大于采集间隔帧缓冲越堆越多。解决办法是开一个独立线程专门抓帧只保留最新一帧给推理用旧帧直接丢弃。或者把cv2.VideoCapture的缓冲大小设成 1cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)这样每次读到的都是最新画面。6. 把毕业设计做成能演示的作品几个让答辩加分的细节走到这里检测、识别、表情分类三条线已经能串起来跑了但毕业设计不只是跑通脚本答辩老师看的是你能不能把技术讲清楚、把效果展示出来。我自己的习惯是做一个简单的 PyQt 或 Gradio 界面左边放实时画面右边显示识别到的人名和表情标签再加一个底库注册按钮现场录入一张照片就能识别。这个界面不需要多漂亮但能让老师一眼看懂你在做什么。验证方法上别只报一个准确率数字。人脸检测报 mAP0.5 和推理速度身份识别报底库 10 人时的 Top-1 准确率和误识率表情分类报混淆矩阵特别是指出「生气」和「厌恶」容易混因为两者在低分辨率下眉毛和嘴角差异很小。把这些边界说清楚比硬吹 99% 准确率更让老师信服。还有一个容易被忽略的点底库特征的管理。很多同学把底库特征硬编码在脚本里换个人就要改代码。正确做法是写一个gallery.npy和对应的names.json注册时追加写入推理时加载。这样演示时你可以现场加人不用重启程序。另外表情分类的输出最好做时间平滑连续 5 帧里取众数避免画面里人脸一晃就跳标签看起来会稳很多。最后说个我踩过的坑答辩前一定要在答辩用的那台电脑上完整跑一遍包括摄像头权限、CUDA 驱动版本、底库文件路径。我见过太多人在自己机器上好好的换到教室电脑上因为 OpenCV 版本不对直接报错。把依赖导出成requirements.txt提前装好别等到上台前十分钟还在 pip install。希望帮到你。本文还有配套的精品资源点击获取