简介:面向计算机专业毕业设计及人脸识别应用开发场景,这套基于深度学习的人脸识别签到系统实现方案,覆盖人脸注册、模型训练、识别签到与记录查询等环节,前后端模块划分清晰,适合需要完成课设、毕设或搭建同类考勤系统的开发者参考。压缩包为 zip 格式,共 38 个文件,约 203.34MB。文件类型主要包括 Python 源码、HTML 前端页面、SQLite 数据库、人脸识别模型与配置文件;Python 脚本承担后端逻辑,HTML/CSS 构成签到界面,模型与数据库分别支撑人脸特征提取和签到数据存储。目前已有 109 人学习,说明其具备一定参考价值。通过该资源可掌握完整项目框架、核心功能实现及模块衔接方式,既能理解人脸识别签到的落地流程,也便于在此基础上扩展功能或适配自身业务场景,对毕业设计选题和答辩准备均有实际帮助。
1. 当毕设题目同时押中深度学习、人脸识别和签到系统:先想清要交付什么
“毕业设计基于深度学习的人脸识别签到系统的设计与实现”这个标题,几乎每年都会出现在高校毕设选题名单里。它把深度学习、人脸识别、签到系统三件事串成一条完整的落地链路:用深度学习模型完成人脸检测与特征提取,用比对结果判断“来者是不是已登记的人”,再把结果写入签到记录。对做毕设的学生而言,这个题目最值钱的地方在于它不是一个纯算法题,而是一个系统工程题——数据、模型、算法指标之外还要处理数据库、界面和实时调用,任何一个环节出问题,演示现场都会翻车。
我按自己带过的类似项目把这条路拆开讲一遍,覆盖技术路线选型、数据集准备、核心代码结构、参数调优和踩坑记录。无论你是第一次接触深度学习新人,还是已经会跑模型但没做过完整业务闭环的熟手,这篇文章都能让你对着标题把方案完整补出来。
2. 技术路线怎么选:端到端识别模型、特征比对与系统架构落点
2.1 为什么不用传统方法,深度学习方案到底选哪种
很多人想到人脸识别,第一反应是用 OpenCV 自带的LBPHFaceRecognizer或者 EigenFace 那套东西。这类传统方法在固定光照、固定角度、证件照场景下能出结果,代码也短,但有两个硬伤:一是对姿态和光照极其敏感,二是答辩时老师会直接问“你的深度学习体现在哪里”。所以毕设题目既然点名了深度学习,就要老老实实走深度模型路线。
常见的深度学习人脸识别方案分两种。第一种是把检测和识别分开:检测用深度学习模型,比如 OpenCV DNN 加载的 SSD、ResNet、YuNet,或者 MTCNN、RetinaFace;特征提取用预训练的 FaceNet、ArcFace 或 dlib 的face_recognition模型。第二种是端到端的单模型方案,输入一张图直接输出“这是谁”,但这类方案通常是针对固定人员集合训练的分类模型,换人就失效,可复现性和毕设答辩的扩展性都很差。我一般推荐第一种,业界做门禁系统也基本是这个思路:检测是检测,比对是比对,两边各自换模型都方便。
2.2 人脸比对用特征向量还是用分类头,阈值参数怎么定
特征向量方案把人脸编码成一个固定维度的向量,FaceNet 是 128 维,ArcFace 常见的是 512 维,然后通过计算欧氏距离或余弦相似度来判断是不是同一个人。这个方案的好处是换人不用重训模型,新增一个学生就拍几张照片提取向量存进库里,运行时把实时帧的向量和库里的向量算距离,小于阈值就算匹配。
阈值设置是很关键的操作参数:用face_recognition时距离小于 0.6 算同一个人,这个 0.6 是官方经验值,但毕设现场通常要再收紧到 0.5 左右,不然容易把长得像的两个人误判成同一个。用余弦相似度时阈值一般取 0.7 到 0.85 之间。具体值不要照搬,要在你的数据集上做一次小规模验证,把班级同学的误报率测出来再定。这个测试过程本身也是毕设论文里“实验与分析”章节的素材。
2.3 系统架构落点:本地主机方案比服务器方案更适合毕设演示
做过实际项目的人都知道,毕设最怕的是演示时依赖外部环境——要联网、要服务器、要摄像头权限都没到位。所以架构我建议是“本机摄像头采集 + 本机模型推理 + 本机 SQLite 数据库 + 本机 GUI/Web 页面”,完全不依赖局域网和云端。用 PyQt5 做桌面窗口,或者用 Flask 起一个本地网页再打开摄像头都行,前者依赖更少,后者界面更好看且答辩时能远程打开,可以根据个人熟悉程度选。
这套架构下数据流是:摄像头逐帧获取图像→人脸检测框出人脸→提取特征向量→遍历人员特征库算距离→得到最小距离和对应人员→判断是否达标→写入签到记录。每一步都是独立函数,后期写论文画框图也清晰。
2.4 环境搭建:深度学习环境配置的具体坑
环境层面的历史教训很多:Python 版本太新容易装不上dlib,PyTorch 和 TensorFlow 混着装会互相干扰。我建议直接用 miniconda 建一个独立环境:
conda create -n face_check python=3.10 -y conda activate face_check pip install opencv-python face_recognition flask pyqt5 sqlite3-utils如果pip install face_recognition在 Windows 上报 dlib 编译失败的错,常见做法是去安装 Visual Studio 的 C++ Build Tools,或者直接改用opencv-python和face_recognition_models混用。最容易耽误时间的点是 conda 下载太慢,可以给 conda 配国内镜像源,三行配置就能把环境搭建时间从一小时压到十分钟。配完之后在终端里跑一句验证命令:
python -c "import face_recognition, cv2; print(cv2.__version__)"能正确打印 OpenCV 版本就说明环境基本通了。这一步建议放在项目第一天做完,不要拖到写代码时再排查环境。
3. 从自采数据集到人脸特征库:把数据整理成模型能直接用的样子
3.1 数据集从哪来:自采照片的采集规范与数量底线
很多做毕设的人一上来就想用 LFW 或 WIDER Face 这种公开数据集,但实际上签到系统关心里的是“班里这几个人”,公开数据集的人脸和你要签到的学生不是同一批,反而不能直接作为人员注册库使用。正确做法是自采数据集:找 10 到 20 个真实参与演示的同学,每人采集 10 到 20 张照片,覆盖正面、左右偏转、抬头低头、室内灯光、自然光、戴不戴眼镜等状态。数量不需要多,但规范性比数量重要。
采集时用手机或笔记本摄像头都行,推荐用同一台电脑的摄像头采集,因为模型推理和演示用的都是这台电脑,采集和运行环境的成像特征一致,识别成功率高得多。
每张照片裁好之后统一放到dataset/目录下,按“姓名_序号.jpg”命名,这个命名后面直接用于生成标签,省得再维护映射表。
3.2 人脸检测与对齐的预处理脚本
采集完原始图片后不能直接拿去提特征,因为模型输入要求人脸对齐。常见做法是用 dlib 或 OpenCV 的检测器框出人脸,再缩放成统一尺寸。下面这段脚本把人脸检测、裁剪、缩放、保存一次完成:
import cv2 import os from pathlib import Path def preprocess_face(img_path, output_size=(160, 160)): img = cv2.imread(img_path) img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # face_recognition 内部用 dlib 的检测器,返回人脸位置 boxes = face_recognition.face_locations(img_rgb, model="cnn") # cnn 模型精度更高 if len(boxes) == 0: boxes = face_recognition.face_locations(img_rgb, model="hog") # 退到 hog 快很多 face_imgs = [] for top, right, bottom, left in boxes: # 适当向外扩一圈,避免裁掉额头和下巴 top = max(0, top - 10) bottom = min(img.shape[0], bottom + 10) left = max(0, left - 10) right = min(img.shape[1], right + 10) face = img_rgb[top:bottom, left:right] # 缩放到统一尺寸,避免模型输入尺寸不一致 face = cv2.resize(face, output_size) face_imgs.append(face) return face_imgs for img_path in Path("dataset").glob("*.jpg"): faces = preprocess_face(str(img_path)) for i, face in enumerate(faces): out_path = f"aligned/{img_path.stem}_{i}.jpg" os.makedirs("aligned", exist_ok=True) cv2.imwrite(out_path, cv2.cvtColor(face, cv2.COLOR_RGB2BGR))这里有两个参数值得注意:output_size设为 160 是配合 FaceNet 输入尺寸,如果用的 ArcFace 相关模型可以设 112;model="cnn"检测精度更高但推理慢,对离线预处理来说无所谓,但实时识别时就别用 cnn 模式,否则帧率上不去。
3.3 提取特征并写入本地特征库
人脸特征库是整个系统的核心数据,每个登记人员对应一个特征向量列表。同一个人的多张照片可以提取出多个向量,识别时取最小距离即可。下面的脚本扫描aligned/目录,输出一个 JSON 特征库:
import face_recognition import json import numpy as np from pathlib import Path known_faces = {} # 遍历每个对齐后的图片,用模型提取 128 维人脸特征 for face_path in Path("aligned").glob("*.jpg"): name = face_path.stem.split("_")[0] # 由文件名取得姓名 img = face_recognition.load_image_file(str(face_path)) encodings = face_recognition.face_encodings(img) if len(encodings) == 0: continue known_faces.setdefault(name, []).append(encodings[0].tolist()) # 保存为本地 JSON 特征库,后续识别时直接加载,不用重算 with open("face_db.json", "w", encoding="utf-8") as f: json.dump(known_faces, f, ensure_ascii=False, indent=2)这段脚本的关键是setdefault用法,一个人多张照片的向量都挂到同一个姓名下。特征库文件只有几十 KB,加载速度很快,毕设答辩时即便现场没有 GPU 也能跑。实际运行时不需要全部照片的向量都参与比对,一个人保留 3 到 5 个代表性向量就够了,否则比对耗时随人员数量线性上涨。
3.4 特征库的维护逻辑:录入覆盖与删除规则
签到系统必须支持“新增学生”和“删除学生”两个操作,不能靠手工改 JSON。常见的做法是提供一个注册函数:传入一张现场图片,先跑人脸检测,如果检测到且与库中已有人员的距离大于阈值,则追加为新人员;如果与某人距离小于阈值,则提示“该人已登记”,并把新特征追加到该人名下。删除操作按姓名删掉 JSON 里的整组向量。
这里有个容易被忽略的细节:每次新增后要重新写 JSON 文件,并保证写文件的原子性——先写临时文件再改名,不然中途断电会损坏特征库。写过一次踩坑之后我都是用tempfile加os.replace处理,几行代码的事,但能省掉不少麻烦。
4. 把模型接进签到闭环:核心代码结构与必调参数
4.1 实时人脸检测和识别的最小实现
签到系统的主循环是读取摄像头帧,每隔几帧跑一次检测和识别。下面是完整的核心逻辑:
import cv2 import face_recognition import numpy as np import json import time # 加载特征库 with open("face_db.json", "r", encoding="utf-8") as f: known_faces = json.load(f) known_names = [] known_encodings = [] for name, encodings in known_faces.items(): known_names.append(name) known_encodings.append(np.array(encodings[0])) video_capture = cv2.VideoCapture(0) # 0 表示默认摄像头 process_every_n_frames = 3 # 每 3 帧才跑一次识别,降低 CPU 占用 frame_count = 0 while True: success, frame = video_capture.read() if not success: break frame_count += 1 if frame_count % process_every_n_frames != 0: continue small_frame = cv2.resize(frame, (0, 0), fx=0.5, fy=0.5) # 缩小一半,提速明显 rgb_frame = cv2.cvtColor(small_frame, cv2.COLOR_BGR2RGB) # 检测人脸位置 face_locations = face_recognition.face_locations(rgb_frame, model="hog") # 提取实时帧的人脸特征 face_encodings = face_recognition.face_encodings(rgb_frame, face_locations) for face_encoding in face_encodings: # 计算与所有已知人员特征的欧氏距离 distances = face_recognition.face_distance(known_encodings, face_encoding) min_distance = float(np.min(distances)) match_index = int(np.argmin(distances)) # 距离阈值 0.5,越小越严格 if min_distance < 0.5: name = known_names[match_index] else: name = "Unknown" print(f"Detected: {name}, distance: {min_distance:.3f}") # 显示画面,方便演示 cv2.imshow("Face Check System", small_frame) if cv2.waitKey(1) & 0xFF == ord("q"): break video_capture.release() cv2.destroyAllWindows()这段代码里三个参数需要重点理解:model="hog"是实时识别首选,CNN 模型精度高但一帧可能要跑几百毫秒,演示时基本不可用;fx=0.5把图像长宽各缩小一半,像素减少到四分之一,识别速度能提升 3 倍以上;process_every_n_frames=3则是跳帧逻辑,让识别频率不要和摄像头帧率绑定。
4.2 签到的业务逻辑:比对成功之后写入数据库
识别出姓名后要做的事是写入签到记录,而不是只打一行 print。我用 SQLite 建两张表,一张存人员信息,一张存签到记录:
CREATE TABLE IF NOT EXISTS students ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, student_id TEXT, face_features TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS attendance ( id INTEGER PRIMARY KEY AUTOINCREMENT, student_name TEXT NOT NULL, sign_time DATETIME DEFAULT CURRENT_TIMESTAMP, distance REAL, UNIQUE(student_name, date(sign_time)) -- 每人每天只能签到一次 );第二张表上加了唯一约束,防止演示时摄像头连续识别到同一个人就重复签到。这个去重设计在答辩时非常加分,老师会问“如果一个人站在镜头前不走,系统会不会刷屏”,你可以直接指这张表的UNIQUE约束。
写入逻辑用 Python 的 sqlite3 处理,需要捕获sqlite3.IntegrityError来判断重复签到:
import sqlite3 conn = sqlite3.connect("attendance.db") try: conn.execute( "INSERT INTO attendance (student_name, distance) VALUES (?, ?)", (name, min_distance), ) conn.commit() print(f"{name} 签到成功,距离 {min_distance:.3f}") except sqlite3.IntegrityError: print(f"{name} 今天已经签过了")这里的distance字段建议保留下来,既方便事后分析阈值是否合理,也是论文里可以放的一张统计表数据来源。
4.3 几个必调参数及其对应后果
体验过现场演示的人都知道,参数调不好就是灾难。识别延迟、漏检率、误识别率都和三个参数强相关。第一个是输入分辨率,分辨率和距离的关系有耐心课;分辨率越高,小目标越容易被检测出来,但推理耗时上升;建议实时阶段用 640x480,识别阶段用原图。第二个是跳帧间隔,帧间隔越大 CPU 越轻松,但人走过镜头时可能刚好被跳过,所以 2 到 4 帧是合理区间。第三个是相似度阈值,阈值调高会导致陌生人被误认为已知人员,调低则会出现漏签,合理做法是上台展示前花五分钟用两个同学做十次“签到/拒识”测试。
还有个常见误区是使用 RGB 通道直接处理,没有做光线归一化;在实验室白炽灯下没问题,但在窗边自然光下会导致识别率下降。常见做法是在预处理时加一层 CLAHE 自适应直方图均衡化,OpenCV 一行调用就能显著改善光照不均问题。这个提升在演示现场非常明显。
5. 踩坑记录:人脸识别签到系统常见问题与排查方法
5.1 现象:换了一台电脑或摄像头后,识别率骤降
原因定位容易踩坑。face_recognition底层用的是 dlib 的预训练模型,模型的输出本身和摄像头无关,但训练人脸编码的样本如果和你现场画面的成像分布差异大,比如训练时用的是手机前置摄像头,现场用的是笔记本摄像头,色温、畸变、肤色还原都不一样,识别率自然会掉。
解决方法是让采集和识别使用同一个相机,或者把对齐后的图片多做几次颜色增强。更直接的是在 preprocessing 阶段增加 CLAHE 均衡化,使用cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))对灰度图作处理,就能抹掉大部分光照差异。这个步骤放在特征提取之前,而不是放在原图上,否则会引入噪声。
5.2 现象:dlib 装不上,环境搭建卡住一整天
Windows 上装face_recognition最常见的失败点是编译 dlib 时报 CMake 或 C++ 编译器错误。表面看是 pip 安装失败,实际原因是缺少 MSVC Build Tools 或 CMake 版本不对。
解决顺序有两条路:一是安装 Visual Studio Build Tools 勾选“使用 C++ 的桌面开发”工作负载,然后pip install dlib;二是直接放弃face_recognition,改用 OpenCV DNN 加载现成的face_detection_yunet模型做人脸检测,再用insightface或 ONNX 特征提取模型做比对,这类模型都是预编译好的 ONNX 格式,不需要本地编译。我推荐第二条路,省时间且可控。
5.3 现象:张三被识别成李四,或者识别框在两人之间反复横跳
这种情况往往不是模型有问题,而是特征库里张三和李四的照片特征太相近,并且距离阈值设得太大。排查方式是把两张照片提取出的 128 维向量直接打印出来算距离;如果距离小于 0.4,说明两个人在特征空间里本来就很接近,不该同时出现在库里。
解决方法是给距离阈值加一个“置信度区间”,小于 0.35 直接认定匹配,位于 0.35 到 0.6 之间时输出“可能是某人,请靠近摄像头”,大于 0.6 直接拒绝。这里的 0.35 和 0.6 不是标准值,建议在自己的特征库上统计同类距离和异类距离的分布后确定。
5.4 现象:把照片或手机屏幕上的脸也识别成真人,被老师当场指出
这是人脸识别毕设的高频翻车点。纯静态人脸识别不加活体检测,确实会认照片。
最廉价的活体检测是要求用户眨眼或轻微转头,OpenCV 配合 dlib 的 68 点人脸关键点检测可以计算眼睛纵横比,连续三帧以内出现“睁开-闭上-睁开”状态就判定为活体。但实现量偏大,毕设时间紧张的话可以用结构光交替方案替代:在摄像头上用屏幕制造短时亮度变化,观察画面亮度响应,但效果不稳定。最稳妥的做法是直接用一台带红外深度感知的摄像头,比如常见的人脸识别门禁机硬件方案,这在答辩时也可以作为硬件选型亮点介绍。
5.5 现象:多人出现在画面里时,后方的同学总是被漏检
原因在于 dlib 的 HOG 检测器对近处人脸效果好,对远处小人脸容易忽略,而且多人时后层人脸被遮挡。
解决方法是换用 OpenCV DNN 的YuNet模型,它比 HOG 更擅长检测小尺寸人脸,检测速度在 CPU 上也能跑到 20 帧以上。另外一个辅助手段是调整face_locations的number_of_times_to_upsample参数,增大采样倍率能提高小脸检出率,但推理时间会翻倍,更推荐控制签到距离在 0.5 米到 1.2 米之间,超过这个范围提示“请靠近摄像头”。这个提示本身就是一个合规的签到流程设计点。
6. 让毕设多走一步:可视化验证与方法改进技巧
6.1 给摄像头画面加上实时指标输出
不要只让系统输出文本结果,改成在画面上直接绘制检测框、姓名和距离值,这能极大提升现场演示的说服力。完整显示代码如下:
for (top, right, bottom, left), face_encoding in zip(face_locations, face_encodings): distances = face_recognition.face_distance(known_encodings, face_encoding) min_distance = float(np.min(distances)) match_index = int(np.argmin(distances)) name = known_names[match_index] if min_distance < 0.5 else "Unknown" color = (0, 255, 0) if name != "Unknown" else (0, 0, 255) cv2.rectangle(small_frame, (left, top), (right, bottom), color, 2) cv2.putText(small_frame, f"{name} {min_distance:.2f}", (left, top - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)显示距离值这个动作极其重要。老师会看到数字从 0.55 降到 0.48 再判定通过,能直观理解“阈值”在系统里到底是什么作用。我自己的毕设经验是永远不要只给一个“匹配/不匹配”的二元结果,把中间量拿出来给人看,才显得系统不是黑匣子。
6.2 录一段视频做离线自动化验证
毕设报告里需要写识别准确率和误识率,手动现场测试不可重复,也拿不出量化数据。技巧是把摄像头环节替换成视频文件:
video_capture = cv2.VideoCapture("test_video.mp4")用一段包含多位同学入镜、离开、再入镜的录制视频跑同一个识别主循环,自动统计总检测次数、识别成功次数、失败次数和平均距离,输出成 CSV。这样你可以在交论文前调整阈值,让数据达到一个好看的平衡点。录视频时建议固定三脚架,保持和真实签到一致的摆放位置。
6.3 给系统加一个“今日签到名单”的展示页
很多签到系统只做到识别和写入数据库就结束了,但老师更想看到“这个系统解决了签到管理问题”。用 Flask 加一个极简页面,查询当天attendance表里的记录并按班级分组展示,就补上了“管理端”这一环。这个页面不需要好看,清晰列出姓名和签到时间即可。你也可以用 PyQt5 在桌面端直接塞一个QTableWidget展示今天的签到结果,数据源头仍然是同一张 SQLite 表。
在我看来,人脸识别签到系统的难点从来不在单点技术上,而是识别流程的可靠性、阈值和业务规则怎么配合。最终能打动人心的作品,是把检测、比对、去重、展示、录入几个模块都跑通、且现场不出状况的作品。希望这篇拆解能帮你在毕设路上少走一点弯路。
本文还有配套的精品资源,点击获取