资讯动态

基于深度学习的人脸识别考勤系统:从MTCNN+ArcFace到工程落地

发布时间:2026/10/7 5:56:24 来源:尧图企业网站定制
简介这是一套面向高校课程设计与毕业设计场景的深度学习人脸识别考勤系统完整项目包适合具备一定Python与神经网络基础、希望快速搭建可运行考勤原型的开发者。项目以YOLO实时目标检测为核心串联数据采集、图像预处理、CNN模型训练、考勤集成、前后端管理及安全加密等环节并预留指纹或密码等容错验证方式覆盖从算法到部署的完整链路。压缩包共121个文件约19.67MB以31个py源码脚本、37张jpg人脸样本、18个pyc编译文件为主另含npy特征数据、csv考勤记录、md说明文档及可执行程序目录结构清晰便于按模块查阅与二次开发。目前已有44人学习下载。读者可据此获得一套可直接运行的人脸识别考勤方案理解YOLO与CNN在真实项目中的协作方式并参考其数据组织、模型训练与系统集成思路用于课程作业、毕设或原型验证。1. 从一张.zip说起人脸识别考勤系统到底能不能直接跑起来拿到「基于深度学习的人脸识别考勤系统.zip」这个标题的人十有八九是在做课程设计、毕业设计或者公司行政提了个「能不能刷脸打卡」的需求。先泼一盆冷水压缩包里大概率是一份能跑通 demo 的代码但离「明天就上线打卡」还差着十万八千里。人脸识别考勤系统本质是三件事拼起来——人脸检测把人从画面里框出来人脸识别把框出来的人对上工号考勤业务把「谁在几点出现了」翻译成迟到早退。深度学习在这里主要解决前两件事尤其是识别环节的准确率和鲁棒性。适合谁看手里有 Python 基础、装过 PyTorch 或想装、能看懂命令行报错的人。如果你连pip install都没敲过建议先补环境配置再回来。这一章不写代码先把这套系统的边界和预期讲清楚免得你跑通 demo 就以为能交付。2. 人脸识别考勤系统的技术选型为什么是 MTCNN ArcFace 而不是别的2.1 检测与识别为什么要拆成两段很多人第一次做考勤系统会想用一个模型端到端搞定「输入图片输出工号」。这条路在实验室能跑在真实打卡场景会翻车。原因很简单考勤机摄像头视野里可能同时出现三张脸也可能一张脸都没有端到端模型没法优雅处理「零个或多个目标」。所以常见做法是拆成两阶段——检测阶段负责回答「脸在哪」识别阶段负责回答「这是谁」。检测环节主流选择有三个Haar 级联、MTCNN、RetinaFace。Haar 速度快但侧脸和遮挡基本废掉RetinaFace 精度最高但模型大在低端考勤机上推理慢MTCNN 是精度和速度的折中而且对戴口罩、戴眼镜的容忍度比 Haar 好很多。我一般会选 MTCNN因为它的三个子网络P-Net、R-Net、O-Net可以按需裁剪考勤场景不需要检测极小脸P-Net 的最小检测尺寸可以调大来换速度。识别环节的选择更关键。早期方案用 FaceNet 加三元组损失但训练不稳定需要精心构造 triplet。ArcFace 把分类损失改造成角度间隔让同类特征更紧凑、异类特征更分散在同样训练数据下识别准确率明显更高。热词里出现的 arcface人脸识别 不是偶然工业界人脸门禁和考勤基本默认 ArcFace 系。深度学习框架选 PyTorch因为 ArcFace 官方实现和大多数开源考勤项目都是 PyTorch 写的换 TensorFlow 会给自己找麻烦。2.2 最小可跑通的目录结构和依赖拿到 .zip 之后别急着python main.py先看目录。一个结构正常的项目通常长这样face_attendance/ ├── configs/ │ └── default.yaml ├── models/ │ ├── mtcnn.py │ └── arcface.py ├── data/ │ └── faces/ # 每人一个子文件夹文件夹名即工号 ├── utils/ │ ├── face_align.py │ └── database.py ├── train.py ├── enroll.py └── recognize.py如果压缩包里所有代码都堆在一个文件里说明作者没打算让你扩展你得自己拆。依赖方面核心是torch、torchvision、facenet-pytorch里面自带 MTCNN、opencv-python、numpy、pyyaml。安装命令pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install facenet-pytorch opencv-python numpy pyyaml第一行装 GPU 版 PyTorchcu118对应 CUDA 11.8你的显卡驱动如果只支持 CUDA 11.6 就换成cu116。第二行里facenet-pytorch直接提供了 MTCNN 和 InceptionResnetV1 的预训练权重省得自己找。注意别用pip install mtcnn那个包和facenet-pytorch里的 MTCNN 接口不一样混用会报张量维度错误。2.3 人脸对齐被大多数人跳过但影响识别率的一步MTCNN 返回的不只是框还有五个人脸关键点左右眼、鼻尖、左右嘴角。ArcFace 训练时输入的是对齐后的人脸如果你直接把检测框裁出来送进识别网络识别率会掉 5 到 10 个百分点。对齐的逻辑是用五个关键点做相似变换把眼睛和嘴角摆到固定位置。import cv2 import numpy as np def align_face(img, landmarks): # ArcFace 标准对齐后的五点目标位置112x112 输入 dst np.array([ [38.2946, 51.6963], # 左眼 [73.5318, 51.5014], # 右眼 [56.0252, 71.7366], # 鼻尖 [41.5493, 92.3655], # 左嘴角 [70.7299, 92.2041], # 右嘴角 ], dtypenp.float32) src np.array(landmarks, dtypenp.float32) # estimateAffinePartial2D 只做旋转缩放平移不引入透视形变 M, _ cv2.estimateAffinePartial2D(src, dst) aligned cv2.warpAffine(img, M, (112, 112), borderValue0.0) return alignedestimateAffinePartial2D比estimateAffine2D更稳因为它不允许剪切和透视人脸对齐不需要这两个自由度。borderValue0.0保证旋转后露出的边缘是黑色和训练时的填充一致。目标五点坐标是 ArcFace 论文里的标准值不要自己改改了就和预训练权重不匹配。对齐后统一成 112x112这是 ArcFace 的输入尺寸送 128x128 或 96x96 都会让识别率下降。3. 从零跑通考勤流程注册、训练、识别三步走3.1 人脸注册每人采多少张、怎么存注册就是把员工的脸变成特征向量存进数据库。采集数量上我一般让每人采 10 到 20 张覆盖正面、左右侧脸各 15 度、戴眼镜和不戴眼镜。少于 5 张ArcFace 提取的特征均值不稳定多于 30 张边际收益很低还拖慢注册速度。import os import cv2 import numpy as np import torch from facenet_pytorch import MTCNN, InceptionResnetV1 device torch.device(cuda if torch.cuda.is_available() else cpu) mtcnn MTCNN(keep_allTrue, devicedevice) # 加载 ArcFace 风格的预训练权重facenet-pytorch 提供 vggface2 版本 resnet InceptionResnetV1(pretrainedvggface2).eval().to(device) def enroll(emp_id, img_dir, save_pathdata/embeddings.npz): embeddings [] for fname in os.listdir(img_dir): img cv2.imread(os.path.join(img_dir, fname)) img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) boxes, probs, landmarks mtcnn.detect(img_rgb, landmarksTrue) if boxes is None: continue # 只取最大的一张脸注册场景不应出现多人 idx np.argmax([(b[2]-b[0])*(b[3]-b[1]) for b in boxes]) aligned align_face(img_rgb, landmarks[idx]) # 归一化到 [-1, 1]与预训练权重的训练预处理一致 tensor torch.tensor(aligned).permute(2, 0, 1).float() tensor (tensor - 127.5) / 128.0 tensor tensor.unsqueeze(0).to(device) with torch.no_grad(): emb resnet(tensor).cpu().numpy().flatten() embeddings.append(emb) if not embeddings: raise ValueError(f{emp_id} 没有可用人脸) mean_emb np.mean(embeddings, axis0) # L2 归一化后续用余弦相似度比对 mean_emb mean_emb / np.linalg.norm(mean_emb) np.savez(save_path, **{emp_id: mean_emb}) return mean_emb关键点在预处理(tensor - 127.5) / 128.0把像素从 0-255 映射到 -1 到 1这是facenet-pytorch预训练权重的标准输入范围。如果你用 0-1 归一化特征向量会整体偏移余弦相似度还能用但阈值全错。keep_allTrue让 MTCNN 返回所有脸注册时取最大的一张。存的时候用np.savez追加模式要注意np.savez会覆盖同名文件多人注册应该先读出来合并再写或者每人存一个.npy文件。3.2 识别与比对阈值到底设多少识别阶段把摄像头帧里的人脸对齐、提特征然后和库里所有注册特征算余弦相似度取最高分。分数超过阈值就判定为该员工否则标记为陌生人。def recognize(img_bgr, db, threshold0.55): img_rgb cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) boxes, probs, landmarks mtcnn.detect(img_rgb, landmarksTrue) results [] if boxes is None: return results for box, lm in zip(boxes, landmarks): aligned align_face(img_rgb, lm) tensor torch.tensor(aligned).permute(2, 0, 1).float() tensor (tensor - 127.5) / 128.0 tensor tensor.unsqueeze(0).to(device) with torch.no_grad(): emb resnet(tensor).cpu().numpy().flatten() emb emb / np.linalg.norm(emb) best_id, best_score None, -1.0 for emp_id, ref in db.items(): score float(np.dot(emb, ref)) if score best_score: best_id, best_score emp_id, score if best_score threshold: results.append((best_id, best_score, box)) else: results.append((unknown, best_score, box)) return results阈值 0.55 是 vggface2 预训练权重下的经验值。低于 0.5 陌生人容易误识别成员工高于 0.65 员工本人经常被拒。实际部署时应该用一批已知身份的测试图跑 ROC 曲线找等错误率点。注意余弦相似度对特征模长不敏感所以两边都做了 L2 归一化np.dot等价于余弦相似度。如果库里人多逐个算 dot 会慢超过 1000 人建议上 FAISS 做近邻检索。3.3 考勤业务逻辑打卡记录怎么写识别出工号只是第一步考勤系统还要判断这次识别算不算一次有效打卡。常见规则是同一人 60 秒内重复识别只记一次上班打卡取当天最早时间下班打卡取最晚时间。import sqlite3 from datetime import datetime, timedelta def init_db(pathattendance.db): conn sqlite3.connect(path) conn.execute(CREATE TABLE IF NOT EXISTS records ( emp_id TEXT, ts TEXT, direction TEXT, PRIMARY KEY (emp_id, ts))) return conn def mark_attendance(conn, emp_id, directionin, cooldown60): now datetime.now() cur conn.execute( SELECT ts FROM records WHERE emp_id? ORDER BY ts DESC LIMIT 1, (emp_id,)) row cur.fetchone() if row: last datetime.fromisoformat(row[0]) if now - last timedelta(secondscooldown): return False # 冷却期内忽略 conn.execute(INSERT OR REPLACE INTO records VALUES (?,?,?), (emp_id, now.isoformat(), direction)) conn.commit() return TruePRIMARY KEY (emp_id, ts)防止同一秒写入两条。冷却时间 60 秒是经验值太短会让人在摄像头前晃一下就多次打卡太长会让员工以为没打上又刷一次。方向in和out的判定可以按时间段比如中午 12 点前算上班、之后算下班更复杂的排班规则建议单独建表。4. 避坑与排查考勤系统上线前必须过的五道坎4.1 现象识别率白天正常晚上集体翻车原因摄像头红外补光开启后画面偏灰MTCNN 对低对比度人脸检测框偏移对齐关键点跟着偏特征向量整体漂移。解决在注册阶段就采集一部分夜间或低照度照片或者对输入做自适应直方图均衡化CLAHE再送检测。别指望识别模型自己扛光照检测环节一偏后面全废。4.2 现象同一个人换副眼镜就识别成陌生人原因注册时只采了戴眼镜或不戴眼镜的一种ArcFace 特征对眼镜框这种高频边缘敏感。解决注册时强制采集戴和不戴两种状态各 5 张取特征均值。如果已经上线让该员工重新注册别去调低全局阈值那会引入更多误识。4.3 现象GPU 显存够但推理速度只有 3 FPS原因MTCNN 默认对每帧做图像金字塔考勤机分辨率 1080P 时金字塔层数多P-Net 计算量大。解决把摄像头采集分辨率降到 640x480或者设置mtcnn MTCNN(min_face_size60)小于 60 像素的脸不检测。考勤场景人脸离摄像头近不需要检测远处小脸。4.4 现象数据库里特征向量越存越多识别越来越慢原因每次注册都往npz里追加旧特征没清理离职员工特征还在库里参与比对。解决建一个employees表记录在职状态识别前先过滤掉离职员工的特征。特征库超过 500 人时改用 FAISS 的IndexFlatIP内积检索比 Python 循环快两个数量级。4.5 现象ArcFace 训练 loss 不下降准确率卡在 10%原因ArcFace 的s和m参数没设对。s是缩放因子通常 64m是角度间隔通常 0.5。如果s设成 30logits 太小softmax 梯度消失。解决检查配置文件里s64.0, m0.50并且确认输入特征和权重都做了 L2 归一化。另外类别数要等于实际员工数多一个少一个都会让分类头错位。5. 把 demo 变成能交付的系统活体检测与批量注册的工程技巧跑通识别不等于能交付。员工拿手机里的一张照片对着摄像头系统就给他打卡这种考勤系统上线第一天就会被行政骂。活体检测是绕不过去的。轻量方案是动作配合屏幕上随机提示「眨眼」或「转头」用 MTCNN 关键点判断眼睛纵横比或鼻尖相对位置变化。这个方案不需要额外模型但用户体验一般员工会嫌麻烦。更顺滑的是静默活体用一个小卷积网络判断输入是真人还是屏幕翻拍但需要采集正负样本训练成本高。我一般建议课程设计用动作配合真实项目上静默活体。批量注册也有技巧。如果公司有几百人一个个拍照不现实。常见做法是让 HR 提供员工工牌照但工牌照背景复杂、光照统一直接送 MTCNN 可能检测不到。我一般会先用一个通用人脸检测模型把工牌照里的脸裁出来人工过一遍再走注册流程。裁出来的脸如果分辨率低于 112x112先做超分或者直接放弃低分辨率注册进去就是噪声。验证方法上别只看训练集准确率。准备一个测试集每人 3 到 5 张不在注册集里的照片算 Rank-1 准确率和误识率。Rank-1 低于 95% 就别上线误识率高于 1% 就调阈值。测试集要包含戴眼镜、侧脸、不同光照否则数字好看但没用。最后说个习惯每次改完阈值或预处理把识别结果和分数写日志跑一周看分布。我吃过亏有次把归一化从 -1 到 1 改成 0 到 1离线测试没发现上线后所有人相似度都集中在 0.4 到 0.5阈值 0.55 把员工全拒了。日志里分数分布一眼就能看出问题。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑