简介一套完整的人脸识别系统毕业设计资料包面向计算机视觉、图像处理与模式识别方向的本专科学生尤其适合需要完成从算法原理到工程实现全流程毕业设计的读者。压缩包共80个文件约2.18MB以C源码为主包含25个头文件和24个cpp文件覆盖人脸检测、特征提取与匹配模块同时配有开题报告、摘要、答辩相关文档及学术参考文献便于理解设计思路并规范撰写论文。内容既有基于Haar特征、PCA/LDA等经典方法的实现也涉及VFW图像采集、数据库存储等配套环节整体结构完整。目前已有207人学习浏览适合作为课设/毕设的对照参考、代码复现或文档撰写模板。1. 人脸识别系统设计毕业设计从零到可答辩的完整闭环每年到了毕设季总有人拿着人脸识别系统设计的题目来问我一开口就是“能不能直接跑起来”。其实人脸识别这个题目难倒人的往往不是算法本身而是“怎么把一堆开源模型拼成一个能演示、能答辩、数据流能讲清楚的作品”。本文要拆的这套基于 Python 的人脸识别门禁考勤系统走的是 OpenCV MTCNN FaceNet SVM PyQt5 这条经典路线逻辑链路完整检测、特征提取、分类对比、UI界面、SQLite数据库记录一应俱全适合计算机、电子、自动化方向的本科毕业设计直接复现也适合想快速搭一套人脸识别原型的新手工程师。这套系统的核心思路是把“识别”拆成三步先用人脸检测模型找到脸在哪再用深度网络提取成 128 维特征向量最后丢给 SVM 分类器判断是谁。搞清楚这条链路答辩时不管老师怎么追问都能兜住。接下来我们从选型、核心实现、界面联调到踩坑一步步把整个项目拆开。2. 系统技术选型为什么是 MTCNN FaceNet 而不是暴力模板匹配2.1 三层架构检测、特征、分类各司其职人脸识别门禁机这类产品一般内部也是分层的。最底层是人脸检测负责从摄像头画面里找出人脸框和关键点位置中间层是特征提取把人脸图像映射成一个固定维度的数值向量最上层是特征比对与身份判定决定“这个人是不是注册过的人”。毕设做系统设计必须把这三层在文档里讲清楚因为答辩老师最爱问的就是“你的系统里每一层各自承担什么责任”。我推荐这套组合的原因很直接每一层都有独立的开源实现替换任意一层不影响全局。检测层用 MTCNN它同时输出人脸框、五个关键点双眼、鼻尖、嘴角和置信度对齐效果好于 OpenCV 自带的 Haar Cascade。特征层用 FaceNet 的 Inception-ResNet v1 结构输入 160×160 的 RGB 图像输出 128 维向量这个向量对姿态、光照、表情有一定鲁棒性。分类层用 SVM因为采集到的训练样本通常只有每人 10~20 张图SVM 在小样本分类上比深度学习分类器更稳训练也快。除此之外还有个备选方案是直接用几十层卷积神经网络做端到端分类但那需要大量训练数据毕设场景下往往只有几个人、几百张图强行端到端很容易过拟合答辩时根本解释不清训练集划分逻辑。用 SVM 做分类层的好处是你能明确说出“训练集 80%、测试集 20%、核函数 RBF、惩罚系数 C1.0”这些参数这比“我调了一个晚上损失函数降到 0.01”要可信得多。2.2 开发环境与依赖安装清单整个系统的运行环境以 Python 3.8 为基准GPU 可选。如果没有独立显卡CPU 模式也能跑只是 FaceNet 特征提取单张图大概需要 200~400 毫秒每秒只能处理约 3 到 5 帧对门禁考勤这种低频识别完全够用。以下是核心依赖清单python3.8 opencv-python4.5.5.64 numpy1.21.6 scikit-learn1.0.2 facenet-pytorch2.5.3 pyqt55.15.7 sqlite3Python 内置无需单独安装安装命令建议使用 pip 逐个安装不要一次性全装方便排查依赖冲突。facenet-pytorch 这个库封装了 MTCNN 和 FaceNet 的模型权重首次调用会自动下载预训练权重文件大约 100MB 左右需要提前确认网络可达 shard 存储源。pip install opencv-python4.5.5.64 pip install numpy1.21.6 pip install scikit-learn1.0.2 pip install facenet-pytorch2.5.3 pip install pyqt55.15.7有人问为什么指定版本号这算是我踩出来的血泪经验。facenet-pytorch 2.5.3 对 numpy 1.24 以上版本会有类型兼容警告OpenCV 新版本对 PyQt5 的 QImage 格式转换接口也有细微变化。把这些版本锁住能省掉大量答辩前“软件突然跑不起来”的翻车事故。安装完成后可以先跑一段极简测试确认 MTCNN 能否正确检测摄像头画面中的人脸from facenet_pytorch import MTCNN import cv2 detector MTCNN(keep_allFalse) cap cv2.VideoCapture(0) ret, frame cap.read() boxes, probs detector.detect(frame) print(boxes, probs) cap.release()这段测试代码做了三件事创建 MTCNN 检测器、读取摄像头一帧、输出人脸框坐标和置信度。看到类似[[174.5, 88.3, 325.1, 312.6]]的输出说明环境搭建成功可以进入下一步。2.3 各模块之间的数据流设计数据的流转方向是摄像头原始帧 → MTCNN 裁剪出人脸区域并缩放到 160×160 → FaceNet 网络前向传播生成 128 维特征向量 → SVM 分类器输出人员 ID → 比对数据库记录并写入考勤日志。这个流程在你做系统设计文档时需要画成数据流图放在开题报告里答辩 PPT 也建议保留一页。其中有个容易被忽视的细节MTCNN 返回的人脸框坐标是相对于原始图像像素的而 FaceNet 的输入要求是归一化到 [0,1] 区间的浮点张量。所以每次得到人脸框后必须做一次 crop 和 resize再转成张量并除以 255。这些预处理代码虽然简单但如果没有统一封装后期测试不同来源图片时容易出现维度对不上的问题。我这里给出一段完整的数据预处理封装后面所有模块都会复用import torch import numpy as np from PIL import Image def preprocess_face(frame, box): x1, y1, x2, y2 [int(v) for v in box] face frame[y1:y2, x1:x2] face cv2.cvtColor(face, cv2.COLOR_BGR2RGB) face_img Image.fromarray(face).resize((160, 160)) face_tensor torch.tensor(np.array(face_img), dtypetorch.float32).permute(2, 0, 1) / 255.0 return face_tensor.unsqueeze(0)这一段之所以单独拿出来讲是因为它决定了整个系统特征提取的一致性。训练时注册的人脸照片和识别时摄像头抓拍的人脸必须走完全相同的预处理路径否则提取出的特征向量会受图像尺寸、色彩空间差异的影响导致识别准确率骤降。我一般会把这段代码放到utils.py里统一 import不在其他模块里重复写。3. 系统核心实现从人脸检测到身份判定的完整代码链路3.1 人脸检测与对齐MTCNN 的参数配置和输出解读MTCNN 有三个子网络P-Net 生成候选框R-Net 精筛O-Net 输出最终人脸框和关键点坐标。facenet-pytorch 库把这三个网络封装成了一个高层接口毕设场景下不需要关心内部结构但要理解关键参数的含义否则答辩被问“人脸框为什么有大有小”就答不上来。from facenet_pytorch import MTCNN mtcnn MTCNN( image_size160, margin32, min_face_size20, thresholds[0.6, 0.7, 0.7], factor0.709, post_processTrue, keep_allFalse, devicecuda if torch.cuda.is_available() else cpu )这里thresholds是三个子网络的置信度阈值[0.6, 0.7, 0.7] 表示第一层宽松一点、后两层严格一点能减少误检min_face_size20表示小于 20×20 像素的人脸直接忽略factor是图像金字塔缩放比例0.709 是原始论文的推荐值改得太大容易漏检小人脸改得太小检测速度会明显变慢。margin32是在裁剪人脸时往外扩 32 像素这样提取的特征能包含更多头部上下文信息比紧贴人脸框效果好。检测完成后需要从返回的boxes中提取五个人脸关键点。关键点信息后续有两个用途一是做对齐二是做人脸活体判断的辅助特征。MTCNN 返回的关键点坐标格式如下# 返回 landmarks 形状为 (batch, 5, 2)分别对应左眼、右眼、鼻尖、左嘴角、右嘴角 landmarks result.landmarks[0] left_eye landmarks[0] right_eye landmarks[1] nose landmarks[2]有了关键点坐标就可以计算人脸倾斜角度并做旋转校正这一步对戴眼镜、歪头的场景提升尤其明显。不过我实测下来MTCNN 自带检测已经能覆盖大多数毕设演示角度旋转校正可以在答辩展示时作为“系统优化点”单独提出来而不必默认开启因为旋转操作会增加每帧约 15 毫秒的额外开销。3.2 特征提取FaceNet 把图像变成 128 维向量的原理和代码验证FaceNet 的核心思想是让同一个人的不同人脸照片在向量空间里距离更近不同人之间距离更远。它通过三元组损失函数训练网络使得锚点到正样本的距离加上一个间距约束后仍然小于锚点到负样本的距离。这就是搜索热词里提到“人脸识别图像进入神经网络到输出高维度向量的过程”——本质上就是卷积层逐层提取纹理、边缘、五官结构等语义信息最后通过全连接层压缩成 128 维紧凑向量。在 facenet-pytorch 中加载预训练模型非常简洁值得注意的是模型推理前一定要切换到评估模式否则 BatchNorm 层的统计量会根据当前 batch 动态更新导致特征向量不稳定from facenet_pytorch import InceptionResnetV1 model InceptionResnetV1(pretrainedvggface2, classifyFalse, num_classesNone) model.eval() model model.to(device) def extract_embedding(face_tensor): with torch.no_grad(): embedding model(face_tensor) return embedding.cpu().numpy().flatten()classifyFalse是最关键的一个参数它决定了模型输出的是 128 维特征向量而不是类别概率分布。如果用默认的classifyTrue输出维度是预训练数据集 VGGFace2 的类别数 8631那就不叫特征提取而是分类了。pretrainedvggface2表示加载在 VGGFace2 数据集上预训练的权重这个数据集合计有 330 万张人脸图像覆盖 9131 个身份泛化能力在开源权重里属于第一梯队。验证特征提取效果有个简单方法——用欧氏距离判断同一人两张照片是否匹配。同一人两张不同光照照片的欧氏距离通常在 1.0 以下不同人的距离通常在 1.2 以上可以取阈值 1.1 做粗判定import numpy as np from scipy.spatial.distance import euclidean emb1 extract_embedding(preprocess_face(frame1, box1)) emb2 extract_embedding(preprocess_face(frame2, box2)) dist euclidean(emb1, emb2) print(f特征距离: {dist:.3f}) print(判定结果:, 同一人 if dist 1.1 else 不同人)这里euclidean函数来自 scipy也可以直接用 numpy 的np.linalg.norm(emb1 - emb2)计算 L2 距离。毕设答辩时建议提前准备好两组测试图片一组是同一人不同角度一组是不同人现场跑一遍这个距离对比比任何 PPT 截图都有说服力。3.3 分类器训练与识别流程SVM 参数选择与模型持久化因为毕设场景的人脸数量一般不超过 20 人每个人注册 10~20 张照片总量最多三四百张用深度学习分类器反而容易过拟合。SVM 是最适合这个数据规模的选择。我推荐使用 RBF 核函数它能把 128 维向量映射到更高维空间线性不可分的特征也能找到决策边界。from sklearn.svm import SVC from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler import joblib embeddings [] # 所有人脸特征向量列表 labels [] # 对应的标签如 zhangsan # 假设已通过上述流程提取所有训练图片特征 X np.array(embeddings) y np.array(labels) X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, stratifyy, random_state42) scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) clf SVC(kernelrbf, C1.0, gammascale, probabilityTrue) clf.fit(X_train_scaled, y_train) print(测试集准确率:, clf.score(X_test_scaled, y_test)) joblib.dump(scaler, models/scaler.pkl) joblib.dump(clf, models/svm_classifier.pkl)StandardScaler的作用是把 128 维特征向量标准化到均值为 0、方差为 1 的分布上这一步不能省。FaceNet 输出的特征向量各个维度的数值范围不完全一致直接丢给 SVM 会导致数值较大的维度主导决策边界标准化之后每个维度权重才均衡。gammascale让 scikit-learn 自动根据数据量推算 gamma 值在样本量只有几百的情况下手动调 gamma 反而是玄学auto 模式更容易翻车。SVM 的probabilityTrue参数开启概率输出这样识别界面可以显示“置信度 92.3%”答辩演示更直观但代价是训练时间会稍长。另外要强调的是stratifyy的作用——保证划分训练集和测试集时每个人的人脸照片按比例分配不会出现某个人的照片全在测试集里导致准确率虚低的情况。得到训练好的分类器后识别流程就很简单了摄像头抓帧 → MTCNN 检测 → FaceNet 提取特征 → StandardScaler 变换 → SVM 预测。整个链路封装成一个recognize(frame)函数返回人员姓名和置信度后面做 PyQt5 界面时直接调用。4. UI 界面与考勤数据库把裸算法变成能演示的门禁系统4.1 PyQt5 主界面设计摄像头区域、识别结果面板与考勤记录表大部分毕业设计如果只做到“算法能跑、控制台输出识别结果”答辩分数基本就是及格线。想要冲良好以上必须有一个可视化的操作界面。PyQt5 是性价比最高的选择它和 OpenCV 的衔接方式很成熟网上可以搜到大量现成控件用法。主界面布局我建议分成三个区域左侧是摄像头视频流显示区域右侧上方是当前识别结果面板显示姓名、工号、置信度、时间右侧下方是 SQLite 考勤记录表QTableWidget 展示最近 10 条打卡记录。顶部放两个按钮“开始识别”和“添加人员”。视频流实时显示的核心代码是基于 QTimer 定时器周期性从摄像头读取帧并刷新 QLabelimport cv2 import sys from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget from PyQt5.QtCore import QTimer from PyQt5.QtGui import QImage, QPixmap class FaceRecognitionUI(QMainWindow): def __init__(self): super().__init__() self.cap cv2.VideoCapture(0) self.timer QTimer(self) self.timer.timeout.connect(self.update_frame) self.timer.start(100) # 每 100 毫秒更新一帧约 10 FPS def update_frame(self): ret, frame self.cap.read() if not ret: return rgb_frame cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb_frame.shape bytes_per_line ch * w qt_image QImage(rgb_frame.data, w, h, bytes_per_line, QImage.Format_RGB888) self.video_label.setPixmap(QPixmap.fromImage(qt_image))QTimer的时间间隔 100 毫秒是一个需要权衡的参数。间隔太短比如 30 毫秒会导致 MTCNN 检测和 FaceNet 推理来不及完成界面就会卡顿间隔太长比如 300 毫秒又会有明显延迟感。我实测算下来默认设置为 100 毫秒比较合适既保证画面连贯又给算法留出了推理时间。如果你的 CPU 较弱可以改成 150 毫秒牺牲流畅度换稳定性。4.2 SQLite 考勤记录表设计字段定义与防重复打卡逻辑考勤记录表是整个系统设计里文档上最容易出彩的部分因为这里能展示你对数据库设计的理解。我设计的表结构如下CREATE TABLE attendance_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, person_name TEXT NOT NULL, person_id TEXT NOT NULL, confidence REAL NOT NULL, check_time DATETIME DEFAULT CURRENT_TIMESTAMP );这里除了基本的人员姓名和打卡时间外还记录了confidence置信度字段。答辩时可以解释为“系统自动记录每一次识别的置信度用于后续质量分析和误检排查”。同时还要在代码里增加一个防重复打卡逻辑——同一人 5 分钟内只记录一条打卡记录避免学生站在摄像头前反复试脸把数据库刷爆import sqlite3 from datetime import datetime, timedelta conn sqlite3.connect(attendance.db) cursor conn.cursor() def add_attendance(name, person_id, confidence): last_time cursor.execute( SELECT MAX(check_time) FROM attendance_log WHERE person_name ?, (name,) ).fetchone()[0] if last_time: last_dt datetime.strptime(last_time, %Y-%m-%d %H:%M:%S) if datetime.now() - last_dt timedelta(minutes5): return False # 5 分钟内不重复记录 cursor.execute( INSERT INTO attendance_log (person_name, person_id, confidence) VALUES (?, ?, ?), (name, person_id, confidence) ) conn.commit() return Truetimedelta(minutes5)窗口时长可以根据场景需求调整实验室门禁可以设 1 分钟公司考勤可以设 10 分钟。这套 SQLite 方案不需要额外安装数据库服务答辩演示时也不会出现“MySQL 连不上”这种尴尬。数据文件就是本地一个attendance.db文件想导出报表直接用 pandas 读取。4.3 人员注册流程录入人脸照片与特征入库系统要支持“添加人员”功能操作路径是用户点击添加按钮 → 输入姓名和工号 → 摄像头连续采集 15 帧人脸图像 → 逐帧提取特征向量 → 连同标签存入训练集 → 重新训练 SVM。这一套流程保证了系统在演示现场也能演示“未知人员注册后立即识别”。注册过程中有个关键细节连续采集 15 帧时需要做简单筛选只有 MTCNN 检测置信度大于 0.9 的帧才保留否则像模糊帧、半脸帧都会混进训练集污染特征分布。我一般会同时计算相邻两帧人脸框的重叠度重叠度低于 60% 的帧说明人脸位置剧烈跳变也丢弃。def collect_faces(cap, detector, max_frames15): collected [] last_box None while len(collected) max_frames: ret, frame cap.read() if not ret: continue boxes, probs detector.detect(frame) if boxes is None or probs[0] 0.9: continue if last_box is not None: iou compute_iou(boxes[0], last_box) if iou 0.6: continue collected.append(frame) last_box boxes[0] return collectedcompute_iou需要自己实现就是标准的两个矩形框交并比计算。这个筛选逻辑虽然增加了代码量但能让你在答辩时讲出“数据质量控制”这个词这在毕设评分标准里是加分项。5. 人脸识别系统部署避坑与常见问题排查五个最容易翻车的环节5.1 摄像头无法打开或画面全黑现象程序运行后 OpenCV 报returned False或者画面区域是纯黑色。原因摄像头被其他应用占用是最常见的情况比如钉钉、微信视频通话、OBS Studio 后台挂着。其次是默认摄像头索引不对笔记本自带摄像头索引是 0外接 USB 摄像头可能是 1 或 2。另一种情况是 OpenCV 4.5 版本在部分 Windows 笔记本上对 MJPG 格式摄像头支持不稳定。解决先关掉所有可能占用摄像头的软件然后把VideoCapture(0)改成VideoCapture(1)测试或者在初始化时加一行cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(M, J, P, G))强制使用 MJPG 编码。如果是在虚拟机里跑开发记得在虚拟机设置里把 USB 摄像头设备直通给虚拟机。5.2 FaceNet 首次运行下载权重超时或卡住现象代码第一次执行到InceptionResnetV1(pretrainedvggface2)时长时间没有反应或者报连接超时错误。原因模型权重托管在 GitHub Release 和第三方对象存储上国内网络访问不稳定时经常会中断而且这个权重文件有 100MB 左右断点续传支持不好。解决不要等代码在线下载手动到 facenet-pytorch 官方仓库找到权重文件用浏览器或下载工具下载好后放到项目~/.cache/torch/checkpoints/目录里。文件名必须是官方命名比如vggface2_feat_dim512.pth。从那以后我每次搭建新环境都强制走一遍“提前下载权重”的流程再也没被超时坑过。5.3 识别准确率低经常认错人或识别不出现象注册时明明存了照片但运行时对着摄像头就是识别不准确要么输出错误人名要么直接报 unknown。原因大概率是训练集和识别时的预处理不一致。比如注册时用的是手机照片分辨率高、背景干净识别时是摄像头实时帧光照暗、有运动模糊。另外如果训练样本每人才 3~5 张图特征空间覆盖不够SVM 决策边界就会很怪。解决统一所有输入图像的质量标准。注册照片也用摄像头现场采集不要用手机拍好再导入每人的训练照片采集 15 张以上覆盖正面、左右侧脸、抬头、低头四个姿态特征提取前统一对图像做直方图均衡化代码是cv2.equalizeHist(gray)能明显提升暗光场景鲁棒性。5.4 界面卡顿掉帧严重CPU 占用率飙升现象界面启动后操作不流畅QTimer 刷新节奏明显变慢任务管理器显示 Python 进程 CPU 占用率超过 100%。原因人脸检测、特征提取、SVM 预测全部跑在主线程里QTimer 每次触发都要同步等待这些任务完成阻塞了界面刷新。解决把识别流程放到 QThread 子线程中执行通过信号槽把识别结果传回主线程。这是 PyQt5 程序设计的标准做法也能展示你对 GUI 线程模型的理解。下面是最小可用的线程封装from PyQt5.QtCore import QThread, pyqtSignal class RecognitionThread(QThread): result_ready pyqtSignal(str, str, float) def __init__(self, cap, recognizer): super().__init__() self.cap cap self.recognizer recognizer self.running True def run(self): while self.running: ret, frame self.cap.read() if not ret: continue name, person_id, conf self.recognizer.recognize(frame) if name ! unknown: self.result_ready.emit(name, person_id, conf) self.msleep(100)pyqtSignal定义了一个携带字符串和浮点数信号的传输通道子线程识别完成后通过emit把结果发回主线程更新界面。注意信号里不能直接传 OpenCV 的 frame 对象因为 QThread 的跨线程传参必须是可序列化的简单类型frame 这种大型 numpy 数组容易触发 Qt 的引用计数问题。5.5 特征向量距离阈值判定结果反复横跳现象同一人距离在 0.8 到 1.3 之间波动把阈值设为 1.1 时一会识别成功一会识别失败。原因光照变化对人脸特征影响很大同一个工位上午和下午的自然光色温不同MTCNN 检测到的人脸区域也存在细微位置偏移导致 FaceNet 提取的向量有波动。解决不要单帧判定采用连续 5 帧投票机制。5 帧中同一人出现次数 ≥ 3 次才确认识别结果同时取中间帧的置信度作为该次识别置信度。from collections import Counter vote_window [] def vote_recognize(frame): global vote_window name recognizer.recognize_raw(frame) vote_window.append(name) if len(vote_window) 5: vote_window.pop(0) counter Counter(vote_window) best_name, count counter.most_common(1)[0] if count 3 and best_name ! unknown: return best_name, count / len(vote_window) return unknown, 0.0这种滑动窗口投票机制在工业界的野外人脸识别产品里也很常见能平滑单帧波动。答辩时可以提一句“参考了工业门禁机的多帧确认策略”这个细节会让老师觉得你的系统设计不是玩具。6. 验证方法与答辩准备用 LFW 数据集评估模型并把系统做成完整演示6.1 模型评估在公开数据集上验证特征提取器的有效性很多毕设论文只写了“系统识别准确率 95%”但这个数据是在自己采集的 10 个人上测出来的缺乏说服力。有一个有效的验证手段是在公开数据集上评估 FaceNet 特征提取器的质量——具体做法是下载 LFWLabeled Faces in the Wild数据集的子集随机抽取 1000 对同类人脸对和 1000 对异类人脸对计算每一对的特征向量欧氏距离然后画出 ROC 曲线并计算 AUC。from sklearn.metrics import roc_auc_score, roc_curve import matplotlib.pyplot as plt distances_same [] # 同一人特征距离 distances_diff [] # 不同人特征距离 labels [1] * len(distances_same) [0] * len(distances_diff) all_dists distances_same distances_diff fpr, tpr, thresholds roc_curve(labels, all_dists, pos_label1) auc roc_auc_score(labels, all_dists) print(fAUC: {auc:.4f}) plt.plot(fpr, tpr) plt.xlabel(False Positive Rate) plt.ylabel(True Positive Rate) plt.title(fLFW Subset ROC (AUC{auc:.4f})) plt.savefig(roc_curve.png)这套验证过程本身不需要训练任何模型只是用现成模型做推理耗时大概十几分钟。如果 AUC 能到 0.95 以上VGGface2 预训练权重在这个任务上通常轻松达到就说明特征提取层可靠系统误差来源主要在数据采集和 SVM 分类环节。论文里放一张 ROC 曲线图加一段分析比你写两千字定性描述都管用。6.2 完整演示动线设计从门禁模拟到考勤记录导出毕设答辩现场时间紧张建议把演示流程固定在两分钟内按照以下顺序启动系统 → 点击“开始识别” → 用注册过的人脸靠近摄像头 → 显示识别成功并弹出姓名和工号 → 切到考勤记录标签页展示新插入的打卡记录 → 点击“导出今日考勤”生成 CSV 文件。导出功能用一行 pandas 就能完成import pandas as pd df pd.read_sql_query(SELECT * FROM attendance_log, conn) df.to_csv(attendance_export.csv, indexFalse, encodingutf-8-sig)encodingutf-8-sig是为了让生成的 CSV 用 Excel 打开不乱码这是中文环境下导出数据最常见的坑。如果答辩老师要求看实时视频流的原始画面可以加一个“显示检测框”的开关开启后绘制人脸框和关键点这个实现很直接用 OpenCV 的cv2.rectangle画框、cv2.circle画点、cv2.putText写标签即可。整个系统在答辩现场只依赖笔记本自带摄像头、本地 SQLite 文件、本地模型权重不需要联网也不依赖任何云端 API。这一点建议在答辩开场时就说明——因为有些老师会追问“你的系统依赖百度云或阿里云的接口吗如果离线怎么办”。能在完全离线环境下独立运行天然回答了这个疑虑。6.3 把系统做成可复现的工程requirements 文件与项目目录规范最后有一个容易被新手忽略但答辩老师一定会看的地方——项目目录是否规范。我维护的毕设项目目录结构通常长这样face_recognition_system/ ├── main.py # 程序入口启动 PyQt5 界面 ├── requirements.txt # 依赖清单必须带版本号 ├── models/ │ ├── scaler.pkl # 标准化模型 │ └── svm_classifier.pkl # 训练好的分类器 ├── weights/ # FaceNet 和 MTCNN 预训练权重 ├── dataset/ │ ├── zhangsan/ # 每个人的训练照片单独建文件夹 │ ├── lisi/ │ └── wangwu/ ├── ui.py # 界面代码 ├── recognizer.py # 识别核心逻辑 ├── database.py # SQLite 操作 ├── utils.py # 公共预处理函数 └── eval_roc.py # LFW 评估脚本requirements.txt必须把所有依赖及版本号写死用pip freeze requirements.txt自动生成后人工检查删除无关项。答辩时会要求现场部署没有版本锁定的项目在别人电脑上大概率跑不起来这是我见过太多同学在答辩现场翻车后总结出的血泪教训从那以后我每次交付毕设项目都强制自己走一遍“新建虚拟环境 → 按 requirements 安装 → 跑通全流程”的验证。希望帮到你把你的毕设做得既让老师点头、也让代码配得上那份自信。本文还有配套的精品资源点击获取