ARTICLE DETAIL

资讯详情

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

YOLOv5人脸识别改进:检测、特征嵌入与部署实战

YOLOv5人脸识别改进:检测、特征嵌入与部署实战 简介基于YOLOv5的人脸识别改进项目包含完整源码、测试图片与说明文档面向计算机、电子信息、数学等专业学生适用于课程设计、期末大作业或毕业设计参考。包内共95个文件涵盖36个Python脚本检测、训练、推理、模型转换等、19个YAML配置文件数据集与模型结构定义、12张JPG与7张PNG测试图像、6个Shell脚本、4个MAT数据文件以及Cython扩展、动态库、Dockerfile等辅助文件压缩包约8.94MB结构完整。资源已吸引791人学习具有较高参考价值。通过源码可了解YOLOv5在WIDER Face等数据集上的训练流程、人脸检测改进思路以及TensorRT加速、多尺度推理等实现细节配套说明文档梳理环境配置与使用步骤便于快速跑通并进行二次开发。注意该资料作为参考资料而非定制方案需具备一定基础能够自行调试与扩展功能。1. 基于YOLOv5的人脸识别改进到底改的是哪一环先说一个反直觉的结论开箱即用的 YOLOv5 不会告诉你画面里这张脸是谁它只能告诉你“这里有一张脸、框在哪、置信度多少”。所以任何一个挂着“基于YOLOv5实现人脸识别改进”标题的项目真正的工作量都落在两处一是把通用检测模型改造成适合人脸尺度和遮挡场景的检测器二是在检测框之上补上身份判定的链路。改进不是调大一个参数而是重新设计输出头、损失函数和推理流程。这类压缩包里常见的是源码、标注图片和说明文档说明作者默认你会自己跑通训练和验证而不是拿预训练权重直接识别。适合的人群也明确已经跑通过 YOLOv5 基础流程手里有目标设备PC、Jetson 或者门禁机的工程师。下面按模型改造、数据训练、身份识别、部署验证这条线展开每一步都给到能直接复制改用的命令和代码。2. YOLOv5识别改进的模型选型与输出层设计2.1 先区分人脸检测、人脸识别与“改进”边界人脸识别在工程上通常被拆成两段人脸检测face detection和人脸身份特征提取face recognition。YOLOv5 原生能力属于前者输出的是边界框、置信度和类别概率人脸识别需要的是对同一张脸在不同角度、光照下保持稳定的一串特征向量。所谓“基于 YOLOv5 的人脸识别改进”从公开源码和常见做法看大致有三条路只做检测改进识别交给 FaceNet、ArcFace 或 CosFace 等专用识别模型YOLOv5 负责把脸从整帧里切出来。这是最稳妥、效果最可控的路线。在 YOLOv5 的检测头旁边并联一个特征嵌入分支让模型同时输出“框”和“128/512 维身份向量”。这条路端到端但训练难度明显更高需要精心调整损失函数配比。不追求人脸识别只做人脸检测的关键点回归比如眼睛、鼻子、嘴的坐标用于活体检测或人脸对齐。很多打着“人脸识别改进”的源码实际是在做这件事。判断一个未知源码属于哪一类第一步看它的损失函数文件。如果只有一个class Loss函数计算 box、obj、cls那么身份识别必须在后处理阶段外挂如果损失函数里出现了arcface_loss或者embedding字样那说明作者改过模型头。下载源码后不要急着训练先把models/yolo.py里的Detect层输出维度、类别数和损失函数对应关系理清否则跑起来报维度不匹配时根本不知道去哪改。2.2 在YOLOv5头部加关键点或嵌入分支的改动方式2.2.1 以yolov5s.yaml为底座的head修改示例YOLOv5 的模型结构用 YAML 文件定义修改头部不需要改 C 代码只需调整head中的输出通路。下面是一个常见的“检测3 点关键点”改造适合做人脸对齐或姿态粗估计# modified_yolov5s_face.yaml # 仅列出 head 部分的关键改动 head: - [-1, 1, Conv, [128, 1, 1]] - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 6], 1, Concat, [1]] - [-1, 1, C3, [128, False]] # 检测头常规输出 nc1人脸 关键点 kpt3*2 - [-1, 1, Conv, [256, 3, 3]] - [-1, 1, DetectFace, [nc, anchors, kpt]] # 自定义 DetectFace对应的DetectFace需要继承 YOLOv5 的Detect重写 forwardclass DetectFace(Detect): def __init__(self, nc1, anchors(), kpt3, ch()): super().__init__(nc, anchors, ch) self.kpt kpt # 在原基础上增加关键点回归分支 self.kpt_branch nn.Conv2d(self.no, kpt * 2, 1) # 每个关键点输出 x,y def forward(self, x): z [] for i in range(self.nl): x[i] self.m[i](x[i]) # 原检测卷积 bs, _, ny, nx x[i].shape x[i] x[i].view(bs, self.no, ny, nx).permute(0, 2, 3, 1).contiguous() kpt_out self.kpt_branch(x[i].permute(0, 3, 1, 2)).permute(0, 2, 3, 1) if not self.training: y self._decode(x[i], kpt_out) z.append(y) return x if self.training else (torch.cat(z, 1), x)这段代码的核心是给每个检测层增加一个独立卷积输出维度从原来的41nc扩展为41nckpt*2。注意训练阶段返回的是特征图列表推理阶段要经过 decode 才能得到与输入图像分辨率对齐的关键点坐标。改完后维度变化会牵动损失函数常规做法是在loss.py中单独加一个keypoint_loss使用 Wing Loss 或 Smooth L1 计算关键点与标注坐标的偏差并设置权重一般初始给 0.5 比较稳。2.2.2 训练标签与匹配策略调整加入关键点或嵌入分支后数据标签不再是单纯的 class x_center y_center width height而要在每行末尾追加关键点归一化坐标例如0 0.500 0.420 0.200 0.300 0.510 0.410 0.490 0.430 0.500 0.520依次是左眼、右眼、鼻尖的 x y 归一化坐标。训练时 YOLOv5 的默认匹配只基于框的 IoU关键点不参与正样本匹配所以哪怕关键点标得偏一点只要框没偏训练仍能继续但如果你把关键点坐标顺序搞错loss 不会明显上升推理结果却会左右眼颠倒。这里最容易踩的坑是WIDER FACE 官方标注只给了框没有关键点需要额外使用 300W-LP 或自定义标注集才能训练关键点分支。因此压缩包里如果只有 WIDER FACE 图片和bbox标注作者大概率没有做关键点改进身份识别是走外挂网络的。下表列出三种改进方向的输出设计差异下载源码时对照着看可快速定位作者思路改进方向输出维度增加损失函数典型标注格式适用场景人脸关键点kpt*2Wing Loss / Smooth L1框坐标点人脸对齐、活体辅助身份分类封闭集类别数人员ID数CrossEntropy框类别 ID会议室签到、小规模白名单身份嵌入开放集128/512 维向量Triplet / ArcFace框ID构造样本对门禁机、动态陌生人识别判断源码是“真改进”还是“套壳”就看它有没有为上述新增输出写对应的数据处理脚本。很多项目只在yaml里加了输出通道训练标签却还是普通检测格式这类代码根本无法复现放弃即可。3. 用自己的数据集跑通YOLOv5人脸训练3.1 数据目录与YOLO格式标注WIDER FACE怎么转YOLOv5 训练人脸检测的数据目录通常长这样face_dataset/ ├── images/ │ ├── train/ # 训练图片 │ └── val/ # 验证图片 ├── labels/ │ ├── train/ # 与图片同名的 .txt 标签 │ └── val/ └── face.yamlWIDER FACE 的官方标注是x1 y1 x2 y2的矩形需要先转成 YOLO 的归一化格式。下面这段脚本可以把 WIDER FACE 的wider_face_train_bbx_gt.txt转换成 YOLOv5 可用的标签import os def convert_wider(gt_path, img_dir, out_dir): with open(gt_path, r, encodingutf-8) as f: lines f.readlines() idx 0 while idx len(lines): img_name lines[idx].strip() img_path os.path.join(img_dir, img_name) if not os.path.exists(img_path): idx 1 continue num_boxes int(lines[idx 1].strip()) h, w get_image_size(img_path) # 用 cv2 读取即可 out_lines [] for j in range(num_boxes): parts lines[idx 2 j].strip().split() x1, y1, w_b, h_b map(float, parts[:4]) # 忽略过小的人脸节省算力 if w_b 20 or h_b 20: continue x_c (x1 w_b / 2) / w y_c (y1 h_b / 2) / h w_n w_b / w h_n h_b / h out_lines.append(f0 {x_c:.6f} {y_c:.6f} {w_n:.6f} {h_n:.6f}) label_path os.path.join(out_dir, img_name.replace(.jpg, .txt).replace(.png, .txt)) os.makedirs(os.path.dirname(label_path), exist_okTrue) with open(label_path, w) as f: f.write(\n.join(out_lines)) idx 2 num_boxes这段代码有几个点需要注意一是 WIDER FACE 里同一个人脸可能出现多次转换时不用去重YOLOv5 训练时会把它们当作同一个类别下的不同目标重复框反而增大了正样本量二是过滤w_b 20的人脸是为了减少难以学习的极小目标如果你的场景是远距离抓拍这个阈值建议降到 10同时把 YOLOv5 的--img参数提高至 1280否则小脸根本学不到特征。3.2 训练参数与冻结策略train.py命令参数解释确认face.yaml内容无误后基础训练命令如下python train.py \ --data face.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --freeze 10 \ --workers 8 \ --device 0各参数含义参数值作用与调整建议--img640训练尺度。人脸密集小目标时提升到 960显存不够就降低 batch--batch16受显存限制。NVIDIA 3060 12G 可跑 328G 只能 16--freeze10冻结前 10 层主干保留 ImageNet 预训练特征新手必开--workers8数据加载线程数 Windows 建议 4否则容易 DataLoader 卡死--hypdata/hyps/hyp.scratch-low.yaml自定义超参文件改进识别时重点改hsv_h和degrees--weights yolov5s.pt的意义在于迁移学习。YOLOv5 在 COCO 上训练过前几层学习到的边缘、纹理特征对人脸同样有效只训练模型后半部分能大幅减少训练时间。如果你的数据集只有几千张人脸冻结前 10 层通常收敛更快但如果你的图片包含大量极端光照应把--freeze降到 4让主干也能适应新域。训练过程中最该盯的是val/obj_loss和val/cls_loss。人脸检测只有 1 个类别cls_loss下降通常很快主要瓶颈在obj_loss它负责判断特征图上某个位置是否有目标。如果训练 50 轮后obj_loss仍在 0.03 以上先检查标注是否有漏框再看是不是小脸太多。3.3 改进常用训练策略超参数与数据增强YOLOv5 的人脸改进项目里最值得改的超参数是hyp.scratch-low.yaml中的这几项hsv_h: 0.015 # 色相扰动人脸肤色敏感不要超过 0.02 hsv_s: 0.5 # 饱和度扰动 hsv_v: 0.4 # 明度扰动 degrees: 5.0 # 旋转角度人脸数据集建议 0~10 fliplr: 0.5 # 水平翻转注意左右眼关键点要跟着翻转 mosaic: 1.0 # mosaic 增强小脸密集场景建议保留 mixup: 0.1 # mixup 会稀释人脸特征身份识别项目建议关掉人脸识别改进项目要尤其小心fliplr。如果你的目标是检测出人脸框翻转没问题但如果你加了关键点分支翻转后标注的关键点坐标也要交换例如左眼变右眼。很多源码在数据增强这里只翻转了框没翻转关键点导致关键点 loss 不降反升。另一个常用策略是“难负样本挖掘”在训练后期把置信度高于 0.5 但 IoU 低于 0.3 的误检区域复制到更多负样本图片里逼迫模型降低误检率。4. 从检测框到身份FaceNet/ArcFace嵌入与YOLOv5解耦或联合4.1 解耦方案的优势实际工程中我一般建议把 YOLOv5 人脸检测和身份识别网络解耦而不是强行端到端。原因有三点第一检测和识别的最优输入尺度不同检测用 640x640 能兼顾速度与小脸召回识别网络则希望人脸区域至少 112x112 或 160x160解耦后可以分别满足第二身份识别需要超大训练集百万级 ID只靠项目压缩包里的几千张图片很难训练出鲁棒特征第三解耦后替换识别模型非常方便今天用 FaceNet明天换 ArcFace检测部分完全不用动。压缩包里的“源码图片说明文档”如果是完整项目通常detector/和recognizer/是两个独立目录。4.2 用YOLOv5检测人脸并输入识别网络预处理细节下面的代码演示了推理阶段最常见的 pipelineYOLOv5 出框ArcFace 提取特征余弦距离判断身份import cv2 import numpy as np import torch def recognize_face(detector, recognizer, img, known_embeddings, threshold0.4): detector: YOLOv5 加载后的模型 recognizer: 输出 512 维归一化向量的识别网络 results detector(img) # 假设返回 xyxy, conf, cls boxes results.xyxy[0].cpu().numpy() for box in boxes: x1, y1, x2, y2, conf, cls box if conf 0.5 or (x2 - x1) 20: continue # 扩展边界给人脸周边留出上下文 margin int((x2 - x1) * 0.1) face_crop img[max(0, int(y1)-margin):int(y2)margin, max(0, int(x1)-margin):int(x2)margin] face_align align_face(face_crop) # 使用关键点或直接 resize 到 112x112 face_rgb cv2.cvtColor(face_align, cv2.COLOR_BGR2RGB) face_tensor torch.from_numpy(face_rgb).permute(2,0,1).float() / 255.0 face_tensor face_tensor.unsqueeze(0).to(device) with torch.no_grad(): emb recognizer(face_tensor) emb emb / torch.norm(emb, dim1, keepdimTrue) # L2 归一化 best_sim -1.0 best_id unknown for identity, known_emb in known_embeddings.items(): sim (emb known_emb.T).item() # 余弦相似度 if sim best_sim: best_sim sim best_id identity if best_sim threshold: best_id unknown print(f{best_id}: similarity{best_sim:.3f} box({int(x1)},{int(y1)}))预处理是关键。align_face这一步很多人忽略直接 resize 会损失识别精度。标准做法是用人脸关键点眼睛、鼻尖做仿射变换把双眼对齐到固定位置。你可以复用 2.2 节改出的关键点分支也可以直接用独立的 RetinaFace 做对齐效果都远好于无对齐直接送网络。关于阈值余弦相似度阈值是一个需要反复测试的参数。同一人不同照片的相似度通常在 0.6~0.85不同人一般在 0.2~0.5。threshold0.4是保守值误识别少但会漏掉部分低质量抓拍门禁场景建议 0.35。这里不要照抄任何博客的数值在你自己的验证集上画出相似度分布曲线再定。4.3 联合训练时embedding head的输出与损失如果坚持做端到端的身份嵌入常见做法是给 YOLOv5 检测头增加一个embedding卷积层输出通道设为 512训练时使用 ArcFace 损失class ArcFaceLoss(nn.Module): def __init__(self, in_features, num_classes, s32.0, m0.5): super().__init__() self.weight nn.Parameter(torch.FloatTensor(num_classes, in_features)) nn.init.xavier_normal_(self.weight) self.s s self.m m def forward(self, features, labels): # features: (N, 512) 已经 L2 归一化 cosine torch.mm(features, self.weight.t()) cosine cosine.clamp(-1, 1) theta torch.acos(cosine) target_mask torch.zeros_like(cosine).scatter_(1, labels.view(-1, 1), 1.0) theta_m torch.where(target_mask.bool(), theta self.m, theta) output torch.cos(theta_m) * self.s return output联合训练最大的问题是检测损失和识别损失的尺度不匹配。检测损失通常是 Tensord 级别的值ArcFace 损失输出在 20~40直接把两者相加会让梯度完全被识别分支主导。常规做法是为识别分支设置一个较小的权重lambda_emb 0.01到0.05并冻结检测头前 20 轮等框的预测稳定后再开启端到端微调。如果你的数据只有几百个 ID不要做联合训练过拟合会让嵌入向量退化成“对训练图片死记硬背”换个人就失效。5. 改进项目落地门禁设备/摄像头流上的部署与验证5.1 导出ONNX/TensorRT加速人脸识别门禁机和 Jetson 系列边缘设备通常没有完整的 PyTorch 环境项目交付前必须做模型导出。YOLOv5 自带的导出命令同样适用于改动后的检测头但要保证自定义层有对应的 ONNX 实现python export.py \ --weights best.pt \ --include onnx \ --img 640 \ --simplify \ --opset 12导出后先用onnxruntime做一次精度对比重点检查 Detect 层输出形状与原始 PyTorch 是否一致。加入关键点或嵌入分支后常见报错是Unsupported operator: DetectFace这时需要把自定义层拆成标准卷积和sigmoid操作或者改用 TensorRT 的 plugin 机制。如果目标是 Jetson Nano 这类低算力设备建议先做 TensorRT FP16trtexec --onnxbest.onnx \ --saveEnginebest_fp16.engine \ --fp16 \ --minShapesimages:1x3x640x640 \ --optShapesimages:1x3x640x640 \ --maxShapesimages:4x3x640x640实际测量下来Jetson Nano 上 YOLOv5s FP16 推理约 15~25 ms/帧比较吃紧。这时可以把检测输入从 640 降到 480或者换 YOLOv5n。注意识别网络同样要导出成 TensorRT最好把检测器和识别器放进同一个 engine 的显存池里避免来回分配显存。注意不要使用任何网络加速手段正常的推理优化不受影响。5.2 实测验证置信度、人脸最小尺寸、阈值三个指标部署完成后用一段真实摄像头视频验证三个可量化指标置信度阈值conf: 从 0.25 到 0.6 每次加 0.05统计误检数与漏检数。门禁场景建议 0.45 以上越低越容易把门框、海报上的脸当成真人。人脸最小尺寸min_face: 在代码中设置if x2-x1 40 or y2-y1 40: continue低于该尺寸的框直接丢弃。因为过小的人脸即使检测出来送入识别网络后特征也无效反而拉高误识别。余弦距离阈值threshold: 准备 50 个真人和 50 个访客样本画出相似度分布取两个分布交点。验证代码控制在几十行内输出一张 ROC 风格的结果表比肉眼观察靠谱得多python evaluate_face.py \ --detector best.pt \ --recognizer arcface.onnx \ --video test.mp4 \ --conf 0.45 \ --min-face 40 \ --threshold 0.38 \ --known-dir ./known_faces/known-dir下每个子文件夹代表一个人员存放 5~10 张不同角度的人脸照片程序自动生成平均嵌入向量并缓存到embeddings.npy。下次启动直接加载缓存省去重复推理。这个脚本输出两个数字准确率识别正确的帧数/总帧数和漏检率完全没框出人脸的帧数/总帧数。漏检率若高于 5%不要调识别阈值回去加大检测数据集或调低conf。5.3 一个可复现的验证脚本性能与精度同时压测最后给你一个可落地的性能验证脚本它模拟摄像头流式输入测量“检测对齐识别”的端到端耗时import time import numpy as np def benchmark_pipeline(frames, detector, recognizer, rounds10): # 先 warm up排除显存初始化和 CUDA kernel 编译影响 for f in frames[:2]: recognize_face(detector, recognizer, f, known_embeddings) latencies [] for _ in range(rounds): for frame in frames: t0 time.perf_counter() recognize_face(detector, recognizer, frame, known_embeddings) latencies.append((time.perf_counter() - t0) * 1000) fps 1000.0 / np.mean(latencies) p95 np.percentile(latencies, 95) print(favg: {np.mean(latencies):.1f}ms, p95: {p95:.1f}ms, fps: {fps:.1f})跑这个脚本时注意观察 GPU 显存占用是否持续上涨。如果显存上涨说明识别网络或对齐网络在处理时产生了张量泄漏通常是被跳过的分支没有释放计算图加一行del face_tensor即可解决。门禁设备上的验收标准一般是在 720P 视频流下端到端耗时小于 80ms也就是说每秒能稳定处理 12 帧以上。如果达不到优先把对齐网络换成移动端专用的 PFLD 轻量模型再考虑裁剪识别网络输入尺寸。本文还有配套的精品资源点击获取
返回列表