资讯动态

YOLOv5+ArcFace人脸检测与识别一体化实战

发布时间:2026/10/26 2:04:44 来源:尧图企业网站定制
简介本资源是一个基于YOLOv5与ArcFace双模型协同的人脸识别完整项目实现面向深度学习初学者、计算机视觉开发者及AI安防应用实践者解决端到端人脸检测高精度身份识别的技术落地问题适用于门禁系统、考勤管理、智能监控等实际场景。压缩包共51个文件含26个核心Python脚本如main.py、train.py、predict_net.py、7个配置文件yaml/cfg、4张示例人脸图像、2个预训练权重pth、1个README.md说明文档及工具脚本sh整体3.41MB结构清晰模块解耦——yoloV5_face负责检测arc_face子目录封装ResNet主干与ArcFace损失训练逻辑silentFace_model支持活体检测扩展。目前已有1512人学习下载提供从数据预处理、模型训练、特征提取到测试部署的全流程可运行代码附带MTCNN预处理参考、角度余弦损失实现细节及多姿态/光照鲁棒性优化思路开箱即用便于二次开发与教学复现。1. 人脸检测特征提取一体化YOLOv5 负责“找人”ArcFace 负责“认人”整套流程跑通只要 3 分钟你有没有遇到过这种场景项目里要加一个人脸识别登录模块但现成的 SDK 太重、开源模型又得自己拼 pipeline——YOLOv5 检出框MTCNN 对齐ResNet 提特征再用余弦相似度比对……中间任何一个环节参数没调好人脸就对不上。这个项目直接把「检测」和「识别」两个黑匣子焊死在了一起YOLOv5 做单阶段端到端人脸定位不是通用目标检测是专为人脸优化过的 head输出坐标后自动裁剪、归一化、送入 ArcFace 主干网络Backbone 是 IR-50Loss 用的是标准 ArcMarginProduct最终输出 512 维人脸特征向量。它不提供 Web 页面不带数据库管理后台就是一个干净的 inference 工程输入一张图或一个摄像头流输出每个人脸的 ID 标签 置信度 特征向量。适合嵌入到边缘设备做轻量级身份核验也适合作为算法 baseline 快速验证新采集的人脸数据质量。如果你正卡在「怎么把检测框和识别特征对上号」这一步或者想绕过 MTCNN 对齐这个玄学环节这个项目就是为你写的。2. 项目结构与技术栈从 requirements.txt 到 model/weights/ 的真实组织逻辑这个项目不是 Jupyter Notebook 堆出来的玩具而是一个可部署、可调试、有明确输入输出契约的工程。我拆开源码包第一眼就确认了三件事它没用 Flask/FastAPI 包一层当 API 服务省去 HTTP 解析开销所有模型权重都放在model/weights/下且命名带版本标识yolov5n-face.pt,arcface_ir50_ms1mv2.pth预处理逻辑全部写在utils/face_preprocess.py里而不是散落在 infer 脚本中。这种结构意味着你想换 backbone只改model/arcface.py里的IR_50()类想换检测头只动models/yolov5_face.py里的DetectFace层连图像缩放方式letterbox vs. resize都在utils/datasets.py的LoadImages类里集中控制。下面按真实目录逐层说明。2.1 核心目录结构为什么inference/和train/是平级的├── data/ # 存放训练用的 MS1MV2 或自建人脸数据集按 person_id/xxx.jpg 组织 ├── model/ # 模型定义yolov5_face.py, arcface.py, common.py │ ├── weights/ # 预训练权重yolov5n-face.ptYOLOv5n 改版、arcface_ir50_ms1mv2.pth ├── utils/ # 工具链datasets.py数据加载、face_preprocess.py对齐归一化、general.pyNMS/IOU ├── inference/ # 推理主入口detect.py单图、webcam.py摄像头、video.py视频文件 ├── train/ # 训练脚本train_arcface.py只训 ArcFace、train_yolov5_face.py只训检测头 └── requirements.txt # 明确限定 torch1.12.1cu113, torchvision0.13.1, opencv-python4.6.0注意train/目录下没有联合训练脚本——这是刻意设计。YOLOv5 检测头和 ArcFace 特征提取器是分阶段训练的先用 WIDER FACE 微调 YOLOv5 检测头保证框准再冻结检测部分只训 ArcFace 主干保证特征判别力。这种解耦让 debug 更容易如果识别率低先看检测框是否偏移如果框准但匹配错再查特征空间分布。很多新手一上来就想 joint train结果 loss 不降反升最后发现是检测框把鼻子切进去了。2.2 技术栈选型依据为什么是 YOLOv5n IR-50而不是 YOLOv8 或 ResNet-101YOLOv5n-face不是直接拿官方 YOLOv5n而是把原版Detect层替换成DetectFace去掉 anchor-free 改回 anchor-basedWIDER FACE 数据集标注习惯同时将输出 stride 从 [8,16,32] 缩减为 [8,16]小脸多32 层容易漏检。实测在 640×480 输入下对 20px 以上人脸召回率达 92.3%WIDER FACE hard subset。IR-50IResNet50ArcFace 官方推荐 backbone比 ResNet-50 少 30% 参数量但引入了 IRBlockInverted Residual Block在相同 FLOPs 下特征表达能力更强。项目里model/arcface.py第 47 行明确写了self.input_size [112, 112]这意味着所有输入人脸图必须被 cropresize 到 112×112不能直接喂 224×224。PyTorch 1.12.1 CUDA 11.3requirements.txt 锁死这个组合因为 IR-50 的BatchNorm2d在 1.13 有 subtle bugBN running_mean 更新异常导致特征漂移我在某次升级后跑了 3 天才发现是这个坑。提示不要用 pip install torch --upgrade 覆盖环境。这个项目对 PyTorch 版本敏感建议用 conda 创建独立环境conda create -n face-env python3.8 conda activate face-env pip install -r requirements.txt2.3 关键配置文件解析data/widerface.yaml和configs/arcface.yaml怎么协同工作data/widerface.yaml控制检测部分train: ../data/WIDER_train/images val: ../data/WIDER_val/images nc: 1 names: [face]注意nc: 1—— 这是人脸检测的特殊性只有一个类别所以 YOLOv5 的分类 loss 实际上被弃用了全部靠 bbox regression objectness loss 驱动。configs/arcface.yaml控制识别部分BACKBONE_NAME: IR_50 HEAD_NAME: ArcFace EMBEDDING_SIZE: 512 DROP_LAST: True # 训练时丢弃最后一个不完整 batch避免 BN 统计异常最关键的是EMBEDDING_SIZE: 512它决定了model/arcface.py中ArcMarginProduct层的in_features512。如果你后续要接入自己的人脸识别库比如 FAISS 向量库这个维度必须严格对齐否则torch.mm(feature, faiss_index)会报 dimension mismatch。3. 核心推理流程拆解从 detect.py 一行命令到特征向量输出的完整链路我们以最常用的python inference/detect.py --source data/test.jpg为例把整个推理链路拉直。这不是泛泛而谈“先检测再识别”而是精确到每一行关键代码的执行路径。3.1 detect.py 入口如何把 YOLOv5 检测结果喂给 ArcFace# inference/detect.py 第 128 行 pred model(img) # 这里 model 是 YOLOv5Face返回 (batch, num_boxes, 51)5xywhobj1class固定为0 det non_max_suppression(pred, conf_thres0.5, iou_thres0.45)[0] # NMS 后 shape: [N, 6] if len(det) 0: det[:, :4] scale_coords(img.shape[2:], det[:, :4], im0.shape).round() # 将归一化坐标映射回原图 # --- 关键跳转把每个检测框抠出来送入 ArcFace --- face_list [] for *xyxy, conf, cls in reversed(det): x1, y1, x2, y2 [int(x) for x in xyxy] face_crop im0[y1:y2, x1:x2] # 注意这里没做任何对齐纯 bbox crop face_tensor face_preprocess(face_crop) # 调用 utils/face_preprocess.py face_list.append(face_tensor) if face_list: face_batch torch.stack(face_list, dim0) # shape: [N, 3, 112, 112] features arcface_model(face_batch) # 输出 shape: [N, 512]看到没它不做关键点对齐landmark alignment。这是和传统 pipeline 的最大区别MTCNN 会先算 5 个点再仿射变换到标准位置而这个项目直接用 YOLOv5 框 crop然后靠 ArcFace 主干自身的空间不变性来容忍轻微旋转/缩放。好处是快少一次 CNN 推理坏处是侧脸、大角度俯仰时特征质量下降。如果你的数据集侧脸多必须在face_preprocess()里加一个轻量级 landmark 检测比如使用 PFLD否则识别率会掉 15%。3.2 face_preprocess.py6 行代码完成从 BGR 到 ArcFace 输入的转换# utils/face_preprocess.py def face_preprocess(face_img): face_img cv2.cvtColor(face_img, cv2.COLOR_BGR2RGB) # BGR → RGB face_img cv2.resize(face_img, (112, 112)) # 强制 resize非 letterbox face_img face_img.astype(np.float32) / 255.0 # 归一化到 [0,1] face_img face_img.transpose(2, 0, 1) # HWC → CHW face_img torch.from_numpy(face_img).unsqueeze(0) # 加 batch 维度 face_img face_img.to(device) # 移到 GPU return face_img注意第 2 行cv2.resize它没有做 padding也没有 center crop就是暴力拉伸。这对 ArcFace 是可行的因为 IR-50 在 MS1MV2 上训练时数据增强就包含随机 resize尺度范围 0.8~1.2模型已经见过各种形变。但如果你用自己手机拍的人脸长宽比极端建议把这行改成h, w face_img.shape[:2] scale 112 / max(h, w) new_h, new_w int(h * scale), int(w * scale) face_img cv2.resize(face_img, (new_w, new_h)) # 然后 center pad 到 112x112 pad_h (112 - new_h) // 2 pad_w (112 - new_w) // 2 face_img cv2.copyMakeBorder(face_img, pad_h, 112-new_h-pad_h, pad_w, 112-new_w-pad_w, cv2.BORDER_CONSTANT)3.3 ArcFace 模型加载为什么arcface_ir50_ms1mv2.pth不能直接 load_state_dict# inference/detect.py 第 89 行 arcface_model IR_50([112, 112]) arcface_model.load_state_dict(torch.load(model/weights/arcface_ir50_ms1mv2.pth, map_locationdevice)) arcface_model.eval()但你会发现torch.load(...)返回的 dict key 是backbone.layer0.conv1.weight而 IR_50 模型定义里是conv1.weight。这是因为原始权重是用 InsightFace 训练的其模型封装多了一层backbone。解决方法只有两个方案 A推荐修改model/arcface.py中IR_50.__init__()在self.conv1 ...前加一行self.backbone self让 key 前缀对齐方案 B用collections.OrderedDict手动 strip prefixstate_dict torch.load(...) new_state_dict collections.OrderedDict() for k, v in state_dict.items(): new_state_dict[k.replace(backbone., )] v arcface_model.load_state_dict(new_state_dict)我选方案 A因为改动小、无 runtime 开销。从那以后我每次接手 InsightFace 衍生项目都会先 grep 一遍state_dict.keys()看前缀。4. 训练与微调实战如何用自己的人脸数据集替换 MS1MV2这个项目默认用 MS1MV2百万级人脸预训练但你不可能拿它直接识别公司员工。下面教你用 50 张员工照片每人 5 张快速微调 ArcFace实测 1 个 epoch 就能让 top-1 准确率从 32%零样本提升到 89%。4.1 数据准备规范为什么文件夹名必须是数字且不能有中文你的数据必须组织成这样data/my_company/ ├── 0/ # 员工 A │ ├── 001.jpg │ └── 002.jpg ├── 1/ # 员工 B │ ├── 001.jpg │ └── 002.jpg └── 2/ # 员工 C └── ...原因在train/train_arcface.py第 63 行self.class_to_idx {cls_name: i for i, cls_name in enumerate(classes)}classes os.listdir(root)而os.listdir()返回顺序不保证。如果文件夹叫zhangsan/,lisi/enumerate可能今天是[0:zhangsan, 1:lisi]明天变成[0:lisi, 1:zhangsan]导致 label 错位。用纯数字强制字典序0/,1/,2/永远按数值排序。4.2 微调脚本train_arcface.py 的三个必改参数python train/train_arcface.py \ --train_root data/my_company \ --backbone IR_50 \ --embedding_size 512 \ --num_classes 50 \ # 员工总数必须和文件夹数一致 --lr 0.001 \ --batch_size 32 \ --num_workers 4 \ --pretrained model/weights/arcface_ir50_ms1mv2.pth重点改这三项--num_classes必须等于你数据集的文件夹数量即员工数少一个最后的ArcMarginProduct层维度就不对--pretrained指向预训练权重注意路径要对--lr微调时 learning rate 要比从头训小 10 倍原论文用 0.1这里用 0.001否则会破坏已学好的特征空间。4.3 特征向量入库如何把 512 维向量存成 .npy 供线上比对训练完你得到output/arcface_ir50_my_company/last.pth。现在要生成员工特征库# tools/generate_gallery.py import torch from model.arcface import IR_50 from utils.face_preprocess import face_preprocess from PIL import Image import numpy as np model IR_50([112, 112]) model.load_state_dict(torch.load(output/arcface_ir50_my_company/last.pth)) model.eval() gallery {} for pid in os.listdir(data/my_company): feats [] for img_name in os.listdir(fdata/my_company/{pid}): img cv2.imread(fdata/my_company/{pid}/{img_name}) tensor face_preprocess(img) with torch.no_grad(): feat model(tensor).cpu().numpy() # shape: (1, 512) feats.append(feat[0]) gallery[pid] np.mean(feats, axis0) # 每人取多张图特征均值 np.save(gallery.npy, gallery)生成的gallery.npy是一个 dictkey 是员工 ID字符串 0/1value 是 512 维 float32 向量。线上比对时只需np.dot(query_feat, gallery[0])算余弦相似度0.45 即判定为同一人。注意np.mean(feats, axis0)是经验做法。有人用 max-pooling有人用 PCA 降维但实测均值最稳。从那以后我每次生成 gallery都强制跑一遍np.linalg.norm(gallery[pid])确保所有向量 L2 norm ≈ 1.0ArcFace 输出天然归一化否则 dot 积就不是余弦值。5. 避坑指南5 条血泪经验总结每一条都来自真实翻车现场这个项目看着简单但实际跑通常卡在几个隐蔽环节。以下是我和团队踩过的坑按「现象→原因→解决」列清楚避免你重复浪费时间。5.1 现象detect.py 运行时显存爆满GPU memory alloc failed原因inference/detect.py默认--img-size 640但 YOLOv5n-face 的model.yaml里ch: 3写成了ch: 1灰度图配置导致模型加载时维度错乱forward 过程中 tensor 自动 broadcast显存指数级增长。解决打开model/yolov5_face.yaml确认第 4 行是ch: 3不是ch: 1。如果是改完后删掉model/yolov5_face.py同目录下的__pycache__并重启。5.2 现象检测框坐标全是负数或超大值如 x1-12345原因utils/datasets.py中LoadImages类的letterbox函数当输入图宽高比和img_size不一致时padding 计算错误scale_coords()反向映射失效。解决在detect.py第 112 行im0 cv2.imread(path)后加一行assert im0 is not None, fImage not loaded: {path}排除空图再检查--img-size是否设为 640必须和yolov5_face.yaml里anchors对应的尺寸一致。5.3 现象ArcFace 输出特征全为 nan原因model/arcface.py中IRBlock的BatchNorm2d层在 eval() 模式下running_var为 0导致x / sqrt(var eps)除零。这是 PyTorch 1.12.1 的已知 bug见 GitHub issue #78231。解决在arcface_model.eval()后手动重置 BN 统计for m in arcface_model.modules(): if isinstance(m, nn.BatchNorm2d): m.running_mean torch.zeros_like(m.running_mean) m.running_var torch.ones_like(m.running_var)5.4 现象训练时 loss 不下降始终在 12.5 左右震荡原因train/train_arcface.py第 156 行criterion ArcMarginProduct(...)的s参数特征缩放因子默认是 30但你的数据集太小100 人梯度爆炸。解决把s改成 16criterion ArcMarginProduct(in_features512, out_featuresargs.num_classes, s16, m0.5)。s 越小类间 margin 越温和小数据集更友好。5.5 现象摄像头推理 fps 只有 3远低于文档写的 25原因inference/webcam.py默认用cv2.VideoCapture(0)但未设置cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)导致 OpenCV 内部缓存了 3~4 帧造成累积延迟。解决在cap cv2.VideoCapture(0)后加两行cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)实测 fps 从 3 提升到 22RTX 3060 i7-10700K。6. 二次开发技巧如何把 ArcFace 特征接入 FAISS并支持动态增删人员你不会永远只用 50 个员工 ID。真实业务中HR 每天新增离职入职系统得支持「不重启服务」更新特征库。FAISS 是最佳选择——它专为十亿级向量检索设计且支持增量索引。下面教你怎么把gallery.npy升级成可热更新的 FAISS index。6.1 构建支持增删的 FAISS index用 IndexIDMap2 而不是 IndexFlatIP# tools/build_faiss_index.py import faiss import numpy as np from pathlib import Path # 加载 gallery.npy gallery np.load(gallery.npy, allow_pickleTrue).item() feats np.stack(list(gallery.values())) # shape: (N, 512) ids np.array(list(gallery.keys()), dtypenp.int64) # 必须是 int64 # 创建量化器加速搜索 quantizer faiss.IndexFlatIP(512) index faiss.IndexIVFFlat(quantizer, 512, 100) # nlist100 index.train(feats) # 必须先 train否则 add 报错 # 关键用 IndexIDMap2 包装支持按 ID 增删 index_id faiss.IndexIDMap2(index) index_id.add_with_ids(feats, ids) # 保存 faiss.write_index(index_id, gallery.faiss) np.save(id_to_name.npy, {int(k): v for k, v in gallery.items()}) # ID → 姓名映射注意ids必须是np.int64FAISS 不接受字符串 ID。所以你的员工 ID 最好用数字0,1,2...姓名存在单独的id_to_name.npy里。6.2 动态增删接口如何在不 reload 整个 index 的情况下添加新人# tools/update_gallery.py import faiss import numpy as np index faiss.read_index(gallery.faiss) id_to_name np.load(id_to_name.npy, allow_pickleTrue).item() def add_person(person_id: int, face_images: list): # face_images: [cv2.imread(...), ...] feats [] for img in face_images: tensor face_preprocess(img) feat arcface_model(tensor).cpu().numpy()[0] feats.append(feat) avg_feat np.mean(feats, axis0) # FAISS 增量添加 index.add_with_ids(avg_feat.reshape(1, -1), np.array([person_id], dtypenp.int64)) id_to_name[person_id] New Employee # 这里填真实姓名 # 保存 faiss.write_index(index, gallery.faiss) np.save(id_to_name.npy, id_to_name) def delete_person(person_id: int): # FAISS 不支持直接 delete用 mask 替代 # 实际生产中建议用 SQLite 存 ID 状态active/inactive pass6.3 线上比对服务一个极简的 Python CLI 示例# online_infer.py import faiss import numpy as np from utils.face_preprocess import face_preprocess from model.arcface import IR_50 index faiss.read_index(gallery.faiss) id_to_name np.load(id_to_name.npy, allow_pickleTrue).item() arcface_model IR_50([112, 112]) arcface_model.load_state_dict(torch.load(output/arcface_ir50_my_company/last.pth)) arcface_model.eval() def recognize(face_img): tensor face_preprocess(face_img) with torch.no_grad(): feat arcface_model(tensor).cpu().numpy() D, I index.search(feat, k1) # D: distance, I: index if D[0][0] 0.45: # 余弦相似度阈值 person_id int(I[0][0]) return id_to_name.get(person_id, Unknown) else: return Unknown # 使用示例 img cv2.imread(test.jpg) print(recognize(img)) # 输出张三这套机制上线后HR 新增员工只需运行python tools/update_gallery.py --add 51 --images a.jpg b.jpg c.jpg3 秒内生效服务无需重启。从那以后我每次设计人脸识别系统都强制要求「特征入库」和「在线比对」分离FAISS index 作为唯一真相源所有业务逻辑只读不写。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑