
简介这是一套面向Python开发者与计算机视觉初学者的人脸考勤系统实战源码聚焦YOLO算法在实时人脸检测与考勤管理中的工程化落地解决传统打卡方式效率低、易代刷等痛点适用于校园、企业等轻量级智能考勤场景。资源共105个文件含15个核心Python脚本含人脸检测、眨眼活体验证、FaceNet特征比对等模块、26个PNG与16个JPG素材图界面展示与测试样例、7个XML配置文件、5个xlsx考勤表、4个Qt UI设计文件及3个pickle模型序列化文件压缩包大小167.37MB结构清晰涵盖model_face_detection、model_blink_detection、model_facenet和utils等关键功能目录。已有135人学习下载。读者可直接运行Jupyter Notebook含Development Logs等交互式开发日志复现从图像采集、YOLOv3/v5人脸定位、Landmark关键点校验、活体检测到考勤记录自动写入Excel的完整流程并基于caffemodel、shape_predictor_68_face_landmarks.dat等预训练模型快速调试部署。1. 做之前先想清楚人脸考勤系统到底要解决什么问题我在刚接触“基于Yolo的人脸考勤系统”这个题目时第一反应也是去搜源码、跑通Demo。但真正动手做之后才发现考勤场景里的难点根本不在“能不能检测到人脸”而在于一连串更实际的问题同一个员工站在摄像头前几秒钟不能刷出几十条打卡记录下午光线一变人脸就识别不出来了员工注册时只有一张模糊的证件照系统也得认得出来。所以这篇文章我不会只丢给你一个“能跑的源码”而是把这套系统从需求拆解、模型选型、识别链路、业务逻辑到工程落地的完整设计过程讲清楚。你照着这个思路去搭无论是交作业、做毕业设计还是真的想在公司内部署一套考勤工具都能直接落地。1.1 考勤场景里的隐藏需求表面上看人脸考勤就是“摄像头拍到人 → 认出是谁 → 记录时间”。但剖开考勤业务本身它至少包含四个隐性要求准确率要足够高公司几十上百号人如果频繁把人认错考勤数据就没有公信力后续算工资、算绩效全是麻烦。不能重复打卡员工站在摄像头前等电梯系统不能每帧都记录一次。必须要有“时间窗口去重”机制同一人在五分钟内只算一次有效打卡。要能追溯每次打卡最好保存一张现场抓拍照片万一有人投诉“我没打过卡”管理员能调出证据。识别速度要跟上早晚高峰多人同时经过摄像头从检测到识别出结果最好控制在1秒以内否则门口会堵人。这些需求决定了系统的整体架构而不是单纯训练一个模型就能交差。你往下看就会明白Yolo在这套系统里只承担“定位人脸”的任务真正决定考勤能不能用的是检测之后的识别链路和业务逻辑。1.2 Yolo在系统里的真实位置很多初学者有一个误区以为Yolo模型可以直接输出“这是张三”或“这是李四”。这是把目标检测和图像分类混为一谈了。Yolo这类目标检测算法核心输出是目标类别 边界框坐标 置信度。你拿Yolo做车牌识别、做工业缺陷检测本质上都是在做“定位分类”。但人脸考勤有一个特殊性人脸分类的类别是动态变化的。今天公司入职一个新员工模型不可能为了新增一个人就重新训练一次。所以常规做法是拆成两步Yolo负责从整帧画面中找到“人脸”这个目标给出坐标框。把裁剪出的人脸图送入人脸识别网络如InsightFace、FaceNet提取一个512维的特征向量再和数据库中已注册员工的特征向量做相似度比对。这个“检测 识别”的拆分是整个人脸考勤系统最核心的架构决策。它的好处显而易见新增员工不用重新训练模型只需要提取一次特征存入数据库。检测模型和识别模型可以独立升级。比如觉得检测不够快可以只换检测模型识别准确率不够可以只换识别模型。Yolo本身可以复用同一套检测能力去处理其他任务比如同时检测人脸和人体为以后做“人证比对”或“人员轨迹”预留扩展空间。1.3 整体技术选型一览在动手写代码之前我先把这套系统的技术栈列出来后面所有章节都会围绕它展开模块方案说明人脸检测YOLOv8n-faceUltralytics提供的轻量人脸检测权重约6MBCPU也能跑实时人脸对齐OpenCV仿射变换根据五个关键点把脸部矫正到标准姿态提升识别鲁棒性特征提取InsightFaceArcFace输出512维归一化特征向量开源社区使用最广的识别方案之一特征比对余弦相似度计算两个向量的相似度设定阈值判断是否为同一人数据库SQLite小规模考勤足够用零配置换MySQL也只是改连接串的事界面PyQt5 或 Web 前端桌面端部署简单Web端便于多人同时查看推理加速ONNX Runtime导出ONNX模型后CPU推理速度明显提升这个选型清单不是唯一的答案但它是我实测下来“性价比最高”的组合全部使用开源组件不需要额外付费对硬件要求也不高——我用一台不带独立显卡的笔记本跑帧率依然能维持在15~20FPS对于一个考勤场景来说完全够用。2. Yolo人脸检测落地用哪个权重、要不要自己训练2.1 首选直接可用的预训练模型很多人会默认去训练一个自己的Yolo模型但说实话如果你只是做考勤系统第一选择应该是直接用社区成熟的预训练权重。我对你最常见的三个选择做一个对比权重来源适用场景实际效果yolov8n-face.ptUltralytics基于WIDER FACE训练通用人脸检测识别率高CPU速度快首选yolov8n.ptCOCO预训练通用模型人、车、物体检测检测人脸效果一般不推荐直接用自训练权重自己标注数据训练特定场景俯拍、密集人群等可控性高但需要数据和时间成本这里要解释一下为什么通用权重不适合COCO数据集中虽然有“person”这个类但标注的往往是整个人体人脸在画面里通常很小Yolo在COCO上学到的人脸特征非常有限。而WIDER FACE是人脸检测领域最经典的公开数据集包含3万多张图像、39万个人脸标注框专门用来训练人脸检测模型。所以yolov8n-face.pt直接用就行这是“站在前人肩膀上”的最优解。模型加载和推理代码非常简单from ultralytics import YOLO # 加载轻量级人脸检测模型 model YOLO(yolov8n-face.pt) # 对单帧图像推理conf是置信度阈值iou是NMS阈值 results model(frame, conf0.5, iou0.45, verboseFalse) # 解析检测结果 for box in results[0].boxes: x1, y1, x2, y2 box.xyxy[0].tolist() conf box.conf[0].item() # 这里就拿到了人脸框的坐标和置信度框拿到之后裁出来、传给下一步的识别模块。这一条链路就是整个系统的地基。2.2 什么时候才需要自己训练人脸检测模型虽然预训练权重很好用但也不是万能的。我实测下来如果遇到下面几种情况你就要认真考虑自己准备数据做微调了摄像头安装角度特殊比如考勤机安装在门禁上方俯拍角度很大WIDER FACE里大量正脸样本的泛化能力就不够了会出现漏检。人脸在画面中占比极小比如教室后排的考勤整张画面里人脸可能只有20×20像素Yolo默认的输入尺寸640×640和模型下采样倍数决定了它对小目标的检测能力有限。这种情况可以自己训练或者提高输入分辨率。固定场景非常特殊例如工厂车间里所有人戴安全帽人脸下半部分还被口罩遮挡预训练模型可能频繁误检/漏检。但对我来说90%以上的考勤场景预训练模型加上合理的工程处理就已经足够了。自训练是一个“听起来很有必要、做起来成本很高”的选项不是第一优先级。2.3 如果真要训练数据集和标注怎么准备假设你确实处于特殊场景决定自己训练我给你一条跑通的最小路径下载公开数据集首选WIDER FACE包含各种角度、光照、遮挡条件的人脸。也可以用FDDB、Mall Dataset等补充特定场景数据。转换成Yolo格式Yolo标注格式是“类别id、中心点x、中心点y、宽度w、高度h”坐标都是归一化到0~1之间。WIDER FACE官方提供的是ground truth文本文件需要写个小脚本转换。划分训练集和验证集建议8:2或者9:1且保证验证集里包含和你实际场景接近的照片。修改训练配置新建一个face.yaml文件指定训练集和验证集的路径类别只有face一类path: ./datasets/face train: images/train val: images/val names: 0: face启动训练yolo detect train dataface.yaml modelyolov8n.pt epochs50 imgsz640 batch16训练完成后用best.pt替换原来的yolov8n-face.pt即可。这里有一个经验值epochs不是越多越好我在小数据集上训练时发现30~50轮之后val loss就开始反弹提前终止early stopping能帮你节省大量时间。3. 检测到人脸不等于认出是谁识别链路怎么接3.1 人脸对齐最容易被忽略却最影响准确率的一环把Yolo检测到的人脸框直接丢进识别网络你会发现准确率飘忽不定。原因在于人脸识别网络对输入图像的姿态和五官位置有较强的先验要求例如InsightFace的标准输入是112×112的图像并且五官在图像中的位置基本固定。如果直接裁剪原图缩放侧脸、俯仰、旋转都会让特征质量大幅下降。所以在送入识别网络之前需要做人脸对齐。标准流程是用人脸关键点检测模型InsightFace内置了106点/5点关键点模型找到双眼、鼻尖、嘴角等位置。计算当前关键点与标准模板位置的仿射变换矩阵。用OpenCV的cv2.warpAffine把整张人脸矫正到标准姿态。核心代码大致是这样import cv2 import numpy as np # 五个关键点的标准位置InsightFace buffalo_l 的标准模板 TEMPLATE np.array([ [38.2946, 51.6963], [73.5318, 51.5014], [56.0252, 71.7366], [41.5493, 92.3655], [70.7299, 92.2041] ], dtypenp.float32) def align_face(img, landmarks): # landmarks 是检测到的五个关键点坐标 M cv2.estimateAffinePartial2D(landmarks, TEMPLATE)[0] aligned cv2.warpAffine(img, M, (112, 112)) return aligned这一步做完人脸就变成了“正面、居中、眼睛在标准高度”的112×112标准图。后面无论是提取特征还是比对待识别都稳定很多。3.2 特征提取模型与向量比对逻辑人脸对齐之后就到了整个系统最关键的一步提取特征向量。我推荐使用InsightFace提供的ArcFace模型它训练出的特征向量在同一人不同姿态下距离很近不同人特征距离很远这是现在开源社区普遍认可的最高精度方案之一。from insightface.app import FaceAnalysis app FaceAnalysis(namebuffalo_l) app.prepare(ctx_id0) # 有GPU填0CPU填-1 # 对对齐后的人脸提取特征 faces app.get(aligned_face) if len(faces) 0: embedding faces[0].normed_embedding # 512维归一化向量拿到向量之后身份判定的核心就是计算余弦相似度def cosine_similarity(vec_a, vec_b): return float(np.dot(vec_a, vec_b) / (np.linalg.norm(vec_a) * np.linalg.norm(vec_b)))这里有个工程细节必须反复强调阈值不是拍脑袋定的。我在实际项目里不同摄像头环境下的最优阈值差别很大阳光充足的办公室0.55就能很准而光线昏暗的走廊可能需要降到0.45。建议上线前用200~300张不同环境下的抓拍图跑一遍同人/不同人的相似度分布画一条ROC曲线在“误认”和“漏认”之间取平衡点。3.3 人脸库管理与注册流程考勤系统必然需要一个“员工人脸库”。我建议的表结构很简单字段类型说明idINTEGER自增主键employee_noTEXT工号唯一索引nameTEXT姓名embeddingBLOB512维特征向量的二进制存储photo_pathTEXT注册照片的保存路径注册流程是管理员在界面填写工号姓名 → 摄像头拍一张照片或上传一张照片→ 检测到人脸后提取特征 → 存入数据库。这里我想多说一句尽量给每个员工注册2~3张照片。一张正面照加一张戴眼镜/不戴眼镜的照片比对时取最大相似度能显著降低“换了眼镜就认不出”这种尴尬情况。实测下来三张照片的考勤系统比单张照片的误识率能降低一半以上。4. 考勤业务逻辑打卡去重、迟到早退与数据库设计4.1 一次打卡记录的完整生命周期业务逻辑看着简单实际写代码时全是细节。一次“有效打卡”要经历这么几步摄像头采集一帧画面Yolo检测出若干个人脸框。对每个人脸框做对齐、特征提取、与库里所有员工特征比对。得到匹配的员工ID和相似度。如果相似度低于阈值直接忽略可能是访客。命中员工后查最近一条打卡记录判断是否已经打过卡。如果五分钟内没有记录则插入一条新的打卡记录并保存当前帧抓拍图片。界面显示打卡成功提示姓名。第4步的去重逻辑是整个流程里最容易出Bug的地方。我一开始没做这个机制测试时站摄像头前两秒数据库里多了三十多条记录导出考勤表的时候人都麻了。去重逻辑用一个字典就能实现from datetime import datetime, timedelta # 记录每个员工最近一次打卡时间 last_check {} def should_check_in(employee_id): now datetime.now() if employee_id not in last_check: last_check[employee_id] now return True if now - last_check[employee_id] timedelta(minutes5): last_check[employee_id] now return True return False这里的时间窗口建议根据实际场景调整。车间工人从摄像头走到工位可能需要两三分钟窗口设太短会导致重复记录如果窗口设太长员工早上去倒水经过摄像头时就不会触发新的打卡记录就没有这个问题。所以五分钟是经过折算后的合理默认值。4.2 数据表结构怎么设计SQLite建表语句我直接贴在下面-- 员工表 CREATE TABLE employees ( id INTEGER PRIMARY KEY AUTOINCREMENT, employee_no TEXT UNIQUE NOT NULL, name TEXT NOT NULL, embedding BLOB NOT NULL, photo_path TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 考勤记录表 CREATE TABLE attendance ( id INTEGER PRIMARY KEY AUTOINCREMENT, employee_id INTEGER NOT NULL, employee_no TEXT NOT NULL, name TEXT NOT NULL, check_type TEXT NOT NULL DEFAULT check_in, check_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, photo_path TEXT, FOREIGN KEY(employee_id) REFERENCES employees(id) );注意这里有一个设计取舍attendance表里冗余了employee_no和name。虽然可以通过employee_id关联employees表查到姓名但考勤记录一旦生成员工的姓名或工号可能因人事调整而改变冗余字段能保证历史考勤记录不被“误伤”。这是一个典型的“空间换可靠性”做法。光有表还不够要给attendance表加上索引否则人多了之后按日期查询会越来越慢CREATE INDEX idx_attendance_employee_date ON attendance(employee_id, check_time);4.3 迟到、早退、缺卡的判定规则考勤系统不会只记录“打了卡”还要能统计迟到、早退、缺卡。最简单的规则配置放在一个config文件里WORK_START_TIME 09:00 WORK_END_TIME 18:00 LATE_GRACE_MINUTES 5 # 宽限期比如迟到5分钟内不算迟到每次写入考勤记录时就根据打卡时间和班次时间计算状态def calc_status(check_time, check_type): if check_type check_in: if check_time datetime.strptime(WORK_START_TIME, %H:%M).time(): return late return normal if check_type check_out: if check_time datetime.strptime(WORK_END_TIME, %H:%M).time(): return early_leave return normal return unknown这里“宽限期”是我特别想强调的是考勤规则的制定一定要和实际业务方对齐。有的公司规定迟到1分钟也算迟到有的公司规定10分钟内不扣钱这些业务逻辑应该在代码层面做成可配置参数而不是硬编码。因为你今天写死了9:00明天行政说上班时间改成9:30你还要改代码重新部署太被动了。4.4 掉线与多人同时经过的边界情况真实场景永远不会按照你的理想流程走。我实测中遇到过的边界情况摄像头断流USB摄像头偶尔会掉线OpenCV的read()会一直返回False如果不加判断程序会卡死。建议加入断线重连逻辑检测到连续读取失败就自动重新初始化摄像头。多人同时出现Yolo一次性检测出5~8个人脸框代码里循环处理即可。但要算好单帧处理时间如果处理一帧超过300ms就得降低帧率否则视频画面会越来越卡。同一个人重复识别员工在摄像头前停留时间较长虽然去重逻辑挡住了重复打卡但界面上的“打卡成功”提示会反复触发。建议命中后设置一个“界面冷却时间”比如3秒内不再刷新该员工的提示。员工没注册检测到人脸但比对不上任何库中特征。这种情况要友好提示“未识别”而不是什么都不显示。这些问题不会出现在任何教程里但恰恰是决定考勤系统能不能稳定运行的关键。5. 源码结构与关键模块实现5.1 目录组织与职责划分源码的目录结构决定了后续维护的难易程度。我建议这么组织face_attendance/ ├── main.py # 程序入口 ├── config/ │ └── config.yaml # 所有可调参数集中管理 ├── models/ │ ├── detector.py # Yolo人脸检测封装 │ ├── recognizer.py # 特征提取与比对 │ └── face_db.py # 人脸库增删改查 ├── core/ │ ├── attendance.py # 考勤业务逻辑去重、状态判定 │ └── camera.py # 摄像头采集与断线重连 ├── ui/ │ └── main_window.py # 界面 ├── data/ │ ├── registered/ # 注册照片 │ ├── snapshots/ # 打卡抓拍照片 │ └── attendance.db # SQLite数据库 └── requirements.txt模块划分的原则是“各管一摊”detector.py只负责把人脸框从图像里找出来不管后面认不认识recognizer.py只负责提取特征和比对attendance.py只负责业务判断不关心画面里有多少张人脸。这样如果以后你想把YOLOv8换成YOLOv11或者把InsightFace换成MobileFaceNet只需要改对应模块内部实现其他代码一行都不用动。5.2 主流程如何串起来主流程用伪代码描述大概是这个样子def main_loop(): camera Camera() detector FaceDetector() recognizer FaceRecognizer() attendance AttendanceProcessor() while True: ok, frame camera.read() if not ok: camera.reconnect() continue faces detector.detect(frame) # 1. Yolo检测 for face_box in faces: aligned align_face(frame, face_box) # 2. 对齐 embedding recognizer.extract(aligned) # 3. 提取特征 employee recognizer.match(embedding) # 4. 比对 if employee: attendance.process(employee, frame) # 5. 记录考勤这里有一个性能优化的关键点不要把检测、识别、数据库写入都放在同一个循环里死等。我的做法是主线程负责摄像头读取和Yolo检测这部分比较快。检测到人脸后把裁剪图像放入一个queue.Queue。另一个线程从队列里取人脸图像执行特征提取和数据库写入。这样做的好处是即使识别网络偶尔慢了一帧摄像头采集和检测也不会被阻塞视频预览依然流畅。代码量只多了几行体验却提升一个档次。5.3 界面选型与交互设计界面上我试过两种方案方案一PyQt5桌面界面优点部署简单直接打包成exe就能给前台用可以嵌入摄像头实时画面。缺点不好做远程查看和管理。方案二Flask Web前端优点浏览器访问任何设备都能看适合管理员在办公室远程查看考勤记录。缺点摄像头推流到浏览器需要额外处理可以用MJPEG流或WebRTC。我个人更推荐PyQt5作为起点。因为它实现一个带实时视频、员工管理、考勤记录表格的桌面程序大约只需要500行代码而Web方案要处理的前后端交互复杂度会高一截。如果后续有远程查看需求再给PyQt5程序加一个Flask接口把考勤查询用HTTP暴露出去即可两手都抓。界面核心元素建议包含视频预览区域实时显示摄像头画面人脸框实时绘制名字实时标注。打卡日志区滚动显示最近识别记录包括姓名、工号、时间、状态。员工管理按钮打开新窗口支持添加、删除、编辑员工。考勤查询按钮按日期筛选考勤记录支持导出CSV。6. 实测踩坑与调优6.1 光照条件对系统的影响光照是影响人脸考勤系统稳定性的头号敌人这部分我要重点说。我第一版系统在办公室测试正对窗户的机位下午逆光Yolo检测率直接掉到60%以下人脸根本看不清。后来做了两件事才解决第一是预处理。在送入模型推理前对图像做自适应直方图均衡化CLAHE增强人脸局部对比度。这个操作对CPU的额外开销很小但逆光场景的检测率能提升20%以上。import cv2 def preprocess_frame(frame): lab cv2.cvtColor(frame, cv2.COLOR_BGR2LAB) l, a, b cv2.split(lab) clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) l clahe.apply(l) lab cv2.merge((l, a, b)) return cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)第二是设备调整。考勤摄像头尽量选择带补光灯的型号。我后来的项目里换了一款带红外补光的USB摄像头夜间关灯环境也能稳定检测这个钱不能省。6.2 照片打卡与活体问题人脸考勤上线后最先被人试探的就是“拿照片能不能打卡”。很遗憾答案是可以——打印一张照片就能骗过基础的摄像头。如果你只是做个毕业设计或者内部实验可以不管活体检测但如果你想真的部署至少要加一层最简单的防护动态指令活体界面随机显示数字要求员工读出数字系统做一个唇动验证。眨眼检测通过关键点计算眼睛开合度判定是否有眨眼动作。静默活体基于LightCNN或SpoofNet的模型直接对单张人脸图判活。在Yolo InsightFace这套架构下一个很务实的中间方案是连续采集3~5帧要求检测到的人脸框中心位置有微小移动说明是真人不是静止照片且人脸关键点能稳定跟踪。这虽然挡不住视频回放但能挡住绝大多数“用照片试试”的行为。6.3 性能优化组合拳最后聊聊性能。这套系统在CPU上跑是完全可以做到的但需要做几个优化优化手段效果实现难度输入尺寸从640降到320推理时间缩短一半以上一行配置导出ONNX模型推理速度提升20%~40%一行命令检测与人脸识别稀疏调度不是每帧都做全流程每隔2~3帧做一次识别中等用半精度/INT8量化GPU可用FP16CPU可走INT8中等Yolo导出ONNX的命令model.export(formatonnx, imgsz320, halfTrue)然后在检测模块里改用ONNX Runtime推理import onnxruntime as ort session ort.InferenceSession(yolov8n-face.onnx, providers[CPUExecutionProvider])对于人脸识别网络InsightFace也可以用model.export(formatonnx)导出或者直接使用OpenCV的DNN模块加载ONNX推理。这些调优做下来我在一把i5-8250U的老笔记本上视频画面分辨率720p检测识别全流程能稳定在15FPS左右比原始YOLOv8n在CPU上快一倍。对考勤场景来说这个速度已经非常充裕了。有一点我想特别提醒考勤系统不是视频监控不需要每一帧都全量识别。你可以把画面预览和识别逻辑分离——识别模块每隔200ms跑一次预览画面保持30FPS。这样既保证了实时性又给CPU留足了余量。从架构设计到最终调优这套系统我已经完整跑通过三轮。最大的体会是基于Yolo的人脸考勤系统真正的工程量不在Yolo本身而在于“检测之后”的识别、业务和工程细节。只要把上面这些文章里提到的环节都处理好你不仅能在测试环境看到框框和名字还能在真实场景里连续运行数周不崩。源码组织也给你了按这个目录去填充、开发比从零摸索要省下大量时间。本文还有配套的精品资源点击获取