
简介面向计算机相关专业毕业设计及课程设计场景这份基于Python与OpenCV的人脸识别考勤打卡系统源码是作者大四阶段经导师指导并获98分的高分项目。系统以OpenCV为人脸检测与识别基础整合face_recognition主程序完成人脸特征提取与比对覆盖用户注册、人脸采集、实时识别、考勤打卡、记录查询与界面展示等完整流程适合正在做毕业设计、需要项目实战练习的本科生及初级学习者参考借鉴。压缩包共25个文件整体约239.37MB结构层次分明.py源码实现业务逻辑与用户界面png图片提供操作界面背景、用户信息展示等素材ttf字体用于保证界面中文字体正常显示ico图标标识程序txt与config等文件记录环境配置与使用说明。资源内另附人脸识别成品软件压缩包并含face_recognition主程序可快速运行查看实际效果界面静态图和配置原文件也有助于理解打卡页面布局、用户列表展示与识别流程。目前已有430人学习整体方案完整、文档思路清晰代码注释较充分可直接作为毕业设计、课程设计或期末大作业的对照参考与二次开发基础。1. 为什么这套人脸识别考勤系统能在毕设里拿到98分做考勤打卡的毕设选题每年都不少但大多数作品停留在“调用摄像头拍张照、比对一下相似度”的演示层面。这套基于PythonOpenCV的人脸识别考勤系统拿到98分核心不在于用了多前沿的模型而在于它把“人脸识别”和“考勤业务”完整闭环了人脸注册、实时检测、特征比对、签到记录、考勤统计、界面展示全部跑通而且代码组织方式清晰Ui_test_01.py、face.py、config这些文件划分明确答辩时能讲清楚每一个模块的数据流这才是高分的关键。项目里用到了face_recognition库底层是dlib的深度学习人脸特征提取模型配合OpenCV做视频帧处理前端用PyQt5搭的桌面界面数据库层通过config管理连接参数。对于正在做毕设的学生来说这个组合比纯OpenCV Haar级联方案识别率高很多又比直接上深度学习框架训练自己的模型简单得多——不需要GPU不需要标注数据装好依赖就能跑。下面我从环境搭建、模块结构、核心识别流程、踩坑点、以及答辩时怎么把系统讲出深度这几个方面拆开讲。适合谁看正在做人脸识别类毕设、课程设计的学生想快速搭一个“能演示、能截图、能答辩”的考勤系统的开发者以及想把OpenCV face_recognition这套组合用到实际小项目里的工程师。2. OpenCV face_recognition 的人脸识别原理与环境搭建2.1 这套系统为什么选 face_recognition 而不是纯 OpenCV很多人一提到OpenCV人脸识别第一反应是cv2.CascadeClassifier加载Haar特征分类器但Haar级联在考勤场景下有几个致命问题光照变化时检测框乱跳、侧脸基本失效、误检率高。这套毕设用的是face_recognition库它是对dlib的封装底层跑的是ResNet残差网络提取128维人脸特征向量配合HOG或CNN做人脸检测。简单说系统的工作流程是OpenCV负责从摄像头读取视频帧、把BGR图像转换成RGBface_recognition要求RGB顺序、在画面上绘制检测框face_recognition负责定位人脸位置、提取128维特征向量、与已注册的人脸向量做欧氏距离比对距离小于阈值就判定为同一个人。这个分工清晰答辩时也容易讲出“为什么不用纯OpenCV”的选型理由。2.2 依赖安装与版本坑这套代码运行在Python 3.7~3.9环境下最稳。face_recognition依赖dlib而dlib在Windows上安装很容易踩坑。我建议按下面的顺序装能避开大部分报错。# 建议使用 Python 3.8兼容性最好 conda create -n face_attendance python3.8 conda activate face_attendance # 先装 dlibWindows 下用 pip 直接装可能失败 pip install dlib19.24.2 # 再装 face_recognition pip install face_recognition1.3.0 # OpenCV 用 4.5.x 系列不建议装最新版 pip install opencv-python4.5.5.64 # PyQt5 用于桌面界面 pip install PyQt55.15.9 # 数据库连接这套代码用的是 MySQL pip install pymysql注意如果pip安装dlib报错先检查是否有Visual C Build Tools。另外一个常见的坑是face_recognition在新版Python 3.10上编译dlib会出现fatal error C1083所以别用太新的Python。装完可以快速验证python -c import face_recognition, cv2; print(cv2.__version__, face_recognition.__version__)输出中能同时看到OpenCV和face_recognition的版本号说明核心依赖装好了。如果只报错face_recognition多半是dlib没装成功回退pip install cmake后再装dlib。2.3 config文件与项目结构解析解压后的源码里config文件是关键。这个系统把数据库连接参数、摄像头ID、人脸匹配阈值、照片存储路径都集中放在配置里而不是散落在各个py文件中。下面是这类项目常见的config内容形式。# config.py import os # 数据库连接信息 DB_HOST localhost DB_PORT 3306 DB_USER root DB_PASSWORD 123456 DB_NAME face_attendance # 人脸识别参数 FACE_TOLERANCE 0.4 # 欧氏距离阈值越小越严格 FACE_MODEL hog # 人脸检测模型hog 或 cnn USE_CAMERA 0 # 摄像头ID0表示内置摄像头 # 路径配置 BASE_DIR os.path.dirname(os.path.abspath(__file__)) FACE_IMAGE_DIR os.path.join(BASE_DIR, face_images) # 已注册人脸照片 VIDEO_SAVE_DIR os.path.join(BASE_DIR, video_record) # 打卡视频留存FACE_TOLERANCE是这套系统里最值得调的参数。face_recognition自带的compare_faces默认阈值是0.6但对于考勤打卡这种安全敏感场景默认阈值太宽松了容易出现误打卡。这套毕设里写的是0.4意思是两张人脸的128维特征向量欧氏距离小于0.4才认为是同一个人。调低到0.35会更严格但光线不足时容易识别失败调到0.5以上则会出现替打卡风险。建议答辩演示时用0.4~0.45既不会频繁漏检也能挡住长得像的两个人。3. 从摄像头到打卡记录核心识别流程与模块拆解3.1 整体流程注册、识别、打卡、入库这套系统把考勤逻辑拆成了四个状态人脸注册、人脸识别、打卡成功、打卡失败。运行时主循环每一帧都在重复下面这条链路。读取摄像头帧 - OpenCV转为RGB - face_recognition检测人脸位置 - 提取128维特征 - 与库中已注册特征比对 - 距离小于阈值? - 是: 标记姓名签到时间 - 写入数据库 - 否: 显示未注册这个流程看起来简单但代码里有两个容易忽略的细节。第一摄像头帧是BGR格式face_recognition.face_encodings直接处理的是RGB如果忘了转换识别率会断崖式下降而且OpenCV画框时的颜色还会错乱。第二每次检测没有必要对整帧所有人脸都做特征提取先做人脸检测再用已知人脸位置裁剪出区域特征提取速度会快不少。3.2 人脸注册模块如何保证录入质量注册模块做的事是摄像头拍摄用户正脸 - 提取特征向量 - 把向量和姓名存库。源码里face.py承担了大部分逻辑注册时通常会截取几帧从多帧中选一张质量最好的保存。我这里给出一个简化但可用的注册核心代码思路。# register.py - 简化版人脸注册 import cv2 import face_recognition import pymysql import numpy as np from config import DB_HOST, DB_USER, DB_PASSWORD, DB_NAME def register_face(student_id, student_name): cap cv2.VideoCapture(0) ret, frame cap.read() cap.release() if not ret: print(无法读取摄像头) return False # OpenCV 的 BGR 转 RGBface_recognition 才能正确提取特征 rgb_frame cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # 检测人脸位置 face_locations face_recognition.face_locations(rgb_frame, modelhog) if len(face_locations) ! 1: print(画面中必须只有一张正脸) return False # 提取 128 维特征向量 face_encoding face_recognition.face_encodings(rgb_frame, face_locations)[0] # 特征向量转成 bytes 存入 MySQL类型用 BLOB/LONGBLOB sql INSERT INTO employee (student_id, student_name, face_encoding) VALUES (%s, %s, %s) conn pymysql.connect(hostDB_HOST, userDB_USER, passwordDB_PASSWORD, databaseDB_NAME) cursor conn.cursor() cursor.execute(sql, (student_id, student_name, face_encoding.tobytes())) conn.commit() conn.close() return True这段代码里face_encoding.tobytes()是把numpy数组转成二进制字节流存到MySQL的BLOB字段里。取出来比对时要先用numpy.frombuffer()还原否则形状不对。注册时建议从摄像头多取3~5帧挑一张人眼清晰、光线均匀的这张基准照片的质量直接决定后期识别率。如果注册时光线过暗之后在阳光下打卡会频繁匹配失败。3.3 实时打卡主循环每帧做什么打开Ui_test_01.py主窗口里面跑的是一个while循环不断从摄像头读帧处理完后把结果显示在PyQt5的QLabel上。这是典型的“摄像头循环 界面刷新”模式下面给出核心逻辑的伪代码级还原。# attendance_loop.py - 考勤主循环核心片段 import cv2 import face_recognition import numpy as np from PyQt5 import QtGui, QtCore def update_frame(self): ret, frame self.cap.read() if not ret: return # 缩小帧以加快人脸检测等比缩放后处理完再画回原图 small_frame cv2.resize(frame, (0, 0), fx0.5, fy0.5) rgb_small_frame cv2.cvtColor(small_frame, cv2.COLOR_BGR2RGB) # 每 2 帧做一次检测降低 CPU 占用 if self.frame_skip % 2 0: self.face_locations face_recognition.face_locations(rgb_small_frame, modelhog) self.face_encodings face_recognition.face_encodings(rgb_small_frame, self.face_locations) for encoding in self.face_encodings: matches face_recognition.compare_faces(self.known_encodings, encoding, tolerance0.4) distances face_recognition.face_distance(self.known_encodings, encoding) best_match_index np.argmin(distances) if matches[best_match_index]: name self.known_names[best_match_index] self.mark_attendance(name) # 写入打卡记录 else: name 未注册人员 # 把检测框画在原图上显示到窗口 for (top, right, bottom, left) in self.face_locations: # 坐标乘回 2因为之前缩小了一半 top * 2; right * 2; bottom * 2; left * 2 cv2.rectangle(frame, (left, top), (right, bottom), (0, 255, 0), 2) self.display_frame(frame) self.frame_skip 1这个片段里有两个关键参数值得在答辩时展开讲。modelhog使用HOG方向梯度直方图检测人脸速度快CPU上每帧约几十毫秒。改成modelcnn精度更高但依赖GPU或者速度慢很多实时考勤用hog足够。frame_skip跳帧处理。摄像头通常30fps但人脸识别不可能每帧都做实际2帧取1帧或者3帧取1帧人眼感知不到延迟CPU占用能降三分之一。compare_faces face_distancecompare_faces返回布尔列表face_distance返回具体距离数值。只用compare_faces容易出现误判配合distance取最小值更稳。代码里如果best_match_index对应的是False说明距离小于0.4的人不存在标记为未注册。3.4 防重复打卡与签到记录设计考勤系统不能让人在同一时间段重复打卡。这套源码里在打卡记录表中设计了定位字段比如时间戳和考勤日期。每次识别成功后先去数据库查今天是否已经打过卡如果没有才insert。核心SQL逻辑类似这样。-- 打卡记录表 CREATE TABLE attendance_record ( id INT PRIMARY KEY AUTO_INCREMENT, student_id VARCHAR(20), student_name VARCHAR(50), check_time DATETIME DEFAULT CURRENT_TIMESTAMP, attendance_date DATE, status TINYINT DEFAULT 1, -- 1正常 0迟到 2重复 UNIQUE KEY uk_student_date (student_id, attendance_date) ); -- 判断今天是否已打卡 SELECT COUNT(*) FROM attendance_record WHERE student_id 2021001 AND attendance_date CURDATE(); -- 插入打卡记录 INSERT INTO attendance_record (student_id, student_name, attendance_date) VALUES (2021001, 张三, CURDATE());这里用UNIQUE KEY在学生ID和日期上做唯一约束数据库层面杜绝同一人同一天重复插入。如果day记录已存在代码里应该捕获IntegrityError然后只更新最后一次打卡时间而不是新增记录。毕设里能突出“唯一约束防止数据冗余”这个点比单纯写insert语句要加分得多。4. 界面联动与数据库设计考勤系统怎么才算“完整”4.1 PyQt5界面里各元素如何协同这套系统的界面一共包含几个关键区域左侧是摄像头实时画面右侧是用户信息列表、打卡状态图标llz_userinfo_all.png、sign_status.png这些资源文件都是界面组件顶部是刷新、导出、注册等功能按钮。Ui_test_01.py里把这几部分封装成了多个方法比如load_user_list()从数据库拉取人员列表show_sign_status()根据当前识别结果切换打卡状态的图片。需要特别说明的是PyQt5的界面刷新不能直接在识别循环里调用self.label.setPixmap()因为那样会卡界面。标准做法是用QTimer定时器驱动update_frame()同时把耗时的人脸识别放到线程里执行。源码中采用的是QTimer定时触发 主线程处理的方式对于演示级别的项目完全够用。如果识别速度慢导致画面不流畅可以把识别部分塞进QThread只把结果信号传回主线程更新界面。4.2 员工表与打卡记录的关联设计考勤系统的数据库至少需要两张表员工表和考勤记录表。员工表存储人脸特征向量和基本信息考勤记录表存储每次打卡的时间戳。两表通过student_id外键关联。这段源码里employee表的字段设计可以用下面这个DDL做参考。CREATE TABLE employee ( id INT PRIMARY KEY AUTO_INCREMENT, student_id VARCHAR(20) UNIQUE NOT NULL, student_name VARCHAR(50) NOT NULL, department VARCHAR(50), face_encoding BLOB NOT NULL, -- 128维特征向量序列化后存入 created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE attendance_record ( id INT PRIMARY KEY AUTO_INCREMENT, student_id VARCHAR(20) NOT NULL, student_name VARCHAR(50), check_time DATETIME DEFAULT CURRENT_TIMESTAMP, attendance_date DATE NOT NULL, status TINYINT DEFAULT 1, FOREIGN KEY (student_id) REFERENCES employee(student_id), UNIQUE KEY uk_student_date (student_id, attendance_date) );face_encoding字段用BLOB存储还有一个好处特征向量是float数组每个浮点数4字节128维总共512字节BLOB足够。不要把特征转成JSON字符串存TEXT那样不仅浪费空间而且在反序列化时容易丢失精度。代码里取出来的时候用np.frombuffer(blob_data, dtypenp.float64)就行前提是存入时用tobytes()保持一致。4.3 考勤统计功能查询界面怎么组织毕设答辩时必问“考勤数据怎么查”。这套系统里应该提供一个查询面板支持按日期范围、按姓名筛选记录。常规做法是用SQL拼接条件。def query_attendance(student_idNone, start_dateNone, end_dateNone): sql SELECT ar.id, ar.student_id, ar.student_name, ar.check_time, ar.attendance_date, ar.status FROM attendance_record ar WHERE 11 params [] if student_id: sql AND ar.student_id %s params.append(student_id) if start_date: sql AND ar.attendance_date %s params.append(start_date) if end_date: sql AND ar.attendance_date %s params.append(end_date) sql ORDER BY ar.check_time DESC # 执行查询并返回表格数据注意这里的WHERE 11不是语法缺陷而是为了后面追加AND条件时不用判断是不是第一条代码维护时减少嵌套。查询结果可以显示在QTableWidget里还可以加一个“导出Excel”的功能用pandas的to_excel导出这样一个毕设的完整度就非常高了。5. 高分毕设答辩技巧与排错把代码讲出深度5.1 答辩演示时最容易翻车的点摄像头在教室或答辩现场光线通常偏复杂很多学生演示时人脸识别失败当场社死。这里给你三个稳妥的规避措施。第一演示前先在本地注册好5张以上人脸照片并且注册时的照片背景和答辩现场不要反差太大。第二在代码里加一个人脸检测框的调试显示当画面里出现绿色框但识别不出名字时快捷键把当前帧保存下来用于事后分析。第三把识别阈值临时调高一点比如从0.4调到0.45现场成功率会明显上升答辩结束后再调回来。还有一个容易被忽略的问题MySQL连接不稳定。答辩时笔记本上MySQL服务没启动程序启动直接报错。建议在代码里加一个连接重试机制或者在main函数里先检测MySQL连通性再打开摄像头否则数据库异常会中断整个程序。5.2 让导师觉得有深度的三个改进点如果拿98分还不够你可以在这套系统基础上低成本增加下面三个功能任何一个都能让答辩老师追问细节。第一个是活体检测。现在的代码只做静态特征比对拿一张照片就能骗过系统。用OpenCV做一个简单的眨眼检测检测眼睛关键点计算EAR眼睛纵横比连续几帧EAR从高到低再升高就认为是眨眼。人脸识别成功后再判断是否眨眼能大幅提高可信度。# 活体检测 - 眨眼判断EAR算法 def eye_aspect_ratio(eye): # 利用 dlib 的 68 点关键点提取左右眼 A np.linalg.norm(eye[1] - eye[5]) B np.linalg.norm(eye[2] - eye[4]) C np.linalg.norm(eye[0] - eye[3]) ear (A B) / (2.0 * C) return ear第二个是迟到判断。考勤记录表里加一个work_start_time字段打卡时对比当前时间晚于9:00就在status字段标记为1迟到统计时按状态聚合。这个改动只需要在insert前多一个if判断。第三个是GPU加速。如果学生机有NVIDIA显卡可以把face_recognition的人脸检测从hog切换到cnncuda加速后准确率更高。代码里只需要改config中的FACE_MODELcnn但要注意dlib必须用CUDA版本编译。5.3 常见错误清单表排错时建议对照这张表逐项检查大多数运行时报错都能在里面命中。报错现象可能原因解决方式AttributeError: module face_recognition has no attribute face_locationsface_recognition版本旧或dlib未绑定pip install --upgrade face_recognition dlib19.24.2cv2.error: OpenCV(4.x) ... source array is not a float32特征向量数组类型不对比对前用np.array(encodings, dtypenp.float32)转换OSError: Unable to open DB file打开了损坏的h5/训练模型文件重新解压原.zip确认face.py同目录下资源完整MySQL连接超时用户权限只允许localhost执行GRANT ALL ON face_attendance.* TO rootlocalhost摄像头黑屏不显示摄像头被其他程序占用关闭微信/钉钉等占用摄像头的应用后重试同一人多次打卡未处理重复打卡逻辑在insert前查询今天记录或利用UNIQUE KEY异常捕获5.4 模块化改进把识别服务单独封装源码结构目前是UI和业务逻辑混在一起虽然能跑但复用性弱。如果你想让代码显得更工程化可以把人脸识别部分拆成单独的face_service.py文件对外暴露register_face、recognize_face、mark_attendance三个接口。这样UI层只负责显示数据库层只负责存取核心识别逻辑可以单独用命令行脚本测试。面试官或导师看到你主动拆分模块会认为你有软件工程意识。拆分后的结构大致是这样face_attendance/ ├── app.py # 程序入口 ├── ui_main.py # PyQt5 界面 ├── face_service.py # 人脸注册/识别接口 ├── db_service.py # MySQL 操作封装 ├── config.py # 全局配置 ├── static/ # 图片、CSS 等界面资源 ├── face_images/ # 注册人脸临时照片 └── video_record/ # 打卡视频留存我在实际项目中一般还会在face_service.py里加一个缓存字典把已注册的人脸特征提前加载到内存避免每次比对都查一遍数据库。加载后字典结构是{name: encoding}识别时直接遍历字典就行。这种优化在人员数量不超过几千时效果明显响应时间从200ms降到20ms以内。5.5 演示视频录制与文档配套毕设最终不只是交代码还需要演示视频和说明文档。录制视频时优先用脚本把整个流程固定下来启动系统 - 展示注册界面 - 注册一个人脸 - 摄像头前识别成功 - 展示打卡记录 - 导出Excel。这样避免现场操作失误也让答辩老师一眼看到完整链路。说明文档里除了写原理和环境安装建议把FACE_TOLERANCE这个参数单独写成“参数实验”小节用0.3、0.4、0.5、0.6四组阈值分别测试误识率和漏识率用表格列出数据。这种实验数据能直接证明你的系统经过调优验证比空谈“识别率高”更有说服力。本文还有配套的精品资源点击获取