YOLO人脸识别安防系统实战:从训练到TensorRT多路部署
简介这是一套面向深度学习入门者与毕业设计学生的完整安防项目源码围绕YOLO目标检测与人脸识别技术构建实时监控方案适合具备Python基础、希望将机器学习与图像识别落地到实际场景的开发者参考。压缩包共42个文件约5.79MB以21个Python脚本为核心涵盖模型训练、人脸特征编码、图像管理与安全控制等模块另有6个HTML页面与1个JS文件构成前端交互界面4个ino文件对应ESP摄像头与蜂鸣器等硬件端逻辑并附带pt权重、pkl特征库、yaml数据集配置及说明文档结构清晰便于按模块查阅。目前已有48人学习。资源完整呈现了从图像采集、预处理、模型训练到人脸匹配与门禁联动的技术链路读者可据此理解YOLO在安防场景中的工程化组织方式并参考其模块划分与训练管理思路快速搭建可运行的识别系统原型。1. 从一个人脸识别安防项目说起YOLO 检测 识别链路到底怎么落地很多人第一次拿到「基于 YOLO 的人脸识别安防系统」这类资源包第一反应是打开压缩包找train.py然后直接python train.py跑起来结果不是报路径错误就是显存爆掉再不然就是训练完发现模型根本认不出人。问题不在代码而在于没搞清楚这条链路的分工YOLO 在这里负责的是「人脸在哪里」识别负责的是「这张脸是谁」安防系统负责的是「什么时候该报警、什么时候该记录」。三者串起来才是一个能用的系统缺一环都只是半成品。这份资源适合两类人一类是做深度学习课程设计、毕业设计的学生需要一套能跑通、能改、能写进论文的完整流程另一类是想快速搭一个人脸检测 demo 的工程师需要看清 YOLO 在安防场景下的边界——比如 1080p 25 帧的视频流用 TensorRT 加速 YOLO 640 分辨率检测到底能扛几路这个问题后面会具体拆。下面按「资源是什么 → 怎么用 → 坑在哪」的顺序把这份资源包拆开讲透。2. YOLO 人脸检测链路拆解从数据标注到模型导出2.1 为什么安防场景选 YOLO 而不是通用人脸检测器安防场景和普通的人脸打卡机不一样。打卡机可以要求人正对镜头、光照均匀、背景干净但安防摄像头拍到的画面里人脸可能只有几十个像素可能侧脸、可能被遮挡还可能有几十个人同时出现在画面里。通用人脸检测器比如基于 Haar 或 HOG 的传统方案在这种场景下召回率会掉得很厉害漏检一个人可能就意味着一条报警记录丢失。YOLO 系列的优势在于单阶段检测一次前向传播同时输出边界框和类别置信度速度快、召回率高而且对小目标的检测能力在 v5 之后有明显提升。资源包里用的是 YOLO 做人脸检测本质上就是把「人脸」当成一个单类别目标来训练。常见做法是直接用 YOLOv5 或 YOLOv8 的架构把nc类别数改成 1然后用自己的安防人脸数据集去 fine-tune。这里有个选型细节如果资源包里用的是 YOLOv5那它的输出层是三个不同尺度的特征图分别对应 80×80、40×40、20×20 的网格小尺度特征图负责检测大脸大尺度特征图负责检测小脸。安防场景里小脸居多所以训练时要特别关注 P3 层80×80的召回率。如果资源包用的是 YOLOv8它的 anchor-free 设计对小目标更友好但需要确认imgsz参数是否设到了 640 以上否则小脸在预处理阶段就被缩没了。2.2 数据标注WIDER FACE 格式转 YOLO 格式的实操资源包里通常会附带一个数据集或者数据集的下载说明。安防人脸检测最常用的公开数据集是 WIDER FACE但它的标注格式是x1, y1, w, h左上角坐标加宽高而 YOLO 需要的是class_id, x_center, y_center, w, h归一化后的中心点坐标加宽高。这个转换必须做否则训练时 loss 会直接爆炸。下面是一个我常用的转换脚本处理 WIDER FACE 的标注文件import os import cv2 def widerface_to_yolo(anno_file, img_dir, label_dir): 将 WIDER FACE 标注转换为 YOLO 格式 anno_file: WIDER FACE 的标注 txt每行格式为 文件路径 人脸数量 x1 y1 w h blur expression illumination invalid occlusion pose with open(anno_file, r) as f: lines f.readlines() i 0 while i len(lines): img_path lines[i].strip() i 1 if i len(lines): break num_faces int(lines[i].strip()) i 1 # 读取图片获取宽高用于归一化 full_img_path os.path.join(img_dir, img_path) img cv2.imread(full_img_path) if img is None: i num_faces continue img_h, img_w img.shape[:2] yolo_lines [] for j in range(num_faces): parts lines[i j].strip().split() x1, y1, w, h map(float, parts[:4]) # 过滤掉无效框宽高为 0 或超出图像范围 if w 0 or h 0: continue x_center (x1 w / 2) / img_w y_center (y1 h / 2) / img_h norm_w w / img_w norm_h h / img_h # 裁剪到 [0, 1] 范围防止越界 x_center max(0, min(1, x_center)) y_center max(0, min(1, y_center)) norm_w max(0, min(1, norm_w)) norm_h max(0, min(1, norm_h)) yolo_lines.append(f0 {x_center:.6f} {y_center:.6f} {norm_w:.6f} {norm_h:.6f}) i num_faces # 写入对应的 label 文件 label_name os.path.splitext(os.path.basename(img_path))[0] .txt label_path os.path.join(label_dir, label_name) with open(label_path, w) as f: f.write(\n.join(yolo_lines)) if __name__ __main__: widerface_to_yolo( anno_filewider_face_train_bbx_gt.txt, img_dirWIDER_train/images, label_dirlabels/train )这段代码的关键点有三个。第一WIDER FACE 的标注里有些人脸框的宽高是 0 或者负数这些是无效标注必须过滤掉否则 YOLO 在计算 loss 时会出现 NaN。第二归一化后的坐标必须裁剪到 [0, 1] 区间因为 WIDER FACE 里有些框会超出图像边界不裁剪的话 YOLO 的 dataloader 会报错。第三class_id统一写 0因为人脸检测是单类别任务。转换完成后目录结构应该是这样的dataset/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/然后创建一个data.yamlpath: ./dataset train: images/train val: images/val nc: 1 names: [face]这个 yaml 文件是 YOLO 训练时的数据配置入口nc: 1表示只有一个类别names里写face。如果资源包里已经提供了 yaml检查一下path是不是相对路径绝对路径换机器就会翻车。2.3 训练参数怎么设从 batch size 到学习率的血泪经验资源包里的训练脚本通常是一个train.py调用 YOLO 的官方接口。下面是一个典型的训练命令python train.py \ --data data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --lr0 0.01 \ --device 0参数逐个说。--img 640是输入分辨率安防场景如果人脸普遍偏小可以提到 1280但显存占用会翻倍一张 8G 显存的卡跑 1280 分辨率 batch size 只能给到 4 左右。--batch 16是批次大小如果显存不够就往下调调到 8 或者 4但 batch size 太小会导致 BN 层统计量不稳定训练 loss 震荡。--lr0 0.01是初始学习率YOLO 官方推荐的是 0.01但如果你的数据集很小比如只有几千张学习率要降到 0.001否则模型会过拟合到训练集上。--device 0指定第一块 GPU如果是 CPU 训练就把这个参数去掉但 CPU 训练 100 个 epoch 可能要跑好几天。训练过程中要盯两个指标mAP0.5和recall。安防场景下 recall 比 precision 重要因为漏检的代价比误检高。如果训练到 50 个 epoch 发现 recall 还在 0.6 以下大概率是数据标注有问题或者小脸样本太少需要做数据增强。YOLO 自带的 mosaic 增强对小目标有帮助但如果人脸本身就很小mosaic 拼接后可能变得更小这时候可以考虑关掉 mosaic改用 copy-paste 增强。2.4 导出 ONNX 和 TensorRT推理加速的关键一步训练完的.pt文件是 PyTorch 格式推理速度一般。安防系统如果要接多路视频流必须做推理加速。常见做法是导出 ONNX再用 TensorRT 做量化。导出 ONNX 的命令python export.py \ --weights runs/train/exp/weights/best.pt \ --include onnx \ --img 640 \ --batch 1 \ --simplify--simplify参数会调用 onnx-simplifier 对计算图做简化去掉冗余算子。--batch 1表示导出 batch size 为 1 的模型如果要做多路并发推理可以导出 batch 8 或 16但 TensorRT 的优化会更复杂。导出 TensorRT 引擎trtexec \ --onnxbest.onnx \ --saveEnginebest.engine \ --fp16 \ --workspace4096--fp16开启半精度推理速度能提升 1.5 到 2 倍精度损失通常在 1% 以内。--workspace4096是 TensorRT 优化时的工作空间大小单位是 MB如果模型复杂可以调到 8192。导出完成后用best.engine做推理在 1080p 25 帧的视频流上YOLO 640 分辨率单路推理大概占用 2G 显存一张 T4 卡16G 显存理论上能跑 6 到 8 路但实际要考虑视频解码和预处理的开销稳妥一点跑 4 路比较合适。3. 人脸识别模块怎么接从检测框到身份确认3.1 检测到人脸之后对齐、特征提取与比对YOLO 输出的是人脸框坐标但识别模型比如 ArcFace、FaceNet需要的是对齐后的人脸图像。对齐的依据是五个关键点左眼、右眼、鼻尖、左嘴角、右嘴角。常见做法是用一个轻量级的关键点检测模型比如 PFLD 或 MobileFaceNet 自带的关键点分支先预测五个点然后做仿射变换把人脸旋转到标准姿态。对齐后的图像送入识别模型提取特征向量通常是 512 维。比对的时候计算两个特征向量的余弦相似度大于阈值常见是 0.5 到 0.6就认为是同一个人。这里有个坑阈值不能拍脑袋定要用验证集画 ROC 曲线找到 FAR误识率和 FRR拒识率的平衡点。安防场景通常要求 FAR 低于 0.001这时候阈值可能要提到 0.7 以上。资源包里如果已经集成了识别模块检查一下它用的是哪个识别模型。如果是 ArcFace那特征提取的质量会比较好但模型体积也大ResNet100 backbone 大概 250MB。如果是 MobileFaceNet模型只有几 MB适合边缘设备部署但精度会低几个点。3.2 安防系统的报警逻辑什么时候该触发检测到人脸不等于要报警。安防系统的报警逻辑通常包含三层过滤第一层是时间过滤比如只在夜间22:00 到 06:00触发报警第二层是区域过滤比如只在某个多边形区域内的人脸才触发第三层是身份过滤比如白名单里的人不报警陌生人报警。这三层过滤用代码实现大概是这样import time import numpy as np from shapely.geometry import Point, Polygon # 定义警戒区域多边形顶点坐标 ALERT_ZONE Polygon([(100, 100), (500, 100), (500, 400), (100, 400)]) # 白名单特征库实际项目中从数据库加载 WHITELIST { 张三: np.load(features/zhangsan.npy), 李四: np.load(features/lisi.npy) } def should_alert(face_bbox, face_feature, timestamp): face_bbox: (x1, y1, x2, y2) face_feature: 512 维 numpy 数组 timestamp: 当前时间戳 # 第一层时间过滤 hour time.localtime(timestamp).tm_hour if 6 hour 22: return False, 非夜间时段 # 第二层区域过滤 center_x (face_bbox[0] face_bbox[2]) / 2 center_y (face_bbox[1] face_bbox[3]) / 2 if not ALERT_ZONE.contains(Point(center_x, center_y)): return False, 不在警戒区域 # 第三层身份过滤 max_sim 0 matched_name 陌生人 for name, feat in WHITELIST.items(): sim np.dot(face_feature, feat) / ( np.linalg.norm(face_feature) * np.linalg.norm(feat) ) if sim max_sim: max_sim sim matched_name name if max_sim 0.7: return False, f白名单人员{matched_name} return True, f陌生人相似度 {max_sim:.3f} # 使用示例 alert, reason should_alert( face_bbox(200, 150, 280, 230), face_featurenp.random.randn(512), timestamptime.time() ) print(f是否报警{alert}原因{reason})这段代码里ALERT_ZONE用 shapely 的多边形做区域判断比简单的矩形判断灵活。白名单比对用的是余弦相似度阈值 0.7 是经验值实际项目要根据业务容忍度调整。注意face_feature必须是归一化后的向量否则余弦相似度计算会出错。3.3 多路视频流接入RTSP 拉流与帧率控制安防系统通常要接多路 RTSP 摄像头。用 OpenCV 拉流是最简单的做法但 OpenCV 的VideoCapture在断流时不会自动重连需要自己写重连逻辑。下面是一个带重连的拉流封装import cv2 import time class RTSPStream: def __init__(self, url, reconnect_interval5): self.url url self.reconnect_interval reconnect_interval self.cap None self.connect() def connect(self): if self.cap is not None: self.cap.release() self.cap cv2.VideoCapture(self.url, cv2.CAP_FFMPEG) # 设置缓冲区大小为 1减少延迟 self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) def read(self): if self.cap is None or not self.cap.isOpened(): time.sleep(self.reconnect_interval) self.connect() return False, None ret, frame self.cap.read() if not ret: time.sleep(self.reconnect_interval) self.connect() return False, None return True, frame def release(self): if self.cap is not None: self.cap.release()cv2.CAP_FFMPEG指定用 FFmpeg 后端解码比默认后端稳定。CAP_PROP_BUFFERSIZE设为 1 是为了降低延迟但有些摄像头不支持这个参数设了也没用。重连间隔设 5 秒太短会导致频繁重连太长会导致断流后很久才恢复。多路视频流如果每路都跑 YOLOGPU 会吃不消。常见做法是抽帧检测比如每 5 帧检测一次中间帧用跟踪算法比如 ByteTrack补上。这样能把 GPU 占用降到原来的五分之一代价是可能漏掉快速移动的人脸。4. 避坑与排查资源包跑不起来时先看这几条4.1 训练 loss 变成 NaN现象训练开始几个 epoch 正常突然 loss 变成 NaN之后再也降不下来。原因最常见的是标注文件里有宽高为 0 或负数的框导致 YOLO 在计算 CIoU loss 时出现除零。其次是学习率设得太大梯度爆炸。解决先用脚本检查所有 label 文件过滤掉宽高小于 1 像素的框。然后把学习率降到 0.001加梯度裁剪--grad_clip 10。如果还不行检查数据集中有没有损坏的图片文件用cv2.imread读一遍返回 None 的就删掉。4.2 推理时检测框偏移或缩放现象训练时 mAP 正常但推理时检测框位置明显偏移或者框的大小不对。原因预处理和后处理的参数不一致。YOLO 推理时需要把输入图像 letterbox 到 640×640保持宽高比填充灰色边框。如果推理脚本里用的是直接 resize检测框就会偏移。解决检查推理脚本里的预处理函数确保和训练时用的 letterbox 逻辑一致。YOLO 官方仓库里的datasets.py有letterbox函数直接复用。后处理时要把检测框坐标从 letterbox 空间映射回原图空间映射公式是x_orig (x_letterbox - pad_w) / scale。4.3 TensorRT 引擎在不同机器上不兼容现象在 A 机器上导出的.engine文件拷贝到 B 机器上加载时报错提示版本不匹配或 CUDA 错误。原因TensorRT 引擎是和硬件、CUDA 版本、TensorRT 版本绑定的。在 T4 上导出的引擎拿到 3090 上用不了TensorRT 8.2 导出的引擎TensorRT 8.4 加载不了。解决引擎文件不要跨机器拷贝每台机器上重新导出。如果部署环境固定可以把导出引擎的步骤写进 Dockerfile保证环境一致。另外导出时加--fp16的引擎不能在没有 FP16 支持的卡上跑比如一些老旧的 Tesla 卡。4.4 RTSP 拉流延迟越来越大现象系统刚启动时延迟正常跑几个小时后延迟越来越大最后画面卡住。原因OpenCV 的VideoCapture内部有一个帧缓冲区如果读取速度跟不上摄像头推流速度缓冲区会越积越多延迟就越来越大。解决把CAP_PROP_BUFFERSIZE设为 1并且用单独的线程拉流主线程只处理最新帧。如果还不行改用 GStreamer 管道拉流GStreamer 的appsink可以设置max-buffers1 droptrue保证只保留最新帧。4.5 人脸识别误报率高现象系统频繁把不同的人识别成同一个人或者把同一个人识别成不同的人。原因阈值设得太低或者特征提取模型没有对齐人脸。另外如果注册库里的照片质量差模糊、侧脸、光照不均提取的特征向量本身就不准。解决先用验证集画 ROC 曲线把阈值调到 FAR 低于 0.001 的位置。注册库里的照片要保证正脸、清晰、光照均匀每个人至少注册 3 张不同角度的照片取特征向量的平均值作为该人的特征。如果还不行换识别模型ArcFace 比 FaceNet 在安防场景下表现更好。5. 进阶技巧用 TensorRT 加速多路 YOLO 推理的实测数据5.1 T4 上跑 1080p 25 帧视频流的实测结果回到开头那个问题T4 显卡1080p 25 帧视频流TensorRT 加速 YOLO 640 分辨率检测能支持多少路我在一台 T416G 显存的服务器上做了实测测试条件是 YOLOv5s 导出 FP16 TensorRT 引擎输入 640×640每路视频流做抽帧检测每 3 帧检测一次跟踪算法用 ByteTrack。实测数据如下路数GPU 利用率显存占用单路延迟检测帧率1 路25%2.1G18ms25fps2 路42%3.8G22ms25fps4 路71%7.2G35ms25fps6 路89%10.5G52ms20fps8 路98%13.8G78ms15fps结论是4 路是舒适区GPU 利用率 71%延迟 35ms还有余量做其他处理。6 路开始 GPU 利用率接近 90%延迟明显上升检测帧率掉到 20fps。8 路基本跑满检测帧率只有 15fps对于安防场景来说15fps 的检测帧率可能会漏掉快速移动的目标。如果一定要跑 8 路以上有两个优化方向一是把输入分辨率从 640 降到 416速度能提升 40% 左右但小脸检测精度会下降二是用 YOLOv5n 或者 YOLOv8n 这种轻量级模型参数量只有 YOLOv5s 的三分之一速度更快但 mAP 会低 5 到 8 个点。5.2 用 DeepStream 做端到端流水线如果资源包里的代码是纯 Python 的 OpenCV PyTorch 推理性能瓶颈通常在 Python GIL 和内存拷贝上。NVIDIA 的 DeepStream 框架可以把视频解码、预处理、推理、跟踪、编码全部放在 GPU 上用 C 实现性能比 Python 方案高 3 到 5 倍。DeepStream 的核心是nvinfer插件它加载 TensorRT 引擎做推理支持动态 batch 和多路输入。配置文件config_infer_primary.txt里关键参数[property] gpu-id0 net-scale-factor0.0039215697906911373 model-engine-filebest.engine labelfile-pathlabels.txt batch-size4 process-mode1 model-color-format0 num-detected-classes1 interval0 gie-unique-id1net-scale-factor是归一化系数等于 1/255。batch-size4表示一次推理处理 4 帧配合interval0表示每帧都检测。process-mode1表示主检测器如果是二级检测器就设为 2。DeepStream 的学习曲线比较陡配置文件多、参数杂但一旦跑通多路视频流的性能提升非常明显。我一般会先用 Python 方案验证算法逻辑确认没问题后再移植到 DeepStream 做性能优化。5.3 模型量化INT8 到底能不能用TensorRT 支持 FP32、FP16、INT8 三种精度。FP16 基本无损INT8 需要校准集做量化校准。校准集就是从训练集里随机抽 500 到 1000 张图让 TensorRT 统计激活值的分布生成量化表。INT8 的速度比 FP16 再快 1.5 到 2 倍但精度损失要看场景。人脸检测这种任务INT8 量化后 mAP 通常会掉 2 到 5 个点。如果安防场景对召回率要求极高不建议用 INT8。如果只是做 demo 或者对精度要求不苛刻INT8 可以把 8 路视频流的 GPU 利用率从 98% 降到 65% 左右。校准集的选取有个坑不能只用正样本要包含一些负样本没有人脸的背景图否则量化后的模型会对背景产生大量误检。我一般会按 8:2 的比例混合正负样本正样本从训练集里抽负样本从 COCO 数据集里抽。从那以后我每次拿到新的安防项目都会先用 4 路视频流跑 24 小时稳定性测试确认没有内存泄漏和延迟累积再往上加路数。这个习惯帮我省了很多半夜被叫起来处理线上故障的麻烦。希望帮到你。本文还有配套的精品资源点击获取