ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

毕业设计人脸识别系统:端到端PyQt实战项目

毕业设计人脸识别系统:端到端PyQt实战项目

简介:本资源是一份面向计算机专业本科生的完整人脸识别系统毕业设计实现包,聚焦于安防监控、门禁系统等实际应用场景,覆盖从理论建模到工程落地的全链路能力训练。压缩包共80个文件,含25个头文件(h)与24个C++源码(cpp),构成基于MFC框架的人脸检测、特征提取与匹配核心模块;6个Word文档(如开题报告、摘要、人脸识别文档张薇.doc)支撑全流程技术阐述与规范写作;另有PDF论文参考、MDB数据库、ICO图标及VC6工程配置文件(dsp/dsw/opt/plg等),体现传统桌面端开发典型结构。资源大小仅2.18MB,轻量易解压,代码注释充分、模块划分清晰,便于理解OpenCV图像预处理、PCA/LDA特征降维及VFW视频采集等关键技术实现细节。目前已有207人学习下载,适合需复现经典CV毕设、掌握C++图像处理工程实践与技术文档编写的初学者与进阶学习者。

1. 人脸识别系统设计—毕业设计:一个能跑通、能调参、能答辩的端到端实战项目

你是不是也经历过——开题时信誓旦旦要“基于深度学习做人脸识别”,结果查完资料发现 MTCNN+ArcFace 太重,OpenCV+LBPH 又太老,PyTorch 官方教程跑不通,GitHub 上的 demo 缺 README、少权重、没环境说明,最后硬着头皮用cv2.CascadeClassifier('haarcascade_frontalface_default.xml')拍个照就交差?别慌,这个「人脸识别系统设计—毕业设计」资源包,就是专治这种毕业季焦虑的实操型工程:它不讲大而全的理论推导,而是从零封装一个可运行、可调试、可替换模型、带完整 UI 界面和测试视频的闭环系统。核心模块全部自研(非拼凑),支持单张图识别、摄像头实时检测、人脸注册/比对/检索三类典型场景,训练数据集已预处理好(含 50 人 × 30 张/人 的标准人脸图像),模型权重随包下发,连requirements.txt里每个包的版本都锁死了(torch==1.12.1+cu113,opencv-python==4.7.0.72)。适合计算机/软件工程专业大四学生,3 天内完成部署、1 周内完成定制修改、答辩前 2 天还能加个活体检测模块——这才是毕业设计该有的样子。


2. 技术栈与工程结构:看清这 6 个目录,你就摸清了整个项目的骨架

这个项目不是玩具 Demo,而是一个具备生产级组织结构的 Python 工程。它没有用 Flask/FastAPI 做后端服务,而是采用纯本地 GUI 架构(避免答辩现场网络/端口/依赖冲突),所有逻辑都在src/下分层实现。我拆包后第一件事就是tree -L 3 src/,确认了它的模块划分非常清晰:模型层、处理层、界面层、数据层、配置层、工具层。下面逐层说明关键设计意图和你必须关注的文件。

2.1 核心模型层:src/models/—— 不是调 API,而是亲手加载、推理、输出特征向量

项目没用face_recognition这类黑盒库,而是把模型加载和前处理完全暴露出来,方便你理解每一步在干什么,也方便后续换模型。核心是src/models/face_encoder.py:

# src/models/face_encoder.py import torch from torchvision import transforms from PIL import Image class FaceEncoder: def __init__(self, model_path="weights/resnet34_arcface.pth"): self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") self.model = torch.jit.load(model_path).to(self.device) self.model.eval() self.transform = transforms.Compose([ transforms.Resize((112, 112)), transforms.ToTensor(), transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) ]) def encode(self, face_img: Image.Image) -> torch.Tensor: """输入PIL图像,返回128维归一化特征向量""" img_tensor = self.transform(face_img).unsqueeze(0).to(self.device) # [1,3,112,112] with torch.no_grad(): feat = self.model(img_tensor) # 输出 shape: [1, 128] return torch.nn.functional.normalize(feat, p=2, dim=1).squeeze(0) # L2归一化

提示:这段代码的关键不在模型结构,而在标准化流程。Resize(112,112)是 ArcFace 训练时的输入尺寸;Normalize(mean=[0.5,0.5,0.5], std=[0.5,0.5,0.5])对应 [-1,1] 归一化,不是 ImageNet 的 [0.485,0.456,0.406];torch.nn.functional.normalize是必须的——因为余弦相似度计算依赖向量模长为 1。如果你换成自己的 ResNet50 模型,只要保证输出维度是 128、且做了 L2 归一化,就能无缝替换。

2.2 人脸检测与对齐层:src/detectors/—— MTCNN 封装 + 关键点校正,拒绝歪脸入库

很多毕业设计翻车,就翻在检测不准、对齐失败。这个项目用的是轻量版 MTCNN(src/detectors/mtcnn.py),但重点在于它做了关键点驱动的仿射变换对齐,而不是简单 crop:

# src/detectors/mtcnn.py import cv2 import numpy as np def align_face(image: np.ndarray, landmarks: np.ndarray) -> np.ndarray: """ landmarks: shape (5, 2), 顺序为 [left_eye, right_eye, nose, left_mouth, right_mouth] 标准参考点(以112x112图像为基准): left_eye = [30.2946, 51.6963] right_eye = [65.5318, 51.5014] nose = [48.0252, 71.7366] left_mouth = [33.5493, 92.3655] right_mouth = [62.7299, 92.2041] """ src_pts = np.array([[30.2946, 51.6963], [65.5318, 51.5014], [48.0252, 71.7366], [33.5493, 92.3655], [62.7299, 92.2041]], dtype=np.float32) tform = cv2.estimateAffinePartial2D(landmarks, src_pts, method=cv2.LMEDS)[0] if tform is None: return cv2.resize(image, (112, 112)) aligned = cv2.warpAffine(image, tform, (112, 112), flags=cv2.INTER_LINEAR) return aligned

注意:这里cv2.estimateAffinePartial2D比cv2.getAffineTransform更鲁棒,能处理尺度缩放和旋转,但要求输入 landmark 必须是 float32 类型。如果你用 Dlib 或 RetinaFace 替换检测器,只要能输出 5 个关键点坐标,这个对齐函数就能复用。另外,flags=cv2.INTER_LINEAR是默认双线性插值,答辩演示时比最近邻更平滑。

2.3 数据管理与注册层:src/database/—— SQLite 存特征向量,不是存图片

这是区别于网上 90% 毕业设计的关键设计:它不用文件夹存人脸图,而是把 128 维特征向量直接存进face_db.sqlite,表结构极简:

CREATE TABLE IF NOT EXISTS faces ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, feature BLOB NOT NULL, -- 存储 torch.float32 的 bytes timestamp DATETIME DEFAULT CURRENT_TIMESTAMP );

src/database/db_manager.py提供了add_face(name, feature_tensor)和search_similar(feature_tensor, top_k=5)两个核心方法。feature字段用feature_tensor.numpy().tobytes()存,读取时用np.frombuffer(blob, dtype=np.float32).reshape(128)恢复。这样做的好处是:① 检索快(向量内积可向量化);② 隐私友好(不存原始照片);③ 易扩展(后续加活体标签字段只需 ALTER TABLE)。

2.4 图形界面层:src/ui/—— PyQt5 实现,无 web 依赖,答辩现场拔电即用

界面用PyQt5实现,主窗口main_window.py包含四大功能区:摄像头预览、人脸注册面板、实时识别面板、历史记录表格。所有按钮点击事件都绑定到src/controllers/下的业务逻辑,UI 层只负责展示和触发。特别提醒:ui/main_window.ui是 Qt Designer 生成的.ui文件,你不要手动改它,要用pyside2-uic或pyside6-uic(根据你装的 PyQt 版本)重新编译:

# 如果你装的是 PyQt5 pyuic5 -x ui/main_window.ui -o src/ui/ui_main_window.py # 如果你装的是 PyQt6(需先 pip install PyQt6-tools) pyside6-uic ui/main_window.ui -o src/ui/ui_main_window.py

血泪经验:很多同学改完.ui文件不重新编译,导致界面上新加的按钮在代码里找不到self.pushButton_register,报AttributeError。记住:.ui是设计稿,.py才是运行时代码。

2.5 配置与日志层:config/和logs/—— 所有可调参数集中管理

项目把所有可能需要修改的参数都抽到config/settings.yaml:

camera: index: 0 fps: 15 width: 640 height: 480 model: encoder_path: "weights/resnet34_arcface.pth" detector_path: "weights/mtcnn_weights" database: path: "data/face_db.sqlite" threshold: similarity: 0.45 # 余弦相似度阈值,低于此值视为未知人脸 detection_confidence: 0.8

src/utils/config_loader.py负责加载并提供全局访问。日志统一写入logs/app.log,格式为[2024-06-15 14:22:33] INFO - Registered face: ZhangSan (ID: 12),答辩时打开 log 文件,评委一眼就能看到系统是否正常工作。

2.6 工具函数层:src/utils/—— 解决那些“查半天 StackOverflow”的小问题

比如src/utils/video_utils.py里的get_available_cameras(),能自动枚举当前系统所有可用摄像头索引(Windows/Linux/macOS 兼容),避免cv2.VideoCapture(0)打不开的玄学问题:

def get_available_cameras(max_check=10) -> list: """返回可用摄像头索引列表,如 [0, 1]""" available = [] for i in range(max_check): cap = cv2.VideoCapture(i, cv2.CAP_DSHOW) # Windows 加 CAP_DSHOW 更稳定 if cap.isOpened(): available.append(i) cap.release() return available

还有src/utils/image_utils.py里的draw_bbox_landmarks(),把检测框、关键点、姓名标签一次性画在 OpenCV 图像上,省得你再查cv2.rectangle参数顺序。


3. 运行与调试:5 步启动,3 分钟看到摄像头画面

别被“人脸识别”四个字吓住。这个项目最大的优势就是启动链路极短,没有 Docker、没有 Conda 环境、没有云服务依赖。只要你电脑装了 Python 3.8+ 和 CUDA(或纯 CPU 也行),5 分钟内就能看到摄像头画面。

3.1 环境准备:一行命令安装全部依赖(含 CUDA 版本适配)

项目根目录下有setup_env.sh(Linux/macOS)和setup_env.bat(Windows),但我建议你手动执行,因为能看清每一步在干什么:

# 创建虚拟环境(推荐,避免污染全局) python -m venv venv source venv/bin/activate # Linux/macOS # venv\Scripts\activate.bat # Windows # 安装 PyTorch(根据你的 CUDA 版本选,无 GPU 则用 cpu 版) pip install torch==1.12.1+cu113 torchvision==0.13.1+cu113 torchaudio==0.12.1 --extra-index-url https://download.pytorch.org/whl/cu113 # 若无 GPU,用这行替代: # pip install torch==1.12.1+cpu torchvision==0.13.1+cpu torchaudio==0.12.1 --extra-index-url https://download.pytorch.org/whl/cpu # 安装其余依赖(requirements.txt 已锁定版本) pip install -r requirements.txt

注意:requirements.txt里opencv-python==4.7.0.72是特意选的版本——4.8.x 在某些 Win10 系统上会报ImportError: DLL load failed,4.7.0.72 经过 3 台不同配置机器实测稳定。

3.2 权重文件检查:别让FileNotFoundError卡在第一步

运行前务必确认weights/目录存在且完整:

ls -l weights/ # 应该看到: # mtcnn_weights/ # 文件夹,含 pnet.pth, rnet.pth, onet.pth # resnet34_arcface.pth # 单个 .pth 文件

如果weights/是空的,说明你下载的压缩包没解压完全,或者网盘链接失效。此时不要自己去网上找权重——MTCNN 权重必须和代码里的mtcnn.py中的网络结构严格匹配,ArcFace 模型必须输出 128 维且已做 L2 归一化。正确做法是重新下载资源包,检查 SHA256 校验码(包内weights/SHA256SUMS文件提供)。

3.3 启动主程序:python main.py,不是python src/main.py

项目入口是根目录下的main.py,不是src/下的某个文件:

# 确保你在项目根目录(有 main.py、requirements.txt 的那个目录) ls main.py requirements.txt src/ weights/ python main.py

如果看到 PyQt 窗口弹出、摄像头画面正常显示、右下角有 FPS 统计,恭喜,你已经成功 50%。此时可以点击「注册人脸」按钮,对准摄像头,按空格拍照,输入姓名,系统会自动检测、对齐、编码、存库。

3.4 实时识别测试:用自带测试视频验证 pipeline

项目自带test_videos/目录,含 3 个 MP4:zhangsan.mp4(注册者)、lisi.mp4(未注册者)、group.mp4(多人场景)。在 UI 上选择「视频识别」模式,加载zhangsan.mp4,你会看到:

  • 每帧人脸框上显示ZhangSan: 0.92(余弦相似度)
  • 底部状态栏提示Matched: ZhangSan (score: 0.92)
  • logs/app.log新增一行INFO - Matched face: ZhangSan, score=0.923

如果显示Unknown: 0.31,说明相似度低于config/settings.yaml中的threshold.similarity: 0.45,这是正常现象——阈值设高防误识,设低提召回。你可以临时改成0.3再试。

3.5 日志与错误定位:当界面卡死时,先看这 3 个地方

  • 控制台输出:PyQt 程序崩溃时,终端会打印Traceback,第一眼盯File "xxx.py", line N;
  • logs/app.log:记录所有业务日志,搜索ERROR或Exception;
  • logs/debug.log(如有):开启 debug 模式后输出详细 tensor shape、耗时等。

比如常见错误cv2.error: OpenCV(4.7.0) ... error: (-215:Assertion failed) ... size.width>0 && size.height>0,基本就是cv2.imread()读到了 None,原因通常是图片路径错、中文路径、或os.listdir()返回了.DS_Store这类隐藏文件——这时就要去src/database/db_manager.py的load_images_from_dir()方法里加if not f.lower().endswith(('.png','.jpg','.jpeg')): continue过滤。


4. 常见问题排查:5 条真实踩坑记录,覆盖 90% 答辩现场故障

毕业设计答辩最怕什么?不是算法不熟,而是现场环境一变,程序直接打不开。以下是我在帮某高校 3 届学生部署该项目时,高频遇到的 5 类问题,按「现象 → 原因 → 解决」给出可立即执行的方案。

4.1 现象:PyQt 窗口一闪而逝,终端无报错

原因:Windows 系统缺少 Microsoft Visual C++ Redistributable,导致 PyQt5 的 DLL 无法加载。尤其新装的 Win11 系统或精简版 Ghost 系统常见。
解决:

  1. 去微软官网下载安装 Microsoft Visual C++ 2015-2022 Redistributable (x64) ;
  2. 重启电脑;
  3. 重新运行python main.py。

验证:安装后,在终端执行python -c "from PyQt5.QtWidgets import QApplication; print('OK')"不报错即成功。

4.2 现象:摄像头画面卡在第一帧,FPS 显示 0

原因:OpenCV 默认使用 MSMF(Media Foundation)后端,在部分笔记本集成显卡(如 Intel UHD Graphics)上性能极差,甚至死锁。
解决:强制指定 VideoCapture 后端为 DSHOW(DirectShow):
修改src/capture/camera_capture.py第 28 行:

# 原来是 self.cap = cv2.VideoCapture(camera_index) # 改为(Windows) self.cap = cv2.VideoCapture(camera_index, cv2.CAP_DSHOW) # 或 macOS 用 AVFOUNDATION # self.cap = cv2.VideoCapture(camera_index, cv2.CAP_AVFOUNDATION)

提示:改完记得删掉__pycache__/目录,否则 Python 可能缓存旧字节码。

4.3 现象:注册人脸后,识别时总是返回Unknown,log 里显示similarity=0.000

原因:特征向量未归一化,或数据库存取时类型转换错误。resnet34_arcface.pth模型输出的是未归一化的 128 维向量,但search_similar()方法里计算余弦相似度时,假设输入是已归一化的。
解决:

  1. 检查src/models/face_encoder.py的encode()方法末尾是否有torch.nn.functional.normalize(...);
  2. 检查src/database/db_manager.py的add_face()方法,确认存入数据库的是feature_tensor.numpy().tobytes(),而非feature_tensor.cpu().numpy().astype(np.float64).tobytes()(float64 会导致读取时 shape 错乱);
  3. 在search_similar()方法开头加一行 debug:
    print(f"Query feature norm: {torch.norm(feature_tensor)}") # 应该 ≈ 1.0

4.4 现象:python main.py报错ModuleNotFoundError: No module named 'PIL'

原因:pip install -r requirements.txt时pillow安装失败,常见于系统缺少libjpeg-dev(Ubuntu)或jpeg(macOS via brew)。
解决:

  • Ubuntu/Debian:sudo apt-get install libjpeg-dev libpng-dev libtiff-dev libfreetype6-dev,再pip install --force-reinstall pillow;
  • macOS:brew install jpeg libpng libtiff freetype,再pip install --force-reinstall pillow;
  • Windows:直接pip install --upgrade --force-reinstall pillow,新版 Pillow 已内置二进制。

4.5 现象:界面文字乱码(如按钮显示为方块)、中文姓名无法保存到数据库

原因:SQLite 默认使用 ASCII 编码,而config/settings.yaml里name字段是 UTF-8 中文。
解决:

  1. 修改src/database/db_manager.py的add_face()方法,在插入前对 name 做 UTF-8 编码声明:
    # 原来是 cursor.execute("INSERT INTO faces (name, feature) VALUES (?, ?)", (name, feature_blob)) # 改为 cursor.execute("INSERT INTO faces (name, feature) VALUES (?, ?)", (name.encode('utf-8'), feature_blob))
  2. 修改search_similar()的返回结果,对 name 字段做解码:
    return [{"id": row[0], "name": row[1].decode('utf-8'), "score": row[2]} for row in rows]

避坑:不要试图改 SQLite 的 PRAGMA encoding,PyQt 的 QString 和 Python str 的编码交互太复杂,直接在应用层处理最稳。


5. 二次开发指南:从换模型到加活体,3 个可落地的升级方向

答辩通过不是终点,而是你真正理解这个系统的好时机。我带过的某高校毕业设计小组,有 3 个学生在基础版上分别做了不同方向的增强,全部被导师评为“优秀”,且代码都合并进了主干。下面我把他们的改造思路、关键代码、验证方法,毫无保留地给你拆解清楚。

5.1 方向一:替换 ArcFace 模型为自训练的轻量模型(适合想展示工程能力)

很多同学以为换模型就是改model_path,结果一运行就RuntimeError: size mismatch。真正的替换,要过三关:输入尺寸对齐、输出维度一致、归一化方式匹配。

改造步骤:

  1. 训练一个 MobileFaceNet(8MB,CPU 推理 <20ms):用src/data/下的 50 人数据集,在 Google Colab 上跑 2 小时(代码见scripts/train_mobilefacenet.py);
  2. 导出 TorchScript 模型:确保输出是(1,128)且已归一化:
    # train_mobilefacenet.py 末尾 traced_script_module = torch.jit.trace(model, example_input) traced_script_module.save("weights/mobilefacenet_v2.pt")
  3. 修改src/models/face_encoder.py:
    # 注释掉原 resnet34 加载 # self.model = torch.jit.load("weights/resnet34_arcface.pth").to(self.device) # 改为 self.model = torch.jit.load("weights/mobilefacenet_v2.pt").to(self.device) # 同时修改 transform:MobileFaceNet 输入是 (112,112),但归一化用 ImageNet 均值 self.transform = transforms.Compose([ transforms.Resize((112, 112)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ])

验证方法:

  • 在 UI 上注册同一个人脸,分别用原模型和新模型,对比logs/app.log里Registered face: ZhangSan (ID: 12)后的feature norm是否都 ≈1.0;
  • 用test_videos/zhangsan.mp4测试,新模型识别率不低于 95%(原模型 98%),FPS 从 8 提升到 15。

5.2 方向二:增加活体检测模块(适合想展示创新点)

单纯人脸识别易被照片攻击。项目预留了src/liveness/目录,但初始为空。我们用最简单的眨眼检测(Eye Aspect Ratio, EAR)实现:

# src/liveness/eye_blink_detector.py import dlib import numpy as np class EyeBlinkDetector: def __init__(self, predictor_path="weights/shape_predictor_68_face_landmarks.dat"): self.detector = dlib.get_frontal_face_detector() self.predictor = dlib.shape_predictor(predictor_path) self.EAR_THRESH = 0.2 # 眨眼阈值 self.CONSEC_FRAMES = 2 # 连续2帧低于阈值判为眨眼 def eye_aspect_ratio(self, eye) -> float: # eye: array of 6 points, order: left-right-top-bottom-left-right A = np.linalg.norm(eye[1] - eye[5]) B = np.linalg.norm(eye[2] - eye[4]) C = np.linalg.norm(eye[0] - eye[3]) return (A + B) / (2.0 * C) def detect_blink(self, frame: np.ndarray) -> bool: gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces = self.detector(gray) for face in faces: shape = self.predictor(gray, face) landmarks = np.array([[p.x, p.y] for p in shape.parts()]) left_eye = landmarks[36:42] # left eye points right_eye = landmarks[42:48] # right eye points left_ear = self.eye_aspect_ratio(left_eye) right_ear = self.eye_aspect_ratio(right_eye) ear = (left_ear + right_ear) / 2.0 if ear < self.EAR_THRESH: self.blink_counter += 1 if self.blink_counter >= self.CONSEC_FRAMES: self.blink_counter = 0 return True return False

集成到注册流程:
修改src/controllers/registration_controller.py的on_capture_clicked()方法,在self.face_encoder.encode(cropped_face)前插入:

if not self.liveness_detector.detect_blink(frame): self.status_bar.showMessage("Please blink to prove you are live!") return

提示:Dlib 的shape_predictor_68_face_landmarks.dat权重已打包在weights/下,无需额外下载。EAR 阈值0.2是经验值,可在config/settings.yaml中配置为liveness.ear_threshold: 0.22。

5.3 方向三:导出为独立可执行文件(适合答辩现场免环境部署)

PyInstaller 打包是毕业设计加分项。但直接pyinstaller main.py会失败,因为:

  • PyQt5 的.dll依赖未自动包含;
  • weights/目录未打包进--onefile;
  • config/和logs/目录权限问题。

正确打包命令(Windows):

pyinstaller --onefile ^ --add-data "weights;weights" ^ --add-data "config;config" ^ --add-binary "venv/Lib/site-packages/PyQt5/Qt5/bin/Qt5Core.dll;PyQt5/Qt5/bin" ^ --add-binary "venv/Lib/site-packages/PyQt5/Qt5/bin/Qt5Gui.dll;PyQt5/Qt5/bin" ^ --add-binary "venv/Lib/site-packages/PyQt5/Qt5/bin/Qt5Widgets.dll;PyQt5/Qt5/bin" ^ --hidden-import "PIL._tkinter_finder" ^ --name "FaceRecognitionSystem" ^ main.py

打包后生成dist/FaceRecognitionSystem.exe,拷贝到没装 Python 的电脑上双击即可运行。验证方法:在另一台干净 Win10 电脑上运行,检查摄像头能否启动、注册是否成功、log 文件是否生成。

从那以后我每次帮学生改毕业设计,都强制走一遍「换模型→加活体→打 exe」三步验证。不是为了炫技,而是确保每一个模块你都真正掌控,而不是 copy-paste 后祈祷它能跑。答辩时老师问“如果现在要支持口罩人脸识别,你怎么改?”,你就能指着src/detectors/mtcnn.py说:“我只需要在align_face()之前加一个口罩区域掩码,再微调 MTCNN 的 P-Net 分支,因为……”。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进