简介:这份资源面向人脸识别入门与进阶学习者,聚焦YoloV5目标检测、ArcFace特征提取与活体检测三大技术的结合实践,帮助读者从理论到代码完整理解人脸识别系统的搭建流程。压缩包共54个文件,约3.4MB,以26个Python源码为核心,辅以7个yaml配置、2个pth权重、9个pyc编译文件及少量jpg示例图、txt说明与md文档,覆盖模型定义、训练测试、数据生成与推理预测等模块。内容预览显示包含anti_spoof_models、yoloV5_face、arc_face、MiniFASNet等目录,涉及损失函数、特征度量与静默活体检测实现。已有175人学习,适合希望掌握检测、识别与防伪全链路的中级开发者参考,可据此复现项目结构、理解关键算法调用与工程组织方式。
1. 从一张误报的抓拍说起:这套人脸识别工程到底能干什么
去年帮一个做园区门禁的朋友看系统,他们上线三个月,保安室每天要处理几十条“识别失败”的申诉。翻日志发现,问题不在模型精度,而在于有人拿手机里的一张照片对着摄像头晃了一下,闸机就开了。这件事让我意识到,一个人脸识别项目能不能落地,关键往往不在“认得出谁”,而在“认得出这是不是活人”。这份名为人脸识别_YoloV5_ArcFace_活体检测_学习实践的工程包,恰好把这三个环节串成了一条完整链路:YoloV5 负责在画面里框出人脸,ArcFace 负责把框出来的人脸映射成特征向量做比对,活体检测负责判断镜头前是真人还是照片、屏幕或面具。它适合两类人:一类是想跑通“检测→对齐→特征提取→比对→活体”全流程的初学者,另一类是手里有业务场景、想拿一套可改的骨架快速验证方案的开发者。整包是学习实践性质,代码结构比生产级项目更直白,模块边界清晰,改起来不容易迷路。
2. 技术选型与目录结构:为什么是 YoloV5 + ArcFace 这套组合
2.1 三个模块各管一段,拼起来才是一条链路
人脸识别系统拆开看,本质是三个独立问题。第一个是“人在哪”,属于目标检测,YoloV5 在这个位置几乎是默认答案——单阶段检测、速度快、对小目标人脸也够用,而且工程包里通常直接带预训练权重,不用自己从零训。第二个是“这是谁”,属于特征提取与度量学习,ArcFace 的核心贡献是在 Softmax 上加了一个角度间隔,让同类特征更紧凑、异类特征更分散,比早期的 FaceNet 三元组损失更稳定,训练时对样本对构造的依赖也小。第三个是“是不是活的”,属于二分类或辅助任务,常见做法是用轻量 CNN 对 RGB 或红外图做真假判断,也有用眨眼、张嘴等动作配合的。
这三个模块单独拎出来都不难,难的是接口对齐。YoloV5 输出的是xyxy坐标加置信度,ArcFace 需要的是对齐后的 112×112 人脸图,活体检测又需要原始帧或裁剪区域。工程包里如果没把这几步的输入输出格式定死,后面调参会非常痛苦。这套代码的价值就在于它把这条链路显式地写出来了,每个模块的输入输出都能在代码里找到对应。
2.2 目录结构:按功能分层,不按文件类型堆
拿到一个工程包,我习惯先看目录。这套代码的结构大致是这样:
face_project/ ├── configs/ # 配置集中管理 │ ├── detect.yaml # YoloV5 检测参数 │ ├── arcface.yaml # 特征提取与比对阈值 │ └── liveness.yaml # 活体检测参数 ├── models/ │ ├── yolov5/ # 检测模型定义与权重加载 │ ├── arcface.py # 特征提取网络 │ └── liveness.py # 活体检测网络 ├── utils/ │ ├── face_align.py # 五点对齐、仿射变换 │ ├── preprocess.py # 归一化、尺寸变换 │ └── visualize.py # 画框、打标签 ├── data/ │ ├── gallery/ # 底库人脸特征 │ └── test_images/ # 测试图 ├── run_demo.py # 单图/视频入口 └── requirements.txt这个结构的好处是配置和模型分离,换阈值不用改代码,换权重不用动逻辑。utils/face_align.py是容易被忽略但很关键的一层,ArcFace 对输入人脸的姿态很敏感,如果直接拿 YoloV5 的框去裁剪、不做五点对齐,识别率会掉得很明显。常见做法是用 MTCNN 或 RetinaFace 先取五个关键点,再做相似变换把眼睛和嘴角摆正,这套工程里应该也是类似思路。
2.3 依赖与运行环境:先把版本锁死
requirements.txt里通常会有torch、torchvision、opencv-python、numpy、pyyaml这几项。YoloV5 对 torch 版本比较敏感,太新或太旧都可能遇到算子不兼容。我一般会先建虚拟环境,再按下面步骤走:
# 创建独立环境,避免和系统里的 torch 冲突 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 安装依赖,建议先装 torch 再装其余 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install -r requirements.txt # 确认 GPU 是否可用(有卡的话) python -c "import torch; print(torch.cuda.is_available())"如果输出False而你确实有显卡,多半是装成了 CPU 版 torch,需要按 CUDA 版本重装。这一步不确认,后面跑检测会慢到怀疑人生。
3. 核心链路拆解:从一帧画面到识别结果
3.1 检测阶段:YoloV5 输出框和置信度
检测这一步的目标很明确:把画面里所有人脸的位置找出来。YoloV5 的推理接口通常封装成一个类,初始化时加载权重,调用时传入 BGR 图像。下面是一段典型的调用代码:
import cv2 import torch from models.yolov5 import YoloV5Detector # 初始化检测器,指定权重路径和置信度阈值 detector = YoloV5Detector( weights="weights/yolov5_face.pt", conf_thres=0.5, # 低于这个置信度的框直接丢弃 iou_thres=0.45, # NMS 的 IoU 阈值,控制重叠框合并 device="cuda" if torch.cuda.is_available() else "cpu" ) img = cv2.imread("data/test_images/group.jpg") # 返回列表,每个元素是 [x1, y1, x2, y2, conf] boxes = detector.detect(img) for box in boxes: x1, y1, x2, y2, conf = box cv2.rectangle(img, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2)这里conf_thres和iou_thres是两个最常调的参数。conf_thres调低会召回更多人脸,但误检也会变多;iou_thres调低会让 NMS 更激进,密集人脸场景下容易漏掉挨得近的人。我一般先在测试集上把这两个值扫一遍,看 PR 曲线再定,而不是拍脑袋。
3.2 对齐与预处理:ArcFace 前的必要一步
拿到框之后不能直接送进 ArcFace。ArcFace 训练时用的是对齐后的人脸,如果推理时输入的是歪的,特征分布会对不上。对齐的做法是先用关键点检测拿到两眼、鼻尖、两嘴角五个点,再算一个相似变换矩阵,把脸摆到标准位置。
import numpy as np from utils.face_align import norm_crop # 假设 landmarks 是 5x2 的关键点坐标 # 标准五点位置是 ArcFace 训练时用的参考坐标 landmarks = detector.get_landmarks(img, box) aligned_face = norm_crop(img, landmarks, image_size=112) # 归一化:ArcFace 输入通常是 (x - 127.5) / 128 face_input = (aligned_face.astype(np.float32) - 127.5) / 128.0 face_input = np.transpose(face_input, (2, 0, 1)) # HWC -> CHW face_input = np.expand_dims(face_input, axis=0) # 加 batch 维image_size=112是 ArcFace 的标准输入尺寸,不要随意改,改了就得重新训。归一化那一步也容易写错,有的实现用/255,有的用(x-127.5)/128,必须和训练时保持一致,否则特征会整体偏移。
3.3 特征提取与比对:余弦相似度定阈值
ArcFace 输出的是一个 512 维向量,比对时用余弦相似度。底库里每个人存一条特征,识别时算当前特征和底库所有特征的相似度,取最大值,再和阈值比。
import torch from models.arcface import ArcFace # 加载特征提取网络 arcface = ArcFace(weights="weights/arcface_r50.pth", device="cuda") arcface.eval() with torch.no_grad(): feat = arcface(torch.from_numpy(face_input).to("cuda")) feat = feat / feat.norm(dim=1, keepdim=True) # L2 归一化 # 底库特征同样归一化后存成矩阵 gallery_feats = torch.load("data/gallery/features.pt") gallery_feats = gallery_feats / gallery_feats.norm(dim=1, keepdim=True) # 余弦相似度 = 归一化后的点积 sims = torch.mm(feat, gallery_feats.t()) best_sim, best_idx = sims.max(dim=1) threshold = 0.45 # 这个值需要根据业务场景调 if best_sim.item() > threshold: print(f"识别为 ID {best_idx.item()}, 相似度 {best_sim.item():.3f}") else: print("未注册人员")阈值0.45不是固定的。安防场景宁可漏识也别误识,阈值可以往 0.5 以上调;考勤场景希望少漏,可以降到 0.4 左右。这个值最好用一批标注好的正负样本对跑一遍 ROC,取等错误率附近的点。
3.4 活体检测:在识别前先挡一道
活体检测放在识别之前,能省掉大量无效比对。常见实现有两种:一种是静默活体,直接对单帧做真假分类;另一种是配合动作,要求用户眨眼或转头。这套工程里如果是学习实践性质,大概率是静默活体,用一个轻量 CNN 输出真/假概率。
from models.liveness import LivenessNet liveness = LivenessNet(weights="weights/liveness.pth", device="cuda") liveness.eval() # 输入可以是原始人脸区域,不一定要对齐 with torch.no_grad(): prob = liveness(face_tensor) # 输出 [假, 真] 的概率 is_real = prob[1].item() > 0.6 if not is_real: print("检测到非活体,拒绝识别") else: # 继续走 ArcFace 比对 ...活体模型的阈值同样要按场景调。如果误拒率高,用户会抱怨“我明明是真脸”;如果误接受率高,照片就能混过去。我一般会把活体概率和识别相似度一起输出,方便排查是哪个环节出的问题。
4. 避坑与排查:跑这套代码最容易翻车的几个点
4.1 检测框正常但识别全错:对齐没做或做错了
现象是 YoloV5 能框出人脸,但 ArcFace 比对相似度普遍低于 0.3,底库人员也认不出来。原因通常是对齐环节被跳过,或者关键点顺序和参考点顺序不一致。解决方法是打印对齐后的人脸图,肉眼确认眼睛是否水平、脸是否居中。如果关键点检测本身不准,可以换更稳的关键点模型,或者退而求其次用框的几何中心做粗略对齐,但识别率会打折。
4.2 活体检测把真人判成假:阈值和光照问题
现象是用户正对镜头却被拒绝,日志里活体概率在 0.4 到 0.5 之间徘徊。原因可能是训练数据里正样本的光照条件和现场差异大,或者阈值设得过高。解决方法是先收集一批现场误拒样本,看它们的活体概率分布,再把阈值往下调,同时检查输入活体模型的图像是否做了和训练一致的预处理。如果现场有逆光或强补光,最好在采集端加一个简单的亮度归一化。
4.3 GPU 显存不够:batch 和分辨率没控制
现象是跑视频流时程序崩溃,报CUDA out of memory。原因是 YoloV5 和 ArcFace 同时驻留显存,加上视频解码的帧缓存,很容易超。解决方法是把检测和识别的 batch 设为 1,降低输入分辨率,或者用torch.cuda.empty_cache()在每帧处理后释放缓存。如果还是不够,可以把活体模型换成更小的 backbone,或者把三个模型分时加载。
4.4 底库特征和查询特征不一致:归一化方式不同
现象是离线算的底库特征和在线算的查询特征,明明是同一个人,相似度却只有 0.2。原因是两次特征提取用了不同的预处理或不同的模型权重。解决方法是把底库特征生成脚本和在线推理脚本的预处理代码抽成同一个函数,确保归一化、尺寸、通道顺序完全一致。这个坑很隐蔽,因为单看代码都没错,错在两边不一致。
4.5 视频流延迟越来越高:没有丢帧策略
现象是处理实时视频时,画面越跑越慢,延迟从几百毫秒涨到几秒。原因是每帧都做完整检测+识别,处理速度跟不上采集速度,帧在队列里堆积。解决方法是在采集端加一个丢帧逻辑,只保留最新帧,或者用跳帧策略,每 N 帧做一次检测,中间帧复用上一次的框。这个改动对体验影响不大,但能显著降低延迟。
5. 二次开发:换模型、加接口、做量化,以及我踩过的那些坑
拿到这套代码之后,最常见的改造方向有三个。第一个是换检测模型,比如把 YoloV5 换成更轻的 YoloV5n 或 YoloV8n,适合边缘设备。换的时候注意输出格式,YoloV8 的输出和 V5 略有不同,NMS 那一步要跟着改。第二个是加业务接口,比如把run_demo.py里的逻辑封装成 FastAPI 服务,对外提供/recognize接口,入参是 base64 图片,出参是人员 ID 和相似度。这一步的关键是把模型加载放在服务启动时,不要每次请求都重新加载。
from fastapi import FastAPI from pydantic import BaseModel import base64 import cv2 import numpy as np app = FastAPI() # 服务启动时加载一次模型 detector = YoloV5Detector(...) arcface = ArcFace(...) liveness = LivenessNet(...) class ImageRequest(BaseModel): image_base64: str @app.post("/recognize") def recognize(req: ImageRequest): img_bytes = base64.b64decode(req.image_base64) img = cv2.imdecode(np.frombuffer(img_bytes, np.uint8), cv2.IMREAD_COLOR) # 检测 -> 活体 -> 对齐 -> 特征 -> 比对 ... return {"person_id": pid, "similarity": sim, "is_real": is_real}第三个方向是模型量化,把 FP32 转成 FP16 或 INT8,减少显存占用和推理时间。FP16 比较简单,model.half()加输入转 half 就行,但要注意有些算子不支持 half。INT8 需要校准集,流程长一些,收益也更大。我一般先在 FP16 上验证精度掉点是否可接受,再决定要不要上 INT8。
这里说一个我自己的教训。有一次为了赶进度,直接把底库特征生成脚本里的归一化从(x-127.5)/128改成了/255,因为当时觉得“都是归一化,差不多”。结果上线后识别率从 95% 掉到 60%,排查了一整天才定位到这一行。从那以后我每次改预处理代码,都强制走一遍“同一张图,离线特征和在线特征相似度必须大于 0.99”的自检,不过这一关不许提交。希望这套工程包能帮你少走一段弯路,把精力花在真正值得调的地方。
本文还有配套的精品资源,点击获取