十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于YOLOv5的实时人脸识别与异常行为检测系统解析

基于YOLOv5的实时人脸识别与异常行为检测系统解析 简介基于Yolov5与Python实现的视觉分析源码整合人脸识别、细粒度表情识别与异常行为检测三项功能面向计算机视觉方向的毕业设计、课程设计及项目实战也适合希望快速上手YOLO框架的初学者。代码含详尽注释逻辑完整并配有GUI界面便于理解与二次开发。资源包共107个文件压缩包24.81MB。以Python脚本为主37个py辅以YOLO配置文件18个yaml、训练权重pt、界面文件ui、说明文档及测试图片等覆盖数据配置、模型推理、结果显示等环节。已有268人学习下载适合需要可直接运行的高分项目范例。资源内含Dockerfile可支持容器化部署同时提供多张测试图与动态演示gif方便快速验证效果。整体模块划分明确既可作为Yolov5实际应用的入门学习素材也能直接作为毕业设计或期末大作业的完整方案具备较高的实用与参考价值。1. 一套Yolov5Python的识别源码实际是把三条检测管线合成一条人脸识别门禁、课堂专注度分析、安防摄像机这三种场景看起来毫不相关但它们底层共享同一个前提先知道“人”在哪。所谓“基于Yolov5Python实现的人脸识别、细粒度表情识别、异常行为检测”本质上是把Yolov5这个目标检测器当成公共底座在其上并联了三条不同深度的分析管线——人脸比对走特征向量路线表情识别走分类网络路线异常行为走跟踪与时序判断路线。难点不在Yolov5本身而在于三套下游算法如何共用同一个检测输出、如何在CPU或边缘GPU上轮流工作而不互相拖慢。这篇文章按“检测层→识别层→行为层→部署层”的顺序把每一段的可运行代码、参数取法和常见坑位一起讲清楚适合正在做毕设、准备小型安防原型、或想快速把手头Yolov5模型扩展成多任务系统的工程师。2. 用Yolov5训练自己的数据集人脸与人体检测的模型选型和标注2.1 检测层选Yolov5而不是MTCNN或OpenCV的理由人脸识别领域有专门的小模型MTCNN就是经典选择召回率不低速度也快。但这套源码的特别之处在于同一个检测器还要负责人体检测供异常行为分析使用。如果人脸用MTCNN、人体用另一个检测器系统里就要维护两套预处理、两套NMS参数、两个推理引擎边缘设备上内存和显存都会被压得很紧。Yolov5一个模型可以同时输出人脸框和人体框只要分类列表里同时包含“face”和“person”两个类别。训练好的Yolov5对遮挡、暗光、侧脸的鲁棒性也好于传统人脸检测器。OpenCV的Haar或DNN人脸检测在校正用户正对镜头时够用但摄像头装在走廊顶棚或者教室侧面时俯视和侧脸场景会让Haar召回率掉到60%以下。Yolov5基于anchor的目标检测方式对“部分可见”的目标天然不敏感配合mosaic增强后能撑住大部分真实监控角度。2.2 标注与人脸/人体数据集的搭配策略检测层不建议从头训练。Yolov5官方仓库提供了在COCO上训练好的权重其中已经包含person类人体检测直接可以用。人脸检测推荐在WIDER Face上微调或者直接使用社区训练好的face模型。自建数据时标注格式是YOLO txt格式每一行内容如下0 0.4531 0.3828 0.1211 0.1641 1 0.7314 0.5210 0.0986 0.2134第一列是类别id人脸设为0、人体设为1后面四列分别是归一化后的中心点x、中心点y、宽度w、高度h。标注工具用LabelImg或X-AnyLabeling都可以导出时选择YOLO格式。建议在标注时注意一个细节人脸框不要紧贴下巴和颅顶留出约5%的边距因为后续做人脸对齐时需要用到眼睛和嘴巴的位置框太紧会把一部分五官切掉。2.3 训练命令与需要在yolov5超参数里调整的三个值假设数据目录结构为datasets/face_person/内部包含images/train、images/val、labels/train、labels/val四个子目录训练命令如下python train.py \ --img 640 \ --batch 16 \ --epochs 100 \ --data ../datasets/face_person/face_person.yaml \ --weights yolov5s.pt \ --hyp data/hyps/hyp.scratch-low.yaml \ --cache ramface_person.yaml里至少要写明nc: 2类别数量和names: [face, person]。--hyp指定超参数文件--cache ram表示把图片加载进内存以换取更快的训练速度。训练时最值得调整的三个超参数在hyp文件里超参数默认值调整建议影响范围lr00.01人脸人体这类类别少、目标尺寸差异大的任务建议降到0.005学习率偏大会导致小目标不收敛mosaic1.0如果人脸目标在画面中占比很小保持1.0目标接近正方形可降到0.8控制训练时是否做马赛克拼接增强fl_gamma0.0正负样本极端不平衡时设为1.5focal loss的gamma参数人脸样本占比低时有效这三个值中lr0的影响最直接。很多人直接拿官方的coco训练参数去跑自己的小数据集结果是loss看起来在降但val集的mAP0.5死活上不去换成更小的学习率后两个epoch就看到改善。如果使用--weights yolov5s.pt做迁移学习Yolov5会自动冻结前三层可以先不改动其他参数跑10个epoch确认类别loss在下降后再做微调。2.4 检测输出解析把txt结果转成可下游使用的bbox训练完成后运行自带的detect.py导出检测结果python detect.py \ --weights runs/train/exp/weights/best.pt \ --source ../datasets/face_person/sample.mp4 \ --save-txt --save-conf --project runs/detect --name sample每帧图片对应的txt文件会落在runs/detect/sample/labels/下内容格式为0 0.4531 0.3828 0.1211 0.1641 0.92 1 0.7314 0.5210 0.0986 0.2134 0.87对比之前的标注格式多了最后一列置信度。注意这里的坐标仍然是归一化的而且顺序是class x_center y_center width height conf。在Python中解析并转成像素坐标时有一个容易错的地方直接用PIL或OpenCV读图的尺寸乘以归一化坐标会得到正确的值但不能把x_center和y_center当成左上角如果后面接人脸对齐或跟踪算法时推荐转成[x1, y1, x2, y2]的整数坐标格式避免浮点误差累积。转坐标的通用代码段import cv2 import numpy as np def yolo_to_pixel(box_norm, img_w, img_h): cx, cy, w, h, conf box_norm x1 int((cx - w / 2) * img_w) y1 int((cy - h / 2) * img_h) x2 int((cx w / 2) * img_w) y2 int((cy h / 2) * img_h) return x1, y1, x2, y2, conf这里先把归一化中心点坐标减去宽高的一半得到左上角坐标再乘以图像宽高。之所以强调顺序是先减后乘而不是先乘后减是因为前者的误差取决于单次乘法后者会引入两次独立的舍入误差虽然差异只有一两像素但对后续人脸对齐中的关键点坐标计算会产生连锁偏差。3. 细粒度表情识别人脸对齐、轻量分类器与AU辅助训练3.1 细粒度与粗粒度表情识别的差别先明确结论FER2013式的七分类生气、厌恶、恐惧、开心、悲伤、惊讶、中立属于粗粒度表情识别区分的是基本情绪类别。“细粒度”关注的是同类表情内部的强度差异和局部变化——同样是开心嘴角上扬5度和上扬15度在七分类里都叫happy但细粒度要求模型能区分这两种状态。细粒度识别的关键不是加深网络而是让网络注意到那些“类内差异小、类间差异近”的面部区域。做法上常见两个方向一是回归FACS动作单元AU系数比如AU12对应嘴角拉伸AU6对应眼轮匝肌收缩用这些系数的组合表达微表情强度二是设计注意力模块让模型自动关注眼睛、眉毛、嘴角这几个区域。后者更省事适合直接接在Yolov5后面做分类。3.2 从Yolov5检测框到表情分类的最小通路实现的最小串联链路是Yolov5检测人脸 → 裁剪人脸区域 → 仿射变换对齐到112×112 → 送入表情分类网络。对齐这一步经常被忽略但实际用户体验差异最大的就是这个环节。同一个人的脸倾斜5度后再缩放到112×112像素分布完全不同模型输出的情绪强度值波动会很大。import cv2 import numpy as np import torch from models.experimental import attempt_load from utils.general import non_max_suppression def align_face(img, bbox, landmarksNone): cx (bbox[0] bbox[2]) / 2 cy (bbox[1] bbox[3]) / 2 width bbox[2] - bbox[0] height bbox[3] - bbox[1] scale 1.0 if landmarks is not None: left_eye landmarks[0] right_eye landmarks[1] angle np.degrees(np.arctan2(right_eye[1] - left_eye[1], right_eye[0] - left_eye[0])) eyes_center ((left_eye[0] right_eye[0]) / 2, (left_eye[1] right_eye[1]) / 2) M cv2.getRotationMatrix2D(eyes_center, angle, scale) aligned cv2.warpAffine(img, M, (img.shape[1], img.shape[0])) bbox_cx (bbox[0] bbox[2]) / 2 bbox_cy (bbox[1] bbox[3]) / 2 face_crop aligned[int(bbox_cy - height/2):int(bbox_cy height/2), int(bbox_cx - width/2):int(bbox_cx width/2)] else: face_crop img[int(cy - height/2):int(cy height/2), int(cx - width/2):int(cx width/2)] face_crop cv2.resize(face_crop, (112, 112)) return face_crop这段代码的关键点是当有面部关键点时先按双眼连线角度旋转图像再从旋转后的图上截取人脸而不是直接在原图上截取后再旋转。后者的坏处是旋转后四角出现黑色填充区域这些区域被缩放到112×112后会被模型当成无效纹理信息。如果Yolov5没有输出关键点就退回用bbox中心做旋转实际效果会差一些但也能用。3.3 模型结构用注意力模块学习局部特征细粒度表情识别网络不需要大。一个基础的ResNet18加上通道注意力模块就能达到可用水平参数量约11M在CPU上的单帧推理时间约18毫秒放在检测管线后面不会造成明显瓶颈。下面是一个可替换的attention残差块定义class SELayer(nn.Module): def __init__(self, channel, reduction16): super(SELayer, self).__init__() self.avg_pool nn.AdaptiveAvgPool2d(1) self.fc nn.Sequential( nn.Linear(channel, channel // reduction, biasFalse), nn.ReLU(inplaceTrue), nn.Linear(channel // reduction, channel, biasFalse), nn.Sigmoid() ) def forward(self, x): b, c, _, _ x.size() y self.avg_pool(x).view(b, c) y self.fc(y).view(b, c, 1, 1) return x * y.expand_as(x) class BasicBlock(nn.Module): def __init__(self, inplanes, planes, stride1): super(BasicBlock, self).__init__() self.conv1 nn.Conv2d(inplanes, planes, kernel_size3, stridestride, padding1, biasFalse) self.bn1 nn.BatchNorm2d(planes) self.relu nn.ReLU(inplaceTrue) self.conv2 nn.Conv2d(planes, planes, kernel_size3, padding1, biasFalse) self.bn2 nn.BatchNorm2d(planes) self.se SELayer(planes) self.downsample None if stride ! 1 or inplanes ! planes: self.downsample nn.Sequential( nn.Conv2d(inplanes, planes, kernel_size1, stridestride, biasFalse), nn.BatchNorm2d(planes) ) def forward(self, x): identity x out self.conv1(x) out self.bn1(out) out self.relu(out) out self.conv2(out) out self.bn2(out) out self.se(out) if self.downsample is not None: identity self.downsample(identity) out identity out self.relu(out) return out中间层的SE模块用全局平均池化计算每个通道的权重再与原特征图相乘。它的作用是让网络判断当前人脸时更依赖某些通道——例如检测到嘴角区域有强纹理响应时提高对应通道的激活值。downsample分支解决的是stride1的残差块里输入输出通道数不等时的维度匹配问题。输出层根据实际任务决定如果是AU系数回归替换成nn.Linear(512, 17)接Sigmoid如果是细粒度强度分类接Softmax。3.4 训练技巧中心损失与FACS动作单元辅助监督只用交叉熵训练细粒度表情识别网络得到的结果往往是同一表情的不同强度被分到同一类模型自信但区分度低。常见做法是再加一个中心损失在训练时约束同一类样本的特征向量靠近类中心同时配合交叉熵保持类间可分。总损失的计算方式criterion_cls nn.CrossEntropyLoss() criterion_center CenterLoss(num_classesnum_exp_classes, feat_dim512, use_gpuTrue) for images, labels in dataloader: features model(images, return_featuresTrue) output model.classifier(features) loss_cls criterion_cls(output, labels) loss_center criterion_center(features, labels) loss loss_cls 0.01 * loss_center optimizer.zero_grad() loss.backward() for param in criterion_center.parameters(): param.grad.data * (0.5 / (0.01 * 1.0)) optimizer.step()CenterLoss的权重取0.01后续反向传播时对中心参数的梯度额外乘以0.5用于控制类中心更新的速度。如果训练数据里带AU标签可以再加一个辅助分支做AU多标签分类与表情分类共享backbone的底层特征提升对嘴角和眼部局部形态的感知能力。提示细粒度表情模型需要的小技巧是数据增强中要多用随机擦除。在实际监控画面上人脸经常被口罩、头发、手部遮挡随机擦除能让模型学会在部分区域缺失时仍然准确推理。4. 异常行为检测跟踪轨迹、速度阈值与姿态判据4.1 为什么行为检测必须做跟踪单帧判定误报率太高异常行为本质是时序概念。单帧图像里一个人躺在地上可能是摔倒也可能只是坐在懒人沙发上休息一个人和另一个人靠近可能是打架也可能只是握手。只依赖单帧目标检测做判断误报率通常在每小时数次到数十次之间无法商用。合理的判断顺序是先用Yolov5检测人体框再用跟踪算法给每个目标分配独立id维护每条轨迹的历史坐标序列最后基于轨迹序列做行为和姿态判断。Yolov5本身不做跟踪每帧输出的目标没有任何身份信息DeepSort或ByteTrack的作用就是把相邻帧的检测框关联成轨迹。4.2 DeepSort与Yolov5串联的必调参数max_age与min_hits常见做法是用DeepSort做跟踪。下面是与Yolov5串联的核心代码逻辑from deep_sort_realtime.deepsort_tracker import DeepSort tracker DeepSort( max_age30, max_iou_distance0.7, n_init3, nms_max_overlap1.0, max_cosine_distance0.4 ) for frame in video_stream: detections model(frame) # Yolov5输出 [x1, y1, x2, y2, conf, cls] person_dets [d for d in detections if d[5] 1] tracks tracker.update_tracks(person_dets, frameframe) for track in tracks: if track.is_confirmed(): track_id track.track_id ltrb track.to_ltrb()各参数的实际含义和调整方式参数默认值作用异常行为场景下的调整max_age30目标丢失后轨迹保留的帧数目标被遮挡后重新出现需要保留轨迹可提高到50max_iou_distance0.7检测框与预测框的IOU低于该值则关联失败人体快速移动时框之间IOU明显变小可放宽到0.8n_init3连续匹配成功帧数达到该值才确认轨迹希望更快输出id时可降到2但会造成id切换max_cosine_distance0.4表观特征的最大余弦距离相邻帧人体表观变化大时可提高到0.5max_age是异常行为中最影响判断结果的参数。倒地场景中人倒地后移动速度变慢但检测框高度会骤降如果跟踪器的max_age太短倒地瞬间目标就丢了后续无法判断“这个人在原地静止”。建议优先增大max_age到50再看id切换率是否恶化。id切换的典型表现是同一个人的track_id在画面中反复变化如果出现这种情况优先降低max_iou_distance而不是加更多的形态特征。4.3 倒地、奔跑、打架、禁区闯入的判定逻辑有了轨迹之后四种常见异常行为的判定逻辑分别如下。注意这些逻辑不依赖姿态估计只依赖检测框坐标和尺寸变化优点是CPU也能实时跑缺点是倒地这种语义无法区分“蹲下系鞋带”和“跌倒”。更严格的做法是接Yolov5-pose或用MediaPipe提取关键点判断头部与脚部关键点的连线角度。def detect_fall(history_boxes, current_box, fps): if len(history_boxes) 5: return False prev_h np.mean([b[3]-b[1] for b in history_boxes[-5:]]) cur_h current_box[3] - current_box[1] y_delta current_box[1] - history_boxes[-1][1] frame_delta 1.0 / fps velocity_y y_delta / frame_delta center_x (current_box[0]current_box[2]) / 2 center_x_delta center_x - (history_boxes[-1][0]history_boxes[-1][2]) / 2 if (cur_h prev_h * 0.6 and velocity_y 250): return True if abs(center_x_delta) 2 and velocity_y -150 and cur_h prev_h * 0.7: return True return False倒地检测用的是两个条件第一个条件抓“框高度骤降且伴随向下快速移动”的动作过程第二个条件抓“倒地后静止在低位”的状态。实际工程中第二个条件更可靠因为快速向下移动通常只持续2到3帧处理稍慢就错过了。至于速度阈值250像素/秒是把移动距离除以帧间隔时间得到的实际速度具体值需要根据摄像头安装高度和画面像素密度标定建议在正式使用前采集一段正常行走画面统计正常速度的均值再乘2作为异常阈值。奔跑检测直接用中心点速度即可。打架行为的特征是两个人体的检测框在连续多帧内高度重叠同时中心点在水平方向上高频率抖动可以在代码中维护每个id的IOU矩阵当某个时间窗口内两个目标IOU持续大于0.3且中心点距离波动超过阈值时触发告警。禁区闯入则是判断人体中心点是否在预设多边形区域内用ray casting算法即可不需要引入额外库。5. 把三条管线合成一个服务并压出可用帧率5.1 线程或进程隔离识别慢、检测快的调度问题Yolov5检测每帧耗时约10-20毫秒GPU上人脸特征提取和表情分类各需要大约10-30毫秒。如果按“检测→识别→行为”串行执行一帧总耗时可能达到60毫秒帧率掉到15 fps以下。常见做法是拆成两个线程主线程只跑Yolov5检测和跟踪识别线程消费检测队列做表情与身份识别。人脸特征提取不是每帧都需要做可以用一个简单的策略——目标id没有入库时从头提取一次特征之后每30帧复检一次。提示检测输出与人脸特征提取使用同一份图像数据时要处理好内存引用不能用cv2的直接裁剪共享底层buffer否则一侧修改会导致另一侧的人脸区域数据被破坏。5.2 推理优化半精度、TensorRT与帧采样推理优化的优先顺序应该是TensorRT/Float16 帧采样 降低分辨率。Yolov5本身自带半精度推理python detect.py --weights best.pt --source test.mp4 --half --img 640--half参数将模型转为FP16推理在T4或3090上通常可以取得1.5-2倍加速但因为FP16的动态范围小输入图像数据也会被转成半精度某些光照剧变的视频帧会出现检测框抖动因此要确认检测结果在目标场景下不退化再上线。TensorRT的加速更进一步需要额外做模型转换和校准适合要求稳定的项目。如果摄像头数量多但GPU只有一张帧采样反而是最经济的优化手段。人脸识别做每2帧一次、表情识别做每5帧一次、异常行为检测必须每帧都做——因为跌倒过程往往只有不到1秒跳帧会导致动作过程被整段跳过。5.3 用一张表验证识别效果阈值、验证集划分、误报观察部署完成后不要只看演示视频的观感要把判定结果落成表格来观察。一张推荐的效果验证记录表至少包含这几列时间戳视频帧号检测id人脸置信度表情强度行为判定结果人工复核14:03:21.5184370.930.78normal通过14:03:22.0184970.910.35normal通过14:05:10.3332130.870.62fall误报起身太快观察这张表的重点有两个。第一表情强度值在短时间内剧烈跳变时说明前处理或者模型阈值设置有问题正常人的表情变化是平滑的第二行为判定结果要尽量配合人工复核回看视频连续看半小时的误报比看半小时正确结果更能暴露参数问题。调试时把每次人工复核为误报的帧保存到独立目录统计这些误报里还包含多少人脸检测框——这个方法能快速定位到底是检测层漏检还是行为判断逻辑本身的问题。实际部署时还会遇到一个细节同一张人脸在画面中从远到近移动检测框从64×64变为256×256人脸识别阈值固定不变时会出现在远处无法识别、近处正常识别的情况表情强度也会随之漂移。针对这个问题可以先固定识别阈值为0.6再用检测框的尺寸对表情强度做一次归一化adjusted raw_intensity * (128 / face_width)。这一行补充逻辑通常能显著降低远距离误报。本文还有配套的精品资源点击获取
返回列表