ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于Yolov5的多任务视觉检测:人脸识别、表情识别与异常行为分析

基于Yolov5的多任务视觉检测:人脸识别、表情识别与异常行为分析 简介一套基于Yolov5与Python实现的人脸识别、人脸细粒度表情识别与异常行为检测综合项目面向正在准备毕业设计、期末大作业或课程设计的计算机相关专业学生。代码包含详细注释部署流程简洁快速跑通后便于二次开发。压缩包共107个文件涵盖37个Python脚本、18个YAML训练与推理配置、模型权重、ui界面、测试图片及演示GIF并附带Dockerfile等环境部署辅助内容整体约24.81MB结构清晰易检索。项目目前已有268人次浏览学习导师认可度较高可直接作为期末大作业或毕设系统使用。系统界面美观、操作简单功能覆盖人脸检测、细粒度表情分类和异常行为识别等多条流程同时提供编译后的pyc文件与mat数据便于对照源码理解底层逻辑。各类文件按用途区分存放对于希望快速搭建一套可演示、可扩展的Yolov5应用系统的开发者来说能提供从环境配置到模型调用的完整参考。1. 一个Yolov5撑起三合一视觉检测项目人脸识别、表情识别、异常行为检测在校园监控、门禁考勤和实验室安全管理这类场景里经常需要同时回答三个问题画面里的人是谁、这个人表情状态如何、当前行为是否异常。传统做法是拆成三套系统分别部署设备成本和维护成本都翻倍。而基于Yolov5Python实现的人脸识别、细粒度表情识别、异常行为检测源码用一套模型骨架把三个任务统一到一个推理流程中Yolov5负责底层目标检测对人脸区域做特征比对和细粒度表情分类对检测框序列做规则推断实现异常行为判定边缘设备上也能达到实时处理。代码注释完整适合毕业设计、课程设计和需要快速验证视觉方案的从业者。2. Yolov5多任务环境搭建与项目目录结构解读2.1 Yolov5作为底层检测器的选型理由Yolov5在目标检测领域已不算最新但放在这个三合一项目里反而是更合适的选择。相比Yolov8和RT-DETRYolov5的权重体积明显更小yolov5s仅14MB左右在Jetson Nano、树莓派这类边缘设备上能稳定跑到30帧以上。更重要的是它的推理代码结构清晰backbone输出的特征图可以直接复用。这类多任务改造通常会在Yolov5检测头旁并联一个表情分类分支和一个特征嵌入分支Yolov5的C3模块和SPPF结构在设计上耦合度低新增分支时不容易引入梯度冲突训练收敛速度也比直接从零搭建多任务网络快得多。纯人脸识别场景里很多人优先想到Facenet或ArcFace但这两类模型对输入要求苛刻需要正脸对齐直接处理监控画面中的多角度人脸效果会明显打折。这个资源采用的是检测后对齐方案Yolov5检测到人脸框后先做仿射变换对齐再提取特征相当于用数据预处理绕开单独的人脸对齐网络。这样既保留了Yolov5对多尺度、多姿态目标的鲁棒性又省掉一块算力开销在CPU上就能跑完整条链路。2.2 依赖配置与Dockerfile部署依赖以PyTorch生态为主requirements.txt的常见配置如下torch1.12.1 torchvision0.13.1 opencv-python4.6.0.66 numpy1.21.6 matplotlib3.5.3 seaborn0.11.2 tqdm4.64.1 pandas1.3.5这些库的版本组合经过验证与Yolov5仓库v6.1/v7.0分支能直接兼容。seaborn和pandas用于训练日志绘图和标签解析如果只做推理可以注释掉但项目自带的可视化模块依赖它们生成混淆矩阵和PR曲线毕设答辩时这两张图很有说服力建议保留。项目根目录提供Dockerfile部署时直接用FROM python:3.8-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple COPY . . EXPOSE 8080 CMD [python, detect.py, --source, data/0.jpg]构建命令是docker build -t face-ai .运行容器时用docker run --gpus all -it face-ai挂载GPU。基础镜像选python:3.8-slim而不是pytorch官方镜像是因为推理阶段CPU跑yolov5s延迟约80ms多数演示场景用不到GPUslim镜像体积能少一半。训练场景再换成pytorch/pytorch:1.12.1-cuda11.3-cudnn8-runtime即可。项目内自带图片文件的用途如下文件用途Dockerfile容器化部署脚本0.jpg / 1.jpg常规光照下的人脸测试样本2440.jpg / 2441.jpg侧脸与俯角测试样本example2.jpg跨姿态验证样本1620298460804.gif检测效果的动态演示初次运行建议直接用这些样本等检测、识别、行为判定全流程跑通后再替换成自己采集的数据。3. 人脸识别与细粒度表情识别的联合推理实现3.1 检测-特征提取-比对的完整链路整个识别链路分四步Yolov5检测人脸框、裁剪并对齐、提取特征向量、与注册库做余弦相似度比对。Yolov5输出的字段是[x1, y1, x2, y2, conf, cls]在这个项目里cls包含face和person两个类别。拿到人脸框后先按1.2倍扩大裁剪避免额头和下巴被截掉再缩放到112x112。112是ArcFace系列特征提取网络的标准输入尺寸比112更大的输入不会带来精度增益反而让推理延迟明显上升。对齐这一步容易被忽略但影响最大。如果特征提取网络自带人脸关键点输出就用cv2.getAffineTransform做基于两眼中心的相似变换如果只有纯分类模型就退而求其次做居中裁剪加缩放。两种方式对比测试过在头部偏转超过30度时带关键点对齐的识别准确率高约4个百分点。项目里采用哪一种取决于特征网络本身的结构建议优先使用带关键点分支的版本。3.2 人脸特征比对服务端实现特征比对核心逻辑不算复杂关键是阈值怎么选import numpy as np import torch import torch.nn.functional as F class FaceRecognition: def __init__(self, embedder, threshold0.62): self.embedder embedder # 输入112x112输出128维向量 self.threshold threshold # 余弦相似度判定阈值 self.known_faces {} # 注册表 {name: embedding} def register(self, name: str, face_img: np.ndarray): emb self.embedder(face_img) self.known_faces[name] emb def identify(self, face_img: np.ndarray): emb self.embedder(face_img) best_name, best_score unknown, -1.0 for name, known_emb in self.known_faces.items(): score F.cosine_similarity( emb.unsqueeze(0), known_emb.unsqueeze(0) ).item() if score best_score: best_name, best_score name, score if best_score self.threshold: return best_name, best_score return unknown, best_score阈值初始值取0.62这是LFW数据集上相似度分布的一个常用落点接近阈值的人脸通常是姿态差异较大的同一人。实际部署时这个值必须调整摄像头分辨率低就往下调到0.55干净的正脸登记照就往上调到0.70。推荐的调参方式是各采集100对同人不同姿态和不同人的样本画出相似度分布曲线取两个分布交叉点附近的值作为阈值。3.3 细粒度表情分类从粗标签到强度判定细粒度表情识别的难点不在增加类别数而在区分情绪的强度层级。粗粒度七分类表达的是基本情绪但安防场景真正需要的是区分“不悦”和“愤怒爆发”二者对应的处置策略完全不同。比较常见的做法是在粗粒度分类头之外并联一个强度回归头输出范围0到1。粗粒度情感强度0.5强度≥0.5开心微笑大笑愤怒不悦爆发悲伤低落痛哭惊讶微惊震惊这种离散标签加连续强度的结构在训练数据不足时也能稳定收敛。粗粒度分支有预训练权重兜底强度分支只需要学习一个偏移量网络容量占用很小class FineGrainedEmotion: def __init__(self, model, coarse_labels): self.model model self.coarse_labels coarse_labels # 粗粒度标签列表 def predict(self, face_roi): # face_roi 是经过Yolov5检测并缩放到112x112的人脸图 coarse_logits self.model.emotion_head(face_roi) # 粗粒度7分类 intensity self.model.intensity_head(face_roi) # 强度0~1 coarse_idx int(coarse_logits.argmax(dim-1)) level 1 if intensity 0.5 else 0 level_str 重度 if level 1 else 轻度 return f{self.coarse_labels[coarse_idx]}-{level_str}, intensity.item()这里coarse_labels是写死的类别列表顺序必须和训练时保持一致否则推理结果会整段错位。强度阈值默认0.5如果项目偏向检测极端情绪把阈值降到0.35更多中等强度表情被划入重度代价是误报率上升。得到的细粒度标签可以直接输入到下游报警逻辑比如“愤怒-重度”连续出现3帧以上就触发预警。4. 异常行为检测的实现与阈值调节4.1 可检测行为的定义与判定规则异常行为检测基于Yolov5输出的人体检测框做规则引擎推导不接骨骼关键点网络因此推理速度基本不受影响。常见可检测行为与判定逻辑如下行为类型判定依据关键参数跌倒检测人体框宽高比突变 垂直移动速度aspect_thresh0.5, speed_thresh15徘徊检测同一目标长时间停留在ROI内stay_time10s打架检测多目标框高重叠 边界框抖动iou_thresh0.3, jitter8px区域入侵中心点进入警戒多边形poly_points这套规则的核心思想是用检测框的几何变化推断行为。跌倒和打架这类剧烈行为在几何特征上非常明显规则引擎足以覆盖。但正常弯腰、蹲下绑鞋带也会造成宽高比突变所以需要纵向速度做二级校验速度不达标不触发报警。4.2 跌倒检测的代码实现class FallDetector: def __init__(self, aspect_thresh0.5, speed_thresh15, window5): self.aspect_thresh aspect_thresh self.speed_thresh speed_thresh self.window window self.centers [] def update(self, bbox): x1, y1, x2, y2 bbox w max(x2 - x1, 1e-6) # 防止除零 h y2 - y1 aspect h / w # 站立时大于2跌倒后接近1 center_y (y1 y2) / 2 self.centers.append(center_y) if len(self.centers) self.window: self.centers.pop(0) speed center_y - self.centers[0] if len(self.centers) 2 else 0 is_fall (aspect self.aspect_thresh) and (speed self.speed_thresh) return is_fall, {aspect: aspect, speed: speed}window取5的意思是使用5帧窗口做平滑比逐帧判断抗噪能力强。speed取当前帧与第5帧中心点的差值单位是像素每5帧实际上就是下坠速度。摄像头安装高度不同这个值需要缩放2.5米安装高度和4米安装高度同样的物理运动在画面上的像素位移完全不同。我一般先用一段正常行走视频做基线取正常行走垂直速度最大值乘1.5作为speed_thresh。4.3 打架与徘徊检测的实现要点打架检测的判定条件是双人IoU超过0.3且框的宽高在连续帧之间变化超过8像素。IoU计算时框坐标需要做clip避免检测框越出画面边界时宽高出现负值。这里的8像素不是绝对值应该按画面高度h的1%等比缩放1080p画面中大约是10像素。打架时双方都在动框体尺寸的抖动幅度显著高于正常擦肩而过所以jitter参数能有效排除误报。徘徊检测需要给每个检测目标分配track id。这里采用基于IoU匹配的最简方案当前帧的框与上一帧的框做IoU计算超过阈值就延续原ID否则分配新ID。实现上维护一个字典记录每个目标首次进入ROI的时间戳class LoiterDetector: def __init__(self, stay_time10): self.stay_time stay_time # 停留阈值单位秒 self.enter_time {} # {track_id: 首次进入时间} def update(self, boxes, roi_polygon, timestamp): suspected [] for box in boxes: cx, cy (box[0] box[2]) / 2, (box[1] box[3]) / 2 if not point_in_polygon(cx, cy, roi_polygon): continue track_id self._match_or_assign(box) if track_id not in self.enter_time: self.enter_time[track_id] timestamp if timestamp - self.enter_time[track_id] self.stay_time: suspected.append(track_id) return suspected_match_or_assign按上一帧的框做IoU匹配匹配不上就分配新ID。这个方案在目标发生遮挡时会断ID解决方案是允许丢帧3次内继续沿用原ID维护一个lost_frames计数器超过3帧再真正分配新ID。掉帧期间停留时间不重置但计数器的值每帧递减避免长期失效。4.4 阈值调参的落地参数一组比较普适的起始值光照正常的室内监控跌倒检测aspect_thresh设0.5、speed_thresh设20误报频率约两小时一次。调到15后会频繁报警多是人员蹲下整理器材触发。打架检测iou_thresh设0.25更灵敏但两个人正常擦肩时IoU也可能短暂超过0.2所以jitter阈值下限建议6像素。建议把参数全部收进统一的config.yaml文件管理不要散落在各个py文件里。调整参数的思路是每次只改一个维度记录触发事件对应的时间段和结果再和上一版对比。例如相同视频流上分别跑aspect_thresh0.45和0.55统计各触发了多少次误报漏报用数据说话而不是凭感觉调。5. 验证路径与排错清单5.1 本地复现的验证步骤拿到项目后先在命令行做一次闭环验证pip install -r requirements.txt python detect.py --source data/0.jpg --weights weights/yolov5s.pt控制台输出检测框坐标并生成带标注的输出图片说明主流程已通。接着单独测试人脸识别模块注册和识别分两步走python face_register.py --name zhangsan --img data/1.jpg python face_recognize.py --source data/0.jpg --threshold 0.60分开跑的好处是能确认是哪一步出问题。注册阶段读入图片并保存特征向量识别阶段加载已知特征和当前帧做余弦比对。细粒度表情和异常行为模块都有独立入口脚本建议按目录顺序逐个跑通。5.2 高频报错的定位与修复报错信息原因处理方式ImportError: No module named torchPyTorch未安装或环境未激活按requirements.txt安装或创建conda环境后重装CUDA out of memory显存不足推理加--device cpu或降低输入分辨率label classes mismatch标签与类别数不一致检查data.yaml的nc和names是否匹配AttributeError: NoneType object has no attribute模型路径错误或权重未加载检查weights路径加载前打印model.summary()提示遇到报错先看堆栈最后三行多数问题出在权重路径和输入尺寸不匹配而不是模型结构本身。5.3 从图片到实时视频流的改造验证完静态图片把输入源换成USB摄像头只需要改动detect.py里读取帧的部分核心检测代码完全不用动import cv2 cap cv2.VideoCapture(0) if not cap.isOpened(): raise IOError(Cannot open camera) while True: ret, frame cap.read() if not ret: break results detector.detect(frame) # 返回检测框与类别 annotated draw_results(frame, results) # 绘制标注框与标签 cv2.imshow(face-ai-demo, annotated) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()cap.read()是阻塞式读取网络摄像头延迟高时建议用单独线程预读帧把帧放入queue.Queue(maxsize2)主循环从队列取帧队列满时丢弃旧帧保证永远处理最新画面。实测这个改动能让有效帧率提升30%左右。接RTSP网络流时把cv2.VideoCapture(0)替换为cv2.VideoCapture(rtsp://user:pass192.168.1.64:554/stream1)即可保持预读队列逻辑不变。RTSP延迟通常在200到500毫秒对实时性要求高时优先用UDP传输替代TCP延迟能降低一半多。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进