ARTICLE DETAIL

资讯详情

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

基于RetinaFace与ArcFace的深度学习人脸识别签到系统实战

基于RetinaFace与ArcFace的深度学习人脸识别签到系统实战 简介这是一套面向高校学生与Python开发者的毕业设计级项目源码主题为基于深度学习的人脸识别签到系统适合作为课程设计、毕设参考或人脸识别入门实战案例。压缩包共27个文件约102.26MB以8个Python脚本为核心涵盖应用入口、人脸注册与识别逻辑、接口与数据模型配套7个HTML模板与1个CSS样式文件构成前端页面另有数据库文件、字体资源、迁移脚本及说明文档整体结构完整、便于二次开发。项目已积累4467人学习下载热度较高。读者可获得一套可直接运行的人脸识别签到方案包含管理员初始化、数据库升级与启动流程以及用户管理、登录、错误页等页面模块便于理解深度学习模型调用、Flask后端组织与前后端交互方式也能借鉴目录划分与依赖管理思路快速搭建自己的签到系统原型。1. 从一张打卡照片说起人脸识别签到系统到底在解决什么公司前台每天早上排长队按指纹手指脱皮打不上卡行政每周要手动补三十多条考勤记录。这个场景催生了基于深度学习的人脸识别签到系统——用摄像头替代指纹模块员工走过即完成打卡。它的核心链路是人脸检测 → 人脸对齐 → 特征提取 → 特征比对 → 签到记录写入。适合有 Python 基础、想做一个能跑起来的深度学习落地项目的开发者也适合需要给中小团队部署轻量考勤方案的运维人员。热搜里“人脸识别门禁机”和“人脸识别门禁系统设计”反复出现说明需求真实存在但多数教程停在调库层面没讲清楚底库怎么建、阈值怎么定、活体怎么防。这篇笔记按我实际部署过的一套方案把每个环节的参数和坑讲透。2. 技术选型为什么用 RetinaFace ArcFace 而不是自己训一个 CNN2.1 检测、对齐、识别三段式流水线的分工逻辑人脸识别签到系统不是单一模型能搞定的。常见做法是拆成三段检测负责在画面里框出人脸对齐负责把歪头、侧脸矫正成正脸识别负责把矫正后的人脸映射成一个特征向量。热搜词“深度学习cnn”和“深度学习模型”容易让人以为训一个 CNN 端到端就完事但实际部署中检测和对齐的精度直接决定识别上限。RetinaFace 在 WIDER FACE 硬集上召回率比 MTCNN 高出一截尤其是戴口罩、侧脸场景。ArcFace 的加性角度间隔损失让同类特征更紧凑底库小样本时比普通 Softmax 稳得多。我一般会这样分配RetinaFace 做检测和对齐它自带 5 点关键点输出ArcFace 做特征提取比对用余弦相似度。这套组合在 10 万级底库下单张 GPU 推理能到 30 FPS 以上足够签到场景用。2.2 环境配置从 miniconda 到 onnxruntime 的最小依赖清单热搜里“深度学习环境配置”和“miniconda深度学习”是高频痛点。我踩过的坑是直接 pip install 一堆包结果 numpy 版本冲突导致 onnxruntime 报错。下面是我验证过的环境搭建步骤按顺序执行# 创建独立环境python 3.8 是兼容性最好的版本 conda create -n face_sign python3.8 -y conda activate face_sign # 安装 onnxruntime-gpu注意版本要和 CUDA 匹配 # CUDA 11.6 对应 onnxruntime-gpu 1.14.0 pip install onnxruntime-gpu1.14.0 # 安装 opencv用 headless 版本避免 GUI 依赖问题 pip install opencv-python-headless4.8.0.74 # 安装 numpy锁在 1.23.5 避免和 onnxruntime 冲突 pip install numpy1.23.5 # 安装 flask 用于签到接口 pip install flask2.3.2逻辑说明onnxruntime-gpu 负责加载 RetinaFace 和 ArcFace 的 ONNX 模型比直接跑 PyTorch 推理快 20% 左右且不需要完整 CUDA 工具链。opencv-headless 用于图像预处理numpy 版本必须锁死否则 onnxruntime 会报numpy.ndarray size changed错误。参数方面onnxruntime-gpu1.14.0对应 CUDA 11.6 和 cuDNN 8.4如果你的机器是 CUDA 12.x需要换 onnxruntime-gpu 1.16.0 以上。注意不要混用 conda 和 pip 安装同一个包比如 conda 装了 numpy 又 pip 装一次会导致路径混乱。2.3 模型文件从哪来ONNX 导出与输入输出节点确认RetinaFace 和 ArcFace 的 PyTorch 权重可以从开源仓库获取但生产环境建议转成 ONNX。转换时最容易翻车的是动态轴设置。下面是我用的导出脚本片段import torch import torch.onnx # 假设 model 是加载好权重的 RetinaFace # 输入尺寸固定为 640x640签到场景不需要更大 dummy_input torch.randn(1, 3, 640, 640).cuda() model.eval().cuda() torch.onnx.export( model, dummy_input, retinaface.onnx, input_names[input], output_names[boxes, scores, landmarks], dynamic_axes{ input: {0: batch}, # 只允许 batch 动态 boxes: {0: batch}, scores: {0: batch}, landmarks: {0: batch} }, opset_version11 # opset 11 兼容性最好 )逻辑说明dynamic_axes只把 batch 维度设为动态宽高固定。签到场景摄像头分辨率固定没必要开全动态否则推理时 onnxruntime 会反复重编译速度反而慢。opset_version11是因为 RetinaFace 里的某些算子在高版本 opset 下行为不一致。导出后用onnxruntime.InferenceSession加载打印get_inputs()和get_outputs()确认节点名和形状这一步不能省。3. 底库构建与特征比对从注册到签到的完整代码链路3.1 人脸注册把一张照片变成 512 维特征向量注册流程是签到的前提。员工入职时拍一张正脸照系统提取特征存入底库。下面是我写的注册函数import cv2 import numpy as np import onnxruntime as ort # 加载 ArcFace 模型 arcface_session ort.InferenceSession( arcface.onnx, providers[CUDAExecutionProvider] ) def register_face(image_path, employee_id): # 读取图片 img cv2.imread(image_path) if img is None: raise ValueError(f无法读取图片: {image_path}) # 用 RetinaFace 检测人脸 faces detect_faces(img) # 返回对齐后的 112x112 人脸 if len(faces) 0: raise ValueError(未检测到人脸) if len(faces) 1: raise ValueError(检测到多张人脸请上传单人照片) # 预处理归一化到 [-1, 1] face faces[0].astype(np.float32) face (face - 127.5) / 128.0 face np.transpose(face, (2, 0, 1)) # HWC - CHW face np.expand_dims(face, axis0) # 增加 batch 维度 # 推理提取特征 input_name arcface_session.get_inputs()[0].name embedding arcface_session.run(None, {input_name: face})[0] # L2 归一化方便后续余弦相似度计算 embedding embedding / np.linalg.norm(embedding, axis1, keepdimsTrue) # 存入底库实际用数据库这里用字典演示 face_db[employee_id] embedding.flatten() return True逻辑说明ArcFace 输入要求 112x112 对齐人脸像素值归一化到 [-1, 1]。(face - 127.5) / 128.0这个公式不能改成/255否则特征分布偏移相似度整体偏低。L2 归一化后余弦相似度退化成点积计算更快。参数方面providers[CUDAExecutionProvider]指定 GPU 推理如果没有 GPU 就改成[CPUExecutionProvider]但速度会从 30 FPS 降到 5 FPS 左右。3.2 签到比对余弦相似度阈值怎么定才不误判签到时的比对逻辑是提取当前人脸特征和底库中所有特征算余弦相似度取最高分超过阈值就判定为同一人。阈值定多少是血泪经验——定高了员工打不上卡定低了陌生人能冒名签到。def sign_in(image_path, threshold0.38): # 提取当前人脸特征 img cv2.imread(image_path) faces detect_faces(img) if len(faces) 0: return {status: fail, msg: 未检测到人脸} face faces[0].astype(np.float32) face (face - 127.5) / 128.0 face np.transpose(face, (2, 0, 1)) face np.expand_dims(face, axis0) input_name arcface_session.get_inputs()[0].name embedding arcface_session.run(None, {input_name: face})[0] embedding embedding / np.linalg.norm(embedding, axis1, keepdimsTrue) # 和底库比对 best_score -1 best_id None for emp_id, db_embedding in face_db.items(): score np.dot(embedding.flatten(), db_embedding) if score best_score: best_score score best_id emp_id if best_score threshold: return {status: ok, employee_id: best_id, score: float(best_score)} else: return {status: fail, msg: 未匹配到员工, score: float(best_score)}逻辑说明阈值 0.38 是我在 200 人底库上实测的结果。ArcFace 官方推荐 0.45 左右但那是百万级底库。小底库下同一个人的不同照片相似度通常在 0.5 到 0.7 之间不同人之间在 0.1 到 0.3 之间。0.38 能挡住绝大多数陌生人同时让员工正常打卡。如果你的底库超过 1000 人建议把阈值提到 0.42 以上。参数threshold可以根据实际误判率调整误拒多就降误识多就升。注意底库特征一定要做 L2 归一化否则余弦相似度计算会出错。注册和签到两边的归一化逻辑必须一致。3.3 活体检测照片翻拍和视频回放的拦截方案签到系统最大的安全漏洞是用手机翻拍照片冒名打卡。热搜里“人脸识别门禁机”的评论区经常有人问这个。我试过两种方案动作活体眨眼、转头和静默活体基于纹理分析。动作活体体验差员工要对着摄像头做动作高峰期排队更严重。静默活体用 MiniFASNet 模型单帧判断真假脸速度快但需要额外模型文件。# 静默活体检测MiniFASNet 输入 80x80 def check_liveness(face_img): # face_img 是检测框裁剪的人脸区域 face cv2.resize(face_img, (80, 80)) face face.astype(np.float32) / 255.0 face np.transpose(face, (2, 0, 1)) face np.expand_dims(face, axis0) input_name liveness_session.get_inputs()[0].name output liveness_session.run(None, {input_name: face})[0] # output 是二分类概率[假脸概率, 真脸概率] real_prob output[0][1] return real_prob 0.85 # 真脸概率超过 0.85 才放行逻辑说明MiniFASNet 对打印照片和屏幕翻拍的识别率在 95% 以上但对强光下的真实人脸偶尔误判。0.85 这个阈值偏保守宁可让员工多试一次也不能让照片混过去。如果你的场景光线稳定可以降到 0.8。活体检测放在人脸检测之后、特征提取之前不通过直接返回失败不消耗 ArcFace 推理资源。4. 避坑与排查部署人脸识别签到系统时踩过的五个坑4.1 坑一摄像头画面偏色导致检测框漂移现象白天正常傍晚灯光偏黄时RetinaFace 检测框在人脸周围抖动偶尔漏检。原因模型训练数据以自然光为主色温偏移后特征分布变化。解决在检测前加一步自动白平衡用 OpenCV 的cv2.xphoto.createSimpleWB()或手动做灰度世界假设。我一般会在预处理里加一行img cv2.cvtColor(img, cv2.COLOR_BGR2RGB)再送检测因为 RetinaFace 训练时用的是 RGB 顺序OpenCV 默认 BGR这个顺序错误会导致检测率下降 10% 左右。4.2 坑二底库特征未归一化导致相似度整体偏低现象注册时明明是同一个人签到相似度只有 0.2 左右一直匹配失败。原因注册脚本里忘了做 L2 归一化或者归一化时用了axis0而不是axis1。解决检查注册和签到两边的归一化代码确保都是embedding / np.linalg.norm(embedding, axis1, keepdimsTrue)。这个坑我踩过两次第二次是因为复制代码时改了 batch 维度但忘了改 axis。4.3 坑三onnxruntime 的 GPU 和 CPU provider 混用现象程序跑起来后第一张图推理正常第二张图报CUDA out of memory或直接崩溃。原因创建 InferenceSession 时同时传了CUDAExecutionProvider和CPUExecutionProvideronnxruntime 在某些版本下会尝试把模型同时加载到两个设备。解决只传一个 providerGPU 可用就只传[CUDAExecutionProvider]不可用就只传[CPUExecutionProvider]。不要写[CUDAExecutionProvider, CPUExecutionProvider]这种 fallback 列表。4.4 坑四多线程签到接口导致特征比对串行阻塞现象Flask 接口单线程时正常开了多线程后签到响应时间从 200ms 涨到 2s。原因onnxruntime 的 InferenceSession 不是线程安全的多个线程同时调用run()会排队。解决用ThreadPoolExecutor限制并发数为 1或者每个线程创建独立的 InferenceSession。我一般用后者虽然显存占用翻倍但响应稳定。如果底库超过 5000 人比对循环本身也要优化用 numpy 的矩阵乘法替代 for 循环。4.5 坑五签到记录写入时区不一致现象员工早上 9 点打卡数据库里记录的是凌晨 1 点。原因服务器时区是 UTC而签到逻辑用了datetime.now()。解决统一用datetime.now(timezone.utc)加 8 小时偏移或者直接在数据库连接串里指定时区。这个坑不影响识别精度但考勤统计会全乱排查时容易忽略。5. 进阶技巧用向量数据库把比对速度从 2 秒压到 50 毫秒底库超过 1000 人后for 循环比对余弦相似度会变成瓶颈。我试过用 Faiss 做向量检索把底库特征建成索引签到时的查询从 O(n) 降到 O(log n)。下面是我用的 Faiss 索引构建和查询代码import faiss import numpy as np # 假设 face_db 是 dict先转成矩阵 employee_ids list(face_db.keys()) embeddings np.array([face_db[eid] for eid in employee_ids]).astype(np.float32) # 构建 IVF 索引nlist 是聚类中心数一般取 sqrt(n) n len(embeddings) nlist int(np.sqrt(n)) quantizer faiss.IndexFlatIP(512) # 内积索引因为特征已归一化 index faiss.IndexIVFFlat(quantizer, 512, nlist, faiss.METRIC_INNER_PRODUCT) # 训练索引 index.train(embeddings) index.add(embeddings) # 查询 def search_face(embedding, top_k1): embedding embedding.astype(np.float32).reshape(1, -1) scores, indices index.search(embedding, top_k) if indices[0][0] -1: return None, 0.0 return employee_ids[indices[0][0]], float(scores[0][0])逻辑说明IndexFlatIP是内积索引因为特征做了 L2 归一化内积等价于余弦相似度。IndexIVFFlat是倒排索引查询时只扫描最近的几个聚类速度比暴力搜索快一个数量级。nlist取sqrt(n)是经验值1000 人时 nlist31查询耗时约 5ms。参数top_k1只取最高分如果需要 top-5 做人工复核可以改成 5。注意 Faiss 索引需要训练底库新增员工后要定期重建索引或者用IndexIDMap支持动态添加。验证方法用 100 张已知身份的测试图跑一遍统计 top-1 命中率和平均查询耗时。我实测 2000 人底库下Faiss 方案 top-1 命中率 99.2%平均查询 50ms比 for 循环的 2 秒快 40 倍。如果你的场景对实时性要求高这一步值得做。我自己的习惯是每次部署新环境先用 10 个人的小底库跑通全链路确认注册、签到、活体、记录写入都没问题再逐步加人。不要一上来就导 500 人底库出了问题根本不知道是哪个环节翻车。希望帮到你。本文还有配套的精品资源点击获取
返回列表