ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于PyQT+FaceNet的人脸识别考勤系统设计与实现

基于PyQT+FaceNet的人脸识别考勤系统设计与实现 简介这是一套面向计算机、人工智能及相关专业本科生的毕业设计级学生人脸识别考勤系统实现方案基于PyQt构建图形界面、FaceNet卷积神经网络完成人脸特征提取与比对解决课堂考勤自动化、身份核验与数据管理等实际教学管理问题。资源包共55个文件包含23个核心Python模块如face_recognition.py、facenet.py、Sqlite_UI.py、12个UI界面文件.ui及对应编译生成的.py、3个SQLite数据库文件含学生信息与考勤记录、以及模型参数.npy、说明文档README.md和图像资源等整体压缩后仅1.98MB结构清晰、模块职责明确。已有294人学习下载代码经严格调试支持Windows 10与Ubuntu 20.04双平台兼容TensorFlow 1.15 CPU/GPU版本提供从人脸采集、模型训练到实时检测的完整闭环流程附带详细操作指引与数据库管理逻辑适合课程设计、毕设开发及PyQt深度学习融合实践进阶。 做学生人脸识别考勤系统很多人的第一版想法都是OpenCV摄像头抓脸然后随便调个现成人脸识别库完事。但真拿到“基于PyQTFaceNet卷积神经网络实现的学生人脸识别考勤系统”这个题目你会发现它考的不只是“能不能识别”而是你能不能把界面、算法、数据库、业务规则整套串起来形成一份能演示、能答辩、能落地的完整毕设项目。这篇东西我按自己的实操经验写不罗列废话。核心围绕PyQT界面开发、FaceNet特征提取、卷积神经网络原理、考勤业务逻辑展开再补上环境搭建、模型应用、代码模块、打包成exe和避坑记录。适合正在做这类毕设但卡在某个环节的同学也适合想从零开始做一个桌面人脸考勤系统的开发者参考。1. 项目整体思路与选型分析1.1 为什么选FaceNet而不是传统人脸识别方案人脸识别方案里最容易被新手拿起来就用的是OpenCV自带的LBPH或EigenFace。这两个算法不需要深度学习框架几十行代码就能跑通但它们的泛化能力很差换个光线、换副眼镜识别率就明显下滑。做考勤系统人脸姿态和光线变化是常态LBPH撑不起真实场景。FaceNet是Google提出的深度人脸特征提取模型属于卷积神经网络的一种典型应用。它的做法是把人脸图像映射到一个高维向量空间通常输出128维的embedding向量。两个人的脸相似程度直接通过向量距离度量。这个方案的好处在于不需要对每个学生单独训练一个分类器而是把所有人脸统一变成向量比对时算距离就行。新增一个学生时只需要把这个人的人脸特征向量保存进数据库不需要重新训练整个模型。选FaceNet还有一个现实原因毕设环境往往只有CPU电脑没有GPU服务器。FaceNet的预训练模型在CPU上也能跑虽然推理速度不算快但考勤场景是“检测到人脸后做一次特征提取”一秒钟左右出结果完全够用。要是选大数据集重新训练基本超出毕设范围。1.2 为什么用PyQT做桌面端而不是Web端同样是做考勤系统有人会倾向Web端觉得浏览器界面好看。但毕设项目首先要考虑演示稳定性。Web端要搭前端后端还要处理摄像头权限万一答辩教室网络不好系统直接瘫痪。PyQT做的是桌面应用摄像头通过OpenCV本地读取模型本地加载数据库用SQLite所有东西都在一台电脑上闭环演示时只需一个笔记本和一个摄像头不会受网络环境影响。PyQT上手曲线不算陡。布局用Qt Designer拖出来业务代码用信号槽机制挂按钮事件摄像头画面用QLabel实时刷新整个UI响应逻辑很清晰。最关键的一点PyQT写出来的程序可以用PyInstaller打包成exe答辩前拷到Windows电脑上就能跑这份工程量是Web端没法比的。1.3 系统整体架构与数据流我设计这套考勤系统时把整体拆成了四层界面层PyQT实现负责显示摄像头画面、学生信息、考勤记录、按钮交互。算法层OpenCV和MTCNN做人脸检测FaceNet做人脸特征提取距离度量做身份比对。数据层SQLite存储学生基础信息、人脸特征向量、考勤时间记录。业务层控制识别流程处理“是否重复打卡”“是否识别为未知人员”等规则。数据流大概是这个顺序摄像头采集一帧图像送入人脸检测模块得到人脸区域裁剪并处理成FaceNet需要的输入尺寸再送入卷积神经网络提取特征向量然后把当前特征和数据库里的学生特征进行距离比对通过阈值判断返回学号和姓名最后把考勤时间写入数据库。UI界面只需要在这条链路的起点和终点做文章。这套结构的好处是模块之间低耦合。如果以后想换更轻量的人脸检测器只需要替换算法层的检测模块界面和数据层不用大改。2. 人脸识别核心原理与前置准备2.1 卷积神经网络在人脸识别里到底干了什么很多人把卷积神经网络挂在嘴边但真被问到“它是怎么识别人脸的”就说不清。我用生活化的方式解释一下卷积层相当于拿一组固定大小的放大镜在人脸图片上不断滑动提取局部纹理特征比如眼睛边缘、鼻子弧度、脸部轮廓。图像经过一层层卷积从最开始只能看到像素点逐步组合成“眼睛”“嘴巴”这种高级语义特征。池化层则负责压缩信息保留关键部分去掉冗余细节。最后全连接层把这堆空间特征压成一个向量也就是我们说的embedding。在你这个考勤项目里卷积神经网络不是直接输出“这是张三”而是输出一张脸的“数学指纹”。FaceNet训练的目标就是让同一个人的不同照片生成的向量距离尽量小让不同人的照片生成的向量距离尽量大。具体做判断时比对的是向量距离而不是比对图片本身。2.2 FaceNet的Triplet Loss和特征向量FaceNet比较核心的概念是三元组损失。一个三元组由anchor基准图、positive同一个人另一张图、negative另一个人图组成。训练时网络不断让anchor与positive的距离变小同时让anchor与negative的距离变大。这个思想可以类比成你拿着张三的证件照在一群人中找张三如果看到另一个张三的照片就应该判定为同一人看到李四就应该判定为不是同一人。实际项目中我不建议自己去训练FaceNet。GitHub上有现成的facenet-pytorch库提供了在VGGFace2上预训练好的Inception-ResNet v1模型权重文件几十兆直接下载就能用。调用模型提取人脸embedding时输出是128维的向量正常情况下这个向量会做归一化处理所以比对时用余弦相似度或欧氏距离都行。2.3 环境搭建与依赖版本关键一步这个项目的环境坑非常多版本不匹配很容易出现“装了跑不起来”的问题。我调试过几套组合最稳妥的是以下版本依赖库推荐版本说明Python3.8 或 3.9对PyQT5和PyTorch兼容性最好PyQT55.15.4稳定打包资料多PyTorch1.10.0CPU版即可没必要装CUDA版facenet-pytorch2.5.3自带MTCNN和Inception-ResNet v1OpenCV-Python4.5.5摄像头读取和图像处理NumPy1.21避免过新版本与PyTorch不兼容安装命令可以直接这样写pip install pyqt55.15.4 opencv-python4.5.5 numpy1.21 torch1.10.0 facenet-pytorch2.5.3facenet-pytorch在第一次加载时会自动下载模型权重到用户目录。如果网络不好下载过程会卡住这时候可以手动去GitHub Release页面下载对应的权重文件然后放到项目根目录下的models文件夹通过代码指定模型路径加载。2.4 数据集准备与预处理学生考勤系统的数据来源有两种。一种是用公开数据集比如LFW、CASIA-WebFace但这些人脸和你的学生库没有对应关系只能用来测试模型效果。另一种是自建数据集每人拍10到20张照片覆盖正脸、左右侧脸、戴不戴眼镜、室内白光和暖光等场景。自建数据不需要一次性采集完可以通过系统的“录入人脸”功能从摄像头连续抓拍几个不同的角度。预处理这一步容易被忽视。实际实验下来如果直接用原始照片做人脸识别准确率会低不少。正确流程是先用MTCNN检测到人脸框把脸部区域裁剪出来调整大小到160x160再按ImageNet的均值和标准差做归一化。裁剪对齐这一步很重要因为FaceNet在预训练时对输入图像的尺寸和内容分布有固定预期歪七扭八的人脸会严重影响向量质量。3. 核心模块实现与实操步骤3.1 人脸检测模块实现FaceNet本身只负责特征提取它不检测人脸。所以整个系统里要先有一个独立的人脸检测步骤。我优先推荐facenet-pytorch自带的MTCNN检测器它对小脸和侧脸支持不错还能同时输出人脸关键点坐标。但在CPU上跑MTCNN稍微有点慢如果检测成为瓶颈可以换成OpenCV的DNN人脸检测器。用facenet-pytorch做检测的代码很简洁from facenet_pytorch import MTCNN mtcnn MTCNN( keep_allFalse, devicecpu )摄像头每读取一帧图像调用一次mtcnn.detect会返回人脸框坐标和置信度。需要特别注意的是MTCNN接收的是RGB图像而OpenCV读出来的是BGR格式必须用cv2.cvtColor转换后再送入检测器否则检测效果会异常。rgb_frame cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) boxes, probs mtcnn.detect(rgb_frame)如果检测到多个人脸要针对考勤场景做策略处理。通常一个学生一次考勤只识别一个人所以可以取置信度最高的人脸框。要是想扩展成“同时识别多人”就得循环处理所有人脸框。3.2 FaceNet特征提取与距离阈值设定人脸检测得到人脸框后把框内区域裁剪出来传入FaceNet模型。facenet-pytorch里,使用预训练模型的方式如下from facenet_pytorch import InceptionResnetV1 model InceptionResnetV1(pretrainedvggface2).eval() def get_embedding(face_img): face_img cv2.resize(face_img, (160, 160)) face_img cv2.cvtColor(face_img, cv2.COLOR_BGR2RGB) face_img np.transpose(face_img, (2, 0, 1)).astype(np.float32) face_img (face_img - 127.5) / 127.5 face_img torch.from_numpy(face_img).unsqueeze(0) with torch.no_grad(): embedding model(face_img) return embedding[0].numpy()这里我做了两步关键处理。第一步是把输入图像从OpenCV的BGR转换成RGB因为模型预训练时用的是RGB。第二步是把像素值归一化到-1到1范围FaceNet提出的论文里就是这么做的。特征提取完成后我们拿到的向量通常已经做过归一化所以在判定阶段直接用向量点乘就能得到余弦相似度。阈值设定是直接影响体验的关键参数。如果阈值设得太低系统会频繁把陌生脸识别成某个学生产生误打卡设得过高学生本人又容易被判为未知人员。我的经验是先用同一个人的5到10张不同照片计算类内距离再用不同人的照片计算类间距离取两者中间的数值作为初始阈值。比如类内余弦相似度在0.85到0.95类间在0.4到0.6阈值就取0.75左右。实际系统运行阶段还需要根据现场光线和摄像头质量微调。3.3 考勤业务逻辑与数据库设计识别算法只是系统的一个组成部分考勤业务逻辑才是区分“能用”和“好用”的地方。设计考勤记录表时至少要保留学生ID、姓名、考勤时间、考勤状态。但如果你希望系统能识别出“陌生人”还要在识别结果里增加一个未知人员的处理流程。我的数据库表设计如下CREATE TABLE students ( id INTEGER PRIMARY KEY AUTOINCREMENT, student_no TEXT UNIQUE NOT NULL, name TEXT NOT NULL, face_encoding BLOB NOT NULL, photo_path TEXT, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE attendance ( id INTEGER PRIMARY KEY AUTOINCREMENT, student_id INTEGER, student_no TEXT, name TEXT, check_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, status TEXT, FOREIGN KEY(student_id) REFERENCES students(id) );学生注册时把姓名学号录入界面摄像头拍一张或几张人脸计算特征向量后以BLOB类型存入students表。考勤时实时计算当前人脸特征向量遍历数据库比对找到相似度最高的学生记录如果相似度大于阈值写入attendance表。避免重复打卡是一个很实用的小功能。如果同一学生在两分钟内连续识别系统会认为这是同一事件不重复写入记录。最简单的实现方式是在考勤前查询该学生在最近两分钟内是否有记录。def can_check_in(student_id): cursor.execute( SELECT COUNT(*) FROM attendance WHERE student_id? AND check_time datetime(now, -2 minutes), (student_id,) ) return cursor.fetchone()[0] 03.4 PyQT界面设计与信号槽机制PyQT界面我建议用Qt Designer画而不是纯代码写布局。主窗口做成左右结构左侧放标题和操作按钮中间大块区域放摄像头画面下方放识别结果和最近考勤记录表格。按钮一般需要这三个“录入人脸”“开始考勤”“查看记录”。摄像头实时识别不能放在主线程里否则画面会卡成幻灯片。正确做法是写一个QThread子线程在run方法里循环读取摄像头帧每间隔一定帧数触发一次人脸识别然后把识别结果通过信号传回主线程。这里有一个容易犯的错不要在线程里直接操作UI控件。Qt禁止子线程更新主界面的QLabel或QTableWidget必须通过自定义信号传递数据。我一般这样定义信号class VideoThread(QThread): frame_signal pyqtSignal(object) result_signal pyqtSignal(str) def run(self): cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: continue rgb_frame cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) self.frame_signal.emit(rgb_frame) # 每隔几帧做一次识别 if self.frame_count % 5 0: name, student_no self.recognize(frame) self.result_signal.emit(f{name} {student_no}) self.frame_count 1主线程里把frame_signal连接到更新画面的函数result_signal连接到更新文本标签的函数。这样即便算法耗时较长界面也只是显示上一帧画面不会出现“死窗”。4. 训练与部署实战4.1 用到FaceNet时到底要不要微调很多毕设教程一上来就引导你训练模型但严格来说基于FaceNet的考勤系统完全可以不训练。预训练模型已经在百万级人脸数据上学到了很好的特征表达能力考勤场景只需要对这个特征空间做一次匹配。你真正要建的是“学生特征库”而不是重新训练CNN。不过如果预训练模型在自己测试集上的效果不理想可以考虑在FaceNet输出向量之后加一个分类器。具体做法是先用预训练模型把所有训练图片转换成128维特征向量保存成CSV文件然后用sklearn训练一个SVM分类器。这种方式不需要微调深层网络训练速度快而且在小样本的学生数据集上往往比直接阈值匹配更稳定。实战中我用SVM做兜底方案。流程是数据预处理生成embedding数组和标签数组调用svm.SVC训练保存为joblib文件。识别阶段先提取embedding再送入SVM预测。这样做的好处是避开了调阈值缺点是新学生加入时需要重新训练SVM。针对毕设场景我建议把SVM作为辅助模块而主流程还是用特征向量比对便于你解释系统原理。4.2 模型推理速度优化CPU环境下FaceNet对单张160x160图像推理一次大约需要200到500毫秒加上MTCNN的检测时间每帧识别耗时可能接近一秒。如果摄像头是按30帧读取的就会出现明显卡顿。优化思路有三个。第一不要每一帧都做识别设置一个识别间隔比如每5帧做一次识别结果出来后锁定显示。第二降低输入到人脸检测器的帧分辨率。摄像头读进来是640x480可以先缩放一半再检测人脸检测到人脸后回到原始帧裁剪对应区域做特征提取。第三在模型推理阶段加上torch.no_grad()关闭梯度计算。这三点做完性能大致能提升一倍。如果使用了GPU可以把MTCNN和FaceNet都放到CUDA设备上但这属于加分项不是必须项。我见过不少同学为了在答辩中展示GPU加速结果现场机器CUDA没配好反而翻车。宁可CPU稳定跑也别赌答辩电脑。4.3 PyQT程序打包成exe的完整过程打包是这个项目里最容易被低估的环节。之前见过有人代码跑得没问题打包后双击exe直接闪退后来发现是模型路径没打包进去。PyInstaller默认只打包Python脚本和依赖库不会自动将资源文件拷到目标目录。用PyInstaller打包PyQT项目的推荐命令pyinstaller -w -F main.py \ --add-data models;models \ --hidden-importPyQt5.sip \ --name FaceAttendanceSystem注意Windows下--add-data的源路径和目标路径之间用分号Linux/Mac用冒号。更稳妥的做法是写一个spec文件把模型目录、权重文件都列进去。我第一次打包时没加models目录结果程序启动后MTCNN找不到模型一直报文件不存在。PyInstaller打包出来的单exe文件启动时会把资源文件解压到临时目录程序里不能直接写死“models”这种相对路径需要动态获取资源路径。我封装了一个函数import sys import os def resource_path(relative_path): try: base_path sys._MEIPASS except AttributeError: base_path os.path.abspath(.) return os.path.join(base_path, relative_path)所有加载模型、加载数据库的路径都改用resource_path处理。这样无论是开发环境还是打包后的exe环境都能找到对应文件。5. 常见问题与排查技巧5.1 识别不准、误识别率高怎么办误识别率高的原因大概率出在阈值和图像预处理上。先检查人脸检测后裁剪出来的人脸是否对齐如果人脸只占图片一角特征向量质量一定差。其次调整阈值不要照着网上的数值直接套用。不同型号的摄像头拍出来的色差会影响余弦相似度所以阈值一定要用自己设备实测。还有个小技巧录入学生人脸时尽量多拍几张把多张图的向量取平均再存入数据库。这样能削弱单张照片造成的噪声对光照变化更鲁棒。如果学生戴眼镜建议录入时分别录戴和不戴两张识别时取距离更小的那个结果。5.2 PyQT界面卡顿和识别流程阻塞界面卡顿要不是摄像头帧率问题要不就是UI操作被放到了线程外面。我排查这类问题一般先看CPU占用和内存占用。如果CPU飙到100%说明识别算法和UI抢线程资源。最终方案是把摄像头读取、人脸检测、特征提取全部放进工作线程UI主线程只做显示和接收信号。有时候卡顿是因为QThread里的while循环没有加延时导致CPU空转。优化办法是在每帧处理后加time.sleep(0.03)并在窗口关闭时设置终止标志位让线程正常退出。5.3 打包后模型路径和DLL丢失打包后常见三类问题。第一是模型文件缺失启动报“No such file or directory”使用resource_path解决。第二是OpenCV视频窗口打不开这是因为PyInstaller没有带上OpenCV的FFmpeg插件可以在spec文件中显式加入opencv_videoio_ffmpeg.dll。第三是PyQT的platforms插件缺失报“could not find or load the Qt platform plugin”解决办法是额外添加Qt的platforms目录到包内。# attendance.spec 片段 datas [ (models, models), (C:/Python38/Lib/site-packages/PyQt5/Qt/plugins/platforms, PyQt5/Qt/plugins/platforms) ]5.4 PyQt5还是PySide2的选择两者API基本一致但毕设项目我建议用PyQt5。原因很简单PyQt5出错时搜索到的中文资料更多PySide2部分接口细节不同照抄代码容易出问题。还有一点PyQt5默认带GPL协议但毕设内部演示不受影响。如果后续要做商业产品那再考虑PySide2。5.5 摄像头占用和权限问题摄像头偶尔会被其他程序占用OpenCV的VideoCapture会返回False但不会报错。所以循环里一定要写ret判断否则会黑屏。Windows下如果第一次打开即失败通常要先关闭其他占用摄像头的软件或者检查设备权限是否允许桌面应用访问摄像头。我在实际使用中还有一个习惯把每次识别失败的日志单独存到log文件里。这样即便系统运行中出了状况事后也能复盘是哪一步失败。毕设答辩的时候拿出一份日志分析记录也会让老师觉得项目做得扎实。这个系统后续扩展空间其实很大。你现在用的是FaceNet如果哪天想换ArcFace只需要新增一个特征提取器类保持相同的输入输出格式业务层基本不用动。同样的逻辑也可以把考勤记录导出为Excel或者加一个请假审批模块都是在这个结构上做增量开发。但前提是最初搭框架时不要把每个模块写死多留接口后面改起来会省很多力气。本文还有配套的精品资源点击获取
返回列表