简介基于深度学习的人脸识别签到系统是计算机专业毕业设计中的热门方向。这套源码包定位为高分毕设项目评审分99分代码完整可运行适合正在准备毕业设计、课程设计或期末大作业的计算机相关专业学生以及需要实战练习的Python学习者。资源共27个文件主要包括8个Python源文件后端逻辑与接口、7个HTML页面前端交互界面、4个DAT数据文件、1个SQLite数据库、CSS样式、配置文件及说明文档等压缩包大小约101.48MB目录结构清晰涵盖模型加载、人脸识别、签到记录与后台管理等功能。目前已有159人学习下载。源码包内含Flask框架的app.py与api.py接口、基于深度学习的人脸识别模型、Alembic数据库迁移脚本、用户登录注册与增删改查模板、测试脚本和部署说明可帮助读者快速理解人脸识别签到系统的设计与实现思路支持在此基础上二次开发是毕设选题落地与项目实战入门的可靠资料。1. 从题目到系统这个毕设到底在解决什么问题做“多Python毕业设计基于深度学习的人脸识别签到系统源码”这类题目最现实的问题不是深度学习原理而是能不能在答辩前把摄像头、模型、签到记录这三件事完整跑通并且让老师相信这套系统不是玩具。标题拆开看就三层Python 负责业务逻辑和界面深度学习模型负责“认人”签到系统负责把每次识别落成一条有时间戳的数据库记录。它能解决的是教室、实验室、小型公司里“点名耗时、代签严重、纸质记录难统计”的真实痛点适合正在选毕设题目、做课程设计的学生以及想快速搭一个考勤原型的开发者。整个方案的核心流程只有四步检测人脸、提取特征、相似度比对、写入签到表。我按这个链路把代码和参数拆开讲照着做基本两天内能跑通。2. 技术选型为什么这套方案锁定 MTCNN 检测 FaceNet 特征2.1 传统 opencv 人脸识别为什么在签到场景容易翻车很多人一开始会想到 OpenCV 里现成的 Haar Cascade 或者 LBPH毕竟 opencv人脸识别 的资料一搜一大把代码短、跑起来也快。我的建议是拿来当课设演示可以真正做签到系统别用。第一Haar 检测器对光照和角度极其敏感。教室门口的光线从早到晚都在变逆光和侧光下漏检率立刻上来签到会变成抽奖。第二LBPH 本质上是在算局部纹理直方图它把整张人脸当成一种“纹理密码”发型、眼镜、肤色一变特征就跟着剧烈跳动。我们用同一批人做过对比传统 LBPH 在白天日光直射场景下同一人的相似度最低能掉到 0.6 以下和陌生人几乎没法区分。这种不靠谱在答辩时会被老师两句就问穿。所以既然标题里写了深度学习就干脆把检测和识别都交给神经网络。传统机器学习模型可以留在后端的辅助分类里但不要拿来做核心的人脸比对那一层的误差会一路传导到签到记录最后整个项目看起来就是一个“能运行但不可信”的演示程序。2.2 深度学习模型实测更靠谱的组合MTCNN 检测 FaceNet 特征提取深度学习人脸识别方案已经很成熟核心思路统一成这样先用检测器把人脸从画面里框出来再用特征提取器把这张脸变成一串固定维度的向量最后用向量之间的距离判断是不是同一个人。我实测下来适合毕业设计、且社区资料最全的组合是 MTCNN FaceNet。MTCNN 负责检测人脸框检测的同时输出五个关键点坐标用来做人脸对齐FaceNet 把对齐后的脸映射成 512 维的 embedding 向量。这套方案不依赖 GPU纯 CPU 也能跑只是帧率会低一些对签到这种“看一眼再记录”的场景完全够用。方案检测方式特征维度优缺点OpenCV Haar LBPHHaar 级联256 维直方图代码最少对光照和姿态敏感识别率不靠谱Dlib HOG ResNetHOG 特征128 维方便好用但 Dlib 在 Windows 编译略有门槛MTCNN FaceNetMTCNN 级联网络512 维使用门槛低自动对齐人脸CPU 可跑资料多RetinaFace ArcFaceRetinaFace 检测512 维精度更高但权重下载和部署细节更多网上还能看到 MobileFaceNet、ArcFace 这些新模型精度更好但对毕业设计来说不如 MTCNN FaceNet 实在。选型的一个重要标准是“报错能不能搜到答案”这套组合在 Stack Overflow 和中文社区里的提问帖多得是翻车之后能快速查到解法这比那零点几个点的精度提升值钱得多。2.3 高分毕设的源码与文档目录长什么样拿到“源码 文档说明”这种题目先别急着写人脸识别把工程骨架搭好后面每一步往里填东西思路会很清晰老师看代码结构时也能少费力气。我一般会这样排布目录face-attendance/ ├── requirements.txt # 项目依赖清单 ├── config.py # 摄像头编号、识别阈值、模型路径统一放这里 ├── face_db.py # 特征库读写模块封装 np.save / np.load ├── register_face.py # 人脸注册入口录入某个人并生成特征 ├── attend_check.py # 签到识别入口实时摄像头识别并写库 ├── attendance.db # SQLite 考勤数据库 ├── data/ │ ├── face_db.npy # 已注册人脸的特征向量矩阵 │ └── name_list.npy # 与特征矩阵对应的姓名列表 ├── docs/ │ ├── 需求说明书.md │ ├── 系统设计文档.md │ └── 演示视频.mp4 └── utils/ ├── camera.py # 摄像头封装统一处理跳帧和画面尺寸 └── logger.py # 简单的运行日志config.py 里把阈值、摄像头编号、模型路径抽出来是这类毕设最容易拿分的地方说明你考虑过“参数可配置”而不是把全部值写死在业务代码里。docs 目录很多人用 Word 写但从工程交付的角度用 Markdown 写设计文档再导出 PDF 会更清爽git 记录也干净。这里多说一句文档说明在毕设评分里占的分量不亚于代码目录里把需求、设计、测试三份文档放齐老师翻阅时第一印象就会好很多。3. 拆掉黑匣子注册、识别、签到落库的三段式实现3.1 Python 环境与模型权重装对包才是第一步动手之前先按下面的顺序装环境。Python 版本选 3.8 到 3.10 之间最省心python安装教程 里最常见的坑是 3.11 之后部分包缺少预编译 wheel要现场编译很浪费时间。VSCode 里记得选对虚拟环境解释器否则明明装了包运行时却报 ModuleNotFoundError这种问题最消磨耐心。python -m venv venv source venv/bin/activate # Windows 下执行 venv\Scripts\activate pip install --upgrade pip pip install opencv-python torch torchvision facenet-pytorch numpy scikit-learn这里注意一件事facenet-pytorch 库首次实例化 InceptionResnetV1 时会自动从网络下载 vggface2 预训练权重文件有 100 多 MB。网络不好的时候程序会长时间卡在进度条上看起来像死机。MTCNN 的权重一般随包自带不会卡卡的几乎都是 InceptionResnetV1 这份。提示如果下载非常慢直接手动下载权重文件放到 torch.hub 缓存目录里别让程序干等。缓存路径可以这样查出来python -c import torch; print(torch.hub.get_dir())。装完后用一条命令验证基础环境python -c import torch, cv2, facenet_pytorch; print(torch.__version__, cv2.__version__)能打印版本号说明环境基础是通的。接下来不管你要注册多少个人核心都是同一个流程拿到一张人脸图像 → 送入 MTCNN 裁剪对齐 → 送入 FaceNet 得到 embedding 向量。3.2 注册模块先把每个同学的脸变成特征向量注册功能我拆成一个独立函数后面识别模块和扩展 Web 界面都能复用。下面这段用的是单脸模式每次从帧里取面积最大的人脸思路最清晰。import os import cv2 import numpy as np import torch from facenet_pytorch import MTCNN, InceptionResnetV1 device cuda if torch.cuda.is_available() else cpu # 配置 MTCNN裁剪尺寸 160margin 14最小可检测人脸 30 像素 mtcnn MTCNN( image_size160, margin14, min_face_size30, thresholds[0.6, 0.7, 0.7], factor0.709, keep_allFalse, # 单脸模式只取最大的人脸 devicedevice ) resnet InceptionResnetV1(pretrainedvggface2, evalTrue).to(device) def register_face(person_name: str, save_num: int 3): cap cv2.VideoCapture(0) embeddings [] while len(embeddings) save_num: ok, frame cap.read() if not ok: continue face_tensor mtcnn(frame) # 单脸模式返回 160x160 人脸张量或 None if face_tensor is None: continue with torch.no_grad(): emb resnet(face_tensor.unsqueeze(0))[0].cpu().numpy() embeddings.append(emb) print(f已采集 {len(embeddings)}/{save_num} 张人脸特征) cv2.imshow(register, frame) if cv2.waitKey(1) 27: # ESC 键中断 break cap.release() cv2.destroyAllWindows() if len(embeddings) 0: raise RuntimeError(没有采集到有效人脸注册失败) # 追加到特征库 db_path, name_path data/face_db.npy, data/name_list.npy os.makedirs(data, exist_okTrue) db np.load(db_path) if os.path.exists(db_path) else np.empty((0, 512)) names np.load(name_path, allow_pickleTrue).tolist() if os.path.exists(name_path) else [] db np.concatenate([db, np.asarray(embeddings)], axis0) names.extend([person_name] * len(embeddings)) np.save(db_path, db) np.save(name_path, np.asarray(names, dtypeobject)) print(f注册完成特征库现有 {len(names)} 条人脸特征) if __name__ __main__: register_face(input(请输入姓名), save_num3)逻辑说明核心是 register_face 函数。每帧先用 MTCNN 做一次检测和裁切检测不到人脸就跳过检测到就将人脸张量送入 FaceNet 网络拿 512 维向量连续采 3 份后追加写入 npy 文件。为什么存 3 份因为同一个人的脸在不同角度、不同光线下特征会有波动存多份等于给识别阶段多一些参考票比对时只要其中一份相似度达标就判定为本人整体系统的靠谱程度会明显提升。参数说明min_face_size 太大会漏掉坐在后排的人太小则容易把远处背景里的路人框进来建议默认 30 到 40 之间。thresholds 是 MTCNN 三个级联网络的置信度阈值0.6/0.7/0.7 是社区里常用的数值如果检测结果里误框明显变多可适当往 0.7/0.8/0.8 调。margin 表示检测框向外扩的像素数扩得越大裁剪出来的人脸上下文越多但也可能把旁边的人脸吃进去不要超过 20。3.3 签到识别实时摄像头里的人脸比对与写入签到模块和注册模块最大的区别在于注册是慢慢采集一张脸识别时要处理连续视频帧同时还要防止重复签到。下面这段是完整可跑的主循环关键处都加了注释。import time import sqlite3 import cv2 import numpy as np import torch from facenet_pytorch import MTCNN, InceptionResnetV1 from sklearn.metrics.pairwise import cosine_similarity device cuda if torch.cuda.is_available() else cpu mtcnn MTCNN(image_size160, margin14, min_face_size30, thresholds[0.6, 0.7, 0.7], keep_allTrue, devicedevice) resnet InceptionResnetV1(pretrainedvggface2, evalTrue).to(device) conn sqlite3.connect(attendance.db) cur conn.cursor() cur.execute(CREATE TABLE IF NOT EXISTS sign_record ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, sign_time TEXT NOT NULL, source TEXT DEFAULT camera )) face_db np.load(data/face_db.npy) names np.load(data/name_list.npy, allow_pickleTrue) THRESHOLD 0.80 # 余弦相似度阈值 CONFIRM_FRAMES 3 # 连续几帧确认同一人才签到 confirmed_name None confirm_count 0 cap cv2.VideoCapture(0) frame_idx 0 while True: ok, frame cap.read() if not ok: break frame_idx 1 if frame_idx % 3 ! 0: # 跳帧降低 CPU 占用 continue # 先用 detect 拿人脸框用于画框再裁出人脸张量 boxes, probs mtcnn.detect(frame) if boxes is None: continue faces mtcnn(frame) # 与 detect 结果对应的一组裁剪脸 for face, box, prob in zip(faces, boxes, probs): if prob 0.96: # 置信度过低跳过 continue with torch.no_grad(): emb resnet(face.unsqueeze(0))[0].cpu().numpy().reshape(1, -1) scores cosine_similarity(emb, face_db)[0] best_idx int(np.argmax(scores)) best_score scores[best_idx] if best_score THRESHOLD: candidate str(names[best_idx]) if candidate confirmed_name: confirm_count 1 else: confirmed_name candidate confirm_count 1 cv2.rectangle(frame, (int(box[0]), int(box[1])), (int(box[2]), int(box[3])), (0, 255, 0), 2) cv2.putText(frame, f{candidate} {best_score:.2f}, (int(box[0]), int(box[1]) - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) if confirm_count CONFIRM_FRAMES: today time.strftime(%Y-%m-%d) now time.strftime(%Y-%m-%d %H:%M:%S) cur.execute( SELECT COUNT(*) FROM sign_record WHERE name? AND sign_time LIKE ?, (candidate, f{today}%) ) if cur.fetchone()[0] 0: cur.execute( INSERT INTO sign_record(name, sign_time) VALUES (?, ?), (candidate, now) ) conn.commit() print(f签到成功{candidate} {now}) else: # 今天已经签过重置计数避免重复提示 confirm_count 0 cv2.imshow(attendance, frame) if cv2.waitKey(1) 27: break cap.release() cv2.destroyAllWindows() conn.close()逻辑说明主循环每隔 3 帧做一次完整检测防止 CPU 占用过高。对每一帧里检测出的每张人脸先过滤置信度低的框再提取特征向量与特征库里所有人计算余弦相似度取最高分作为识别结果。CONFIRM_FRAMES 的作用是防止瞬时误判必须连续 3 帧都识别到同一个人才真正写入一条签到记录。参数说明THRESHOLD 是这套系统里最需要谨慎设置的参数默认给 0.80但不同应用场景下这个值通常在 0.65 到 0.85 之间浮动第四章会专门说怎么标定它。CONFIRM_FRAMES 调高能减少误签但会让人在摄像头前多站一会儿。“每天只能签一次”的判断用的是当天日期做前缀模糊查询简单可靠。有一点需要说明代码里 detect 和 mtcnn(frame) 各推理了一遍性能上不划算但这样拿到的 boxes 和 faces 对位清晰演示画框很方便。想真正提高帧率可以用 mtcnn(frame, return_probTrue) 一次拿全结果再配合一个小的跟踪器这部分见第四章。3.4 签到记录表结构SQLite 五分钟建好考勤库签到结果最后要落库。我用 Python 内置的 sqlite3零配置答辩时打开 attendance.db 就能查数据。表结构保持简单实用CREATE TABLE IF NOT EXISTS sign_record ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, sign_time TEXT NOT NULL, source TEXT DEFAULT camera ); CREATE INDEX IF NOT EXISTS idx_name_time ON sign_record(name, sign_time);sign_time 直接用文本格式存不折腾时间戳转换统计当天记录时用 LIKE 2025-06-11% 前缀匹配效率也够。source 字段留给以后扩展比如接 Web 端或者手动补签时可以区分数据来源。索引按“姓名 签到时间”联合创建因为业务上最频繁的查询就是“某个人某天是否已签到”。如果把人员信息和特征向量一起收进库里可以再建一张 personnel 表把 512 维向量存成 BLOB。但我更推荐特征向量用 npy 文件存第四章第 5 小节会细说这个取舍。4. 避坑指南人脸识别签到系统最容易翻车的 5 个点4.1 阈值是玄学认错人和不认人的临界值怎么定现象系统经常把陌生人认成库里的人或者同一个同学换个位置就怎么也识别不通过像是看运气。原因THRESHOLD 是拍脑袋设的 0.8可不同环境、不同摄像头、不同光照下余弦相似度分布差异极大。白天阳光直射时同一人脸得分能到 0.9到了傍晚光线暗淡时只剩 0.7 出头。用固定 0.8 一刀切必然两头受气。解决别靠感觉用数据定阈值。做法是跑第五章的验证脚本采集 20 组“本人正脸”算库内相似度再采 20 组“非本人脸”算库外相似度把阈值放在库内最低分和库外最高分之间的区间里。如果两个分布重叠严重说明注册照片质量不够应该回去重新采集而不是继续硬调阈值。4.2 光线一变就识别失败检测器找不到脸的真相现象白天在窗边识别率还可以一到傍晚或者只开日光灯时程序像“瞎了”一样一个人也检测不到签到直接停摆。原因MTCNN 用三个级联网络逐层过滤候选框暗光下第一层 P-Net 的置信度普遍偏低很多脸在这一层就被过滤掉了后面自然“看不见人”。解决在送入 MTCNN 之前对图像做一次 gamma 校正增强暗部细节让脸部轮廓更明显。更省事的办法是在注册阶段刻意采几张偏暗环境下的照片反正特征库里一人可以存多份牺牲一点存储换识别通过率。换 RetinaFace 这类检测强度更高的模型也可以但毕业设计不建议为此增加复杂度。4.3 CPU 上帧率只有 3跳帧与追踪的取舍现象在纯 CPU 机器上运行识别程序画面卡到两三秒一帧人都走出画面了系统还没反应过来。原因每帧都要跑 MTCNN 检测和 FaceNet 特征提取两个网络在 CPU 上单帧耗时接近 200 到 400 毫秒视频流还在继续推入积压越来越严重。解决跳帧是最直接的手段代码里已经写了 frame_idx % 3 ! 0 就跳过。更好一点的做法是配合 OpenCV 的跟踪器第一帧做全局检测之后若干帧用上一帧的人脸框做局部搜索只有跟踪失败时才重新做全局检测。毕业设计里能做到“跳帧 缩小检测区域”这两步答辩时已经能让老师眼前一亮。4.4 模型权重下载失败网络问题让首次运行卡死现象第一次实例化 InceptionResnetV1 时程序卡在下载进度条等半小时也不动甚至直接抛 URLError。原因facenet-pytorch 默认从外网下载 vggface2 预训练权重文件比较大网络受限或本机配置异常时下载必然失败。解决手动提前下载好权重文件放到 torch.hub 的缓存目录下。路径可以这样查看import torch print(torch.hub.get_dir())把下载好的 vggface2.pt 放进该目录下的 checkpoints 文件夹再运行程序就能跳过下载直接加载。这个坎几乎是每个做这个方向的人都会遇到的第一道坎提前处理掉后面进度会顺畅很多。4.5 数据库写入乱码和重复签到字段编码与唯一约束现象Windows 命令行里打印签到记录时中文名变成乱码另外同一个人连着识别了三遍数据库里出现三条重复记录。原因第一Windows 下 cmd 默认编码是 GBK而 Python 写 SQLite 时用的是 UTF-8控制台展示就乱了。第二防重复判断依赖查询逻辑如果两帧之间恰好跨过日期切换或者连续识别过程中中断了一次就容易漏判。解决连接 SQLite 后显式设置 UTF-8 编码SQLite 层面再补一个唯一索引做兜底。特征向量坚持存 npy人员信息存 SQLite不要把 512 维数组塞进 BLOB否则代码绕老师读起来也累。5. 答辩前怎么自证可靠验证脚本与三个可落地的扩展方向5.1 用相似度分布说话阈值设置的验证脚本能说服评审老师的做法是把相似度分布拿出来。下面这个脚本从特征库里取目标人与其他人的相似度作为“类内参照”再从摄像头采集一张非注册人脸计算它与全库的相似度作为“类外参照”import numpy as np from sklearn.metrics.pairwise import cosine_similarity db np.load(data/face_db.npy) # 取 0 号人作为目标其余人脸作为库内对比 inner cosine_similarity(db[0:1], db[1:])[0] # visitor_emb.npy 是从一张未注册者照片提取出的 512 维特征 visitor_emb np.load(visitor_emb.npy).reshape(1, -1) outer cosine_similarity(visitor_emb, db)[0] print(f类内得分中位数: {np.median(inner):.3f}) print(f类外得分最高分: {outer.max():.3f})如果类内最低分明显高于类外最高分说明这个场景下系统是靠谱的阈值取两者中间即可。如果分布缠在一起回头补注册照片别在识别代码里继续折腾。这个脚本跑出来的数字放进 PPT比写十页系统介绍都有说服力。5.2 从签到扩展到考勤统计闭环才是加分项识别签到只是入口能不能看到统计报表往往是评分差异化的来源。给项目加一个简单的按天按人统计接口SQL 一行就能完成再套一层 Flask API前端展示一个表格整个项目就从“演示程序”变成了“考勤系统”。老师问“数据怎么闭环”时你可以直接从启动签到、实时写库、统计查询一路讲下来这样的一整套流程会让答辩过程顺畅很多。5.3 硬件迁移树莓派和 STM32 门禁的迁移思路如果是想往落地方向再走一步这套代码迁到树莓派上几乎不用改PyTorch 有 ARM 版推理包摄像头换成 CSI 接口就是最常见的基于树莓派的人脸识别做法。往 STM32 这类嵌入式单片机上迁移核心思路是对模型做量化剪枝并转成 TFLite让 512 维特征计算在端侧完成只把结果上报后端。人脸识别门禁机的基本原理也是这个链路只是多了一路继电器控制电磁锁和刷卡规则。这些内容写进文档的“后续工作”可以体现你已经理解了模型部署的完整链路。最后说一个我的习惯每次调完阈值我一定会在 config.py 旁边留一行注释写下当时的测试时间和得分区间。这种用数据做记录的习惯帮我避免过很多次“改到后来忘了最初版本为什么不行”的尴尬。希望帮到你。本文还有配套的精品资源点击获取