ARTICLE DETAIL

资讯详情

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

基于Python的人脸识别考勤系统:从特征提取到GUI实现

基于Python的人脸识别考勤系统:从特征提取到GUI实现 简介一份基于Python的人脸识别课程考勤管理系统项目实例面向具备Python基础、熟悉Web开发与数据库操作的高校师生及研发人员解决课堂考勤效率与公平性问题同时可作为AI综合教学案例。资源以docx格式呈现共1个文件压缩包大小115KB内容涵盖项目背景、技术架构、算法流程、代码实现、MySQL数据库表结构及API接口规范等完整说明。已有95人学习下载。文档详细展开OpenCV与face_recognition的人脸检测、特征提取与匹配逻辑结合Flask/FastAPI后端与Tkinter GUI给出人脸注册、实时识别、考勤存储查询的模块化代码示例并讨论隐私保护、系统鲁棒性与可扩展性涵盖摄像头管理、人脸裁剪、特征库构建到识别结果写入考勤记录的完整链路适合实操部署与教学参考。1. 人脸识别考勤系统的定位与技术栈锁定课程考勤最烦的不是点名本身而是点完之后还要手工录入、统计、催假条。点名三五分钟录入半小时期末核算又是一轮加班。人脸识别考勤系统的价值不在“认出一个人”这个动作而在于把“识别”和“考勤管理”这两件事缝合在一起摄像头拍到人脸系统自动匹配学生信息落库生成迟到早退缺勤记录课程结束后一键导出报表。这个标题对应的正是这样一套小型但完整的课程考勤管理系统覆盖人脸录入、特征提取、实时识别、数据库读写和GUI交互五个环节。适合正在做计算机视觉课程设计、毕业设计或者想在企业内快速搭一套内部考勤原型的开发者用Python全栈完成不依赖商业SDK代码可控运行成本低。2. 人脸识别考勤系统的核心链路检测、对齐、特征提取与比对2.1 人脸检测方案选型Haar、HOG 还是 CNN人脸识别考勤系统的第一步不是“认出谁”而是“找到脸在哪”。检测这一步的选型直接决定系统在教室、实验室这类场景下的可用性三种方案各有取舍我一般按设备算力来定。检测方案底层依赖精度速度适用场景Haar CascadeOpenCV低正面脸为主极快CPU 实时配置受限的老机器简单原型HOG 线性 SVMDlib中可容忍 30° 左右偏转较快CPU 实时普通笔记本推荐默认选这个CNNMMODDlib / face_recognition高侧脸、遮挡更稳慢CPU 掉帧明显有独立显卡或只需离线批量处理对于考勤场景人脸姿态不会太夸张但也不会正对着摄像头纹丝不动。用 HOG 方案在多数 Intel 低压 CPU 上能跑到 20 到 30 FPS足够应付“走近摄像头停留两秒”的考勤节奏。CNN 方案在同样硬件上会掉到 5 FPS 以下只能做离线处理不建议直接用在实时打卡上。2.2 特征提取环节为什么最终落点是 128 维向量检测到人脸之后系统需要把脸转成可以比对的数学表示。常见做法是先用 Dlib 的 68 点关键点检测做对齐把眼睛、鼻子、嘴巴的位置统一到同一坐标系再接一个预训练的 ResNet 模型输出 128 维特征向量。这个向量就是这张脸的数字指纹同一人不同角度、不同光照下的向量距离很近不同人的向量距离较远。对齐这一步很多人会忽略但它对识别准确率的影响非常大。如果不做对齐两张同一人的照片可能因为旋转角度产生很大的特征偏差。考勤场景里学生低头看手机、侧身和同学说话都会让头部姿态变化对齐能把这些干扰压到最低。face_recognition 库把“检测 → 对齐 → 取特征”封装成了一行调用但理解底层链路对后续调参很有帮助代码如下import face_recognition # 读取两张照片并转换为 RGBOpenCV 读入的是 BGR必须转 img_a face_recognition.load_image_file(student_zhang.jpg) img_b face_recognition.load_image_file(student_zhang_live.jpg) # 提取 128 维人脸特征num_jitters10 表示对图像做多次抖动采样再取平均 encoding_a face_recognition.face_encodings(img_a, num_jitters10)[0] encoding_b face_recognition.face_encodings(img_b, num_jitters10)[0] # 欧氏距离越小表示越可能是同一个人 distance face_recognition.face_distance([encoding_a], encoding_b)[0] print(f人脸特征欧氏距离: {distance:.4f})num_jitters参数控制特征提取的稳定度数值越大越稳定但耗时越高。录入人脸时我会设 10识别打卡时设 1因为打卡需要实时响应不能每次识别都做多次采样。face_distance返回的是浮点型的欧氏距离在 0 到 1 之间具体阈值下一节展开。2.3 距离阈值设置0.6 是起点不是终点特征向量比对完成后需要一个判定阈值来回答“是不是同一个人”。face_recognition 库给出的官方参考阈值是 0.6但在真实考勤场景里直接用 0.6 往往会出现两种问题阈值太紧导致同一个人因为没睡好、水肿、发型变化被拒识阈值太松导致两个人被误判成同一个。这里的优化方向是统计自己班级样本的距离分布而不是照搬公开默认值。# 计算当前学生与库中所有已注册学生特征的距离 def match_student(unknown_encoding, known_encodings, ids_list, tolerance0.5): distances face_recognition.face_distance(known_encodings, unknown_encoding) best_idx int(distances.argmin()) if distances[best_idx] tolerance: return ids_list[best_idx], distances[best_idx] return None, Nonetolerance设得越小误识别人进来的概率越低但漏识别率会上升。教室考勤的特点是误打卡可容忍后台可人工删除漏打卡很难处理学生明明在教室却没记录所以调参时要向“宁松勿紧”倾斜。我一般先收集 20 人各 5 张照片算同一人距离的最大值和不同人距离的最小值两者中间取一个略偏向漏识别侧的数值作为最终阈值。之后在运行日志中持续观察距离值的分布再做微调。3. 课程考勤管理系统的数据库设计与 GUI 实现3.1 数据库表结构设计三张表支撑完整考勤链路人脸识别考勤系统落库的数据分三类学生基础信息、人脸特征向量、每次考勤记录。如果还要支持多门课程就得再加课程表和学生选课关系表。常见的做法是拆成这五张表students学生档案、courses课程、enrollments选课关系、face_features人脸特征、attendance_records出勤记录。核心表结构如下表名关键字段说明studentsid, student_no, name, gender, class_name学号唯一姓名用于页面展示face_featuresid, student_id, feature_blob, created_at用 BLOB 存储 numpy 序列化后的 128 维向量attendance_recordsid, student_id, course_id, check_time, statusstatus 用 0/1/2 表示正常/迟到/缺勤feature_blob字段是很多人不知道怎么处理的点。numpy 数组不能直接写进数据库需要先序列化成二进制再存入 BLOB 类型字段。查询时读出再反序列化和当前识别出的特征做向量间距离计算。人脸特征更新频率低、每次写入体积只有约 2KB用 BLOB 完全够用不需要单独建特征文件服务器。CREATE TABLE students ( id INTEGER PRIMARY KEY AUTOINCREMENT, student_no VARCHAR(20) UNIQUE NOT NULL, name VARCHAR(50) NOT NULL, gender VARCHAR(4), class_name VARCHAR(50) ); CREATE TABLE face_features ( id INTEGER PRIMARY KEY AUTOINCREMENT, student_id INTEGER NOT NULL, feature_blob BLOB NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (student_id) REFERENCES students(id) ); CREATE TABLE attendance_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, student_id INTEGER NOT NULL, course_id INTEGER NOT NULL, check_time DATETIME DEFAULT CURRENT_TIMESTAMP, status TINYINT DEFAULT 0, FOREIGN KEY (student_id) REFERENCES students(id) );status字段不存字符串而存整数是为了后续统计时用GROUP BY快速算迟到率、缺勤率同时避免中文编码问题。这个设计在课程设计答辩时也更容易讲清楚一张表的字段为什么这么设计、查询时怎么利用索引。3.2 SQLite 和 MySQL 的取舍课程考勤系统的数据库选型需要回答一个问题单机跑还是多机跑。单机的课程设计、毕业设计、实验室内部考勤直接用 SQLite 即可零配置、单文件备份、Python 自带驱动教师拿着 U 盘拷走数据库文件就能迁移全部数据。但如果是正规院系要同时支持多个摄像头终端并发写入SQLite 的并发写性能会成为瓶颈需要换 MySQL 或 PostgreSQL。用 MySQL 时需要额外安装驱动常见做法是PyMySQL加连接池。连接参数里要特别注意charsetutf8mb4否则姓名里出现生僻字或表情符号时会报编码错误。SQLite 用sqlite3标准库时则需要在连接后执行PRAGMA journal_modeWAL这能显著减少高并发读写时的锁等待。这两种库的 SQL 语法几乎一致只需修改连接方式业务代码不用大改。3.3 GUI 设计Tkinter 实现摄像头预览和控制面板考勤系统的 GUI 用 Tkinter 可以完成它在 Windows 和 macOS 上无需额外安装依赖对 python 的版本兼容性也最好。版本选择上固定用 Python 3.8 到 3.11 之间的版本更稳妥因为 Dlib 和 face_recognition 对 Python 的编译支持有版本窗口。界面布局一般分为三大区域左侧是摄像头实时画面Label 组件承载、右侧是最近识别记录表格Treeview 组件、底部是操作按钮开始考勤、录入人脸、导出报表。import tkinter as tk from tkinter import ttk import cv2 from PIL import Image, ImageTk class AttendanceApp: def __init__(self, root): self.root root self.root.title(课程人脸识别考勤系统) # 摄像头画面区域 self.video_label tk.Label(root, text摄像头画面, width640, height480) self.video_label.pack(sideleft, padx10) # 考勤记录表格区域 self.tree ttk.Treeview(root, columns(time, name, status), showheadings) self.tree.heading(time, text打卡时间) self.tree.heading(name, text姓名) self.tree.heading(status, text状态) self.tree.pack(sideright, padx10)Tkinter 的Label用来显示 OpenCV 中每一帧转换成的ImageTk.PhotoImage注意必须持有一个引用变量否则画面会卡住不刷新。Treeview每次插入新记录时建议只保留最近 50 行超出后删除第一行避免界面长期运行后内存累积。界面刷新频率要单独开一个线程去更新具体在 4.3 节展开。4. 人脸识别考勤系统的代码实现与参数调优4.1 从录入到打卡的整体程序流转一个可交付的人脸识别考勤系统代码层面要跑通五条流程学生信息录入、人脸照片采集、特征入库、实时识别打卡、考勤报表导出。这五条流程有时序依赖录入必须在识别之前完成否则库里没有特征可比对。流程拆解的常见做法是启动时连接数据库读取全部学生的姓名、学号、特征向量到内存列表在录入页面填写学号姓名调用摄像头采集 5 帧人脸分别提取特征后取平均将平均特征序列化存入face_features表打卡时对摄像头每帧做人脸检测检测到人脸后提取特征与内存列表逐一比对比对成功则写入attendance_records并在 GUI 上刷新一条记录这里有一个容易踩的性能坑打卡流程中每次检测到人脸都实时从数据库读特征再比对速度会慢到不可用。正确的做法是启动时一次性把全库特征加载进内存之后比对只在内存中发生。如果学生上千人128 维向量乘 1000 人在内存里也只占约 1MB完全可承受。4.2 关键代码人脸录入与实时打卡录入阶段的核心不是简单存一张图而是提取出稳定的特征。下面这段代码做了两件事连续采集 5 帧检测到的人脸特征并取平均把均值特征存入数据库。取平均能显著降低单帧模糊、光照抖动带来的特征偏差。import pickle import sqlite3 import face_recognition def register_face(student_id, video_frames): video_frames: list, 包含 5 帧 BGR 图像数组 encodings [] for frame in video_frames: # BGR 转 RGB 后检测人脸small_model 模式更省 CPU rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) boxes face_recognition.face_locations(rgb, modelhog) if len(boxes) 1: enc face_recognition.face_encodings(rgb, [boxes[0]])[0] encodings.append(enc) if len(encodings) 3: return False, 有效人脸不足 3 帧请重新采集 # 按列取平均得到稳定特征向量 avg_encoding sum(encodings) / len(encodings) blob pickle.dumps(avg_encoding) conn sqlite3.connect(attendance.db) conn.execute( INSERT INTO face_features (student_id, feature_blob) VALUES (?, ?), (student_id, blob) ) conn.commit() conn.close() return True, 录入成功pickle.dumps把 numpy 数组序列化成二进制字节串数据库读出后用pickle.loads还原。录制视频帧时要注意每一帧里恰好只有一个人脸多人同框会导致检测框数量不为 1当前帧直接丢弃。采集时让学生正对摄像头、摘掉口罩和帽子背景不要有其他人脸5 帧里只要 3 帧合格就写入。实时打卡的逻辑则是反向过程检测人脸、提取特征、遍历内存中的库特征做距离比较。当采用 OpenCV 的VideoCapture读取摄像头时读帧要放在独立线程主线程只负责 GUI 刷新避免摄像头帧率拖垮界面响应。def process_frame(self, frame, known_encodings, known_ids, tolerance0.5): rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) boxes face_recognition.face_locations(rgb, modelhog) encodings face_recognition.face_encodings(rgb, boxes) results [] for enc in encodings: distances face_recognition.face_distance(known_encodings, enc) best_idx distances.argmin() if distances[best_idx] tolerance: results.append((known_ids[best_idx], distances[best_idx])) else: results.append((None, distances[best_idx])) return results打卡记录存在两个隐患同一帧画面里出现多张脸时会同时打卡多个人这当然是期望功能但如果同一个人连续几十帧都被识别到就会产生大量重复记录。处理办法是维护一个打卡时间字典同一学号两次成功打卡的最小间隔设为 30 秒间隔内只更新打卡时间不新增记录。4.3 三个必须调的参数及其影响范围参数默认值作用建议detection_modelhog检测模型选择hog 还是 cnnCPU 跑选 hogGPU 可用时选 cnnnum_jitters1特征提取时图像抖动次数录入时 10识别时 1tolerance0.6特征距离判定阈值先统计本地分布再定参考 0.45 到 0.55detection_modelcnn在 CPU 上检测一帧需要 0.5 秒以上导致考勤画面掉帧严重。如果必须用高精度检测一个推荐做法是降低处理帧率从每秒 25 帧降到每秒 5 帧每 200 毫秒只处理最新的一帧。这样识别耗时虽然还是长但画面不会明显卡顿感。num_jitters在识别阶段保持 1 即可它在录入阶段已经做过一次平均识别阶段再做平均会严重延缓打卡速度。4.4 多线程与 GUI 卡顿的解决思路Tkinter 的主循环是单线程的如果直接在事件循环里跑人脸识别摄像头画面会卡成 PPT鼠标点击按钮也毫无反应。常见做法是用一个子线程持续读帧和处理识别逻辑再把结果通过队列发送给主线程刷新界面。线程之间不能直接操作 Tkinter 组件必须用queue.Queue中转。import queue import threading frame_queue queue.Queue(maxsize1) def camera_thread(): cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: continue # 只保留最新一帧旧帧直接丢弃 if frame_queue.full(): try: frame_queue.get_nowait() except queue.Empty: pass frame_queue.put(frame) def gui_update(): try: frame frame_queue.get_nowait() # 转换后在 Tkinter Label 上显示 self.video_label.config(imageimg_tk) except queue.Empty: pass self.root.after(30, gui_update) # 每 30ms 刷新一次线程之间的帧同步用maxsize1的队列很合适摄像头线程每来一帧先丢旧帧再放新帧保证 GUI 拿到的永远是最新画面。识别处理也放到子线程中把识别结果直接丢进 Tkinter 的after回调中更新表格数据这样即使一帧识别耗时 300 毫秒也不会阻塞界面的按钮点击事件。5. 人脸识别考勤系统的实测校验与规避误用考勤系统合不达标判定标准不光是“能否认出本班学生”而是“非本班人员能不能混进来、本班学生会不会漏掉”。我拿到一个做好的考勤系统第一件事不是看界面漂不漂亮而是用一段 3 分钟的连续视频模拟进出场景把视频帧抽取成图片集批量跑识别统计误报率。录视频时让测试者从正面走近摄像头、侧头交谈、低头看手机三种姿态各 1 分钟。跑完后记录识别成功的帧数占比正常光照下应当高于 90%低于这个数优先检查对齐环节而不是调阈值。采集注册照片时有三个约束条件要明确告诉录入学生摘掉眼镜辨色镜片避免反光遮挡眼睛区域表情自然放松不抿嘴不张嘴避免特征向量和平时差太多保持面部无大面积遮挡刘海盖住眉毛不会明显影响识别精度但口罩和浓重阴影会。录入照片如果明显模糊直接丢弃重拍模糊照片提取的特征本身就是噪声。还有一个经常被忽略的细节是重复注册问题同一学生因为不满意上次的照片再次注册数据库里会出现两条特征记录识别时容易产生冲突票。实现时可以在录入前先检索face_features表如果该学生已有记录用新特征覆盖旧记录而不是追加。这样既控制库容量也让系统行为可预期。考勤原始记录建议保留 180 天后再清理。很多课程设计或院内系统只实现了“能打卡、能导出”缺少对数据生命周期的考虑。定期把attendance_records中的旧数据导出为 CSV 归档再删除数据库中的对应行可以避免 SQLite 单文件体积膨胀到几百兆后性能骤降。归档文件名里带上课程编号和日期范围比如course_CS301_2024_spring.csv方便期末按课程回溯原始记录。本文还有配套的精品资源点击获取
返回列表