尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于CNN与预训练模型的人脸识别考勤系统实践指南

基于CNN与预训练模型的人脸识别考勤系统实践指南 简介一份基于CNN的人脸识别考勤系统资源包面向深度学习初学者、计算机视觉开发者和需要快速搭建人脸考勤原型的工程师。资源围绕卷积神经网络在人脸识别中的应用展开包含完整的预训练模型与可直接运行的代码脚本可帮助读者理解从图像采集、预处理、特征提取到人脸匹配与考勤记录的全流程。压缩包共4848个文件其中4835张jpg人脸样本用于训练与测试另含8个py脚本、1个h5预训练模型face.model.h5、1个UI界面文件、1个XML配置文件及说明文档整体大小108.06MB结构清晰便于对照学习。已有306人学习下载。通过该资源读者可获得开箱即用的考勤系统原型、预训练模型微调思路以及真实数据集处理经验适合作为课程设计、毕业设计或企业考勤方案的技术参考。1. 基于 CNN 的人脸识别考勤系统能开箱即用靠的不是 CNN 是工程一个“包含预训练模型可以直接运行”的人脸识别考勤系统最容易踩的坑不是模型不准而是误以为人脸识别就是把图片丢给 CNN 然后输出一个名字。实际上整套系统由人脸检测、对齐、特征嵌入、特征比对、考勤记录五个独立环节组成预训练模型只覆盖中间的“特征嵌入”这一步。标题里强调“预训练模型”和“直接运行”说明你已经筛选掉了最耗时的训练环节真正的工作量在工程层面把摄像头帧处理、员工特征库、打卡规则串成一条可靠流水线。下面要做的就是把这套链条拆开讲清每一步的参数、代码和坑。2. CNN 人脸识别原理与预训练模型选型考勤场景为什么不需要自己训练 CNN2.1 人脸识别流程拆解从人脸检测到相似度比对人脸识别考勤系统的核心链路是人脸检测、人脸对齐、特征嵌入、相似度比对。其中人脸检测从摄像头帧中找出人脸框和关键点常用 MTCNN、RetinaFace 或 OpenCV DNN 自带的人脸检测器人脸对齐根据眼睛、嘴角等关键点做仿射变换把倾斜、远近不一的人脸矫正到统一形态特征嵌入把对齐后的人脸图缩放到预训练模型要求的尺寸例如 112×112前向传播得到一个特征向量相似度比对再把当前人脸向量和员工特征库里的向量计算余弦相似度或欧氏距离超过阈值的就认为是同一个人。这个链路中预训练 CNN 只负责第 3 步。很多人误以为需要自己收集几千张员工头像训练模型实际上针对考勤这种封闭环境借助大规模人脸数据训练好的预训练模型再用员工注册照片做小样本比对效果远好于自己从零训练。因为一个 CNN 模型的可鉴别性主要由早期卷积层和深层语义特征决定这些通用特征在预训练模型中已经学出来自己重头训练既缺数据也缺算力。考勤场景还有一个和通用人脸识别不同的点注册员工是主动配合的可以要求拍正脸不用处理极端姿态但考勤打卡时员工可能边走边看手机姿态和光照都不可控。因此系统的可靠性短板往往不在 CNN 特征提取而在前面的人脸检测和对齐。如果检测框偏了十来个像素对齐后人脸的五官位置会有明显偏移嵌入特征也会跟着偏移。常见做法是把连续 3 帧的检测框取中值再做对齐和特征提取误识别明显减少。2.1.1 为什么是“预训练模型”而不是重新训练如果你用 ResNet 之类的 CNN 结构从头训练人脸识别至少要准备几十万张标注人脸否则模型的泛化能力会很差换一个摄像头或光线环境就掉点。预训练模型相当于已经把“人脸长什么样”的通用表征固化在权重里。FaceNet、ArcFace、MobileFaceNet 都是常见选择其中 MobileFaceNet 尤其适合考勤闸机这种对延迟敏感的场景。import cv2 import numpy as np # 用 OpenCV DNN 加载 ONNX 格式的预训练人脸识别模型 embedding_net cv2.dnn.readNetFromONNX(models/mobilefacenet.onnx) def get_embedding(face_img): # face_img 是已经检测并对齐好的人脸尺寸为 112x112 blob cv2.dnn.blobFromImage(face_img, 1.0 / 127.5, (112, 112), (127.5, 127.5, 127.5), swapRBTrue) embedding_net.setInput(blob) emb embedding_net.forward().flatten() return emb / np.linalg.norm(emb)这段代码把 OpenCV DNN 作为推理引擎输入一张对齐后的人脸输出 512 维MobileFaceNet 通常是 512 维归一化特征向量。blobFromImage的scale和mean参数必须与预训练模型训练时的预处理保持一致常见是1/127.5配合mean127.5把像素值归一化到[-1, 1]。这一步错一个值特征分布就漂移后续阈值也会失效。2.2 预训练模型选型ResNet 结构与轻量模型的取舍考勤系统通常部署在办公区入口的工控机上不一定有独立显卡CPU 推理是常态。这时模型参数量和单次推理耗时直接决定了摄像头能同时追踪多少人。下面是几类常见 CNN 人脸识别模型在考勤场景下的参考对比模型特征维度参数量级CPU 单帧推理耗时适用场景FaceNet (Inception-ResNet)128约 23M80-150ms对精度要求高机器性能够强ArcFace (ResNet50)512约 44M150-300ms离线批量人脸比对低并发MobileFaceNet128/256/512约 2M15-40ms考勤机、门禁机、实时摄像头OpenFace (NN4)128约 4M30-60ms快速原型简单场景这里想提醒的是特征维度和准确率不是线性关系。MobileFaceNet 的 512 维特征在 LFW 上也能达到很高的精度但它的优势是参数量小一个数量级更容易在无 GPU 的 Windows 工控机上跑出较高帧率。标题里强调“直接运行”意味着你应该优先选导出成 ONNX 就能被 OpenCV 或 ONNX Runtime 直接加载的模型格式避免引入 PyTorch 重依赖。如果要商用优先选择开源且可免费商用的人脸识别模型例如以宽松许可证发布的 MobileFaceNet 权重避免使用仅限学术研究的权重这一点直接决定系统能不能交付给客户。2.2.1 图像增强 CNN 和预处理兜底考勤摄像头经常遇到逆光、过曝、夜间红外补光。图像增强 CNN 算法如 Retinex-Net 这类轻量增强网络可以把暗光细节提出来但这类网络本身也会吃掉几十毫秒推理时间。我的建议是先用传统图像增强方法做兜底比如直方图均衡化或 CLAHE只有在固定点位的光照确实很差时才在识别前插入一个增强 CNN并用独立线程跑避免拖慢主流程。def enhance_face(face_bgr): lab cv2.cvtColor(face_bgr, cv2.COLOR_BGR2LAB) l, a, b cv2.split(lab) clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) l clahe.apply(l) enhanced cv2.cvtColor(cv2.merge([l, a, b]), cv2.COLOR_LAB2BGR) return enhanced这段代码用 CLAHE 对 LAB 空间的 L 通道做对比度限制的自适应直方图均衡化。clipLimit2.0表示限制对比度放大倍数太大会放大噪声太小人脸细节不突出。增强操作只作用于检测出的人脸区域而不是整帧否则高分辨率摄像头会带来不小的开销。2.3 预训练模型权重文件怎么放进工程一个“直接运行”的系统工程目录里必须让模型文件位置固定、配置化。我见过太多把模型权重放在临时目录最后因为路径找不到而启动失败的情况。常见做法是建立一个models/目录在配置文件中指定模型文件路径并在启动时检查文件是否存在。import cv2 from pathlib import Path MODEL_PATH Path(models/mobilefacenet.onnx) if not MODEL_PATH.exists(): raise FileNotFoundError( f预训练模型 {MODEL_PATH} 不存在请将权重文件放在该目录 ) embedding_net cv2.dnn.readNetFromONNX(str(MODEL_PATH))检查路径这个动作在开箱即用场景里很关键。用户拿到代码后最容易遇到的问题就是“我下载了模型但没放到对应目录”所以启动时提前报错比运行时才崩溃友好得多。模型文件版本也要固定不要同一个文件名混用不同迭代次数导出的权重否则特征空间不一致注册过的员工可能全部无法识别。3. 用 Python OpenCV 预训练模型搭出可直接运行的 CNN 人脸识别考勤系统3.1 最小目录结构与启动命令一个能直接运行的人脸识别考勤系统目录结构应当足够克制让使用者不需要读代码就知道放了什么attendance/ ├── config.yaml ├── main.py ├── requirements.txt ├── models/ │ ├── detect.onnx │ └── mobilefacenet.onnx ├── employees.db ├── register.py ├── core/ │ ├── __init__.py │ ├── detector.py │ └── encoder.py └── capture/ └── snapshots/依赖文件requirements.txt保持精简Python 3.9 下我一般只需要三件套opencv-python numpy pyyaml如果你的模型是 PyTorch 权重则还需要torch和对应模型实现库这就违背了“直接运行”的初衷。所以我更倾向于选用导出了 ONNX 的预训练模型用 OpenCV DNN 做推理用户的机器上不需要安装深度学习框架。启动命令也很简单# 第一次运行先初始化数据库并注册员工 python register.py --name 张三 --photos ./photos/zhangsan/ # 开启摄像头进行考勤识别 python main.py --config config.yamlregister.py接收员工姓名和照片目录从照片中检测并提取人脸特征写入employees.dbmain.py启动摄像头实时识别识别成功写入考勤记录。config.yaml里最关键的几个参数如下参数示例值说明model_pathmodels/mobilefacenet.onnx人脸特征模型路径detector_pathmodels/detect.onnx人脸检测模型路径threshold0.55余弦相似度阈值checkin_window60打卡去重窗口秒数camera_width640摄像头采集宽度3.2 员工注册采集多张照片写入特征库注册环节的质量直接决定考勤准确率。一个人只拍一张照片遇到第一次戴眼镜、刘海变化就容易识别失败。我通常要求每位员工提供 3 到 5 张照片覆盖正脸、左右 15 度、戴不戴眼镜的状态提取特征后取平均得到这个人的中心特征。注册脚本核心逻辑如下import sqlite3 import numpy as np import cv2 from pathlib import Path def register_employee(name, photo_dir, db_pathemployees.db): conn sqlite3.connect(db_path) conn.execute( CREATE TABLE IF NOT EXISTS employees (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, embedding BLOB) ) embeddings [] for photo_path in Path(photo_dir).glob(*.jpg): img cv2.imread(str(photo_path)) face, _ detect_and_align(img) if face is not None: embeddings.append(extract_embedding(face)) center_emb np.mean(np.array(embeddings), axis0) center_emb center_emb / np.linalg.norm(center_emb) conn.execute( INSERT INTO employees (name, embedding) VALUES (?, ?), (name, center_emb.astype(np.float32).tobytes()) ) conn.commit() conn.close()detect_and_align是封装好的“人脸检测 对齐”函数返回 112×112 的 RGB 人脸图extract_embedding从预训练 CNN 提取归一化特征。将多张照片的特征取平均并再次归一化能有效降低单张照片姿态和光照带来的偏差。注意不要为了凑数量而重复拍同一角度那样只会让平均值更偏向一个方向。3.3 考勤打卡实时识别与 SQLite 写入实时识别主循环的逻辑是每隔一定帧数取一帧检测人脸对置信度足够的人脸提取特征再与员工库全部特征比对找到余弦相似度最高且超过阈值的员工检查此人今日是否已打卡未打卡则写入考勤表。import cv2 import sqlite3 import numpy as np from datetime import datetime def attendance_loop(db_pathemployees.db): conn sqlite3.connect(db_path) cap cv2.VideoCapture(0) if not cap.isOpened(): raise RuntimeError(无法打开摄像头) employees load_employees(conn) while True: ret, frame cap.read() if not ret: continue face detect_largest_face(frame) if face is None: continue emb extract_embedding(face) best_id, best_score None, -1.0 for emp_id, name, emp_emb in employees: score float(np.dot(emb, emp_emb)) if score best_score: best_id, best_score emp_id, score if best_score 0.55: now datetime.now().strftime(%Y-%m-%d %H:%M:%S) conn.execute( INSERT OR IGNORE INTO attendance (employee_id, check_time) VALUES (?, ?), (best_id, now) ) conn.commit()这里“只处理画面中最大的人脸”是考勤场景下的合理简化。如果需要多人同时打卡就不能只取最大人脸而是要遍历所有检测到的人脸用线程池并行提取特征。INSERT OR IGNORE配合attendance表上的唯一索引可以达到一天只记录第一次打卡的效果。注意INSERT OR IGNORE需要attendance表上建立(employee_id, date(check_time))的唯一索引否则同名同秒的重复写入不会被真正拦截。3.3.1 为什么用余弦相似度而不是欧氏距离只要特征向量都归一化到单位长度余弦相似度和欧氏距离在数学上是等价的。余弦相似度越接近 1 越相似欧氏距离越小越相似。使用余弦相似度时阈值通常落在 0.5 到 0.7 之间具体数值不同模型差异很大。下面这个表可以让你直观感受阈值调整的趋势阈值通过率误识风险漏识风险0.45高高不同员工可能混淆低0.55中高中适合多数考勤场景中低0.65中低低高员工状态变化时易漏这个表描述的是变化趋势具体数值必须基于你的注册照片和摄像头实测后重新标定。4. CNN 人脸识别考勤系统的 3 个必调参数阈值、打卡去重和可视化调试4.1 阈值不是拍脑袋出来的用注册库自检阈值是 CNN 人脸识别考勤系统里最敏感的参数。常见做法是在注册完员工后用员工照片自检把每个员工的照片分别和库里所有员工特征进行比对统计“本人比对”和“他人比对”两类分数分布从中挑一个能让两类分布分离最远的阈值。如果两类分布重叠严重说明注册照片质量差或模型不合适这时候调阈值只是掩盖问题。def find_threshold(employees, photos_by_employee): pos_scores, neg_scores [], [] for name, photos in photos_by_employee.items(): for photo in photos: emb extract_embedding(photo) for emp_id, emp_name, emp_emb in employees: score float(np.dot(emb, emp_emb)) if emp_name name: pos_scores.append(score) else: neg_scores.append(score) # 取正样本最低分与负样本最高分的中间点作为初始阈值 lower_bound max(neg_scores) upper_bound min(pos_scores) return (lower_bound upper_bound) / 2这个函数暴力计算所有组合的相似度然后取一个粗略分界点。如果返回值接近 1.0 或 0.0说明库内不同员工的特征非常相似需要回到注册流程补拍照片而不是继续调阈值。需要注意的是这个函数没有考虑未注册人员实际部署时还要单独设一个拒绝阈值用来拦截不在员工库里的陌生人脸。4.2 打卡去重时间窗口和唯一索引双保险考勤需求往往不是“识别到就记录”而是“一个时间段内只记录第一次”。实现上除了在数据库层加唯一索引还要在应用层加滑动时间窗口。因为摄像头每秒 25 帧同一人连续几十帧都会被识别为同一种结果数据库唯一索引能拦住同一天的重复记录但跨天时时间窗口可以避免切换日期的瞬间误写。last_check_time {} def can_checkin(employee_id, window_seconds60): now time.time() if now - last_check_time.get(employee_id, 0) window_seconds: return False last_check_time[employee_id] now return Truewindow_seconds60表示 60 秒内同一员工只能打一次卡。这个值通常配合考勤规则如果做门禁放行要求几秒内不重复如果做考勤统计可以放宽到 5 分钟甚至 1 小时。双保险的另一个业务价值是应用层拦截能省掉大量无意义的数据库写入数据库唯一索引则能防止并发情况下穿越时间窗口的重复记录。4.3 摄像头帧预处理识别不到人脸先从图像增强排查当系统在光线差的环境中频繁漏检时不要急着换 CNN 模型先检查人脸检测器有没有在暗光下工作。常见的排查顺序是先保存原始帧和检测框叠加图确认检测器输出再对检测框区域做 CLAHE 或灰度归一化送入识别模型如果检测器本身漏检则考虑对整帧做图像增强再重新检测。def preprocess_frame(frame): # 检测前对整帧做直方图均衡化 rgb cv2.cvtColor(frame, cv2.COLOR_BGR2LAB) l, a, b cv2.split(rgb) l cv2.equalizeHist(l) enhanced cv2.cvtColor(cv2.merge([l, a, b]), cv2.COLOR_LAB2BGR) return enhanced这段和之前 CLAHE 的区别是这里是整帧处理用于改善检测器输入之前是只增强人脸区域用于改善识别模型输入。要根据瓶颈位置选择处理对象否则既浪费算力又达不到效果。图像增强 CNN 算法虽然可以更精细地恢复亮度纹理但在 CPU 工控机上会挤压识别模型本身的推理资源务必先用传统方法试探。另一个常被忽视的设置是摄像头分辨率。很多摄像头默认输出 1080p但人脸检测在 720p 甚至 480p 下已经足够把采集宽度调到 640 可以显著提高帧率。只有需要看清屏幕上的人脸细节时才用 720p考勤打卡用 640×480 完全没有问题。5. 用 ROC 曲线重标定 CNN 人脸识别阈值从“能跑”到“可交付”系统能跑不代表能可靠跑。要判断当前预训练模型在你的摄像头和员工数据下到底该用什么阈值最可靠的方式是采集一批真实考勤帧做好标注绘制 ROC 曲线。具体做法准备 100 张员工脸图作为正样本100 张非员工脸图作为负样本分别提取特征和员工库中对应员工比对得到两组相似度分布。然后以阈值从高到低滑动计算真正例率和假正例率画出一条曲线。import numpy as np def compute_roc(pos_scores, neg_scores, thresholds): tp np.mean(pos_scores[:, None] thresholds[None, :], axis0) fp np.mean(neg_scores[:, None] thresholds[None, :], axis0) return tp, fp thresholds np.linspace(0.3, 0.9, 100) tp, fp compute_roc(pos_scores, neg_scores, thresholds) eer_index np.argmin(np.abs(1 - tp - fp)) eer_threshold thresholds[eer_index]上面这段用向量化运算一次性得到所有阈值下的真正例率和假正例率。eer_index选出的阈值是等错误率点适合作为考勤系统的初始阈值。实际部署时我会在 EER 基础上再往严格方向偏 0.02 到 0.05因为漏打卡可以人工补误打卡的信任成本更高。画出 ROC 曲线后还需要观察 AUC。AUC 低于 0.95说明注册照片和摄像头帧分布差异太大比如员工用手机照片注册摄像头输出红外图这时候优先做风格统一而不是继续调阈值。曲线越贴近左上角说明预训练模型在你的数据上表现越好。这一步做完一个“包含预训练模型可以直接运行”的 CNN 人脸识别考勤系统才真正有了可交付的底气。本文还有配套的精品资源点击获取
返回列表