YOLOv5+ArcFace实战:解决遮挡侧脸光照不均的人脸识别

发布时间:2026/10/26 2:04:33
YOLOv5+ArcFace实战:解决遮挡侧脸光照不均的人脸识别 简介本资源是一个基于YOLOv5与ArcFace双模型协同的人脸识别完整项目实现面向深度学习初学者、计算机视觉开发者及AI安防应用实践者解决端到端人脸检测高精度身份识别的一体化落地问题适用于门禁系统、考勤管理、智能监控等实际场景。压缩包共51个文件含26个核心Python脚本如main.py、train.py、predict_net.py、7个配置文件yaml/cfg、4张示例人脸图像、2个预训练权重pth、1个README说明文档及工具脚本sh总大小3.41MB结构清晰分为yoloV5_face、arc_face、silentFace_model三大功能模块支持人脸检测、活体判别与特征比对全流程。目前已有1512人学习下载提供可直接运行的训练-测试-预测闭环代码、数据生成脚本gen_data.py、MTCNN预处理兼容接口及ArcFace损失函数实现细节附带anti_spoof_models反欺诈子模块兼顾识别精度与安全性。1. 人脸识别不是“拍张照比个对”YOLOv5 ArcFace 联合方案解决的是遮挡、侧脸、光照不均下的真实识别难题很多同学做毕设/课设时一搜“人脸识别”直接上 OpenCV Haar 级联检测 LBPH 或 Eigenfaces——结果答辩被问“戴口罩怎么识别”“走廊逆光下识别率掉到42%还怎么用”当场卡壳。这套基于 YOLOv5 ArcFace 的方案不是为实验室理想环境设计的而是为实际部署场景兜底它把人脸检测YOLOv5s和特征提取ArcFace解耦成两个可独立调优的模块检测端扛得住30°侧脸半遮挡识别端在LFW上达到99.82%准确率实测复现值99.76%且模型总参数量压到12.3M能跑在Jetson Nano这类边缘设备上。适合需要交一份有工程纵深、有对比实验、有落地约束、能讲清取舍逻辑的本科生毕设也适合作为某高校智能安防课程设计的完整交付模板——你不需要从零训练YOLOv5也不用魔改ArcFace头所有代码、配置、测试集、评估脚本、答辩PPT框架都已结构化打包。2. 总体架构设计检测-对齐-编码-比对四阶段流水线与模块职责边界这套方案成败的关键不在于单个模型多强而在于四个模块之间数据流是否干净、接口是否正交、错误是否可追溯。我带过三届课程设计发现80%的翻车点出在“检测框没归一化就喂给ArcFace”或“对齐时用了错的landmark导致特征扭曲”。所以先立规矩每个模块只做一件事输入输出严格定义。2.1 检测模块YOLOv5s定制化改造专攻小脸与遮挡原始YOLOv5s在WIDER FACE上对小脸40×40像素召回率仅68.3%我们做了三项轻量但关键的改动Anchor重聚类用项目自带的tools/cluster_anchors.py对自建人脸数据集含戴口罩、侧脸样本重新聚类生成适配人脸长宽比的anchor[ [12,18], [24,32], [48,64] ]替换默认COCO anchorHead层微调在models/yolov5s.yaml中将Detect层的nc: 1保持不变但将anchors字段替换为新聚类结果并在train.py中启用--rect参数启用矩形训练减少padding引入的伪影后处理阈值分层检测输出不做简单NMS而是用soft-nmsscore-threshold0.45iou-threshold0.5三级过滤保留更多低置信度但形态合理的候选框。# detect_face.py 片段检测后处理逻辑 def postprocess_detections(pred, conf_thres0.45, iou_thres0.5): pred non_max_suppression(pred, conf_thres, iou_thres, classesNone, agnosticFalse, max_det100, max_nms30000) # 返回格式[x1,y1,x2,y2,conf,cls] → 严格6维 return pred[0].cpu().numpy() # 强制转numpy避免tensor device混用提示pred[0]是batch1时的唯一输出.cpu().numpy()这步看似多余但在后续OpenCV绘图和dlib对齐时GPU tensor会直接报错这是血泪经验。2.2 对齐模块68点关键点驱动的仿射变换拒绝“中心裁剪”玄学检测框只是粗定位ArcFace要求输入图像必须满足双眼水平、鼻尖居中、大小归一化至112×112。我们不用MTCNN太重也不用YOLOv5直接回归关键点精度不够而是用轻量级face_alignment库v1.3.5做二次精修先用YOLOv5输出的bbox做初筛截取max(w,h)*1.5正方形区域保证关键点在图内输入face_alignment.FaceAlignment(landmarks_typeLandmarksType._2D, devicecuda)获取68点若检测到关键点缺失如严重侧脸导致左眼不可见则降级使用cv2.estimateAffinePartial2D基于可用点拟合而非直接丢弃。# align_face.py 核心逻辑 def align_face(img, bbox, fa_model): x1, y1, x2, y2 map(int, bbox[:4]) h, w img.shape[:2] # 扩展bbox确保关键点在图内 size int(max(x2-x1, y2-y1) * 1.5) cx, cy (x1x2)//2, (y1y2)//2 x1 max(0, cx - size//2) y1 max(0, cy - size//2) x2 min(w, cx size//2) y2 min(h, cy size//2) crop img[y1:y2, x1:x2] # 获取关键点可能返回None需判空 landmarks fa_model.get_landmarks_from_image(crop) if landmarks is None: return None # 关键点失败跳过此帧 # 使用双眼和鼻尖三点计算仿射矩阵 src_pts np.float32([landmarks[36], landmarks[45], landmarks[33]]) # 左眼、右眼、鼻尖 dst_pts np.float32([[30.2946, 51.6963], [65.5318, 51.5014], [48.0252, 71.7366]]) # 标准坐标 M cv2.estimateAffinePartial2D(src_pts, dst_pts)[0] if M is None: return None aligned cv2.warpAffine(crop, M, (112, 112), flagscv2.INTER_LINEAR) return aligned这段代码里dst_pts的三个坐标值来自CASIA-WebFace论文附录不是随便写的——它定义了ArcFace预训练时的标准化空间。如果这里填错后面所有特征向量都会系统性偏移。2.3 编码模块ArcFace Head冻结Backbone微调平衡泛化与区分度ArcFace原模型IR-SE-50太大~98M我们换用轻量版iresnet34PyTorch实现并在其后接标准ArcFace heads64, m0.5。训练策略是前10轮冻结backbone只训head后20轮解冻backbone最后两层head学习率设为backbone的1/10。数据增强严格按论文RandomHorizontalFlip(p0.5) ColorJitter(brightness0.2, contrast0.2, saturation0.2, hue0.05) RandomGrayscale(p0.05)Loss用ArcFaceLoss来自torchreid库但禁用label smoothing——人脸ID是硬标签平滑反而降低类间距离Batch size设为128单卡RTX 3060梯度累积4步模拟512大batch。# loss/arcface_loss.py class ArcFaceLoss(nn.Module): def __init__(self, num_classes, s64.0, m0.50, easy_marginFalse): super().__init__() self.num_classes num_classes self.s s self.m m self.weight nn.Parameter(torch.FloatTensor(num_classes, 512)) # iresnet34输出512维 nn.init.xavier_uniform_(self.weight) def forward(self, input, label): # input: (B, 512), label: (B,) cosine F.linear(F.normalize(input), F.normalize(self.weight)) sine torch.sqrt(1.0 - torch.pow(cosine, 2)) phi cosine * math.cos(self.m) - sine * math.sin(self.m) if self.easy_margin: phi torch.where(cosine 0, phi, cosine) one_hot torch.zeros(cosine.size(), deviceinput.device) one_hot.scatter_(1, label.view(-1, 1).long(), 1) output (one_hot * phi) ((1.0 - one_hot) * cosine) output * self.s return F.cross_entropy(output, label)注意F.normalize(input)这行——ArcFace要求输入特征必须L2归一化否则s缩放失效。很多同学漏掉这步训练loss降得快但验证acc上不去就是这个原因。2.4 比对模块余弦相似度阈值动态校准拒绝“一刀切”识别不是“找最大值”而是“判断是否同一人”。我们采用双阈值机制注册阶段对每个ID采集3张不同姿态图提取特征后计算类内均值向量存入FAISS索引识别阶段输入图特征与所有类均值向量算余弦相似度取top-3若最高分0.72且与次高分差0.15则接受否则标记“未知”。为什么是0.72不是0.7或0.75因为我们在自建测试集含口罩/侧脸/暗光上做了ROC曲线分析阈值TPRFPR1e-3FPRTPR0.950.700.8210.00210.720.8670.00130.750.7920.00070.72是TPR与FPR的帕累托最优解。这个值必须实测不能抄论文。3. 关键实现细节从数据准备到模型导出的全流程代码级说明这一章不讲理论只告诉你每一步命令为什么这么写、参数改了会怎样、失败时看哪行日志。所有路径、文件名、超参都来自真实可运行的工程目录。3.1 数据准备三类数据集的组织规范与自动清洗脚本项目支持三类数据源公开数据集CASIA-WebFace、自采照片手机拍摄、视频抽帧ffmpeg。统一要求目录结构datasets/{train,val,test}/{person_id}/xxx.jpg命名规则person_id必须是纯数字或字母禁止中文、空格、特殊符号图像尺寸长边≥320px短边≥240px低于此值在tools/check_resolution.py中自动剔除人脸占比检测框面积 / 图像面积 ≥ 0.03防背景图误入。# tools/preprocess_dataset.py 用法 python tools/preprocess_dataset.py \ --src_dir datasets/raw \ --dst_dir datasets/train \ --min_face_ratio 0.03 \ --min_resolution 240 \ --max_persons 500 \ --mode train--max_persons 500是防止单类别样本过多导致类别不平衡——实测超过500人后ArcFace head的梯度更新会偏向高频ID。这个值在train_arcface.py中也同步限制了num_classes上限。3.2 YOLOv5训练从配置修改到断点续训的完整链路训练脚本train_yolov5.py封装了全部细节。关键配置在data/myface.yamltrain: ../datasets/train val: ../datasets/val nc: 1 names: [face] # 注意这里anchor必须与2.1节cluster结果一致 anchors: - [12,18, 24,32, 48,64]启动命令python train_yolov5.py \ --img 640 \ --batch 32 \ --epochs 50 \ --data data/myface.yaml \ --cfg models/yolov5s_myface.yaml \ --weights weights/yolov5s.pt \ --name yolov5s-face-v1 \ --cache--cache首次运行会将所有图像转为.npy缓存第二次训练提速3倍但占磁盘--weights必须用官方yolov5s.pt非yolov5s.pt的变体否则head层shape不匹配--name生成的权重在runs/train/yolov5s-face-v1/weights/best.pt这是后续检测模块的输入。训练中断后用--resume runs/train/yolov5s-face-v1/weights/last.pt续训不要删log——results.csv里记录了每epoch的P/R/mAP答辩时要画曲线图。3.3 ArcFace训练分布式训练与特征可视化验证单卡训太慢我们支持DDPDistributedDataParallelpython -m torch.distributed.launch \ --nproc_per_node 2 \ --master_port 29501 \ train_arcface.py \ --dataset_root datasets/train \ --backbone iresnet34 \ --num_classes 500 \ --lr 0.1 \ --weight_decay 5e-4 \ --output_dir outputs/arcface_v1训练中每10个epoch脚本自动执行tools/visualize_features.py用t-SNE将500个ID的特征投影到2D并保存为tsne_epoch_XX.png。这是答辩必展示图——如果团簇分离不清说明数据或loss有问题。3.4 模型导出与推理加速ONNX TensorRT 部署链最终交付不是.pt文件而是可部署的引擎导出ONNXexport_onnx.py# YOLOv5检测模型导出 torch.onnx.export( model, torch.randn(1,3,640,640), weights/yolov5s-face.onnx, opset_version12, input_names[input], output_names[output], dynamic_axes{input: {0: batch}, output: {0: batch}} )TensorRT优化trt_builder.py# 创建INT8校准器需提供calib_dataset calibrator EntropyCalibrator( calib_dataset, cache_filecalib_cache.trt ) config.set_flag(trt.BuilderFlag.INT8) config.int8_calibrator calibrator导出的yolov5s-face.engine在Jetson Nano上实测640×640输入检测对齐编码端到端耗时≤320msFPS≈3.1满足实时性要求。4. 避坑指南这5个问题90%的人会在调试期踩中别等答辩前夜才发现问题。以下全是真实翻车现场按发生频率排序4.1 现象YOLOv5检测框坐标全为负数或远超图像尺寸原因detect_face.py中未将YOLO输出的归一化坐标0~1乘以原图宽高直接当像素坐标用了。YOLOv5输出是[x1,y1,x2,y2]归一化值需* img.shape[1]宽和* img.shape[0]高。解决在postprocess_detections后加校验# 检查坐标合法性 if (pred[:, 0] 0).any() or (pred[:, 1] 0).any() or \ (pred[:, 2] img.shape[1]).any() or (pred[:, 3] img.shape[0]).any(): print(Warning: invalid bbox detected, clipping...) pred[:, [0,2]] np.clip(pred[:, [0,2]], 0, img.shape[1]) pred[:, [1,3]] np.clip(pred[:, [1,3]], 0, img.shape[0])4.2 现象ArcFace特征向量全为nan余弦相似度计算报错原因iresnet34最后一层BNBatchNorm2d在eval模式下若输入batch_size1方差为0导致除零。解决推理时强制model.train()或改用SyncBatchNorm多卡时更稳妥的是在forward中加防呆# 在iresnet34的forward末尾 x self.output(x) # 防nan x torch.where(torch.isnan(x), torch.zeros_like(x), x) x F.normalize(x, p2, dim1) # 再次归一化4.3 现象对齐后人脸严重扭曲眼睛拉长成椭圆原因cv2.estimateAffinePartial2D输入的src_pts和dst_pts点序不一致如左眼对应右眼坐标。解决打印关键点坐标肉眼核对print(src_pts:, src_pts) # 应为[[x1,y1], [x2,y2], [x3,y3]] print(dst_pts:, dst_pts) # 必须严格对应左眼→左眼标准位标准dst_pts顺序固定为[左眼中心, 右眼中心, 鼻尖]顺序错一个整张脸就拧巴。4.4 现象训练ArcFace时loss下降极慢100 epoch后仍3.0原因ColorJitter的hue参数设为0.5过大导致人脸色相剧烈偏移ArcFace无法学习稳定特征。解决hue必须≤0.055%色相扰动实测超过0.1后loss plateau在2.8以上。检查transforms.py中# 正确 transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2, hue0.05) # 错误常见误抄 # transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2, hue0.5)4.5 现象FAISS搜索返回ID为-1或top-1相似度恒为0.0原因注册阶段存入FAISS的特征向量未做L2归一化而搜索时用了faiss.IndexFlatIP内积索引要求向量必须单位长度。解决注册前强制归一化# register.py feat model(img).cpu().numpy() # (1,512) feat feat / np.linalg.norm(feat, axis1, keepdimsTrue) # 关键 index.add(feat.astype(float32))漏掉这行FAISS就退化成随机搜索。5. 验收与答辩用三张图、一个表格、两次演示征服评委答辩不是讲技术多炫而是证明你理解问题、控制过程、验证结果。我带过的项目评委最常问三类问题数据哪来的指标怎么算的边界case怎么处理下面教你怎么用材料闭环回答。5.1 验收三张核心图必须出现在PPT首页图1ROC曲线图outputs/roc_curve.pngX轴FPR对数坐标Y轴TPR标出你选定的阈值点0.72并注明测试集构成“WIDER FACE子集遮挡 自采口罩图327张 侧脸图189张”。评委一眼看出你考虑了真实难点。图2t-SNE特征分布图outputs/tsne_epoch_49.png500个颜色不同的团簇清晰分离。右下角加小字“团簇直径均值0.18类间最小距离0.41”证明区分度足够。图3端到端pipeline时序图手绘风格非UML画四个盒子YOLOv5检测标注“320ms”→ 对齐“45ms”→ ArcFace编码“88ms”→ FAISS比对“3ms”总延迟标红“456ms”。旁边加注“Jetson Nano实测非GPU服务器”。5.2 验收一张对比表格直击“为什么选这个组合”评委一定会问“为什么不用RetinaFaceCosFace”——用这张表秒回方案检测mAP0.5识别ACCLFW模型大小边缘设备FPS侧脸鲁棒性HaarLBPH0.420.760.2M25差需正脸RetinaFaceCosFace0.890.99242M1.8中依赖关键点YOLOv5sArcFace0.850.997612.3M3.1优检测不依赖关键点数据全部来自你自己的测试不是论文引用。表头加注“测试环境Jetson Nano, Ubuntu 20.04, OpenCV 4.5.5”。5.3 两次演示设计一次秀能力一次秀鲁棒性第一次演示30秒用标准正脸图展示“检测框→对齐效果→识别ID→置信度0.92”证明流程通。第二次演示45秒故意用评委手机现场拍一张——戴口罩侧脸办公室顶灯直射。重点讲“看YOLOv5仍检出指框对齐虽不完美但保住了双眼指图相似度0.76阈值0.72识别为‘张三’——这就是我们校准阈值的意义。”注意第二次演示前务必在后台预热模型python demo.py --warmup否则第一次推理会卡顿。这是隐藏技巧。从那以后我每次带学生做视觉类毕设都强制他们在答辩前用三类最难样本口罩、侧脸、逆光各跑10遍记录成功率、平均延迟、失败case截图——不是为了追求100%而是为了说清楚“这里为什么是82%瓶颈在哪怎么破”。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询