简介:这是一套基于Yolov5与Python实现的人脸识别、人脸细粒度表情识别及异常行为检测的完整源码项目,面向计算机视觉方向的初学者、毕业设计学生以及需要完成期末大作业或课程设计的开发者。项目代码配有详细注释,新手也能看懂,部署后即可运行,可直接作为毕设或课程设计使用,系统功能完善、界面美观、操作简单,具有较高的实际应用价值。资源包共107个文件,包含37个py源码、21个txt说明、18个yaml配置、10张jpg与4张png示例图、3个md文档,以及ui界面文件、pt权重、mat数据和Dockerfile等,压缩包约24.81MB,目录结构清晰,便于按模块查阅与二次开发。目前已有269人学习下载。通过该资源,读者可获得人脸检测、表情细粒度分类与异常行为识别的完整实现方案,理解Yolov5在真实场景中的落地方式,并借助注释与配置文件快速完成环境搭建、模型调试与功能扩展,为项目答辩或后续研究提供扎实的代码基础。
1. 从一台门禁机说起:Yolov5+Python 三合一视觉系统到底在做什么
很多人第一次接触「人脸识别门禁机」这类项目,脑子里想的是刷脸开门这么简单。真上手才发现,一台能用的设备要同时回答三个问题:这张脸是不是同一个人、这个人现在什么情绪、这个人有没有做出翻越、摔倒、聚集这类异常动作。标题里的 Yolov5+Python 三合一方案,本质就是把这三件事塞进同一条推理流水线:Yolov5 负责把人脸和人体从画面里框出来,Python 侧再挂人脸识别、细粒度表情分类、异常行为判定三个下游模块。它适合谁?适合手头有 Python 基础、想用一套源码把检测+识别+行为分析串起来的中级开发者,也适合做门禁、考勤、校园安全这类场景的产品同学拿来评估可行性。热词里「yolov5训练自己的数据集」「yolov5环境配置」「人脸识别算法」这几个词,恰好对应了落地时最花时间的三块。下面我按自己搭过的一套流程,把选型、代码、参数和踩过的坑讲清楚。
2. 三合一流水线的架构选型:为什么不是三个模型各跑各的
2.1 检测、识别、行为分析为什么要分层而不是端到端
先说结论:不要指望一个模型同时输出人脸框、身份 ID、表情标签和异常类别。我试过把四类标签塞进 Yolov5 的检测头,结果是小目标(人脸)召回率掉得厉害,表情这种细粒度分类更是被检测损失带偏。合理的分层是:
- 第一层:Yolov5 做人体+人脸检测,输出 bbox 和置信度。人脸框交给识别和表情,人体框交给行为分析。
- 第二层:人脸识别用 ArcFace 或 FaceNet 提特征,和底库做余弦相似度比对。
- 第三层:表情识别用一个小分类网络(ResNet18 或 MobileNetV3 就够),输入是裁剪对齐后的人脸。
- 第四层:异常行为检测不靠单帧,靠人体框的时序轨迹——速度突变、停留超时、框高宽比异常(摔倒时宽高比会翻转)。
这样分层的好处是每层可以独立换模型、独立调阈值,出问题能定位到具体哪一层。坏处是延迟叠加,所以工程上要做帧采样和异步。
2.2 环境配置:conda 建环境 + Yolov5 依赖的版本坑
热词里「conda yolov5」「yolov5环境配置」「vscode python环境配置」出现频率很高,说明这一步劝退了不少人。我一般用 conda 隔离,避免和系统 Python 打架:
# 创建独立环境,python 版本建议 3.8~3.9,太新 torch 轮子可能不匹配 conda create -n face_yolo python=3.9 -y conda activate face_yolo # 安装 pytorch,具体 cuda 版本按自己显卡驱动选,这里以 cu118 为例 pip install torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/cu118 # 安装 yolov5 依赖,注意 requirements 里 opencv 版本别乱升 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt逻辑说明:先建环境再装 torch,是因为 Yolov5 的 requirements 里对 torch 只给下限,不锁版本,直接pip install -r可能拉到和 CUDA 不匹配的版本。参数上,python=3.9是兼容性最稳的区间,3.11 以上有些老依赖会编译失败。装完用python -c "import torch; print(torch.cuda.is_available())"验证,返回 True 才算环境通了。
提示:如果
torch.cuda.is_available()返回 False,先别怀疑代码,九成是驱动版本和 CUDA 版本对不上,用nvidia-smi看驱动支持的 CUDA 上限。
2.3 人脸检测与人体检测的模型分工
Yolov5 官方权重是在 COCO 上训的,person 类现成可用,face 类没有。所以人脸检测要么用现成的人脸检测器(如 RetinaFace、YOLOv5-face),要么自己标数据训一个 face 类。我的做法是:人体检测直接用 yolov5s.pt,人脸检测单独训一个轻量 YOLOv5n-face,输入尺寸 640,因为人脸在门禁场景里通常占画面比例不大,n 版本够快。
import torch # 人体检测用官方权重,人脸检测用自训权重 person_model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True) face_model = torch.hub.load('ultralytics/yolov5', 'custom', path='weights/face_yolov5n.pt') def detect(frame): # 人体检测,只保留 person 类 person_res = person_model(frame, size=640) persons = person_res.xyxy[0][person_res.xyxy[0][:, -1] == 0] # 人脸检测 face_res = face_model(frame, size=640) faces = face_res.xyxy[0] return persons, faces逻辑说明:xyxy[0]拿到的是[x1,y1,x2,y2,conf,cls]格式,[:, -1] == 0过滤出 person 类(COCO 里 person 的 id 是 0)。参数size=640是推理分辨率,门禁场景人脸小可以提到 1280,但帧率会掉一半,要权衡。人脸模型单独训的原因在 2.4 讲。
2.4 自训人脸检测数据集的最小流程
热词「yolov5训练自己的数据集」是绕不开的。人脸检测数据集我一般用 WIDER FACE 打底,再补自己场景的几百张。标注格式转成 YOLO 的 txt(每行cls cx cy w h,归一化到 0~1)。
# data/face.yaml 内容示例 # train: ../datasets/face/images/train # val: ../datasets/face/images/val # nc: 1 # names: ['face'] python train.py --data data/face.yaml --weights yolov5n.pt --img 640 --batch 16 --epochs 100 --name face_yolov5n逻辑说明:--weights yolov5n.pt用官方权重做迁移学习,比从头训收敛快得多。--img 640要和推理时一致,否则小脸漏检。--batch 16是显存 8G 左右的稳妥值,显存大可以加到 32。训完看runs/train/face_yolov5n/下的results.png,重点看 mAP@0.5 和 recall,人脸场景 recall 比 precision 更重要,漏检比误检代价大。
3. 人脸识别与细粒度表情识别的落地实现
3.1 人脸对齐:识别准确率的第一道分水岭
检测框出来的人脸是歪的,直接送识别网络,准确率能掉十几个点。标准做法是用 5 个关键点(双眼、鼻尖、双嘴角)做仿射变换对齐到 112x112。关键点可以用人脸检测模型自带,也可以挂个轻量关键点模型。
import cv2 import numpy as np # 标准 112x112 人脸模板的 5 个关键点坐标 REFERENCE = np.array([ [38.2946, 51.6963], [73.5318, 51.5014], [56.0252, 71.7366], [41.5493, 92.3655], [70.7299, 92.2041] ], dtype=np.float32) def align_face(img, landmarks): # landmarks: 5x2 的检测关键点 src = np.array(landmarks, dtype=np.float32) # 用相似变换求仿射矩阵,只允许旋转缩放平移,不形变 M, _ = cv2.estimateAffinePartial2D(src, REFERENCE) aligned = cv2.warpAffine(img, M, (112, 112), borderValue=0.0) return aligned逻辑说明:estimateAffinePartial2D求的是相似变换,比全仿射更稳,不会把人脸拉变形。参数(112,112)是 ArcFace 系列的标准输入。对齐后的人脸再送识别和表情,两个模块共用,省一次计算。
3.2 人脸识别:ArcFace 特征 + 余弦相似度比对
识别模块我一般用 InsightFace 的 ArcFace 权重,512 维特征,比对用余弦相似度,阈值 0.4~0.5 之间调。
import numpy as np def cosine_sim(a, b): a = a / np.linalg.norm(a) b = b / np.linalg.norm(b) return float(np.dot(a, b)) def recognize(face_feat, gallery, threshold=0.45): # gallery: {name: feature} 底库 best_name, best_score = 'unknown', 0.0 for name, feat in gallery.items(): score = cosine_sim(face_feat, feat) if score > best_score: best_name, best_score = name, score if best_score < threshold: return 'unknown', best_score return best_name, best_score逻辑说明:特征先做 L2 归一化再点积,等价于余弦相似度。阈值 0.45 是门禁场景的经验值,安防场景可以提到 0.5 减少误识,考勤场景可以降到 0.4 减少拒识。底库特征要离线算好存成 npy,别每次现场跑。
3.3 细粒度表情识别:7 类不够用怎么办
「细粒度」是标题里的关键词。常规表情分类是 7 类(中性、高兴、悲伤、惊讶、恐惧、厌恶、愤怒),但门禁场景真正有用的是「是否配合」「是否异常激动」这种粗粒度判断。我的做法是:底层还是 7 类分类网络,上层再做映射——高兴+中性归为「正常」,愤怒+厌恶+恐惧归为「异常激动」,惊讶单独标记。
# 表情分类网络推理,输入对齐后的 112x112 人脸 def predict_emotion(model, aligned_face): # 预处理:BGR->RGB,归一化,加 batch 维度 x = cv2.cvtColor(aligned_face, cv2.COLOR_BGR2RGB) / 255.0 x = np.transpose(x, (2, 0, 1))[None, ...].astype(np.float32) logits = model(torch.from_numpy(x)) prob = torch.softmax(logits, dim=1)[0] idx = int(torch.argmax(prob)) return EMOTIONS[idx], float(prob[idx])逻辑说明:表情网络用 MobileNetV3-small 就够,参数量小、推理快,细粒度分类不需要大模型。EMOTIONS是 7 类标签列表。注意输入必须是对齐后的人脸,歪脸会让表情分类准确率崩掉。如果自己场景数据少,可以用 FER2013 预训练再微调。
3.4 异常行为检测:时序轨迹比单帧分类靠谱
异常行为(翻越、摔倒、聚集、徘徊)单帧很难判断,必须看时序。我的做法是维护每个人体框的轨迹队列,算三个指标:速度、停留时长、宽高比变化。
from collections import deque class Track: def __init__(self, tid): self.tid = tid self.boxes = deque(maxlen=30) # 最近 30 帧的框 self.start_frame = 0 def update(self, box, frame_id): self.boxes.append((frame_id, box)) def is_fall(self): # 摔倒:宽高比从竖长变横长,且持续若干帧 if len(self.boxes) < 10: return False recent = [b for _, b in list(self.boxes)[-10:]] ratios = [(x2-x1)/(y2-y1+1e-6) for x1,y1,x2,y2 in recent] return sum(r > 1.2 for r in ratios) >= 7 def is_loiter(self, cur_frame, threshold=150): # 徘徊:停留超过 threshold 帧且位移小 return cur_frame - self.start_frame > threshold逻辑说明:deque(maxlen=30)只保留最近 30 帧,省内存。摔倒判定用宽高比连续多帧翻转,避免单帧误判。徘徊用停留帧数,阈值 150 帧约等于 5 秒(30fps)。这些阈值都要按自己场景的帧率和摄像头角度调,没有万能值。
注意:异常行为检测的误报率天然偏高,工程上一定要加「连续 N 帧确认」和「人工复核」两道闸,别直接触发报警。
4. 避坑与排查:这套三合一方案最容易翻车的五个地方
4.1 检测框抖动导致识别频繁切换身份
现象:同一个人站在摄像头前,识别结果在两个人之间反复跳。原因:Yolov5 逐帧检测,框的位置有像素级抖动,裁剪出的人脸每次略有不同,特征相似度在阈值附近震荡。解决:给每个 track 维护一个特征队列,取最近 5 帧特征的平均值再比对,同时加「身份切换需连续 3 帧一致」的确认逻辑。
4.2 表情识别在侧脸和遮挡下全线崩盘
现象:正脸表情识别挺准,一转头或戴口罩就乱标。原因:表情分类网络训练数据以正脸为主,侧脸和遮挡样本极少。解决:在检测阶段就过滤掉偏转角度过大的人脸(用关键点算头部姿态),侧脸直接跳过表情识别,返回「未知」而不是硬猜。戴口罩场景要么单独训一个口罩数据集,要么放弃表情只做识别。
4.3 多模型串行推理帧率掉到个位数
现象:单跑 Yolov5 能到 30fps,加上识别和表情后掉到 5fps。原因:四个模型串行跑,每个都占 GPU,且人脸裁剪、对齐是 CPU 操作,和 GPU 推理没重叠。解决:一是人脸识别和表情识别不必每帧都跑,检测每帧跑,识别每 5 帧跑一次;二是用多线程把 CPU 预处理和 GPU 推理流水线化;三是模型量化,Yolov5n + MobileNetV3 用 FP16 推理。
4.4 底库特征和现场特征分布不一致
现象:离线测试相似度 0.8,现场只有 0.3。原因:底库照片是证件照(正脸、光照均匀),现场是监控画面(侧脸、逆光、模糊),域差异大。解决:底库照片要模拟现场条件拍,或者用现场采集的帧做底库。实在不行,把阈值调低并接受一定误识,同时加活体检测防照片攻击。
4.5 异常行为阈值在不同摄像头下全部失效
现象:一个摄像头调好的摔倒阈值,换到另一个摄像头就疯狂误报。原因:摄像头安装高度、俯仰角、分辨率不同,人体框的宽高比基准完全不同。解决:阈值不能写死,要做自适应——用前 100 帧的宽高比均值作为基准,判定时看相对变化而不是绝对值。换摄像头必须重新标定基准。
5. 把三合一系统压进边缘设备的几个实操技巧
前面讲的都是服务器或带独显的机器上的做法。真要做成门禁机、边缘盒子,算力往往只有一块 Jetson 或者树莓派 5 加加速棒。热词里「树莓派5上部署自己训练的yolov5模型」就是这个场景。我的经验是:模型选型比优化技巧更重要,Yolov5n 是底线,再大就跑不动;人脸识别用 MobileFaceNet 而不是 ResNet100;表情网络砍到 0.5M 参数以内。
导出 ONNX 再转 TensorRT 是提速最明显的一步:
# 导出 yolov5 为 onnx,opset 11 兼容性最好 python export.py --weights weights/face_yolov5n.pt --include onnx --opset 11 --img 640 # 用 trtexec 转 TensorRT,FP16 精度 trtexec --onnx=face_yolov5n.onnx --saveEngine=face_yolov5n_fp16.engine --fp16逻辑说明:--opset 11是 TensorRT 支持最稳的算子集版本,太高会转失败。--fp16在 Jetson 上能提速约 1.8 倍,精度损失对人脸检测可忽略。转完 engine 后推理代码要换成 TensorRT runtime,输入输出绑定要对齐,这块最容易出错的是 NMS 后处理,建议把 Yolov5 的 NMS 也一起导出进 ONNX,别在 Python 侧手写。
验证方法:拿一段现场视频,分别用 PyTorch 和 TensorRT 跑,逐帧对比检测框的 IoU,IoU 均值大于 0.95 才算转换无损。我一般会写个小脚本自动比对,比肉眼看靠谱。
最后说个习惯:这套系统我从来不在本地调好就直接上线,一定先在目标设备上跑满 24 小时,看内存有没有缓慢增长、GPU 温度会不会撞墙、长时间运行后帧率是否衰减。边缘设备上的内存泄漏和热降频,是实验室里永远测不出来的。希望帮到你。
本文还有配套的精品资源,点击获取