☰
基于FaceNet与PyQT5的人脸识别考勤系统设计与实现
2026/10/12 12:02:10 网站建设 项目流程

简介:这是一套面向计算机、人工智能及相关专业本科生的毕业设计级学生人脸识别考勤系统实现方案,基于PyQt5构建图形界面,结合FaceNet卷积神经网络完成人脸特征提取与比对,解决传统考勤中人工登记效率低、代签漏洞等问题。资源包共55个文件,含23个Python核心模块(如face_recognition.py、facenet.py、Sqlite_UI.py)、12个UI界面文件(.ui及对应py转换脚本)、3个SQLite数据库文件(含学生信息与考勤记录)、以及模型参数(.npy)、说明文档(README.md)和图标资源(.png),整体仅1.98MB,轻量易部署。已有294人学习下载,代码经严格调试,支持Windows 10与Ubuntu 20.04双平台,兼容TensorFlow 1.15 CPU/GPU版本。使用者可直接运行SetUpMainWindow.py启动主界面,完成用户录入、人脸采集、模型训练与实时检测全流程;亦可基于模块化结构(如tools/、src/、DB/分层目录)快速理解系统架构,适合作为课程设计、毕设原型或AI视觉入门实践项目。

1. 项目从0到1:毕设做一个人脸识别考勤系统,我的完整思路与踩坑记录

最近后台收到不少学生党留言,都在问人脸识别考勤系统怎么做。刚好我手头有个非常典型的毕设项目——基于PyQT+FaceNet卷积神经网络实现的学生人脸识别考勤系统,源码加说明文档一起放出来了。这篇就把整个项目的设计思路、核心实现、以及那些文档里不会写清楚的坑,一次性讲透。

先说这系统是干什么的:学生走进教室,摄像头抓拍一张人脸,系统自动识别出是谁,然后记录考勤状态(出勤、迟到、缺勤)。老师端能查看和管理所有记录。就这么一个场景,覆盖了图像采集、人脸检测、特征提取、特征比对、界面交互、数据库存储这几大块技术点。适合谁参考?计算机、软件工程、电子信息这几个专业做毕设的同学,或者想入门人脸识别落地开发的工程师。

我翻过很多类似的开源项目,多数是“能跑起来,但换台机器就废”的状态。这个项目做得比较完整的地方在于:它把训练好的FaceNet模型直接拿来做推理(不需要你自己训练),PyQT负责界面和业务逻辑,SQLite存考勤数据,整个链路是通的。你拿到源码之后,真正要花时间理解的是“流程怎么串起来的”,而不是“模型怎么调的”。

1.1 为什么选PyQT而不是Web方案

考勤系统这种项目,很多人的第一反应是做成Web页面。但我个人建议毕设阶段用桌面应用,理由很现实:

第一,Web方案要多写一套后端 API,还要处理浏览器摄像头权限问题。你用 Flask 或者 Django 搞个上传接口,前端再调 getUserMedia,光联调就能耗掉一礼拜。PyQT 有 QtWebEngine 和 QCamera 模块,直接调本地摄像头,省掉中间一层网络传输,调试链路短。

第二,桌面应用在答辩演示的时候更稳。你带着笔记本到教室,双击 exe 就能跑,不用开着 MySQL、不用部署 Node 服务、不用担心演示现场 Wi-Fi 断了接口超时。PyQT 封装出来的单机程序,所有逻辑都在本地,出错概率低得多。

第三,PyQT 的信号槽机制天生适合做这种“触发—响应”的交互逻辑。摄像头检测到人脸、识别结果出来、写数据库、刷新表格,每一步都是事件驱动的,用信号槽串起来,代码比写回调函数清晰得多。

所以选 PyQT 不是因为它多高大上,纯粹是“最省事、最稳、最贴近实际考勤场景”的选择。

1.2 FaceNet在2024年的定位:训练太重,推理真香

FaceNet 是 Google 在 2015 年提出的模型,核心思想是把人脸图像映射到 128 维欧氏空间,让同一张人脸的向量距离近、不同人脸的向量距离远。在深度学习人脸识别领域,它属于“老前辈”了,但到现在依然没有过时,特别是在毕设和个人项目里。

为什么不选更新的模型(比如 ArcFace、CosFace)?说实话,2024 年这些新模型确实精度更高,但它们的问题是:预训练权重不好找、训练资源要求高、部署依赖重。而 FaceNet 有一大堆现成的预训练模型,keras-facenet这个库甚至把权重打包好了,pip 装完就能用。对于毕设来说,你的工作量应该花在“系统集成”而不是“训练模型”上,FaceNet 恰好是那个能让你把精力放在系统上的选择。

这里要特别说明的是,项目标题里写着“FaceNet卷积神经网络”,但很多同学容易把卷积神经网络(CNN)和 FaceNet 混为一谈。FaceNet 的骨干网络实际上是 GoogLeNet 的改进版(Inception 结构),本质就是 CNN。所以这个标题的意思是:基于 CNN 架构的 FaceNet 模型。你用 CNN 做的人脸检测也一样叫卷积神经网络,不必被字面绕晕。

2. 系统整体架构与三大核心模块

这个考勤系统的架构不算复杂,但麻雀虽小五脏俱全。整个系统可以画成三个模块:界面与交互层、识别引擎层、数据管理层。下面逐个拆解。

2.1 界面层:从登录到考勤结果展示的完整交互链

界面层用 PyQT5 实现,主窗口分四个区域:

登录区:教师输入账号密码进入系统,这个不是摆设,数据库里有一张 admin 表。有人觉得考勤系统做登录多此一举,但毕设答辩时,评委经常问“这些记录谁能查看,有没有权限控制”,有这个模块就多一个可讲的点。

摄像头预览区:调用本机摄像头实时显示画面,这是整个系统最有视觉冲击力的部分。原理是用 QCamera 和 QCameraViewfinder 搭起来,底层通过 VideoProbe 抓取每一帧画面做识别。

识别结果区:把抓到的学生姓名、学号、识别时间、考勤状态显示在界面上。为什么识别结果和考勤状态要分开显示?因为识别正确不代表考勤记录一定写对,分开显示方便排查问题。

考勤记录表:用 QTableView 展示数据库中的历史记录,支持按日期、班级筛选。

界面交互的核心逻辑是:摄像头预览视频流 — 按下“开始考勤”按钮 — 系统对当前帧做人脸识别 — 识别成功则更新考勤记录并短暂锁定10秒(防止同一人连续刷脸) — 识别失败则显示“未注册学生”的提示。

注意:PyQT5 里 QCamera 的 viewfinder 和 VideoProbe 的关系很多人搞不清楚。Viewfinder 是显示用的,VideoProbe 是抓帧用的,两者互不干扰但共用同一个摄像头资源。很多新手在这里直接卡住,明明画面正常显示但无法抓帧分析,就是忘了加 VideoProbe。

2.2 识别引擎:预训练模型+余弦相似度,简单有效的人脸比对方案

识别引擎是整个系统的技术核心,工作流拆开看就四步:

  1. 人脸检测:用 OpenCV 的 Haar Cascade 分类器检测画面中的人脸框。这个方案虽老,但胜在轻量、CPU 就能跑、速度极快。你项目里写的 CNN 是为了提取特征,检测这一步用传统算法完全够用,不需要杀鸡用牛刀。如果想在答辩时表现得更高级,把检测换成 MTCNN(多任务卷积神经网络)也行,代码改动不大,但可视化效果更炫。

  2. 人脸对齐:检测到人脸框后,FaceNet 要求输入 160x160 的标准化图像。OpenCV 检测到的框可能包含背景,直接缩放训练模型没见过的尺寸会掉精度。所以需要一个预处理步骤:用 OpenCV 的眼睛检测器找到两眼位置,然后通过仿射变换把眼睛对齐到固定坐标。这一步很多人直接跳过,但实测下来对齐前后识别准确率能差 10% 以上。

  3. 特征提取:把对齐后的人脸图像(160x160x3)输入 FaceNet 预训练模型,得到 128 维特征向量。这一步是整个系统最依赖深度学习的部分,模型把高维像素信息压缩成紧凑的语义向量,像给每张人脸做“指纹”。

  4. 特征比对:拿当前人脸的特征向量,和数据库中所有已注册学生的向量算余弦相似度。相似度最大的那个超过阈值(通常取 0.85),就判定为对应学生。否则判定为陌生人。

这里给一个余弦相似度的计算示例:

import numpy as np def cosine_similarity(vec1, vec2): """计算两个128维特征向量的余弦相似度""" dot = np.dot(vec1, vec2) # 这里加一个保护的逻辑:如果范数为0就返回0,防止除零 norm_a = np.linalg.norm(vec1) norm_b = np.linalg.norm(vec2) if norm_a == 0 or norm_b == 0: return 0.0 return dot / (norm_a * norm_b)

为什么用余弦相似度而不是欧氏距离?FaceNet 的训练目标是让同一个人的特征向量在欧氏空间中“几乎重合”,所以严格来说欧氏距离更符合 FaceNet 的原生语义。但实际工程里余弦相似度对特征向量的模长不敏感,在预训练模型没有针对数据集微调的情况下,余弦相似度的稳定性更好。这个取舍在答辩时值得展开讲,评委很吃这种“我不仅做了,还明白为什么这么做”的回答。

2.3 数据层:SQLite轻量存储,让毕设无需繁琐的数据库配置

数据库用 SQLite,理由跟不用 Web 一样:省事。用一个.db文件就能搞定,不需要安装 MySQL 服务,不需要配账号密码,代码里几行就能连上。

整个系统三张表:

表名字段用途
adminid, username, password教师登录账号
studentsid, student_id, name, class_name, face_embedding学生基本信息+128维特征向量
attendanceid, student_id, date, status, time每日考勤记录

students 表里直接把 face_embedding 存成 BLOB 类型(二进制大对象),这样省掉了单独建特征索引表的麻烦。读取的时候用 np.frombuffer 还原成 128 维 float32 数组。有个细节:采集人脸样本的时候,建议一个人采集 3~5 张不同角度的照片,然后把特征向量取平均存起来。这样做的好处是能抵消角度、光照带来的偏差,比单张照片的特征稳定得多。这个“平均特征”的操作在毕设里是一个很自然的加分项,一定要写进项目报告里。

这里要提醒一点:SQLite 的并发写能力弱,如果两个线程同时写数据库会报 database is locked。PyQT 界面线程和识别线程都在跑,写着写着突然报错多半就是这个原因。解决方法是把数据库操作放在主线程,识别线程只负责把识别结果通过信号发回来,由主线程执行写入。如果你用多线程写数据库,再多的数据也会被锁问题劝退。

3. 实操过程:从环境搭建到产出一套完整系统

光讲理论没用,接下来把你从空环境开始,一步一步把项目跑起来的全过程过一遍。这部分我参考了项目源码的实际结构和常见实践,按“环境→数据准备→核心代码→打包部署”四个阶段来讲。

3.1 环境搭建:Python版本、依赖库、模型文件一个都不能少

先交代环境,这不是废话。很多同学拿到源码跑不起来,90% 是环境不一致导致的版本冲突。我的建议环境如下:

  • 操作系统:Windows 10/11(项目打包 exe 最方便)
  • Python:3.8 或 3.9(不要用 3.11+,PyQT5 和老版本 TensorFlow 的兼容性会出问题)
  • 关键依赖:
    • PyQt5==5.15.4
    • opencv-python==4.5.5.64
    • tensorflow==2.6.0 或 tensorflow-cpu==2.6.0
    • numpy==1.19.5
    • keras-facenet(直接 pip 安装,自带预训练权重)
    • mysql-connector 不需要,我们用的是 SQLite

为什么特意把版本号写出来?因为 keras-facenet 内部依赖 TensorFlow 2.x 的特定 API,用新版的 TensorFlow 2.10 以上经常会报各种不兼容的警告,虽然能跑但功耗大且容易出奇怪问题。实际项目中有个典型的坑:TensorFlow 2.7 及以上版本依赖libcudnn的版本号变了,如果你装的是 CPU 版没这个问题,但 GPU 版会突然全部报错。所以 2.6.0 是比较稳的选择。

安装依赖的命令:

pip install pyqt5==5.15.4 opencv-python==4.5.5.64 tensorflow-cpu==2.6.0 numpy==1.19.5 keras-facenet

注意:tensorflow-cpu 和 tensorflow 不能同时装,否则会有冲突。GPU 版需要额外装 CUDA 和 cuDNN,但毕设项目用 CPU 推理足矣,FaceNet 在 CPU 上跑一次识别只要 100ms 左右,考勤场景完全够用。

另外要单独放置模型文件。keras-facenet 会在第一次使用时自动下载权重,但如果网络不稳定,下载失败后几乎无法恢复。我的建议是挂代理下载后手动放到~/.keras/models目录(mac/Linux)或C:\Users\用户名\.keras\models(Windows)下面,文件名一般是facenet_keras_weights.h5。这一步很多同学忽略,然后在运行时报OSError: Unable to open file这种错误,卡半天。

3.2 学生人脸注册:录入流程与特征入库

考勤系统在识别之前,必须先完成“注册”环节——把每个学生的照片特征存进数据库。这一块的实现逻辑比大多数人想的多几个弯。

注册界面长这样:输入学号、姓名、班级,然后摄像头对准学生正面,点击“采集”按钮,系统会连续抓取5帧图像,每帧检测人脸→对齐→提取特征,最后把5个特征向量平均存库。

关键代码如下:

def register_student(self, student_id, name, class_name): """采集多张人脸图像并提取平均特征""" embeddings = [] for i in range(5): # 从视频流抓一帧 frame = self.get_current_frame() # 检测人脸 faces = detect_faces(frame) if len(faces) == 0: continue # 这一帧没有脸,跳过继续 # 提取特征(这里以封装后的FaceNet接口为例) emb = extract_embedding(faces[0]) embeddings.append(emb) # 界面提示“请缓慢转头”,方便采集不同角度 QTimer.singleShot(200, lambda: self.status_label.setText(f"采集第{i+1}/5帧...")) if len(embeddings) == 0: self.show_warning("未检测到人脸,请调整光线和角度重试") return avg_emb = np.mean(np.array(embeddings), axis=0) save_embedding_to_db(student_id, name, class_name, avg_emb) self.status_label.setText("注册成功")

这段代码里我故意留了个细节——采集时提示“请缓慢转头”。实际测试中,如果学生盯着一处不动,5 帧图像几乎一样,平均特征和单张特征没什么区别。只有轻微转脸才能让特征覆盖更多角度,提高后续识别鲁棒性。

注册这个环节还有个细节容易被忽略:姓名中的生僻字。有些学生的名字里带生僻字,在数据录入时如果编码不对(特别是 PyQT5 的默认编码和 Windows 控制台编码不一致),会导致存库后显示乱码。建议所有字符串字段统一用 UTF-8 编码,连接 SQLite 时加上encoding='utf-8'。

3.3 考勤逻辑:识别、去重、状态判定完整流程

识别考勤的主流程是系统最核心的部分。我把核心逻辑简化后大概长这样:

def on_attendance_button_click(self): """点击‘开始考勤’按钮后的处理逻辑""" self.is_attending = True self.last_record_time = {} # 保存每个学生最后一次考勤时间,用于去重 def process_frame(self, frame): """对视频流中的每一帧进行识别处理(在VideoProbe回调中调用)""" if not self.is_attending: return # 没开始考勤就不处理 # 第一步:检测并提取特征 faces = detect_faces(frame) if len(faces) == 0: return # 取画面中最大的人脸做识别 largest_face = max(faces, key=lambda f: f[2] * f[3]) embedding = extract_embedding(largest_face) # 第二步:和数据库所有已注册学生比对 best_match, best_score = self.find_best_match(embedding) # 第三步:判定结果 if best_score > THRESHOLD: student = best_match # 去重逻辑:同一个学生5秒内不能重复打卡 current_time = time.time() last_time = self.last_record_time.get(student.id, 0) if current_time - last_time > 5: self.record_attendance(student.id, self.classify_status()) self.last_record_time[student.id] = current_time self.ui.result_label.setText(f"{student.name} {self.classify_status()}") else: self.ui.result_label.setText(f"{student.name} 已打卡,请勿重复操作") else: self.ui.result_label.setText("未注册学生")

这里有几个值得展开的逻辑点。第一个是“去重逻辑”,没有吃透系统的同学容易漏掉甚至理解反了。实际场景中,摄像头一直在工作,同一张脸连续出现在视频流里,如果不做去重,系统会把同一个人识别成 20 次出勤记录。去重的方式很多,按时间窗口(比如 5 秒内不重复)或者按状态(已出勤的不再写入)都可以,我在代码里用时间窗口方案,简单直接。

第二个是考勤状态判定。刚上课 10 分钟内到 = 迟到,超过 30 分钟还没到 = 缺勤,这是根据当前时间动态算的。这里注意:系统在 9 点上课时,老师点击“开始考勤”,但学生在 9 点 31 分进教室,此时系统应该怎么处理?项目里的方案是:时间超过应到时间 30 分钟后,识别成功但状态标为“缺勤”,但会同时弹一个提示“该学生已迟到,是否修改为出勤”。这个交互细节非常拉好感,做完就能在答辩时展示“我考虑了边界情况”。

第三个是“取最大人脸”的策略。考勤场景往往是学生一个接一个刷脸,画面里可能同时出现多张脸。取最大的那张作为“正在考勤的人”,这个 naive 的策略在教室门口的场景其实够用且不易出错。如果有人同时出现在镜头里,系统会优先识别离镜头近的人,正好符合考勤排队进教室的场景。

3.4 界面美化:让毕设看起来像产品的小技巧

很多毕设源码功能都实现了,但界面丑得让人没眼看。这里分享三个低成本提升界面质感的技巧:

技巧一:用 QSS 统一样式。PyQT 支持类似 CSS 的 QSS 语法,你只需要在代码里加载一份样式表,就能把默认的灰色控件换成有圆角、有渐变色的现代风格。比如给考勤状态标签加颜色:

QLabel#status_ok { color: #2e7d32; background-color: #e8f5e9; border-radius: 8px; padding: 8px; font-weight: bold; } QLabel#status_fail { color: #c62828; background-color: #ffebee; border-radius: 8px; padding: 8px; font-weight: bold; }

技巧二:加一个启动画面。PyQT 有 QSplashScreen,加载模型需要几秒钟,与其让用户对着白屏等着,不如显示一张带 logo 和进度条的启动画面。虽然技术上不复杂,但答辩演示时前 5 秒的观感差别巨大。

技巧三:用 Thread 加载模型。FaceNet 模型加载大概要 5-8 秒,如果放在主线程,启动时窗口会无响应。把模型加载放到 QThread 里,加载完发一个信号通知界面,用户以为程序启动很快,实现也不复杂:

class ModelLoader(QThread): finished_signal = pyqtSignal() def run(self): global model model = load_facenet_model() self.finished_signal.emit()

这三个技巧能显著提升项目的完成度。很多同学问“我的功能和别人一样,为什么分比别人低?”——差异往往就在这种产品化的细节里。

3.5 打包exe:从源码到可交付的完整闭环

毕设交上去,老师多半会让现场演示,也会问“你这个怎么部署,能不能给我发一个可执行文件”。这时候就需要把项目打包成 exe。PyQT 项目打包的常用工具是 PyInstaller,但直接打包经常会遇到各种坑。

先说命令:

pyinstaller --windowed --name AttendanceSystem --icon=icon.ico --add-data "facenet_keras_weights.h5;models" main.py

这里有几个关键点:

--windowed表示不显示命令行窗口,打包出来是纯 GUI 应用。如果用了--windowed又用print()调试,到时候看不到输出。所以在打包前把调试逻辑全删掉,有问题宁愿加日志文件也不要依赖控制台。

--add-data后面用分号(Windows)或冒号(macOS)分隔源文件和目标文件夹。模型文件必须打进包里,否则用户双击 exe 会因为找不到模型而闪退。

还有一个大坑是 OpenCV 和 TensorFlow 的 dll 文件。PyInstaller 在打包 TensorFlow 时经常漏掉一些动态链接库,导致生成的 exe 在其他电脑上运行时报DLL load failed。解决方案是在 spec 文件里手动添加 missing modules,或者用官方推荐的--collect-all tensorflow参数:

pyinstaller --windowed --collect-all tensorflow --name AttendanceSystem main.py

注意:--collect-all tensorflow会让 exe 体积直接膨胀到 500MB+,因为把所有 TensorFlow 组件都塞进去了,但换来的是一键可运行。实际项目中,我发现一个更精细的做法:先正常打包,运行测试报错时,根据缺失的模块名一个个补进去。这可能要多花两小时,但体积能控制在 200MB 以内。毕设就图个稳,用--collect-all保平安也行。

4. 常见问题与排查技巧实录

这部分是我在复现和调试这类项目时最常遇到的问题,整理成速查表,方便直接按图索骥。每个问题都附上排查思路和解决方式。

4.1 问题速查表与解决方案对照

问题现象可能原因解决方法
摄像头画面黑屏无内容摄像头权限没打开,或索引号错了检查系统隐私设置允许摄像头访问;尝试cv2.VideoCapture(0)改为(1)
程序启动后闪退模型文件找不到确认facenet_keras_weights.h5在正确路径;用绝对路径加载(开发阶段)
识别结果全部是“未注册学生”特征比对阈值设置偏高把余弦相似度阈值从 0.9 降到 0.8,观察效果;也可能学生注册时照片质量太差
每次识别耗时 2 秒以上使用了 GPU 版本的额外开销或未做帧间隔降低处理帧率,每 5 帧才跑一次识别;确认使用 tensorflow-cpu
数据库写入报 locked 错误多线程同时写 SQLite所有数据库写操作集中到主线程;或给连接加timeout=10
打包 exe 后运行报 DLL 加载错误PyInstaller 漏掉动态链接库用--collect-all tensorflow重打包;或把错误信息里缺的 DLL 手动从 Python 环境 copying 到 dist 目录
中文姓名显示乱码PyQT 字符串编码问题在代码文件头部声明# -*- coding: utf-8 -*-;数据库连接encoding='utf-8'

4.2 我自己踩过最深的坑:摄像头占用冲突

调试过程中最头疼的问题之一:PyQT 的 QCamera 和 OpenCV 的 VideoCapture 同时抢占摄像头。项目里,启动界面时 QCamera 把摄像头占了,然后你又要用 cv2.VideoCapture(0) 去抓帧,结果后者的画面永远是空的。

这个问题的原因很简单:摄像头设备只允许一个进程打开,同一个进程里也不能同时用两套 API 打开同一设备。解决办法要么统一用 QCamera 的全部链路(通过 QAbstractVideoSurface 抓帧),要么放弃 QCamera 直接用 OpenCV 做出预览。我的经验是:项目和考勤这种简单交互场景,直接用 OpenCV 的VideoCapture加QLabel刷新做预览,比 QCamera 省心得多。QCamera 虽然集成度高,但它的视频流处理接口学习曲线陡,反而把简单问题复杂化了。

代码框架大概是:

class CameraThread(QThread): change_pixmap_signal = pyqtSignal(np.ndarray) def run(self): cap = cv2.VideoCapture(0) while self.running: ret, frame = cap.read() if ret: self.change_pixmap_signal.emit(frame) time.sleep(0.03) # 30 FPS cap.release()

然后主线程里把frame转成 QImage 显示在 QLabel 上,识别的时候直接拿最后一帧用。思路简单,问题也少。

4.3 准确率不够的排查方向

如果你的系统在测试时识别准确率低,先别怀疑模型,按这个顺序排查:

  1. 注册照片质量:注册时摄像头是不是对着一张模糊的、逆光的脸?试试换一个光线均匀的正脸环境重新注册。
  2. 识别距离:FaceNet 训练数据大多是人脸占画面主体的情况,如果你站在 3 米外,人脸框太小,特征提取会出问题。考勤系统应该设计成“学生走到镜头前刷脸”,而不是远距离自动识别。
  3. 遮挡问题:口罩、刘海、眼镜都会影响特征。特别是口罩,FaceNet 没针对遮挡优化,戴上口罩识别率会直线下降。解决方式就是注册时要求不戴口罩,识别时排队摘口罩(或者换一个对遮挡鲁棒的模型,但那是另一个项目了)。
  4. 阈值选择:在系统里加一个调试模式,把相似度分数打到界面上,观察不同人的分数分布。比如同一个人最低相似度可能是 0.87,不同人最高相似度可能是 0.82,那阈值取 0.85 左右就合适。每个班的分布不同,最好做成可配置项,在配置文件里随时调。

5. 项目扩展与答辩加分建议

我把这个项目从“能用”做到“好讲”的过程中,总结了一些扩展方向。毕设答辩时间有限,评委很难在一个不熟悉的项目里深挖细节,但如果你能主动展示系统设计的亮点,就比较容易拿到高分。

5.1 从单机到演示:三个扩展方向

方向一:加入动态检测与出席状态统计。目前系统是一帧一帧识别,考勤记录每天一条。扩展成“按课程”考勤,一个 Excel 导出功能就能展示全班出勤率。用pandas或openpyxl生成统计报表,放在“下课后一键导出”按钮后面。这个功能不复杂,但视觉冲击力强。

方向二:用 MTCNN 替换 Haar Cascade。答辩现场,评委问“你用的传统人脸检测有什么不足?”你答“Haar 在侧脸和遮挡场景下容易漏检”,然后顺带说“我调研过 MTCNN,它通过三个级联 CNN 检测人脸,精度更高,下一步可以替换”,这比背完整段人脸识别发展史更有力。

方向三:界面上加一个实时视频监控画面中的人脸框。很多人以为识别就是对着视频流闷头算,没有把检测到的人脸在画面上画框框出来。实际上 OpenCV 画框很容易:

x, y, w, h = largest_face cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2) cv2.putText(frame, student_name, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)

把这个加到识别线程里,每帧回传的时候把画好框的画面显示出来。视觉效果从“后台在跑”升级成“实时跟踪”,在演示时非常加分。

5.2 答辩时必问的三个问题与回答框架

  1. “FaceNet 的损失函数是什么?”答:三元组损失(Triplet Loss),通过拉近同一人特征向量的距离、推远不同人特征向量的距离来训练模型。你不需要手推公式,但至少能把 Anchor、Positive、Negative 三个样本的关系讲清楚。博主多提醒一嘴:讲损失函数时不要背公式,要用直白的话解释“这是一个让同类聚拢、异类散开的训练方式”。

  2. “为什么不用 ArcFace?”答:ArcFace 在 margin 处加了角度惩罚,分类精度更高,但预训练权重体积大、部署复杂,在毕设场景下选 FaceNet 是因为“模型加载快、权重公开、CPU 可实时推理”,在精度要求够用的情况下优先保证系统流畅度。不要贬低新模型,说“我在毕设中做了权衡”比“新模型没用过”好得多。

  3. “如果老师换一个摄像头,你的系统还能用吗?”答:能。模型输入是 160x160 的标准化图像,和摄像头分辨率无关。只要保证注册时和识别时的人脸质量相近,识别结果不会因为摄像头更换而产生本质差异,但像素太低(如 320x240)会影响小脸检测。这是一个能展示你理解“模型输入输出”的好问题,可以主动展开。

5.3 最后的经验总结

做项目这件事,我从一开始就坚持一个原则:毕设项目最重要的不是模型有多新、功能有多全,而是整个链路真正跑通、你真正理解每一行代码在干什么。这个考勤系统在技术深度上不算顶尖,但每一环都是补全一个“完整产品”的必要拼图。我见过太多人在模型上花了两周调参,结果演示时因为数据库连接超时当场翻车的。

如果你打算直接拿这套源码去改,我建议第一个动手点放在“去重逻辑”上——试着把 5 秒窗口改成“每个学生每次课程只记录一次”,这会让代码结构发生连锁变化,而你恰好可以通过这次重构理解整个系统的数据流。

我个人实际用下来的体会是:考勤系统这种项目特别适合做毕设,是因为它的“演示效果”天然有故事性——你可以现场刷脸签到,跟评委互动。你只需要把其中一条主线(比如识别→入库→统计)讲清楚,就已经是合格的毕设了。别贪多,别想着塞一堆花哨功能进去,专注把一条链路做到可靠,就是最好的设计。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询