深度学习人脸识别实战:PyTorch+FaceNet从原理到毕业设计源码解析

发布时间:2026/9/28 9:12:29
深度学习人脸识别实战:PyTorch+FaceNet从原理到毕业设计源码解析 简介这是一份面向Python学习者与毕业设计开发者的深度学习人脸识别完整项目融合传统人脸检测与深度神经网络方法包含LFFD、DSFD等检测模型及特征提取、识别、日志管理等模块。压缩包共39个文件以18个py源码文件为主辅以模型权重params、配置文件json、人脸关键点dat、模型h5及README文档整体约23MB代码含注释、结构清晰便于快速部署和二次开发。目前已有155人学习下载项目经过严格调试可运行且界面完善适合作为毕业设计、期末大作业或课程设计的直接参考。内容不仅涵盖核心识别算法还提供模型配置文件、训练参数与使用说明方便理解从检测到识别的完整流程兼具实用性与可扩展性。1. 为什么人脸识别这个毕业设计值得用深度学习重做一遍人脸识别门禁机、考勤打卡、支付验证这些天天在用的功能背后几乎都是同一套技术路线先把人脸从画面里检测出来再让一个深度卷积网络把这张脸压成一个特征向量最后拿这个向量去和数据库里的注册特征做比对。这个标题给的是一个高分 Python 毕业设计项目的完整源码加文档说明表面上是一份“可以交作业”的代码包实际的价值在于它把一套能跑通的人脸识别系统按工程化的方式拆开了从数据集整理到模型训练再到摄像头实时推理每一步都有对应的代码和文档。适合两类人一类是正在做深度学习相关毕业设计、需要一套有完整闭环的参考实现的学生另一类是入门 CV 方向、想搞明白 FaceNet 这套思路落地时到底要处理哪些脏活的开发者。把这份源码吃透比你自己从零瞎搭要省太多时间。2. 先把项目源码拆开看一份能跑的人脸识别骨架包含哪些文件拿到一份人脸识别的源代码包别急着装环境跑 train.py。先花二十分钟把目录结构读完搞清楚训练流程和推理流程分别依赖哪些文件这样后面改代码、调参数才不会像无头苍蝇。这个项目既然带文档说明说明作者是按“能复现”的标准来组织的目录一般会分数据处理、模型定义、训练入口、推理入口、工具函数和文档几个部分。2.1 源码目录怎么读从入口文件反推整个流程一份合格的毕设级源码入口文件一般就两三个。train.py 负责训练test.py 或者 recognize.py 负责推理preprocess.py 负责把原始图片整理成训练集。我拿到代码后会先看 requirements.txt 或者 environment.yml确认依赖版本然后顺着 train.py 的导入语句把模型定义、数据集类、损失函数这几个模块找出来。这个项目的依赖一般是 PyTorch 加 OpenCV再加上 numpy、tqdm 这些常规库。如果代码里用了 facenet_pytorch 这种封装库那内部结构会简单一些如果是手写的 ResNet 加三元组损失那就要重点看 model.py 里的网络结构。# 第一步创建虚拟环境并安装核心依赖 conda create -n face_rec python3.8 conda activate face_rec pip install torch torchvision opencv-python numpy tqdm matplotlib # 如果用到 facenet_pytorch 库再补一条 pip install facenet-pytorch逻辑说明用 conda 单独建一个 Python 3.8 的环境是为了避免把系统 Python 搞乱人脸识别涉及到的 OpenCV、PyTorch 这两个大件经常和别的项目冲突。pip 安装的时候我习惯不带版本号让 pip 自动解析但 torch 和 torchvision 的版本必须配套否则会报 libc10.so 找不到之类的链接错误。facenet-pytorch 这个库把 Inception-ResNet v1 网络和预训练权重都封装好了毕设项目用它能省很多事。参数说明Python 3.8 是个相对稳的选择3.10 及以上跑 OpenCV 和老代码容易遇到 numpy API 变动的问题。torch 的 CPU 版还是 GPU 版取决于你有没有 N 卡没有 GPU 就用 CPU 版硬跑数据集小也能出结果就是慢。2.2 训练与推理两条主流程先跑通哪个更划算我的建议是先把推理流程跑通用作者训练好的权重文件直接识别再回头碰训练。原因很简单训练涉及的变数太多数据集有没有对齐、标签有没有配错、损失函数有没有收敛任何一环出错都会让你怀疑人生。推理流程只需要加载两个东西一个是人脸检测器用来框出人脸一个是特征提取模型用来生成 128 维或 512 维的特征向量。识别的时候把这 128 维向量和数据库里的向量算余弦相似度阈值大于 0.7 就认为是同一个人这个阈值是调出来的不是定死的。# recognise.py 核心推理逻辑简化版 import torch import cv2 from facenet_pytorch import InceptionResnetV1, MTCNN # 加载人脸检测器 MTCNN设置最小人脸尺寸和置信度阈值 detector MTCNN(image_size160, margin0, min_face_size20, thresholds[0.6, 0.7, 0.7], devicecpu) # 加载预训练的 Inception-ResNet v1输出 512 维特征向量 model InceptionResnetV1(pretrainedvggface2, classifyFalse).eval() img cv2.imread(test_face.jpg) img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 检测并抠出人脸区域缩放到 160x160 face detector(img_rgb) if face is None: print(no face detected) else: face face.unsqueeze(0) # 增加 batch 维度 with torch.no_grad(): emb model(face) # 得到 1x512 的特征向量 print(emb.shape)逻辑说明这段代码先创建 MTCNN 检测器处理的是“画面里有没有人脸、人脸在哪”的问题再创建 InceptionResnetV1 分类模型但设为 classifyFalse意思是不要最后的分类层只要特征向量。这两步就是整个识别系统的主干前者负责定位后者负责表征两者解耦后续想换更快的检测器或更强的特征网络都不需要大改。参数说明image_size160 是训练时的输入尺寸ResNet 系列固定吃这个值margin0 表示不额外留边如果人脸裁得太紧导致识别率下降可以调到 20 左右试试。thresholds 是 MTCNN 三个阶段P-Net、R-Net、O-Net的置信度阈值默认值在多数场景够用但戴眼镜或暗光环境可以全局下调 0.05。pretrainedvggface2 是预训练权重来源vggface2 数据集比 CASIA-WebFace 在人脸识别任务上效果普遍更好。2.3 文档说明里必须有的三个部分看漏了后面全是坑这个项目带的文档说明重点应该看三块数据集怎么组织的、训练流程怎么复现的、以及各个阈值是怎么定的。如果文档里写清了 train.txt 和 val.txt 的格式你就能确认训练数据是不是按“每个人一个文件夹”的结构组织的。如果文档里写了训练时的 batch size、学习率、迭代轮数你就能大致判断这个模型在什么量级的数据上训练过。最怕的是文档里只写了“使用深度学习技术进行人脸识别”这种空话没有给出任何可复现的具体参数那这个代码包的价值就要打个折扣。我一般拿到文档先看“运行环境”和“数据集说明”这两节这两节正常后面的代码大概率能跑起来。3. 用 Python 跑通人脸识别的最小闭环从 PyTorch 到摄像头实时识别环境装好、代码结构摸清之后就该动手让它跑起来。人脸识别项目最容易翻车的地方不在模型训练而在“摄像头画面里的人脸到底能不能被稳定检测到”。这节直接给一个能跑通的最小闭环先测试纯图片推理再做摄像头实时识别每一步都带可抄的代码。3.1 环境搭建与预训练权重获取先确认三件事在跑任何代码前确认三件事torch 能不能调用 GPUCUDA 版本和 torch 版本是否匹配OpenCV 能不能正常读摄像头。很多人人脸识别项目跑不起来八成不是代码问题是 OpenCV 在虚拟环境里没装好或者笔记本摄像头被其他应用占用了。先把这三件事验证一遍能省出半天排查时间。# check_env.py验证核心运行环境 import torch import cv2 print(CUDA available:, torch.cuda.is_available()) print(Torch version:, torch.__version__) # 测试摄像头是否能打开0 代表默认摄像头 cap cv2.VideoCapture(0) if not cap.isOpened(): print(camera failed, check permission or occupied process) else: ret, frame cap.read() print(camera ok, frame shape:, frame.shape) cap.release()逻辑说明torch.cuda.is_available() 返回 False 不代表不能跑只是训练速度会慢很多倍。摄像头测试这里ret 为 False 时先检查系统权限Windows 上要允许 Python 访问摄像头Linux 上要确认 /dev/video0 存在且有权限。3.2 人脸检测与对齐MTCNN 的三个网络在做什么MTCNN 是这套代码里的检测核心名字叫 Multi-task Cascaded Convolutional Networks翻译过来是多任务级联卷积网络。它分了三个小网络P-Net 先在整张图上快速扫描生成候选框R-Net 把候选框筛一遍去掉大部分假阳性O-Net 做最后精修同时输出人脸关键点——左眼、右眼、鼻尖、左嘴角、右嘴角五个位置。这两个眼睛的位置就用来做人脸对齐通过仿射变换把两只眼睛旋转到水平方向这样输入到特征网络里的人脸是标准姿态识别率会明显提升。很多人忽略这一步直接用原始检测框去识别同一个人的识别率会飘忽不定因为左右转头都会改变特征分布。# align_face.py利用 MTCNN 的眼睛关键点做人脸对齐 import cv2 import numpy as np from facenet_pytorch import MTCNN detector MTCNN(keep_allFalse, devicecpu) img cv2.imread(rotate_face.jpg) img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 返回人脸框和五个关键点坐标 boxes, probs, points detector.detect(img_rgb, landmarksTrue) if points is not None: left_eye points[0][0] right_eye points[0][1] # 计算两眼连线与水平方向的夹角 dy right_eye[1] - left_eye[1] dx right_eye[0] - left_eye[0] angle np.degrees(np.arctan2(dy, dx)) # 构造旋转矩阵绕图像中心旋转 center (img.shape[1] // 2, img.shape[0] // 2) M cv2.getRotationMatrix2D(center, angle, 1.0) aligned cv2.warpAffine(img, M, (img.shape[1], img.shape[0])) cv2.imwrite(aligned.jpg, aligned)逻辑说明detector.detect 返回的 landmarks 参数默认是关闭的必须显式传入 landmarksTrue 才能拿到五个关键点。这里只用了两个眼睛算旋转角度实际工程里还会考虑鼻尖的偏移来判断是否是正脸如果鼻尖偏离两眼连线中点太多说明这是一张侧脸应该直接拒识而不是强行对齐。angle 可能是负值OpenCV 的旋转方向是逆时针为正所以一定要拿实际图片验证转反了会越搞越歪。参数说明cv2.warpAffine 的第三个参数是输出图像尺寸我这里沿用原图大小。如果要输出正方形的人脸区域可以换成 (160, 160)并把旋转中心设为两眼连线的中点效果会更稳。3.3 摄像头实时识别的完整命令把图片流程接到视频流图片推理跑通后把 cv2.VideoCapture(0) 接到同一个处理管线就是实时识别了。这里要把握好一个原则检测和特征提取不能每一帧都全量执行否则帧率会掉到个位数。常见做法是每隔 5 帧做一次检测检测到人脸后再做特征提取其他帧直接复用最近一次的结果这样既能保证实时感又能控制 CPU 占用。# realtime_recognize.py摄像头实时人脸识别主循环 import cv2 import torch from facenet_pytorch import MTCNN, InceptionResnetV1 import numpy as np detector MTCNN(keep_allTrue, devicecpu) model InceptionResnetV1(pretrainedvggface2, classifyFalse).eval() # 模拟数据库名字 - 特征向量 database { zhang_san: torch.zeros(512), li_si: torch.zeros(512), } cap cv2.VideoCapture(0) frame_count 0 last_name unknown while cap.isOpened(): ret, frame cap.read() if not ret: break # 每隔 5 帧做一次完整检测和识别 if frame_count % 5 0: frame_rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) faces detector(frame_rgb) # 返回多个脸的张量 if faces is None: last_name no_face else: with torch.no_grad(): embs model(faces) # 与数据库每个特征算余弦相似度 best_name unknown best_score 0.0 for name, db_emb in database.items(): score torch.cosine_similarity(embs[0], db_emb, dim0).item() if score best_score: best_score score best_name name # 相似度低于阈值的统一按 unknown 处理 last_name best_name if best_score 0.7 else unknown frame_count 1 cv2.putText(frame, last_name, (30, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow(realtime_face, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明database 里初始化的是全零向量实际项目里应该用同一个模型把每个人的注册照片跑一遍把得到的特征向量存成 .npy 文件或者写进 SQLite。主循环每 5 帧才做一次推理这在 CPU 上能把帧率从 2 拉到 15 左右。相似度阈值的判断逻辑里有个细节如果和多个人的相似度都超过阈值取最高分而不是先到先得否则不熟悉的人容易被误判。参数说明keep_allTrue 表示一帧里可能有多张人脸都保留False 只保留置信度最高的一张。torch.cosine_similarity 算的是余弦相似度范围在 -1 到 1 之间对人脸识别来说 0.6 到 0.75 是个常见阈值区间具体取值要看训练数据集的分布后面单独说怎么标定这个值。4. 损失函数与训练策略把 FaceNet 的孪生思路说透如果只是交差用预训练权重做推理就够了。但作为高分毕设训练部分才是真正拉分的点。这章把训练链路讲透深度学习人脸识别用的核心损失函数是什么训练数据该怎么组织以及那些一看就懂的参数背地里是怎么影响模型行为的。4.1 为什么选三元组损失Softmax 能分类但不擅长“区分相似脸”分类网络用 Softmax 损失训练时学到的特征是“这个人属于哪一类”它会把不同人的特征推向远离决策边界的方向但并没有刻意要求“同一个人的不同照片在特征空间里离得近”。人脸识别的核心诉求恰恰是后者同一人不同角度、不同光照下的特征要聚合不同人的特征要分散。三元组损失Triplet Loss直接针对这个目标设计每一步训练同时取三张图锚点Anchor、正样本Positive、负样本Negative。锚点和正样本是同一个人负样本是不同的人。损失函数要求锚点和正样本的距离加上一个间隔 margin 之后仍然小于锚点和负样本的距离这样网络就被逼着把同人脸揉成一团。# triplet_loss.pyPyTorch 手写三元组损失 import torch import torch.nn.functional as F def triplet_loss(anchor, positive, negative, margin0.5): anchor: (batch, 512) 锚点特征 positive: (batch, 512) 同一人的另一张脸特征 negative: (batch, 512) 不同人的脸特征 pos_dist F.pairwise_distance(anchor, positive) # 同类距离 neg_dist F.pairwise_distance(anchor, negative) # 异类距离 # 目标是 pos_dist margin neg_dist loss torch.relu(pos_dist - neg_dist margin) return loss.mean() # 使用示例 anchor torch.randn(4, 512) positive torch.randn(4, 512) 0.1 negative torch.randn(4, 512) 1.0 loss triplet_loss(anchor, positive, negative) print(triplet loss:, loss.item())逻辑说明F.pairwise_distance 默认算欧氏距离实际工程里也有人用余弦距离但 FaceNet 原文用的是欧氏距离并且最后会对特征向量做 L2 归一化让所有特征落在单位球面上这样欧氏距离和余弦相似度在数学上就是等价的。relu 外面加一层保证损失非负如果 pos_dist 已经比 neg_dist 小 margin 以上loss 为零这组三元组就不贡献梯度了是正常现象。参数说明margin 是三元的间隔默认 0.5。调大 margin 会让模型对“不同人必须离远”的要求更严格但也更容易让训练不收敛调小则收敛快但区分力弱。中文互联网上很多帖子推荐 margin0.2这是因为他们用的是人脸识别专用数据集优化过的经验值实际要看自己数据集的难度我习惯先跑 200 个 step 看 loss 有没有下降趋势再决定调不调。4.2 训练数据怎么组织每个人至少 10 张图的底仓逻辑三元组损失对数据组织方式很敏感。最忌讳的是每个人的照片只有一两张因为这样随机采三元组时正样本对太少模型学不到类内聚合。常见做法是数据根目录下每个人一个文件夹里面放 10 到 50 张不同角度、不同光照的图片。文件夹的名字就是标签。训练时用 DataLoader 按“先采样人再采样该人的两张图”的策略组织 batch这个策略写在数据集的getitem方法里比随机从全局采样稳定得多。# triplet_dataset.py面向三元组训练的数据集类 import os import random from PIL import Image from torch.utils.data import Dataset class TripletFaceDataset(Dataset): def __init__(self, root_dir, transformNone): # root_dir/张三/*.jpg, root_dir/李四/*.jpg 这种结构 self.person_dirs [d for d in os.listdir(root_dir) if os.path.isdir(os.path.join(root_dir, d))] self.images {} for pid in self.person_dirs: p os.path.join(root_dir, pid) imgs [os.path.join(p, f) for f in os.listdir(p) if f.endswith((.jpg, .png))] self.images[pid] imgs self.transform transform def __len__(self): return len(self.person_dirs) * 100 # 每个 epoch 采样 100 步 def __getitem__(self, idx): pid random.choice(self.person_dirs) # 同一人至少要有 2 张图才能构成正样本对 anchor_path, pos_path random.sample(self.images[pid], 2) # 选一个不同的人作为负样本 neg_pid random.choice([p for p in self.person_dirs if p ! pid]) neg_path random.choice(self.images[neg_pid]) read lambda p: self.transform(Image.open(p).convert(RGB)) return read(anchor_path), read(pos_path), read(neg_path), pid, neg_pid逻辑说明getitem里先用 random.choice 选人再用 random.sample 从该人的图里抽两张保证每对三元组至少存在一个合法的正样本对负样本从另一人随机抽。这里有个隐藏要求每个人的图片文件数量必须大于等于 2否则 random.sample 会直接抛异常预处理脚本里就该提前过滤掉这种脏数据。返回的 pid 和 neg_pid 用于记录当前加载的是谁和谁方便后面输出训练日志。参数说明图片读取后用 transform 做随机裁剪、水平翻转、颜色抖动这是人脸识别训练里最有效的数据增强手段。尤其水平翻转因为人脸是左右对称的翻一张等于多一张训练数据能显著提升对侧脸的鲁棒性。但要注意眼角位置的关键点标注如果被翻转必须同步翻转坐标否则对齐模块就废了。4.3 训练参数怎么设学习率、batch size 和特征维度训练一个能用的特征提取模型有三个参数决定成败。第一是学习率用 Adam 优化器的话初始学习率我一般给 1e-3跑 30 个 epoch 后手动降到 1e-4再跑 10 个 epoch比用学习率调度器更直观。第二是 batch sizeCPU 上 8 到 16 就好GPU 上 32 到 64batch 太小会导致三元组采样不稳定、loss 抖动很大。第三是特征维度这个项目代码里如果是用 FaceNet 的 Inception-ResNet v1输出是 512 维换成 ResNet50 的话把最后一层全连接改成 128 维即可128 维已经足够区分几万人的规模且计算余弦相似度的开销小很多。# train.py 核心训练循环节选 from torch.optim import Adam device torch.device(cuda if torch.cuda.is_available() else cpu) model InceptionResnetV1(pretrainedvggface2, classifyTrue, num_classes0, dropout_prob0.6).to(device) model.logits torch.nn.Linear(512, 128).to(device) optimizer Adam(model.parameters(), lr1e-3) scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size30, gamma0.1) for epoch in range(40): total_loss 0.0 for anchor, pos, neg, _, _ in train_loader: anchor, pos, neg anchor.to(device), pos.to(device), neg.to(device) # 通过特征提取器后做 L2 归一化 a_emb F.normalize(model(anchor), p2, dim1) p_emb F.normalize(model(pos), p2, dim1) n_emb F.normalize(model(neg), p2, dim1) loss triplet_loss(a_emb, p_emb, n_emb, margin0.5) optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() scheduler.step() print(fepoch {epoch} avg_loss: {total_loss / len(train_loader):.4f})逻辑说明这段代码的关键在 classifyTrue 加后来替换 logits。这样模型的预训练权重全部保留只把最后的分类层换成 128 维输出。对毕设来说这种“在预训练模型上做迁移学习”的思路比从零训练更稳妥训练很快就能收敛需要的训练数据也少很多。F.normalize 在算损失前把特征向量归一化到单位长度这样损失函数的角度解释更清晰实际训练也更稳定。pretrainedvggface2 已经是在超大脸库上训过的权重我们在这基础上微调本质上是让模型适应当前数据集的人脸分布。参数说明dropout_prob0.6 是全连接层前的 Dropout 概率防止过拟合。step_size30 意味着每 30 个 epoch 学习率乘 0.1如果你的训练数据只有几千张40 个 epoch 足够看到 loss 降到不再下降数据量大就改成 60 个 epoch并把学习率衰减改到第 50 个 epoch 触发。训练时如果 loss 曲线一直不降先检查数据是不是有问题看训练集里是不是混了大量背景图或标注错误的人脸。5. 毕设级人脸识别的 5 个常见坑现象、原因、解决这部分直接按“现象 → 原因 → 解决”的格式来。这几条基本都是从真实跑项目里踩出来的有的看着是小问题不解决会直接让识别率坍到没法看。5.1 摄像头画面里人脸是歪的识别率极不稳定现象同一个戴着眼镜的人在摄像头前左右转头识别结果时好时坏有时候相似度 0.85 有时候掉到 0.4。原因MTCNN 输出的原始人脸框没有做对齐转头时两只眼睛不在同一水平线上特征提取网络的输入分布和训练时差异很大预训练模型在正脸图上表现好在歪脸图上就是另一套特征。解决必须在检测后加对齐步骤参考 3.2 里的仿射变换对齐后同一人的特征相似度普遍能提高 10 个百分点以上。如果加了对齐还是不稳把 MTCNN 的 thresholds 从 [0.6, 0.7, 0.7] 降到 [0.5, 0.6, 0.6]让更多候选框进入最后精修减少漏检。5.2 训练损失一直在 0.4 到 0.6 之间震荡下不去现象用三元组损失训练了 20 个 epochloss 曲线在 0.5 附近横跳没有下降趋势。原因最常见的是 margin 设置和距离尺度不匹配。如果特征向量没有做 L2 归一化欧氏距离的绝对数值会随训练阶段变化margin0.5 可能过大。还有一种情况是三元组采样太随机大量简单三元组的 loss 已经为零难分三元组占比太少。解决先把 F.normalize 加进训练管线并且在每个 epoch 里做 hard mining——用当前模型跑一遍训练集挑出距离最近但不是同一人的负样本再把这些难样本喂给模型。常见做法是每 5 个 epoch 重新挖掘一次难样本硬编码在训练循环里而不是每次随机采样。5.3 推理时内存爆炸程序直接卡死现象摄像头实时识别跑了 30 秒后内存从 2G 飙到 8G然后进程被杀。原因MTCNN 的 keep_allTrue 配合 detect() 方法在连续帧上反复调用会把每一帧的中间 tensor 留在计算图里没有释放。PyTorch 的推理模式没有用 torch.no_grad()导致每一帧都保存了梯度计算图。解决推理一定要包在 with torch.no_grad() 里面或者调用 model.eval() 后使用 torch.inference_mode()后者更彻底。另外不要每次循环重新初始化 MTCNN 和模型把它们放在 VideoCapture 之前全局只加载一次。5.4 换了一台电脑代码跑不起来了现象在自己电脑上跑得好好的拷贝到实验室电脑上import torch 直接报错或者 OpenCV 无法打开摄像头。原因PyTorch 的 CUDA 版本不匹配是最常见的另一台电脑显卡是 AMD 或者没有独显代码里 if torch.cuda.is_available() 走了 GPU 分支但 CUDA 装错了。再有的是 Windows 和 Linux 下 OpenCV 依赖的系统库不同Linux 缺 libGL.so.1 之类的动态库。解决在项目文档里写清楚 CPU 也能跑并在代码入口强制 device torch.device(cuda if torch.cuda.is_available() else cpu)。Linux 下缺库就执行 sudo apt-get install -y libgl1 libglib2.0-0。Windows 下摄像头打不开检查系统设置里是不是禁用了相机权限。5.5 识别结果把两个长得很像的人判成同一个人现象双胞胎或者同一个人在不同发型下的照片相似度高达 0.78越过阈值被误判。原因阈值设得太低或者特征提取模型的判别力不够。0.7 这个阈值对 VGGFace2 预训练权重来说偏低尤其是模型没有在当前数据集上微调过时类间距离没有拉开。解决逐个采集“注册照”和“测试照”的相似度分布算一个最合适的阈值。常见做法是在验证集上统计同一个人的相似度均值和不同人的相似度均值取两个均值的中间值作为阈值。如果两条分布重合严重说明模型需要继续微调而不是单纯调阈值能解决的。6. 验证与进阶用一张自拍和一段视频检验整套代码跑通只是第一步验证代码到底达到什么水平才是把一个毕设从“能跑”提升到“高分”的关键。给一个不依赖额外数据的验证流程准备一张自己正面、一张侧面、一张低头照片外加另一张别人的照片用脚本算四个相似度分别对应“同人同角度”“同人不同角度”“不同人”。真正能用的识别系统前两个相似度应该远高于后一个。# validate_model.py用少量照片验证特征模型质量 import torch from facenet_pytorch import MTCNN, InceptionResnetV1 detector MTCNN(image_size160, devicecpu) model InceptionResnetV1(pretrainedvggface2, classifyFalse).eval() def embed(img_path): from PIL import Image face detector(Image.open(img_path).convert(RGB)) if face is None: raise ValueError(fno face in {img_path}) with torch.no_grad(): return model(face.unsqueeze(0))[0] pairs [ (me_front.jpg, me_front.jpg), # 同图应该是 1.0 (me_front.jpg, me_side.jpg), # 同人不同角度应远低于 1.0 但高于陌生人 (me_front.jpg, other.jpg), # 不同人应很低 ] for a, b in pairs: sim torch.cosine_similarity(embed(a), embed(b), dim0).item() print(f{a} vs {b}: {sim:.3f})逻辑说明同图相似度接近 1.0 只能说明模型没崩不代表识别准确。真正要观察的是第二行“同人不同角度”和第三行“不同人”之间的分界线。如果同人不同角度相似度是 0.72而不同人已经到 0.68两者几乎没有间隔说明模型对这个人的特征聚合度不够需要用自己的数据继续微调。进阶的方向有两个。一个是换主干网络把 InceptionResnetV1 换成 MobileFaceNet识别精度略有下降但模型体积和推理速度都能大幅优化这正好可以和标题里的门禁机场景呼应——门禁设备大多算力有限嵌入式部署几乎都是 MobileFaceNet 这类轻量网络的天下。另一个方向是阈值动态化对每个人维护一个自适应阈值注册时用多张照片算一个均值向量再统计每张注册照与均值向量的相似度标准差识别时用“均值相似度 - 2 倍标准差”作为这个人独有的判定线能显著降低撞脸误判。这块有个我自己的血泪教训调试阈值时不要只看正确率要同时统计误识率False Accept Rate和拒识率False Reject Rate两个指标此消彼长调阈值本质是找个平衡点。文档里如果能附上这个验证脚本和两份率值的统计毕设的完整度会高出一大截。代码写到这整套标题里说的“源代码文档说明”就真正变成了一套可以交付、可以答辩、可以继续往嵌入式方向改的资产。希望帮到你。本文还有配套的精品资源点击获取