拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于CNN神经网络的人脸识别考勤系统:Python+OpenCV+PyQt5毕业设计实战

简介这是一套面向高校计算机相关专业学生的毕业设计级项目源码主题为基于CNN神经网络的人脸识别考勤系统采用PyQt5构建图形界面适合作为毕设、期末大作业或课程设计的高分参考方案。项目将深度学习人脸识别与考勤签到业务结合涵盖模型推理、摄像头采集、人脸注册与签到等完整流程代码注释详尽新手也能读懂并快速部署运行。资源包共30个文件约32.54MB包含11个Python源码文件、6个编译文件、3个界面ui文件、3张示例图片以及CNN部署配置prototxt、训练好的caffemodel模型、人脸检测xml与字体文件等结构清晰、模块分明。目前已有142人学习下载。读者可获得一套可直接运行的完整项目理解CNN人脸识别与PyQt5界面联动的实现思路并借助文档说明与注释快速完成二次开发与答辩准备。1. 从一张考勤表说起CNN 人脸识别考勤系统到底在做什么很多做 Python 毕业设计的同学卡住的地方不是模型不会训而是「训完模型之后怎么变成一个能演示、能答辩、能交差的东西」。基于 CNN 神经网络的人脸识别考勤系统本质上是把三件事串成一条线用卷积神经网络把人脸图像变成特征向量用特征比对判断「你是谁」再用 PyQt5 把识别结果落成一条带时间戳的考勤记录。它解决的不是「识别精度能不能刷到 99%」这种学术问题而是「一个普通笔记本摄像头前人走过去系统能不能稳定地记下这个人几点几分到了」。这套方案适合谁适合手里有 Python 基础、装过 OpenCV、但没做过完整 GUI 项目的本科生也适合想快速搭一个可演示原型的开发者。它的技术栈不复杂Python OpenCV 做人脸检测和图像预处理CNN 做特征提取或分类PyQt5 做界面SQLite 或 CSV 存考勤数据。真正决定成败的往往不是网络结构多深而是数据采集的一致性、阈值怎么定、界面线程怎么不卡死。下面按「先立住原理、再动手复现、最后讲坑」的顺序拆开讲。2. CNN 人脸识别考勤系统的技术选型与最小闭环2.1 为什么用 CNN 而不是传统特征脸传统做法里Eigenfaces、LBPH 这类方法在实验室的 ORL、Yale 数据集上能跑出不错的结果但一换到真实摄像头场景就翻车光照一变、角度一偏识别率断崖式下跌。CNN 的优势在于卷积核能自动学到边缘、纹理、局部结构这些对光照和姿态更鲁棒的特征。常见做法是用一个轻量卷积网络做分类或者用预训练模型如 MobileNet、FaceNet 思路抽特征再做距离比对。对毕业设计来说我一般建议两条路线二选一分类路线把每个注册人员当成一个类别CNN 最后一层 softmax 输出 N 类。优点是实现简单缺点是每加一个人就要重新训练。特征比对路线CNN 输出一个固定维度特征向量注册时存向量识别时算余弦相似度或欧氏距离。优点是加人不用重训缺点是阈值需要调。如果答辩要求「能动态加人」选特征比对如果只要求「固定几个人识别准确」选分类更省事。2.2 环境搭建Python、OpenCV、PyQt5 一次装对环境是第一个劝退点。血泪经验是不要混用 conda 和 pip 装 OpenCV容易出现cv2导入报错或 GUI 后端冲突。建议统一用虚拟环境。# 创建虚拟环境Python 3.8~3.10 兼容性最好 python -m venv face_env # Windows 激活 face_env\Scripts\activate # Linux / macOS 激活 source face_env/bin/activate # 一次性装齐核心依赖 pip install opencv-python4.8.1.78 pip install PyQt55.15.9 pip install numpy1.24.3 pip install tensorflow2.13.0 # 或 pytorch二选一 pip install pillow逻辑说明opencv-python负责摄像头读取和 Haar/DNN 人脸检测PyQt5负责界面numpy做矩阵运算深度学习框架按你熟悉的选。参数上OpenCV 版本不要追最新4.8.x 在 Windows 上对摄像头和 GUI 支持最稳。TensorFlow 2.13 是最后一个原生支持 Windows GPU 的版本之一如果只用 CPU 训练版本可以更宽松。装完立刻验证别等到写界面才发现问题import cv2 import numpy as np from PyQt5.QtWidgets import QApplication import sys print(OpenCV:, cv2.__version__) cap cv2.VideoCapture(0) ret, frame cap.read() print(摄像头读取:, ret, frame.shape if ret else 失败) cap.release() app QApplication(sys.argv) print(PyQt5 初始化正常)如果VideoCapture(0)返回 False先查摄像头是否被其他程序占用再查驱动。Linux 下还要确认当前用户有 video 组权限。2.3 人脸检测与数据采集把「注册」这一步做扎实识别准不准七成看采集。我一般会写一个采集脚本用 OpenCV 的 DNN 人脸检测器比 Haar 稳对每个人采集 100~200 张覆盖正脸、轻微左右偏、戴不戴眼镜、室内不同灯光。import cv2 import os # 使用 OpenCV DNN 人脸检测模型需提前下载 deploy.prototxt 和 res10_300x300_ssd_iter_140000.caffemodel net cv2.dnn.readNetFromCaffe( deploy.prototxt, res10_300x300_ssd_iter_140000.caffemodel ) def collect_faces(person_name, save_dirdataset, max_count150): save_path os.path.join(save_dir, person_name) os.makedirs(save_path, exist_okTrue) cap cv2.VideoCapture(0) count 0 while count max_count: ret, frame cap.read() if not ret: break h, w frame.shape[:2] blob cv2.dnn.blobFromImage( cv2.resize(frame, (300, 300)), 1.0, (300, 300), (104.0, 177.0, 123.0) ) net.setInput(blob) detections net.forward() for i in range(detections.shape[2]): confidence detections[0, 0, i, 2] if confidence 0.6: # 置信度阈值低于 0.5 容易误检 box detections[0, 0, i, 3:7] * [w, h, w, h] x1, y1, x2, y2 box.astype(int) face frame[max(0, y1):min(h, y2), max(0, x1):min(w, x2)] if face.size 0: continue face cv2.resize(face, (128, 128)) cv2.imwrite(f{save_path}/{count:04d}.jpg, face) count 1 cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imshow(Collect, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明DNN 检测器输出的是归一化坐标乘回宽高得到像素框confidence 0.6是经验阈值太低会把背景误当人脸太高会漏检侧脸。采集时统一 resize 到 128×128是为了后续 CNN 输入维度一致。参数上max_count建议 150 左右太少模型欠拟合太多采集耗时且冗余。2.4 CNN 模型搭建与训练一个能跑通的轻量结构毕业设计不需要 ResNet-152一个 3 层卷积 全连接的结构足够。下面用 TensorFlow/Keras 写分类路线。import tensorflow as tf from tensorflow.keras import layers, models def build_cnn(num_classes, input_shape(128, 128, 3)): model models.Sequential([ layers.Conv2D(32, (3, 3), activationrelu, input_shapeinput_shape), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), activationrelu), layers.MaxPooling2D((2, 2)), layers.Conv2D(128, (3, 3), activationrelu), layers.MaxPooling2D((2, 2)), layers.Flatten(), layers.Dense(128, activationrelu), layers.Dropout(0.5), # 防过拟合小数据集必加 layers.Dense(num_classes, activationsoftmax) ]) model.compile( optimizertf.keras.optimizers.Adam(1e-3), losscategorical_crossentropy, metrics[accuracy] ) return model逻辑说明三层卷积逐步扩大通道数提取从边缘到语义的特征Dropout(0.5)在小样本下非常关键否则训练集 99%、验证集 60% 是常态。参数上学习率 1e-3 是 Adam 的稳妥起点如果 loss 震荡就降到 5e-4。训练时用ImageDataGenerator做随机翻转、亮度扰动能显著提升真实场景鲁棒性。训练完保存模型识别时加载即可。注意类别标签要和文件夹名对应建议用class_indices存一份 JSON避免上线后标签错位。3. PyQt5 界面与考勤逻辑把模型塞进一个不卡的窗口3.1 界面布局摄像头预览、识别按钮、考勤表格三块PyQt5 界面设计最容易犯的错是把耗时操作读摄像头、跑推理放在主线程结果窗口直接「未响应」。正确做法是用QThread把识别循环丢到子线程通过信号槽把结果传回界面。from PyQt5.QtWidgets import (QMainWindow, QLabel, QPushButton, QTableWidget, QTableWidgetItem, QVBoxLayout, QHBoxLayout, QWidget) from PyQt5.QtCore import QTimer, Qt from PyQt5.QtGui import QImage, QPixmap import cv2 class AttendanceWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(CNN 人脸识别考勤系统) self.cap cv2.VideoCapture(0) self.video_label QLabel(摄像头预览) self.video_label.setFixedSize(640, 480) self.btn_start QPushButton(开始识别) self.table QTableWidget(0, 3) self.table.setHorizontalHeaderLabels([姓名, 时间, 状态]) left QVBoxLayout() left.addWidget(self.video_label) left.addWidget(self.btn_start) right QVBoxLayout() right.addWidget(self.table) layout QHBoxLayout() layout.addLayout(left) layout.addLayout(right) container QWidget() container.setLayout(layout) self.setCentralWidget(container) self.timer QTimer() self.timer.timeout.connect(self.update_frame) self.timer.start(30) # 约 33 FPS def update_frame(self): ret, frame self.cap.read() if not ret: return rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape qt_img QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) self.video_label.setPixmap(QPixmap.fromImage(qt_img).scaled(640, 480))逻辑说明QTimer每 30ms 触发一次刷新保证预览流畅QImage构造时步长参数ch * w不能省否则图像会错位。参数上刷新间隔低于 20ms 会浪费 CPU高于 50ms 会明显卡顿。识别按钮的槽函数里再启动推理线程避免主线程阻塞。3.2 识别线程与考勤写入别让界面卡成黑匣子识别线程负责「检测人脸 → 预处理 → 模型推理 → 发信号」。考勤写入要防重复同一个人 5 分钟内只记一次否则摄像头一直对着你表格会刷屏。from PyQt5.QtCore import QThread, pyqtSignal import time import csv import os class RecognizeThread(QThread): result_signal pyqtSignal(str, float) # 姓名, 置信度 def __init__(self, model, class_names, threshold0.75): super().__init__() self.model model self.class_names class_names self.threshold threshold self.running True self.last_record {} # 姓名 - 上次记录时间 def run(self): cap cv2.VideoCapture(0) while self.running: ret, frame cap.read() if not ret: continue face detect_and_crop(frame) # 复用前面的检测逻辑 if face is None: continue face face.astype(float32) / 255.0 face np.expand_dims(face, axis0) preds self.model.predict(face, verbose0)[0] idx int(np.argmax(preds)) conf float(preds[idx]) if conf self.threshold: name self.class_names[idx] now time.time() if name not in self.last_record or now - self.last_record[name] 300: self.last_record[name] now self.write_attendance(name) self.result_signal.emit(name, conf) cap.release() def write_attendance(self, name): file_exists os.path.exists(attendance.csv) with open(attendance.csv, a, newline, encodingutf-8) as f: writer csv.writer(f) if not file_exists: writer.writerow([姓名, 时间, 状态]) writer.writerow([name, time.strftime(%Y-%m-%d %H:%M:%S), 已签到])逻辑说明threshold0.75是分类路线的经验值低于它宁可判「未知」也不要误记last_record做 300 秒去重防止刷屏。参数上阈值要根据你的验证集调如果误识多就调高如果熟人认不出就调低。写 CSV 用追加模式避免每次覆盖。3.3 考勤数据存储CSV 够用SQLite 更稳毕业设计用 CSV 完全够但如果要支持「查询某人某月记录」SQLite 更合适。建表语句很简单CREATE TABLE IF NOT EXISTS attendance ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, check_time TEXT NOT NULL, status TEXT DEFAULT 已签到 ); CREATE INDEX idx_name_time ON attendance(name, check_time);逻辑说明索引加在name check_time上查询「张三本周记录」时不用全表扫。参数上时间存文本格式便于直接展示如果要做时间范围统计可以额外存一个 Unix 时间戳字段。4. 避坑与排查那些让答辩现场翻车的细节4.1 摄像头被占用VideoCapture一直返回 False现象程序启动后预览黑屏cap.read()返回 False。原因摄像头被其他程序浏览器、会议软件占用或 Linux 下权限不足。解决关闭占用程序Linux 执行sudo usermod -aG video $USER后重新登录Windows 下在设备管理器确认摄像头驱动正常。4.2 训练集准确率 99%实测却认不出人现象训练日志很漂亮但摄像头前识别率极低。原因采集数据和实测数据分布不一致——采集时正脸、光线好实测时侧脸、背光。解决采集时故意覆盖多角度多光照训练时加数据增强随机亮度、对比度、水平翻转识别前做直方图均衡化。4.3 PyQt5 界面点「开始识别」就卡死现象窗口标题栏显示「未响应」表格不更新。原因识别循环写在主线程阻塞了 Qt 事件循环。解决把识别逻辑放进QThread通过pyqtSignal回传结果界面只负责显示不碰耗时操作。4.4 标签错位张三的考勤记到李四头上现象识别结果和实际人员对不上。原因训练时class_indices的顺序和识别时class_names的顺序不一致常见于重新训练后没更新映射文件。解决训练结束立刻把class_indices存成 JSON识别时加载同一份文件不要手写列表。4.5 打包成 exe 后模型加载失败现象PyCharm 里跑得好好的pyinstaller打包后报「模型文件不存在」。原因打包后资源路径变了相对路径失效。解决用sys._MEIPASS判断运行环境把模型和标签文件路径统一封装成一个函数打包时用--add-data把模型文件带进去。5. 进阶技巧把识别阈值调成「可解释」的参数最后一章讲一个我踩过多次坑才养成的习惯不要拍脑袋定阈值用验证集画一条 ROC 曲线。分类路线里softmax输出的置信度不是概率意义上的可靠值但你可以统计不同阈值下的误识率和拒识率选一个平衡点。具体做法准备一组「已知人员 未知人员」的验证图片跑推理记录每张图的最高置信度和是否正确。然后按阈值从 0.5 到 0.95 遍历算两个指标阈值误识率陌生人被认成熟人拒识率熟人被拒0.6012%3%0.754%8%0.851%18%0.900%30%考勤场景里误识比拒识更严重——把陌生人记成员工签到比员工多刷一次脸麻烦得多。所以我一般把阈值定在误识率接近 0 的位置比如 0.85然后给界面加一个「手动补签」按钮兜底。这样答辩时老师问「阈值怎么定的」你能拿出表格讲清楚而不是说「试出来的」。另一个技巧是特征比对路线下的阈值归一化。如果你用余弦相似度不同人的特征模长差异会导致固定阈值不公平。可以先对特征做 L2 归一化再算相似度阈值就能统一到 0.6~0.7 这个区间。这个改动很小但实测稳定性提升明显。我自己的习惯是每次改完模型或采集流程先跑一遍验证集把阈值和对应的误识/拒识记在项目 README 里。答辩前翻一遍比临时调参靠谱得多。希望帮到你。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门