资讯详情

Python+OpenCV+PyQt5人脸考勤系统实战:从源码到落地

📅 2026/9/28 1:25:55 | 华诺云谱 👁 阅读
Python+OpenCV+PyQt5人脸考勤系统实战:从源码到落地
简介这份资源是面向计算机相关专业学生与Python初学者的人脸识别考勤签到系统完整源码包可作为课程设计、毕业设计或自学练手项目。系统以Python为主语言借助OpenCV实现人脸检测与识别采用Haar级联分类器完成人脸定位并结合特征提取方法进行身份比对同时用PyQt5搭建可视化操作界面涵盖签到、查询等功能还涉及数据库存储、文件读写、时间处理与异常捕获等实用环节。压缩包共44个文件约681KB包含12个py源码、16个pyc编译文件、10个xml分类器与界面配置、2个ui界面文件及说明文档结构清晰、注释详尽。目前已有266人学习。读者可据此掌握从人脸检测、界面交互到考勤记录落地的完整实现思路理解各模块协作方式并借助详细注释快速上手二次开发与功能扩展。1. 从一份能跑起来的 PythonOpenCV 人脸考勤源码说起很多人第一次接触人脸识别考勤是在 GitHub 或资源站上翻到一份「PythonOpenCVPyQt5 考勤签到系统」的压缩包解压后一堆 .py 文件加一个 .ui 界面文件双击 main.py 却报ModuleNotFoundError: No module named cv2或者摄像头打开了但窗口一片黑。这份源码真正要解决的问题是把「摄像头采集 → 人脸检测 → 特征比对 → 签到记录落库 → PyQt5 界面展示」这条链路串成一个能日常运行的桌面程序而不是停留在跑通一个cv2.imshow的 demo。它适合两类人一类是刚学完 Python 基础、想找一个综合项目练手的入门者另一类是要给实验室、小公司做一套离线打卡工具、不想接第三方云服务的开发者。下面我按自己实际搭这套系统的顺序把选型、代码、参数和踩过的坑讲清楚你照着改就能落地。2. 技术选型为什么是 OpenCV PyQt5 而不是别的组合2.1 人脸检测用 Haar 还是 DNN先看你的硬件和精度要求OpenCV 自带的人脸检测方案主要有两代Haar 级联分类器和基于深度学习的人脸检测器如 SSD、YuNet。Haar 的优点是零额外模型文件、CPU 上就能跑、代码三行搞定缺点是侧脸、遮挡、逆光下漏检率明显偏高误检也多。DNN 方案精度高很多但需要额外下载模型文件推理速度依赖 CPU 性能。我一般这样选如果只是课程设计、几十个人的小场景Haar 够用先把链路跑通如果要做真实考勤、要求识别率直接上 OpenCV 4.5.4 之后内置的FaceDetectorYNYuNet它只有几百 KBCPU 上单帧几毫秒精度比 Haar 高一个档次。import cv2 # 方案一Haar 级联零依赖适合快速验证 haar cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces haar.detectMultiScale(gray, scaleFactor1.1, minNeighbors5, minSize(80, 80)) # 方案二YuNet DNN 检测器需要 yunet.onnx 模型文件 detector cv2.FaceDetectorYN.create( face_detection_yunet_2023mar.onnx, , (320, 320), score_threshold0.9, nms_threshold0.3, top_k5000 ) detector.setInputSize((frame.shape[1], frame.shape[0])) _, faces detector.detect(frame)scaleFactor1.1表示每次图像金字塔缩小 10%值越小检测越细但越慢minNeighbors5控制误检调高会减少误检但可能漏掉侧脸minSize是能接受的最小人脸像素设太小会把背景噪点当人脸。YuNet 的score_threshold建议 0.85~0.9低于 0.8 会开始出现把墙上的画框认成人脸的情况。2.2 识别环节LBPH 够用但要知道它的边界检测到人脸之后要判断「这是谁」。常见做法有三种LBPHOpenCV 自带、FaceNet 类深度特征、以及直接调用云端 API。离线桌面考勤场景我一般用 LBPH因为它不需要 GPU、不需要额外模型、训练几行代码而且对光照变化有一定鲁棒性。recognizer cv2.face.LBPHFaceRecognizer_create( radius1, neighbors8, grid_x8, grid_y8, threshold70.0 ) # images: 灰度人脸列表, labels: 对应的人名编号 recognizer.train(images, np.array(labels)) label, confidence recognizer.predict(gray_face) # confidence 越小越像低于 threshold 才认为是同一个人threshold是 LBPH 最关键的一个参数它决定「多不像才算陌生人」。设太高会把陌生人认成已注册的人设太低本人也会被拒。经验值 60~80 之间具体要拿你自己的数据试。radius和neighbors控制局部二值模式的感受野一般不用改。注意LBPH 对正脸、光照均匀的场景效果好一旦侧脸超过 30 度或者戴帽子遮挡额头置信度会明显变差。如果你的场景里人走动快、角度大老老实实换深度特征。2.3 PyQt5 负责界面但别把识别逻辑塞进主线程PyQt5 做界面本身没问题坑在于很多人把摄像头循环直接写在主线程里结果界面卡死、按钮点不动。正确做法是把采集和识别放到QThread里通过信号把结果发回主线程刷新界面。from PyQt5.QtCore import QThread, pyqtSignal class CameraThread(QThread): frame_ready pyqtSignal(object) # 传 QImage 给界面 sign_in pyqtSignal(str) # 传签到人名 def run(self): cap cv2.VideoCapture(0) while self.running: ok, frame cap.read() if not ok: continue # 检测 识别逻辑 self.frame_ready.emit(qimage) cap.release()信号槽是跨线程安全的pyqtSignal(object)传 numpy 数组或 QImage 都行。界面里用QLabel.setPixmap刷新画面签到结果用QTableWidget追加一行。这样即使识别一帧要 100ms界面也不会卡。3. 从零把考勤系统跑起来环境、采集、训练、签到四步3.1 环境搭建Python 版本和 OpenCV 安装的坑先说版本。OpenCV 的cv2.face模块LBPH 所在在官方 pip 包里是不包含的必须装opencv-contrib-python不是opencv-python。这是新手最容易翻车的地方——装了opencv-python然后cv2.face报AttributeError: module cv2 has no attribute face。# 推荐 Python 3.8~3.103.11 部分 contrib 轮子还不全 python -m venv venv venv\Scripts\activate # Windows source venv/bin/activate # Linux/macOS # 关键装 contrib 版本不是普通版本 pip install opencv-contrib-python4.8.1.78 pip install PyQt55.15.9 pip install numpy装完验证一下import cv2 print(cv2.__version__) print(hasattr(cv2, face)) # 必须输出 True如果hasattr是 False说明你装的是opencv-python卸载重装 contrib 版。另外 PyQt5 和 OpenCV 都带 Qt 库某些 Linux 发行版上会冲突报Could not load the Qt platform plugin xcb解决办法是pip install opencv-python-headless换成无界面版界面交给 PyQt5。3.2 人脸采集写一个能批量存图的脚本考勤系统第一步是录入人员。我一般写一个采集脚本按人名建文件夹每人采 30~50 张不同角度和光照的灰度人脸。import cv2, os name input(输入姓名: ).strip() save_dir os.path.join(dataset, name) os.makedirs(save_dir, exist_okTrue) haar cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) cap cv2.VideoCapture(0) count 0 while count 40: ok, frame cap.read() if not ok: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces haar.detectMultiScale(gray, 1.1, 5, minSize(100, 100)) for (x, y, w, h) in faces: count 1 face gray[y:yh, x:xw] face cv2.resize(face, (200, 200)) # 统一尺寸LBPH 要求 cv2.imwrite(fdataset/{name}/{count}.jpg, face) cv2.rectangle(frame, (x, y), (xw, yh), (0, 255, 0), 2) cv2.imshow(collect, frame) if cv2.waitKey(1) 0xFF 27: break cap.release() cv2.destroyAllWindows()几个参数要留意minSize(100,100)保证采到的人脸不会太小导致特征糊resize到 200×200 是 LBPH 训练前的统一尺寸不统一会在训练时报维度错误采集时让人左右转头、换几个位置比对着镜头一动不动采 40 张效果好得多。存成灰度图能省一半空间也避免训练时再转换。3.3 训练模型把 dataset 变成可加载的 yml采集完所有人之后跑一次训练LBPH 训练很快几十人几百张图几秒钟。import cv2, os, numpy as np def load_dataset(rootdataset): faces, labels, names [], [], {} for idx, person in enumerate(sorted(os.listdir(root))): names[idx] person pdir os.path.join(root, person) for f in os.listdir(pdir): img cv2.imread(os.path.join(pdir, f), cv2.IMREAD_GRAYSCALE) if img is None: continue faces.append(img) labels.append(idx) return faces, np.array(labels), names faces, labels, names load_dataset() recognizer cv2.face.LBPHFaceRecognizer_create(threshold70.0) recognizer.train(faces, labels) recognizer.save(trainer.yml) # 名字映射单独存yml 里只有数字标签 import json with open(names.json, w, encodingutf-8) as fp: json.dump(names, fp, ensure_asciiFalse)这里有个设计决定LBPH 的trainer.yml只存标签编号不存人名所以人名映射要单独用 json 存。很多人图省事把名字直接当标签传进去训练能过但预测返回的是字符串和predict的返回类型对不上后面判断逻辑全乱。标签用整数、名字单独映射是更稳的做法。3.4 签到主循环识别、去重、写记录主循环要做三件事识别当前人脸、判断是不是同一个人短时间内重复出现、把有效签到写进 CSV 或 SQLite。import cv2, json, csv, time, numpy as np recognizer cv2.face.LBPHFaceRecognizer_create() recognizer.read(trainer.yml) names json.load(open(names.json, encodingutf-8)) haar cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) last_seen {} # 人名 - 上次签到时间戳 COOLDOWN 60 # 同一人 60 秒内不重复签到 cap cv2.VideoCapture(0) while True: ok, frame cap.read() if not ok: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces haar.detectMultiScale(gray, 1.1, 5, minSize(100, 100)) for (x, y, w, h) in faces: face cv2.resize(gray[y:yh, x:xw], (200, 200)) label, conf recognizer.predict(face) if conf 70: name names.get(str(label), unknown) now time.time() if now - last_seen.get(name, 0) COOLDOWN: last_seen[name] now with open(attendance.csv, a, newline, encodingutf-8) as fp: csv.writer(fp).writerow( [name, time.strftime(%Y-%m-%d %H:%M:%S)] ) cv2.putText(frame, f{name} {conf:.0f}, (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()COOLDOWN是防重复签到的关键没有它摄像头每帧都识别成功一秒能写几十条记录。conf 70这个阈值要和训练时的threshold保持一致否则会出现训练说 70、判断用 50 的错位。CSV 用追加模式程序崩了也不丢已签到的数据。4. 避坑与排查这套系统最容易翻车的五个地方4.1 摄像头打开是黑的或者报错现象cap.read()返回 False或者窗口全黑。原因通常是摄像头被其他程序占用比如刚关掉的微信视频、另一个 Python 进程没释放或者VideoCapture(0)的索引不对。解决先确认没有其他程序占用Windows 上可以在设备管理器看摄像头状态多摄像头机器把 0 换成 1、2 试Linux 下检查/dev/video*权限必要时把用户加进 video 组。程序退出时一定要cap.release()否则下次打开还是占用状态。4.2 cv2.face 找不到或者 LBPH 训练报维度错误现象一AttributeError: module cv2 has no attribute face。原因装了opencv-python而不是opencv-contrib-python。解决pip uninstall opencv-python opencv-contrib-python然后只装 contrib 版别两个都装会互相覆盖。现象二训练时OpenCV Error: Bad argument (Wrong input image size)。原因采集的人脸图尺寸不统一LBPH 要求所有训练图同尺寸。解决采集脚本里统一resize到固定尺寸训练前再检查一遍所有图 shape 是否一致。4.3 识别总是认错人或者把陌生人认成已注册的人现象置信度普遍偏低或者陌生人也被识别成某个人。原因有三个一是threshold设太高把不像的也放行二是训练样本太少或角度单一模型没见过变化三是光照差异太大训练时是白天、识别时是晚上。解决先把threshold从 70 降到 60 试观察本人和陌生人的置信度分布找一个能分开两者的值每人补采到 40 张以上覆盖不同角度和光照如果场景光照变化大考虑加直方图均衡化cv2.equalizeHist预处理。4.4 PyQt5 界面卡死、按钮点不动现象摄像头画面能显示但界面按钮无响应或者关窗口时程序不退出。原因识别循环跑在主线程阻塞了 Qt 事件循环。解决把采集识别放进QThread用pyqtSignal回传结果窗口关闭时在closeEvent里把线程的running标志置 False 并wait()等它结束否则线程还在跑程序退不干净。4.5 打包成 exe 后模型文件找不到现象源码里跑得好好的用 PyInstaller 打包后报找不到trainer.yml或haarcascade。原因打包后工作目录变了相对路径失效。解决用sys._MEIPASS判断是否在打包环境把模型文件路径统一走一个resource_path()函数haarcascade可以直接从cv2.data.haarcascades取不用自己带trainer.yml和names.json建议放在 exe 同级的data目录用os.path.dirname(sys.executable)定位。5. 让考勤系统真正好用的两个进阶技巧5.1 用 SQLite 替代 CSV顺便解决并发和查询CSV 适合验证但真用起来会碰到问题多人同时签到写文件可能冲突查某人某月记录要全表扫。换成 SQLite 只要改几行还自带事务。import sqlite3, time conn sqlite3.connect(attendance.db, check_same_threadFalse) conn.execute( CREATE TABLE IF NOT EXISTS records ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, ts TEXT NOT NULL, UNIQUE(name, ts) ) ) def sign_in(name): ts time.strftime(%Y-%m-%d %H:%M:%S) try: conn.execute(INSERT INTO records(name, ts) VALUES (?, ?), (name, ts)) conn.commit() return True except sqlite3.IntegrityError: return False # 重复签到check_same_threadFalse允许跨线程用同一个连接配合UNIQUE(name, ts)约束重复插入直接抛异常比在 Python 里维护last_seen字典更可靠。查询某人本月记录一条 SQL 搞定导出 Excel 也方便。5.2 加一个「活体检测」的土办法挡住照片打卡LBPH 分不清真人和手机里的照片这是离线考勤最尴尬的地方。不引入额外模型的前提下我一般加两个轻量判断一是要求连续 N 帧都识别到同一人且置信度稳定照片对着摄像头也会稳定所以这条不够二是加眨眼检测用 OpenCV 自带的眼睛级联分类器检测到眼睛区域在 2 秒内消失又出现才认为有活体动作。eye_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_eye.xml ) eyes eye_cascade.detectMultiScale(face_roi, 1.1, 10) # 记录连续帧的眼睛数量出现 0 - 有 - 0 的跳变才算眨眼这个办法不完美戴眼镜、眯眼会误判但比完全没有强。真要严格就得上红外摄像头或者深度相机那是另一个成本量级了。5.3 参数速查表参数位置建议值作用scaleFactordetectMultiScale1.1金字塔缩放步长越小越慢越细minNeighborsdetectMultiScale5误检抑制调高减少误检minSizedetectMultiScale(100,100)最小人脸像素thresholdLBPHFaceRecognizer60~80判定陌生人的置信度门槛COOLDOWN签到逻辑60 秒同一人重复签到间隔score_thresholdYuNet0.9DNN 检测置信度门槛这套系统我从最早用 HaarLBPH 跑通到后来换 YuNet、加 SQLite、加眨眼检测前后改了好几版。最大的教训是别一上来就追求高精度先把采集-训练-识别-落库这条链路跑通再拿真实数据去调 threshold。我见过太多人卡在环境安装和线程卡死上其实核心逻辑就那么一百来行。把上面每一步的代码抄下来换成自己的人名和路径半小时就能跑起来。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑