
简介这是一套基于OpenCV人脸识别的考勤签到系统Python项目源码面向计算机相关专业正在做毕业设计、课程设计或大作业的学生以及需要项目实战练习的开发者。项目经导师指导并认可通过评审分98分代码功能经验证可稳定运行适合作为入门进阶或毕设立项演示。压缩包共43个文件约685KB包含12个py源码文件、16个pyc编译文件、10个xml分类器文件、2个ui界面文件及txt依赖说明等涵盖人脸检测、摄像头调用、签到上传、功能函数与主窗口等模块并配有详细注释和GUI界面。资源已有254人学习下载可帮助读者快速理解人脸识别考勤的完整实现流程掌握OpenCV级联分类器调用、界面交互与数据上传等关键环节同时留有丰富的二次开发空间便于在此基础上拓展功能或直接用于项目答辩展示。1. 从一张考勤表说起OpenCV 人脸识别签到系统到底能跑多快很多做毕设的同学第一次接触人脸识别考勤脑子里想的都是“摄像头一开谁来了自动打勾”。真动手才发现光照一变识别率就掉人脸稍微偏一点就认不出GUI 界面和识别线程一卡就假死。这套基于 OpenCV 的人脸识别考勤签到系统核心就是用 Python 把摄像头采集、人脸检测、特征比对、签到记录、GUI 展示串成一条能跑通的链路。它适合两类人一类是毕设需要完整可演示系统的学生另一类是想用 Python 快速验证人脸识别落地效果的开发者。OpenCV 在这里承担的是检测和预处理识别部分常见做法是接 LBPH 或 EigenfacesGUI 用 Tkinter 或 PyQt 都能做。整条链路不复杂但参数没调好翻车是常事。2. 人脸识别考勤系统的技术选型为什么是 OpenCV LBPH Tkinter2.1 检测用 Haar 还是 DNN先看你的机器和场景OpenCV 自带的人脸检测器有两类Haar 级联和 DNN 模块。Haar 级联的优点是轻量、CPU 就能跑、不需要额外模型文件缺点是侧脸和遮挡场景下漏检明显。DNN 模块通常加载 Caffe 或 TensorFlow 模型精度高不少但对环境有要求尤其是opencv-python和opencv-contrib-python版本要对上。我一般会这样选如果只是毕设演示摄像头正对、光照稳定Haar 足够如果要做门禁机那种真实场景建议直接上 DNN。Haar 的检测参数里scaleFactor和minNeighbors是最影响体验的两个。scaleFactor设 1.1 到 1.3 之间太小会慢太大会漏minNeighbors设 3 到 6太低误检多太高漏检多。import cv2 # 加载 Haar 级联检测器文件在 cv2.data.haarcascades 目录下 face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) # 读取一帧图像并转灰度Haar 检测在灰度图上做 img cv2.imread(test.jpg) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # detectMultiScale 三个关键参数 # scaleFactor1.2 表示每次图像尺寸缩小 20%兼顾速度和召回 # minNeighbors5 表示一个候选框周围至少 5 个邻居才保留抑制误检 # minSize(80,80) 过滤掉太小的误检区域 faces face_cascade.detectMultiScale( gray, scaleFactor1.2, minNeighbors5, minSize(80, 80) ) for (x, y, w, h) in faces: cv2.rectangle(img, (x, y), (x w, y h), (0, 255, 0), 2) cv2.imshow(faces, img) cv2.waitKey(0) cv2.destroyAllWindows()这段代码的逻辑是灰度化降低计算量detectMultiScale做多尺度滑动窗口检测返回人脸框坐标。参数说明里scaleFactor越小检测越慢但越不容易漏minNeighbors直接决定误检和漏检的平衡。实际调试时先把minNeighbors调到 5 左右再根据画面里有没有误框来微调。2.2 识别用 LBPH 而不是 Eigenfaces理由很实际OpenCV 的face模块提供三种识别器Eigenfaces、Fisherfaces、LBPH。Eigenfaces 对光照非常敏感训练集里光照一变识别率断崖式下跌。Fisherfaces 稍好但需要每类多张样本。LBPH 基于局部二值模式对光照变化鲁棒而且支持增量训练适合考勤这种“新人随时录入”的场景。LBPH 的核心参数是radius、neighbors、grid_x、grid_y和threshold。radius和neighbors控制 LBP 算子一般 radius1、neighbors8 是默认值够用。grid_x和grid_y把人脸图分成网格默认 8x8如果人脸图分辨率高可以适当加大。threshold是识别阈值超过这个距离就判为未知这个参数直接决定“认错人”和“认不出”的比例。import cv2 import numpy as np # 创建 LBPH 识别器 # radius1, neighbors8 是标准 LBP 算子参数 # grid_x8, grid_y8 表示将人脸区域划分为 8x8 的网格 # threshold80 是距离阈值超过则判为未知需要根据实际数据调 recognizer cv2.face.LBPHFaceRecognizer_create( radius1, neighbors8, grid_x8, grid_y8, threshold80 ) # 假设 faces 是灰度人脸图列表labels 是对应的人员 ID # recognizer.train(faces, np.array(labels)) # 预测时返回 (label, confidence) # confidence 是距离值越小表示越像低于 threshold 才认为是同一人 # label, confidence recognizer.predict(gray_face)逻辑说明LBPH 不直接比对像素而是比对局部纹理直方图所以对整体亮度变化不敏感。threshold是最需要调的参数设太低会把本人判成未知设太高会把别人认成本人。我一般会先用一批测试样本跑一遍看本人和陌生人各自的 confidence 分布再取一个中间值。2.3 GUI 用 Tkinter 还是 PyQt看你要不要打包Tkinter 是 Python 自带不用额外装代码量少适合毕设快速出界面。PyQt 界面更现代但打包成 exe 时体积大而且和 OpenCV 的视频线程配合需要额外注意信号槽机制。考勤系统的 GUI 通常需要这几个区域实时视频显示、签到按钮或自动签到状态、已签到人员列表、日志输出。Tkinter 里用Label配合PIL.ImageTk显示视频帧用Listbox显示签到记录。关键点是视频刷新不能阻塞主线程常见做法是用after方法定时刷新或者把摄像头读取放到单独线程里。import tkinter as tk from PIL import Image, ImageTk import cv2 root tk.Tk() root.title(人脸识别考勤签到系统) # 用于显示视频的 Label video_label tk.Label(root) video_label.pack() # 签到记录列表 sign_list tk.Listbox(root, width40, height10) sign_list.pack() cap cv2.VideoCapture(0) def update_frame(): ret, frame cap.read() if ret: # OpenCV 是 BGRPIL 需要 RGB rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) img Image.fromarray(rgb) imgtk ImageTk.PhotoImage(imageimg) video_label.imgtk imgtk video_label.configure(imageimgtk) # 每 30 毫秒刷新一次约 33 帧 root.after(30, update_frame) update_frame() root.mainloop()这段代码里after(30, update_frame)是 Tkinter 的定时器避免用while循环卡死界面。video_label.imgtk imgtk这一行是必须的否则图像对象会被回收导致显示空白。参数上刷新间隔 30ms 对应约 33fps如果机器性能不够可以调到 50ms。3. 从零跑通签到流程环境、录入、训练、识别四步走3.1 环境安装避开 opencv-python 和 contrib 的版本坑OpenCV 的 Python 包有两个opencv-python和opencv-contrib-python。人脸识别模块cv2.face在 contrib 包里只装opencv-python会报module cv2 has no attribute face。所以考勤系统必须装 contrib 版本。# 建议用虚拟环境避免和系统 Python 冲突 python -m venv venv # Windows 激活 venv\Scripts\activate # Linux/Mac 激活 source venv/bin/activate # 安装 contrib 版本包含 face 模块 pip install opencv-contrib-python # 安装 Pillow 用于 GUI 显示 pip install Pillow # 如果需要用 PyQt # pip install PyQt5安装完成后用下面这行验证cv2.face是否可用import cv2 print(hasattr(cv2, face)) # 应该输出 True如果输出 False说明装的是普通opencv-python需要先卸载再装 contrib 版本。常见报错ModuleNotFoundError: No module named cv2通常是虚拟环境没激活或者 pip 装到了别的 Python 下。3.2 人脸录入采集多少张、怎么存、标签怎么定录入环节决定后面识别的上限。我一般建议每个人采集 30 到 50 张包含轻微的角度变化和表情变化但不要过度夸张。采集时用检测器框出人脸统一缩放到固定尺寸比如 200x200然后存成灰度图。import cv2 import os face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) # 人员 ID 和姓名映射实际项目里可以从数据库或 JSON 读 person_id 1 person_name zhangsan save_dir fdataset/{person_id}_{person_name} os.makedirs(save_dir, exist_okTrue) cap cv2.VideoCapture(0) count 0 while count 40: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, 1.2, 5, minSize(80, 80)) for (x, y, w, h) in faces: # 统一缩放到 200x200减少后续识别时的尺寸差异 face_roi cv2.resize(gray[y:yh, x:xw], (200, 200)) count 1 cv2.imwrite(f{save_dir}/{count}.jpg, face_roi) cv2.rectangle(frame, (x, y), (xw, yh), (0, 255, 0), 2) cv2.putText(frame, fcount: {count}, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow(collect, frame) if cv2.waitKey(1) 0xFF 27: break cap.release() cv2.destroyAllWindows()逻辑说明每检测到一张人脸就保存一张文件名用序号。resize到 200x200 是为了让 LBPH 的网格划分一致。参数上采集数量 40 是经验值太少识别不稳太多训练慢且容易过拟合。保存目录用ID_姓名格式方便训练时解析标签。3.3 训练模型把 dataset 转成 LBPH 能吃的格式训练前需要遍历 dataset 目录读取每张图解析出人员 ID然后调用recognizer.train。训练完成后保存成trainer.yml识别时直接加载。import cv2 import numpy as np import os recognizer cv2.face.LBPHFaceRecognizer_create( radius1, neighbors8, grid_x8, grid_y8, threshold80 ) dataset_dir dataset faces [] labels [] # 目录名格式为 ID_姓名例如 1_zhangsan for dir_name in os.listdir(dataset_dir): dir_path os.path.join(dataset_dir, dir_name) if not os.path.isdir(dir_path): continue # 解析人员 ID person_id int(dir_name.split(_)[0]) for file_name in os.listdir(dir_path): img_path os.path.join(dir_path, file_name) img cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) if img is None: continue faces.append(img) labels.append(person_id) recognizer.train(faces, np.array(labels)) recognizer.save(trainer.yml) print(ftrained on {len(faces)} images, {len(set(labels))} persons)逻辑说明IMREAD_GRAYSCALE保证读进来就是灰度图和录入时一致。labels是整数数组LBPH 内部按标签分类。训练完成后保存trainer.yml这个文件包含直方图特征体积通常不大。参数上如果样本数量少可以适当降低grid_x和grid_y减少特征维度。3.4 识别签到阈值调优和防重复签到识别环节把摄像头帧里的人脸检测出来送进 LBPH 预测得到标签和 confidence。confidence 低于阈值才认为是本人然后检查是否已经签到过避免同一人反复写入。import cv2 import numpy as np from datetime import datetime recognizer cv2.face.LBPHFaceRecognizer_create() recognizer.read(trainer.yml) face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) # 人员 ID 到姓名的映射 id_name_map {1: zhangsan, 2: lisi} # 已签到集合实际项目可以写数据库 signed set() cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, 1.2, 5, minSize(80, 80)) for (x, y, w, h) in faces: face_roi cv2.resize(gray[y:yh, x:xw], (200, 200)) label, confidence recognizer.predict(face_roi) # confidence 越小越像阈值 80 是 LBPH 创建时设的 if confidence 80 and label in id_name_map: name id_name_map[label] if name not in signed: signed.add(name) now datetime.now().strftime(%Y-%m-%d %H:%M:%S) print(f{name} 签到成功 {now}) # 实际项目这里写文件或数据库 cv2.putText(frame, f{name} {confidence:.1f}, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) else: cv2.putText(frame, unknown, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2) cv2.rectangle(frame, (x, y), (xw, yh), (0, 255, 0), 2) cv2.imshow(attendance, frame) if cv2.waitKey(1) 0xFF 27: break cap.release() cv2.destroyAllWindows()逻辑说明predict返回的 confidence 是距离值不是百分比所以阈值要根据实际数据调。signed集合防止重复签到实际项目里应该用数据库唯一约束。参数上如果发现本人经常被拒把阈值从 80 调到 90 或 100如果陌生人被误认就往下调。4. 避坑与排查考勤系统最容易翻车的五个地方4.1 摄像头打开但画面全黑或报错现象cap.read()返回 False或者窗口一片黑。原因通常是摄像头索引不对或者被其他程序占用。解决把cv2.VideoCapture(0)换成 1 或 2 试Windows 下可以在设备管理器看摄像头序号。另外有些笔记本的摄像头需要先按快捷键启用。4.2 识别时 confidence 一直很大本人都认不出现象同一个人录入后识别confidence 始终高于阈值。原因可能是录入和识别时的预处理不一致比如录入时缩放到 200x200识别时没缩放或者灰度化方式不同。解决确保录入和识别都走同一套cvtColor和resize。另外LBPH 的threshold设太低也会导致这个问题可以先临时调高到 120 看 confidence 实际分布。4.3 GUI 卡死视频画面不动现象Tkinter 窗口点一下就无响应视频停住。原因是视频读取和识别放在了主线程阻塞了事件循环。解决把摄像头读取和识别放到单独线程用队列把结果传给 GUI 线程刷新。或者用after定时器每次只处理一帧不写while循环。4.4 训练时报 “labels must be integer” 或形状不匹配现象recognizer.train抛异常。原因通常是labels是字符串或者 list 里混了非整数或者faces里图片尺寸不一致。解决解析目录名时用int()转标签读取图片后统一resize到相同尺寸。如果图片有彩色用IMREAD_GRAYSCALE读。4.5 打包成 exe 后找不到 haarcascade 文件现象PyInstaller 打包后运行报错找不到haarcascade_frontalface_default.xml。原因cv2.data.haarcascades的路径在打包后变了。解决把 xml 文件复制到项目目录用相对路径加载或者在 PyInstaller 的 spec 文件里用datas把 cv2 的数据目录打进去。5. 把签到记录落成 CSV 并做每日统计一个实用收尾技巧签到系统跑通之后真正让毕设加分的是数据落地和统计。我一般会把签到记录写成 CSV字段包括姓名、时间、日期然后用 pandas 做每日统计。这样演示的时候不仅能看实时识别还能展示“今天谁来了、谁没来”。import csv import os from datetime import datetime CSV_FILE attendance.csv def init_csv(): # 文件不存在时写入表头 if not os.path.exists(CSV_FILE): with open(CSV_FILE, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([name, date, time]) def sign_in(name): now datetime.now() date_str now.strftime(%Y-%m-%d) time_str now.strftime(%H:%M:%S) with open(CSV_FILE, a, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([name, date_str, time_str]) init_csv() # 识别成功后调用 sign_in(name)逻辑说明newline是 Windows 下避免多空行的关键encodingutf-8防止中文姓名乱码。每次签到追加一行不做覆盖。统计时用 pandas 按date分组count去重姓名即可。import pandas as pd df pd.read_csv(attendance.csv) # 按日期统计签到人数去重姓名 daily df.groupby(date)[name].nunique() print(daily)参数上如果考勤规则是“一天只算一次”可以在写入前先查 CSV 里当天是否已有该姓名。数据量大了之后CSV 会变慢可以换成 SQLite但毕设演示 CSV 足够。最后说一个我踩过的坑LBPH 的threshold不要一次设死我一般会在代码里留一个配置文件或者命令行参数演示前根据现场光照快速调。还有录入样本时尽量让同一个人换一两件衣服、稍微换个角度否则识别器学到的特征太单一现场一换环境就翻车。希望帮到你。本文还有配套的精品资源点击获取