
简介这是一份Python毕业设计项目基于深度学习技术实现人脸识别签到系统面向计算机相关专业正在做毕设的学生、需要项目实战练习的开发者也可直接用于课程设计或期末大作业。项目经导师指导并认可评审分高达99分代码完整且确保可运行从人脸检测识别、用户信息管理到签到记录查询均覆盖能帮助读者完整掌握深度学习模型与Web应用结合的开发思路。资源包共27个文件压缩包约101.48MB包含8个Python脚本模型处理、API接口、业务逻辑、7个HTML模板登录、用户编辑、签到展示等页面以及SQLite数据库、样式表、配置文件与说明文档目录结构清晰便于按模块对照学习。目前已有158人学习下载适合作为毕业设计参考或项目实战进阶素材尤其适合希望独立上手完整系统的初中级学习者。1. 深度学习人脸识别签到系统别把创新点当高分核心拿到“基于深度学习的人脸识别签到系统”这个毕业设计题目的同学第一反应多半是去追 SOTA 模型把公开数据集上的准确率刷到小数点后三位。但我复核毕业设计时发现评阅老师的打分逻辑正好相反模型再新如果注册、签到、查询这条链路没有完整跑通文档里也说不清训练数据怎么来、阈值怎么定分数一样上不去。这个题目本质上是一道应用题——深度学习提供特征提取能力人脸识别算法负责把照片变成可比对的向量签到系统才是最终要交付的产品。这套实现适合两类人。一类是时间紧、想拿高分的应届生按文章里的顺序能少走大半年弯路另一类是公司里要快速搭一个内部签到 MVP 的工程师去掉答辩文档部分模型和接口部分可以直接复用。下面从选型讲起一路落到部署和答辩验证。2. 人脸识别模型的底座选择从 ArcFace 到 pytorch 实现路径2.1 签到场景与人脸识别门禁机的选型差异人脸识别签到和人脸识别门禁机用的是同一套算法体系但约束条件不同。门禁机经常要处理逆光、遮挡、陌生人闯入而签到场景通常在教室门口或会议室签到处光线相对可控用户会主动正对镜头。这意味着你可以把算力留给大量数据的频繁比对而不是耗在极端难样本上。很多同学一开始直接上 100 层 ResNet理由是“越深越准”。模型在公开 benchmark 上确实表现更好但放到只有几十个学生、每人一两张注册照的课程场景深层网络的收益很小显存占用和推理延迟的代价却很明确。我一般建议先做一轮小对比在同一个验证集上用相同预处理管线评估三个候选而不是凭直觉选主干。Backbone特征维度公开集精度表现显存/部署成本签到场景适用性MobileFaceNet128 或 512够用轻量极低可跑 CPU适合树莓派等边缘设备IResNet18512良好低GPU 上轻松跑推荐首选IResNet50512更好中推理约 10ms 级精度敏感时选用这三个网络在结构上都属于深度学习 CNN 的范畴区别在于深度和残差模块的堆叠方式。对毕设而言IResNet18 配合 ArcFace 损失已经能拿到足够好的特征质量把省下来的精力放到对齐和数据清洗上性价比更高。2.2 ArcFace 为什么是人脸识别算法的主流基线人脸识别最终不是靠分类分类只是中间产物。训练时模型最后一层是 softmax每个类别对应一个人推理时取 embedding 层输出的 512 维浮点向量当作这个人的“人脸特征”去和库里已有向量算余弦相似度。这个设计带来一个关键收益新增人员不需要重训整个模型部署阶段注册接口往向量库加一条记录即可。ArcFace 的贡献在于把 margin 放到角度空间。通俗理解普通 softmax 只要求不同人的人脸特征不重叠ArcFace 强制要求他们必须相距足够远这个“强制距离”就是 angular margin。它比 Triplet Loss 稳定得多训练时不需要精心构造三元组因此在 2019 年之后成为人脸识别算法的主流基线。如果你只看一本理论书《动手学深度学习》里关于 softmax 和 embedding 的章节足够应付答辩提问。2.3 用 pytorch 搭一个 ArcFace 训练骨架下面给出训练部分的最小实现。Backbone 用 IResNet 或 MobileFaceNet 都可以关键是接口约定输入一张对齐后的 112x112 RGB 图像输出 512 维特征。import torch import torch.nn as nn import torch.nn.functional as F # 特征维度统一用 512这是人脸识别算法里的常见设置 EMBEDDING_DIM 512 class ArcFaceLoss(nn.Module): def __init__(self, embedding_dim512, num_classes1000, margin0.5, scale64.0): super().__init__() self.embedding nn.Linear(embedding_dim, num_classes, biasFalse) self.margin margin self.scale scale def forward(self, features, labels): # 特征和权重都做 L2 归一化内积就是余弦相似度 features F.normalize(features, dim1) weight F.normalize(self.embedding.weight, dim1) cos_theta F.linear(features, weight).clamp(-1.0 1e-7, 1.0 - 1e-7) theta torch.acos(cos_theta) # 对目标类别加上角度 margin再写回原余弦值 target_logit torch.cos(theta self.margin) one_hot F.one_hot(labels, num_classesself.embedding.out_features).bool() logits torch.where(one_hot, target_logit, cos_theta) return F.cross_entropy(logits * self.scale, labels)代码里margin0.5控制类间距离scale64.0是特征缩放系数这两个值在大多数公开实现里是固定推荐项。num_classes在训练阶段等于训练集里的人员身份数也就是分类头的类别数推理阶段完全不用这个分类头只取 backbone 输出的 embedding 向量。需要注意torch.acos对接近边界的输入会放大梯度所以前面做了 clamp工程上想省事可以替换为 insightface 官方仓库里的 ArcFace 实现训练曲线更稳。注意train.py 里最常见的错误是让 num_classes 等于课堂人数后每新增同学就重训一次。正确做法是训练时用尽可能大的公开身份数据集把模型训到收敛部署阶段把注册照片重新过一遍模型得到向量入库完全不碰训练代码。3. 人脸识别签到数据管道与训练命令从数据增强到特征收敛3.1 数据目录怎么组织公开数据与自采数据如何搭配训练的第一步不是写模型而是把数据目录定好。最常见的组织方式是一个身份一个子目录目录名可以带学号或工号方便后面做映射。data/ ├── train/ │ ├── 2021001_zhangsan/ │ │ ├── 001.jpg │ │ ├── 002.jpg │ │ └── 003.jpg │ ├── 2021002_lisi/ │ └── ... ├── val/ │ └── ... └── test/ └── ...目录名里包含 ID 和姓名代码里按名字切分即可。训练前要做一次粗筛模糊的、闭眼的、脸部占比过小的直接删除。每个人保持 5 到 20 张不要刻意追求数量但要做到角度和光照有差异。数据增强方面随机水平翻转、小角度旋转、颜色抖动这三项足够过强的增强反而会让小数据集难以收敛。数据来源要在文档里交代清楚。毕设评阅最反感的是把别人训练好的权重说成自己从零训练的。更稳妥的路线是主系统直接使用开源免费商用的人脸识别模型提取特征把训练 ArcFace 的部分作为一个扩展章节写进文档诚实说明“模型结构参考了公开方案权重在公开数据集上预训练”。这条路线既能体现对深度学习 CNN 的理解又回避了自采数据量不够的问题。3.2 人脸检测与对齐比换 Backbone 更影响精度的一步人脸识别算法最容易被忽略的是对齐。同一个人的照片眼睛位置差几个像素余弦相似度可能从 0.75 掉到 0.55。检测可以先用 OpenCV 的 DNN 人脸检测器或者 InsightFace 自带的检测模型拿到人脸框和五个关键点后再做一次相似变换。import math import cv2 def align_face(img, landmarks): landmarks: [左眼, 右眼, 鼻尖, 左嘴角, 右嘴角] 共 5 个点 left_eye landmarks[0] right_eye landmarks[1] # 计算双眼连线与水平方向的夹角 dx right_eye[0] - left_eye[0] dy right_eye[1] - left_eye[1] angle math.degrees(math.atan2(dy, dx)) # 以左眼为旋转中心把双眼转到同一水平线 M cv2.getRotationMatrix2D(left_eye, angle, scale1.0) aligned cv2.warpAffine(img, M, (img.shape[1], img.shape[0]), flagscv2.INTER_CUBIC) # 以双眼中心为基准裁剪 112x112 cx int(left_eye[0]) cy int(left_eye[1]) crop aligned[cy-40:cy72, cx-56:cx56] return cv2.resize(crop, (112, 112))这段代码把双眼连线旋转到水平再按左眼位置固定裁剪区域。112x112 是人脸识别模型最常用的输入尺寸和 InsightFace 系模型直接对齐。旋转中心选左眼裁剪窗口以左眼为中心向上下各取 40 和 72 像素基本能覆盖完整面部。这里如果检测框抖动建议保存对齐中间结果训练时直接用对齐图不要在每次训练轮次里重复做几何变换。3.3 训练命令与超参数表从 pytorch 启动到验证相似度数据准备好之后训练命令如下。建议先创建独立的 conda 环境避免和系统 Python 混在一起。conda create -n face python3.9 -y conda activate face python train.py \ --train-root ./data/train \ --val-root ./data/val \ --backbone iresnet18 \ --loss arcface \ --embedding-dim 512 \ --batch-size 64 \ --lr 0.1 \ --warmup-epochs 5 \ --epochs 30 \ --checkpoint ./weights/best.pt--lr 0.1配合 batch size 64 是 ImageNet 风格训练常见起点人脸数据集通常还要配合余弦退火。--warmup-epochs 5让学习率从 0 线性升到 0.1避免前期梯度爆炸。--epochs 30对几十个类别的身份分类足够再往后训练的收益很小更多是过拟合到训练集的光照条件。参数推荐值说明margin0.5角度间隔太大会不收敛太小区分度不够scale64.0特征缩放与 margin 配合使用embedding_dim512特征向量维度后续比对和存储都依赖它batch_size64 至 128取决于显存太小会导致 BN 统计不稳定warmup_epochs5学习率预热的轮数lr_schedulerCosineAnnealing收敛更平滑答辩时也更好解释训练结束后不要只看 loss。做人脸识别测试最直接的方法是抽一组照片对输出它们的余弦相似度分布正样本对通常集中在 0.6 到 0.9负样本对在 0.2 以下。用验证集扫一遍阈值再去看签到场景需要多少误识容忍度。# verify.py 的核心逻辑提取两张图的 embedding算余弦相似度 def cosine_similarity(emb1, emb2): emb1 emb1 / (torch.norm(emb1, dim-1, keepdimTrue) 1e-8) emb2 emb2 / (torch.norm(emb2, dim-1, keepdimTrue) 1e-8) return float((emb1 * emb2).sum(dim-1).item())verify.py在项目里作为独立脚本保留答辩时可以现场对两张不同姿态的照片做演示。如果相似度普遍偏低先查对齐是否准确再查输入图像的通道顺序是否从 BGR 转成了 RGB这两个问题占了人脸识别训练调试的大部分时间。4. 可运行的签到系统服务用 FastAPI 把模型包成高可用接口4.1 签到系统的接口设计与数据表模型训练好之后系统的核心是把 embedding 提取和比对封装成三个接口注册、签到、记录查询。FastAPI 在 Python 的 Web 框架里调试体验最好自带 OpenAPI 文档答辩演示时浏览器直接打开/docs就能现场调接口对毕设很加分。方法路径请求体返回POST/api/v1/registeruser_id, name, image_base64embedding 已保存POST/api/v1/checkinuser_id, image_base64success, score, timeGET/api/v1/recordsdate 参数当日签到记录列表数据表不需要搞复杂。user 表存用户基本信息和 embedding 向量checkin_records 表存每次签到的用户、时间、相似度得分。两张表足够支撑一个学期的使用。# models.py 中两张核心表的结构 CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT UNIQUE NOT NULL, name TEXT NOT NULL, embedding BLOB NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS checkin_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL, score REAL NOT NULL, check_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP );embedding 以 BLOB 形式存储 512 个 float32单人占用 2KB1000 人也就 2MB完全不需要引入专门的向量数据库。这里用 SQLite 最合适答辩时可以解释清楚为什么不用 MySQL数据量小、单机部署、避免额外服务依赖。4.2 embedding 的注册与比对阈值设定是这里的核心模型权重文件在服务启动时只加载一次。签到照片经过与训练时完全相同的对齐流程提取 512 维向量后与全库向量做余弦相似度匹配。import numpy as np MODEL None # 在 FastAPI 启动事件中加载一次权重 def register_embedding(user_id, name, image): face_img align_and_preprocess(image) # 调 3.2 节的对齐函数 emb MODEL.embed(face_img) # 返回 L2 归一化的 512 维向量 emb_bytes emb.astype(np.float32).tobytes() save_to_db(user_id, name, emb_bytes) return {ok: True} def match_embedding(image, threshold0.40): face_img align_and_preprocess(image) emb MODEL.embed(face_img) # 从数据库读出所有向量组成矩阵emb 与矩阵做点积即余弦相似度 user_ids, emb_matrix load_all_embeddings() scores emb_matrix emb best_idx int(np.argmax(scores)) if scores[best_idx] threshold: return user_ids[best_idx], float(scores[best_idx]) return None, 0.0阈值 0.40 是经验起点但必须用验证集数据重新标定。签到场景里 FAR 和 FRR 的优先级不同宁可让本人多刷一次脸也不能容忍他人代签所以阈值可以适当往高调比如 0.45。这里如果不做归一化点积结果会受向量长度影响训练时输出的 embedding 一定要 L2 归一化后再入库否则阈值没有任何意义。4.3 启动命令与可复现环境vscode python 环境配置与依赖清单服务入口用 uvicorn 启动开发阶段开 reload部署阶段务必关掉。下面的依赖清单直接写入 requirements.txt保证答辩换一台机器也能一键复现。uvicorn app.main:app --host 0.0.0.0 --port 8000 --reloadrequirements.txt 至少包含以下内容。版本号不必锁死到小版本但 torch 和 opencv 建议固定主版本这两个库的大版本升级经常带来接口不兼容问题。fastapi0.110.0 uvicorn0.27.0 torch2.1.0 opencv-python4.9.0.80 numpy1.26.4 python-multipart0.0.9如果用 vscode 写代码vscode python 环境配置这一关一定要提前处理好。在项目根目录建.venv然后conda activate face后让 vscode 选择 face 环境作为解释器避免代码在系统 Python 和虚拟环境之间来回切换导致 opencv 装错地方。接口写完可以用 requests 快速验证注册和签到流程。import base64 import requests img base64.b64encode(open(test.jpg, rb).read()).decode() r requests.post(http://127.0.0.1:8000/api/v1/register, json{user_id: 001, name: 张三, image_base64: img}) print(r.json())这段脚本模拟了注册请求返回里能看到新用户的 embedding 是否成功写入。签到接口的测试逻辑完全一样只是路径换成 checkin。两个请求都通了整个系统的最小闭环就成立了。5. 部署细节与答辩验证让深度学习签到系统稳定跑满一学期5.1 答辩前必看的三项指标与文档说明写法答辩老师不会看训练曲线但一定会问三个问题准确率多高、阈值怎么定、遇到相似脸怎么办。这三个问题全部可以用一张指标表回答。写一个小脚本扫描验证集输出不同阈值下的 FAR、FRR 和准确率。阈值FAR误识率FRR拒识率准确率0.400.5%3.2%96.5%0.450.1%5.0%95.0%0.500.0%8.5%91.8%表格里的数值来自一份 50 人规模验证集。答辩讲解时报准率和误识率的取舍远比报一个孤立的模型精度有说服力。文档说明部分重点写三块数据来源与预处理流程、模型结构图与损失函数说明、接口文档和部署命令。代码注释不用面面俱到但上述三个部分必须详写这是题目标注“文档说明”的得分点。5.2 增量注册与数据备份长期运行的三件事系统要跑满一个学期三件事必须提前做好。第一新同学注册只需要调 register 接口写入向量不需要重训但要写一个独立脚本定期重建内存中的向量矩阵重启服务时从数据库重新加载。第二阈值会随着摄像头位置调整而变化建议每隔两周用留存样本重新跑一遍指标表。第三SQLite 文件和模型权重是唯一需要备份的数据用系统定时任务每天打包一次即可。提示人脸识别签到系统的成败不在模型而在工程。注册照和签到照的光照差异一旦过大再好的 ArcFace 也会失效。部署时固定摄像头位置和角度比调整任何超参数都有效。最后补一个具体操作。如果出现误签到或测试污染管理员需要按日期清除记录直接执行UPDATE checkin_records SET status 0 WHERE check_time LIKE 2025-06-11%;再在签到接口里过滤掉 status 为 0 的记录即可恢复干净状态。这样整套系统从模型训练、服务部署到日常维护就形成了一个可解释、可验证的完整闭环。本文还有配套的精品资源点击获取