恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
基于深度学习的人脸识别考勤系统实战:从ArcFace到活体检测
首页
资讯中心
/
基于深度学习的人脸识别考勤系统实战:从ArcFace到活体检测
基于深度学习的人脸识别考勤系统实战:从ArcFace到活体检测
发布时间:2026/10/7 13:09:56
简介这是一套面向高校课程设计与毕业设计场景的深度学习人脸识别考勤系统完整项目包适合具备一定Python与神经网络基础、希望快速搭建可运行考勤demo的学生和开发者。资源共121个文件以31个py源码、18个pyc编译文件、37张jpg人脸样本图为主另含npy特征数据、csv考勤记录、txt说明及md文档压缩包约19.67MB结构覆盖数据采集、模型训练到系统集成的完整链路。项目采用YOLO进行人脸检测结合CNN完成特征提取与识别并配套Start.exe启动入口与前端页面便于直接运行体验。已有44人学习下载读者可从中获取数据预处理脚本、模型训练代码、特征文件与考勤记录样例理解实时识别流程、容错验证设计及前后端管理模块的实现思路为课程设计或毕业设计提供可复用的工程参考。1. 从一张打卡照片说起深度学习人脸识别考勤系统到底解决了什么早上八点五十公司门口排了二十多号人前台那台老指纹机开始集体翻车——手指脱皮、出汗、按偏了一个接一个识别失败。换成工牌刷卡又总有人忘带。后来我们上了一套基于深度学习的人脸识别考勤系统员工走到门口摄像头前站定一秒内完成比对后台自动写一条打卡记录。这套东西的核心不是摄像头数据库这么简单它要解决的是在真实办公场景下光照忽明忽暗、有人戴口罩、有人侧脸、有人戴眼镜反光系统还得稳定认出这是谁并且判断是本人活体而不是举着照片来代打卡。这个标题里的三个词——深度学习、人脸识别、考勤系统——分别对应算法层、感知层和业务层。深度学习负责把人脸映射成高维特征向量人脸识别负责比对和判定考勤系统负责把识别结果变成可查询、可导出、可对接薪资的记录。适合谁看想自己搭一套能跑起来的人脸考勤的开发者、需要给中小团队做门禁考勤的运维、以及正在做深度学习实战项目案例的学生。下面我按选型→搭环境→跑通识别→接考勤业务→避坑→进阶的顺序把这条链路拆开讲。2. 选型先定死人脸识别模型、检测器和考勤架构怎么配2.1 为什么是 ArcFace 而不是随便一个 CNN人脸识别不是普通图像分类。普通 CNN 做分类最后一层 softmax 把每个人当一个类别人一多类别就爆炸而且新增员工要重训。人脸识别的正确做法是特征提取度量学习把每张脸压成一个 512 维向量同一个人的向量距离近不同人距离远。ArcFace 是目前工程上最稳的损失函数之一它在角度空间上加了一个 margin让类间距离被强行拉开。相比早期的 Softmax、Center LossArcFace 在口罩、侧脸这类困难样本上的类间可分性明显更好。我一般会选 InsightFace 这套开源实现它把 RetinaFace 检测 ArcFace 识别打包好了模型文件直接可用。检测器用 RetinaFace 而不是 Haar 级联原因是 Haar 在侧脸和暗光下漏检严重RetinaFace 对小人脸和遮挡更鲁棒。识别主干用 ResNet50 或 MobileFaceNet服务器端用 ResNet50 精度高边缘设备门禁机那种算力用 MobileFaceNet速度快、模型小。2.2 考勤系统的三种架构别一上来就上微服务架构适用规模优点代价单机脚本SQLite10人以内半天能跑通无法多终端Flask/FastAPI MySQL50~500人易扩展、可对接要维护服务边缘盒子中心库多门店断网可离线识别部署成本高我的建议先用第二种把业务跑通识别服务和人脸库分离。识别服务只干一件事——收到一张图返回是谁相似度考勤业务服务负责打卡时间、迟到判定、报表。两者用 HTTP 或消息队列通信。这样以后换模型、加终端都不用动业务代码。热搜里常出现的人脸识别门禁机其实就是把识别服务塞进了一个带摄像头的边缘设备逻辑是一样的只是部署位置不同。2.3 人脸库怎么建注册照的质量决定上限很多人栽在这一步。注册照随手用手机拍一张就入库结果现场识别率惨不忍睹。注册照的要求正面、无遮挡、光照均匀、分辨率不低于 640×640、人脸区域占画面 1/3 以上。每人建议存 3~5 张不同角度取特征向量的平均值作为该人的底库向量。入库前一定要做质量过滤模糊的、侧脸超过 30 度的、过曝的直接拒绝重拍。这一步偷懒后面识别率上不去你会以为是模型不行其实是底库脏。3. 把环境配起来从零跑通 ArcFace 推理的最小步骤3.1 深度学习环境配置GPU 版和 CPU 版的取舍先明确一点推理阶段 CPU 也能跑只是慢。500 人的底库CPU 单张比对大概 200~400msGPU 能压到 20ms 以内。如果只是做项目验证CPU 版够用要上真实考勤建议 GPU 或带 NPU 的边缘盒子。下面是 Ubuntu 20.04 上配 GPU 版环境的典型流程这也是热搜里ubuntu20.04配置深度学习最常被问到的场景。# 1. 确认显卡驱动和 CUDA 版本匹配 nvidia-smi # 看驱动版本和最高支持的 CUDA # 2. 建独立环境别污染系统 Python conda create -n faceattend python3.9 -y conda activate faceattend # 3. 装 PyTorch版本要和 CUDA 对应这里以 CUDA 11.8 为例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 4. 装人脸识别工具链 pip install insightface onnxruntime-gpu opencv-python numpy逻辑说明nvidia-smi先确认驱动支持的 CUDA 上限避免装了跑不起来的版本。conda 建独立环境是因为不同项目对 numpy、onnxruntime 版本要求经常打架。PyTorch 的 index-url 必须和本机 CUDA 对应装错版本会报 CUDA driver version is insufficient。onnxruntime-gpu 是给 InsightFace 的 ONNX 模型做推理加速的如果只有 CPU换成onnxruntime即可。参数说明python3.9是兼容性最好的版本3.11 以上部分包还没轮子。CUDA 11.8 对应 PyTorch 2.x如果你驱动只支持到 CUDA 11.6就把 index-url 换成 cu116。3.2 用 InsightFace 跑通第一张人脸的检测和特征提取环境好了先别急着接考勤先确认模型能正确检测和出特征。下面这段是最小可运行代码。import cv2 import numpy as np from insightface.app import FaceAnalysis # 初始化指定检测和识别模型ctx_id0 表示用 GPU 0-1 表示 CPU app FaceAnalysis(namebuffalo_l, providers[CUDAExecutionProvider]) app.prepare(ctx_id0, det_size(640, 640)) img cv2.imread(test.jpg) faces app.get(img) # 返回检测到的所有人脸对象 for face in faces: # face.bbox 是检测框face.embedding 是 512 维特征向量 print(bbox:, face.bbox) print(norm:, np.linalg.norm(face.embedding)) # 应接近 1 print(det_score:, face.det_score) # 检测置信度逻辑说明FaceAnalysis一次性加载检测、关键点、识别三个模型。app.get()内部先 RetinaFace 检测再对齐再送 ArcFace 出 512 维向量。det_size(640,640)是检测输入尺寸太小会漏掉远处的小脸太大速度下降。参数说明namebuffalo_l是 InsightFace 的模型包名包含检测和识别。ctx_id0用第一块 GPU没有 GPU 写 -1。det_score低于 0.5 的检测框建议直接丢弃那是误检。embedding的 L2 范数应该接近 1如果差很多说明模型没加载对。3.3 比对逻辑余弦相似度和阈值怎么定拿到两张脸的特征向量后用余弦相似度判断是不是同一个人。def cosine_sim(a, b): a a / np.linalg.norm(a) b b / np.linalg.norm(b) return float(np.dot(a, b)) # 假设 emb1 是底库向量emb2 是现场抓拍向量 sim cosine_sim(emb1, emb2) THRESHOLD 0.38 # ArcFace 常用阈值区间 0.35~0.45 print(same person if sim THRESHOLD else different)逻辑说明余弦相似度衡量两个向量的方向一致性人脸特征经过归一化后同人相似度通常 0.5 以上不同人 0.2 以下中间地带就是阈值要卡的地方。参数说明阈值不是拍脑袋定的。正确做法是拿一批已知同人/不同人的样本对画 ROC 曲线选误识率FAR和拒识率FRR平衡的点。安防场景宁可拒识也不能误识阈值调高到 0.45考勤场景怕员工打不上卡可以降到 0.35。这个值必须用你自己的数据标定网上抄的阈值只能当起点。4. 接上考勤业务从识别结果到一条打卡记录4.1 底库管理和 1:N 检索的实现考勤是 1:N 检索——来一张脸要和底库所有人比找出最像的那个。底库小的时候暴力遍历就行几百人以上建议用向量索引。import numpy as np class FaceGallery: def __init__(self): self.embs [] # 所有底库向量 self.names [] # 对应姓名/工号 def add(self, name, emb): self.embs.append(emb / np.linalg.norm(emb)) self.names.append(name) def search(self, query, topk1): if not self.embs: return None, 0.0 Q query / np.linalg.norm(query) G np.stack(self.embs) # (N, 512) sims G Q # 一次矩阵乘法算完所有相似度 idx int(np.argmax(sims)) return self.names[idx], float(sims[idx])逻辑说明把所有底库向量堆成矩阵一次矩阵乘法就能算出和所有人的相似度比 for 循环快几十倍。返回最相似的人名和分数。参数说明topk在考勤里一般取 1但建议同时看第二名分数如果第一名和第二名差距小于 0.05说明这次识别不可靠应该提示重试而不是硬判。底库超过 1 万人时np.stack的内存和计算会变重这时换 Faiss 的 IVF 索引把检索从 O(N) 降到近似 O(logN)。4.2 活体检测挡住举照片代打卡的那批人不做活体员工拿同事照片就能代打卡这套系统等于白做。工程上常用两种动作活体眨眼、转头和静默活体RGB 单帧判断。动作活体体验差但实现简单静默活体体验好但需要额外模型。中小团队我建议先上动作活体成本低。# 简化版连续帧中检测眨眼基于眼睛关键点纵横比 EAR def eye_aspect_ratio(eye_points): # eye_points 为 6 个眼部关键点坐标 v1 np.linalg.norm(eye_points[1] - eye_points[5]) v2 np.linalg.norm(eye_points[2] - eye_points[4]) h np.linalg.norm(eye_points[0] - eye_points[3]) return (v1 v2) / (2.0 * h) # EAR 低于阈值持续 2~3 帧再回升判定为一次眨眼逻辑说明眼睛睁开时 EAR 约 0.3闭合时降到 0.2 以下。连续帧里出现下降再回升就是一次眨眼。要求用户在摄像头前完成一次眨眼照片无法完成这个动作。参数说明EAR 阈值取 0.21 左右需按摄像头分辨率微调。要求眨眼次数设 1 次即可设多了用户会烦。注意戴眼镜反光可能干扰关键点必要时加红外摄像头做辅助。4.3 打卡记录落库和迟到判定识别通过后写一条记录。表结构别设计太复杂够用就行。CREATE TABLE attendance ( id BIGINT PRIMARY KEY AUTO_INCREMENT, emp_no VARCHAR(32) NOT NULL, punch_time DATETIME NOT NULL, similarity FLOAT, device_id VARCHAR(64), status TINYINT DEFAULT 0, -- 0正常 1迟到 2早退 INDEX idx_emp_time (emp_no, punch_time) );逻辑说明idx_emp_time联合索引是必须的考勤查询几乎都是某人某段时间的记录没这个索引数据一多就慢。similarity存下来方便事后排查误识。参数说明status的迟到判定不要写死在插入时建议用定时任务每天跑一次因为上班时间可能调整。同一个人 5 分钟内重复打卡要合并避免在摄像头前多站几秒就多条记录。device_id用于多终端场景定位是哪台设备打的卡。5. 避坑与排查那些让识别率一夜暴跌的原因5.1 现象白天好好的晚上识别率断崖下跌原因摄像头在暗光下自动拉高 ISO画面噪点暴增RetinaFace 检测框抖动对齐后的脸和底库差异大。解决门口加补光灯是最便宜的方案软件侧在检测前做一次直方图均衡化或 CLAHE能救回一部分。别指望模型自己扛训练数据里暗光样本本来就少。5.2 现象同一个人换个发型或戴眼镜就认不出原因底库只有一张正面照特征覆盖不足。解决每人入库 3~5 张包含戴眼镜和不戴眼镜、不同发型。检索时用底库向量的平均值或者取和底库所有向量的最大相似度。后者更稳代价是计算量翻几倍。5.3 现象两个人长得像经常互相打上对方的卡原因阈值定太低或者这两个人在特征空间里本来就近双胞胎、亲属。解决先调高阈值到 0.45 观察如果还混对这几个易混的人单独建一个二次确认名单识别到他们时要求额外刷工牌。工程上不要追求 100% 纯人脸混合验证更现实。5.4 现象服务跑几天就内存暴涨然后崩原因InsightFace 的 session 没释放或者每来一张图都新建 FaceAnalysis 对象。解决FaceAnalysis 全局初始化一次复用。图像处理完及时del并gc.collect()。用 FastAPI 的话注意别在请求处理函数里反复加载模型。5.5 现象GPU 显存够但推理还是很慢原因图片没缩放就送进模型一张 4000×3000 的图检测要好几秒。解决送检测前先把长边缩到 640~1280检测完再按比例映射回原图坐标。这一步能提速 5~10 倍是血泪经验。6. 进阶把识别率再往上抬的几个具体手段6.1 用质量分决定要不要重拍不是所有抓拍都值得送去比对。可以算一个质量分检测置信度、人脸尺寸、模糊度拉普拉斯方差、姿态角。低于阈值的直接提示请正对摄像头而不是硬比出一个错误结果。这个改动对体验提升很大因为用户知道该怎么配合。def face_quality(face, img): score face.det_score w face.bbox[2] - face.bbox[0] if w 80: # 人脸太小 score * 0.5 gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) lap_var cv2.Laplacian(gray, cv2.CV_64F).var() if lap_var 100: # 太模糊 score * 0.5 return score逻辑说明把检测分、尺寸、清晰度乘起来得到一个综合质量分低于 0.4 就要求重拍。参数说明w 80和lap_var 100是按 1080P 摄像头调的换设备要重新标。6.2 阈值按人自适应全局一个阈值是妥协。更好的做法是给每个人维护一个历史相似度分布动态调整他的判定阈值。识别一直很稳的人阈值可以放宽经常在边缘徘徊的人阈值收紧并要求二次验证。这需要积累数据上线初期先用全局阈值跑一个月后再做自适应。6.3 模型更新和底库版本管理换模型意味着所有底库向量要重新提取因为不同模型的向量空间不通用。所以底库要带版本号模型升级时批量重算别新旧混用。我一般会在数据库里存model_version字段检索时只比对同版本的向量。这个习惯能省掉很多为什么升级后全乱了的排查时间。6.4 验证方法别只看准确率评估一套考勤系统光看识别准确率不够。要分开看误识率把别人认成你最严重、拒识率你本人打不上卡、活体通过率、平均识别耗时。用一批标注好的现场抓拍做测试集跑出这四个数再决定调阈值还是换模型。我吃过亏——早期只看准确率 99%上线后发现拒识率高达 8%每天都有员工打不上卡来找我。后来把阈值降了 0.03拒识率降到 2%误识率只涨了 0.1%这才是划算的买卖。做这类系统模型只是三分之一底库质量、活体策略、阈值标定才是决定成败的地方。我现在的习惯是任何参数改动前先留一份基线数据改完对比四个指标不靠感觉调。希望帮到你。本文还有配套的精品资源点击获取