
1. 项目背景与整体方案选型1.1 毕设选题的来龙去脉做毕业设计那会儿我本来想选一个看起来简单点的题目结果导师直接甩给我一句人脸识别这块儿现在企业需求大你就往深度学习方向做吧。就这么一句话我后面整整半年都泡在数据、模型和调参里面。现在回头看这个选题确实选对了——既赶上了AI的热潮又能把大学四年学的计算机视觉、机器学习、Python编程这些零散知识串成一条线含金量比纯写个管理系统高得多。我要做的系统核心功能其实不复杂给定一张照片识别出这个人是谁。听起来简单但真正落地的时候涉及到的环节非常多——人脸检测、人脸对齐、特征提取、特征比对、数据库管理每一环都决定着最终效果。而且毕业设计不能光做一个能跑的Demo得有完整的工程架构、实验数据、对比分析和论文支撑这就逼着你把每一个环节都搞清楚来龙去脉。这里先把我选定的整体技术路线贴出来方便大家直接对照参考。这个技术选型在当年的公开资料和开源社区里是最主流的组合既有深度学习的先进效果又具备工程上的可实现性开发语言Python 3.8深度学习框架PyTorch 1.7人脸检测MTCNN多任务级联卷积网络特征提取FaceNet预训练模型基于Inception-ResNet v1人脸比对余弦相似度 阈值判定数据库SQLite存储人脸特征向量和人员信息Web展示层Flask 简单前端页面1.2 为什么选用这套方案而不是其他方案其实我调研的时候看到过更轻量的路线——直接用OpenCV自带的Haar级联分类器做检测加上LBPH或EigenFace做识别。这套方案优点是代码量极少、跑起来飞快几行代码就能出结果。但它有个致命短板一到复杂环境就拉胯。光照一变、角度一偏、脸上戴个眼镜识别率直接断崖式下跌。毕设答辩的时候一旦现场演示翻车那场面想想都尴尬。而深度学习方案虽然前期准备复杂需要装框架、下载模型权重但效果是肉眼可见地强。MTCNN检测人脸比传统方法稳健得多FaceNet提取的特征向量在标准数据集上的识别准确率能达到相当高的水平而且对光照、角度、表情的变化有很强的容忍度。这就是为什么我宁可多花两周时间搞定环境配置和模型部署也不愿意赌传统方案在现场演示的稳定性。再来说说特征提取层的选型对比。除了FaceNet我当时还认真考虑过ArcFace和SphereFace。这几个模型的思路其实同根同源都是把图片映射到特征空间让同类样本尽可能贴近、异类样本尽可能远离。FaceNet的特别之处在于它当年的训练策略用上了最难样本挖掘的Triplet Loss模型对类间距离的把控能力很强。而ArcFace则在训练时引入了角边距惩罚在大型人脸数据集上表现更惊艳。我当时选择FaceNet的一个现实原因是因为它的预训练模型和权重在社区里比较齐全有多个版本可以直接拿来用不必从零开始训练——毕设时间本来就紧能直接用的资源当然优先用。我在这里放一张我在选型期间做的对比表虽然数据是在普通测试集上跑出来的不代表在所有场景下都绝对如此但足以反映每种方案的适用边界方案检测能力识别准确率速度部署难度适用场景HaarLBPH弱受限环境低极快极低实验室演示HOGSVM中等中快低简单场景MTCNNFaceNet强抗干扰好高中等中实际场景MTCNNArcFace强更高中等中高高精度要求2. 数据准备与预处理2.1 数据从哪里来做深度学习系统数据永远是绕不过去的坎。毕设要求是人脸识别系统本质上要做的是人脸验证和人脸辨识两个任务。我一开始天真地以为随便找几十张照片就能搞定结果执行下来发现数据的质量和数量直接决定了整个系统性能的上限。我的数据来源主要有三块第一是公开数据集。我当时找了两个公开人脸数据集一个是用于学术研究的标准数据集包含大量不同身份的人脸图像另一个是标注比较干净的受限数据集。这两份数据主要用来评估模型效果而不是直接参与我系统的身份注册。第二个来源是自己拍摄的图片让实验室的同学帮我拍了不同角度的照片每人采集了不同光照、表情和姿态下的面部图像作为系统注册用户的核心数据。第三个来源是历史证件照和日常照片整理用于模拟非实时采集的应用场景。到后期我才发现数据的正确划分比数据总量更重要。人脸识别模型的验证必须坚持训练集和测试集的身份不重叠——也就是说训练时见过的人脸不能在测试阶段又出现同一身份的其他照片。如果划分不干净模型记住了身份而非人脸特征指标虚高答辩时一问便穿帮。数据划分比例我最终定成了训练集占六成、验证集占两成、测试集占两成。这个比例是经过多次实验验证过的既能保证模型充分学习又能留出足够空间做泛化检验。2.2 预处理流程与关键细节数据预处理是人脸识别系统里最容易被忽视却最关键的环节。一张原始图片直接扔进模型效果大概率是灾难性的。我当时把预处理流程拆成了四个环节人脸对齐、图像缩放、归一化、数据增强。人脸对齐这一步是重中之重。MTCNN检测出人脸后会返回左眼、右眼、鼻尖、嘴角等关键点信息。FaceNet模型在训练的时候期望输入的人脸是正的即两只眼睛在同一水平线上。因此我根据眼睛关键点的坐标计算旋转角度用仿射变换把脸部图像旋转到水平位置再裁剪出包含完整面部的区域。这个步骤能明显提升特征提取的稳定性——同一张脸歪着拍和正着拍对齐后提取的特征向量相似度会高很多。人脸对齐示例代码如下这段代码是后来无数次调整过的稳定版本import cv2 import numpy as np def align_face(image, landmarks): # landmarks: 字典包含 left_eye, right_eye, nose, left_mouth, right_mouth left_eye landmarks[left_eye] right_eye landmarks[right_eye] # 计算眼睛连线与水平线的夹角 dy right_eye[1] - left_eye[1] dx right_eye[0] - left_eye[0] angle np.degrees(np.arctan2(dy, dx)) # 以左眼为基准点做旋转 center left_eye rotation_matrix cv2.getRotationMatrix2D(tuple(center), angle, scale1.0) aligned cv2.warpAffine(image, rotation_matrix, (image.shape[1], image.shape[0])) # 根据对齐后的关键点计算裁剪区域 eye_center_x (left_eye[0] right_eye[0]) // 2 eye_center_y (left_eye[1] right_eye[1]) // 2 crop_size compute_crop_size(landmarks) # 按五官距离动态计算 cropped extract_crop(aligned, eye_center_x, eye_center_y, crop_size) return cv2.resize(cropped, (160, 160))图像缩放很简单FaceNet的输入尺寸是160x160所有图片统一放缩到这个尺寸。这里要提醒的是直接resize会导致人脸变形最好是先对齐裁剪成正方形区域再缩放这样能最大程度保留面部比例。归一化操作就是把每个像素的RGB值映射到-1到1的范围。FaceNet的预训练权重是在这个输入范围内拟合出来的直接喂0到255的原始像素会破坏模型的内部数据分布预期。这个地方如果做错特征向量的质量会大打折扣。数据增强这块刚开始我用的方式很粗暴——随机旋转、随机亮度变化、水平翻转。但后来发现人脸识别场景下的训练增强不能太激进。左右翻转虽然能增加数据量但考虑到人的面部并不是严格对称的过度的翻转反而会让模型混淆某些不对称的特征。实践下来最终只在训练集上使用了小角度的旋转和轻微的亮度扰动角度控制在正负10度以内。预处理这一套流程走完之后我重新跑了一遍实验对比同样的模型结构对齐和未对齐的测试准确率差了将近6个百分点归一化做错的时候测试准确率掉了十几个点。这些数值教训比任何理论都直观——预处理不是走流程每一步都在影响模型性能。3. 核心模块设计与实现细节3.1 人脸检测模块选MTCNN而不选YOLO的理由人脸检测模块解决的是人脸在哪儿的问题。主流方案中YOLO系列检测速度快在通用目标检测领域是王者级的存在但它为人脸这个垂直方向做的优化反而没有MTCNN深。MTCNN是专为人脸检测设计的多级联网络通过三个级联的卷积网络P-Net、R-Net、O-Net逐级精修候选框实现从粗粒度到细粒度的渐进式检测。P-Net负责快速生成候选框网络结构很浅扫描全图速度极快牺牲精度换速度先把疑似人脸的区域全部框出来。R-Net接收P-Net的候选框进一步判断这些区域是否真的含有人脸过滤掉大量误检。O-Net是最后的精调环节输出精确的人脸边界框和五个关键点坐标。用MTCNN还有一个隐性的好处它的O-Net输出本身就是我后面人脸对齐所需的五个关键点两只眼睛、鼻尖、两边嘴角。也就是说检测模块顺便把对齐模块的问题一起解决了省去了一次额外的关键点检测流程系统链路更紧凑。实际代码调用MTCNN很简单我用的第三方实现封装得非常好from mtcnn import MTCNN detector MTCNN() result detector.detect_faces(frame_rgb) if result: x, y, width, height result[0][box] keypoints result[0][keypoints] face frame_rgb[y:yheight, x:xwidth]但这里有一个工程上的大坑MTCNN检测到的边界框是矩形但如果人脸倾斜明显或者侧脸超过一定角度边界框会包含过多的背景区域导致裁剪出的人脸不标准。我的解决方案是在边界框基础上向外扩充10%的区域然后用关键点做对齐再根据对齐后的人脸区域重新裁剪。这个扩一点再对齐再裁的顺序听起来啰嗦但真的能明显减少背景干扰。另外需要说明的是MTCNN的检测置信度默认阈值是0.9我实际测试下来把阈值调到0.85会更合适——低了会引入较多误检高了会漏掉一些不清晰的照片。对于毕设演示这种环境0.85是个比较稳妥的平衡点。3.2 特征提取模块FaceNet的工作原理特征提取是整个系统的灵魂环节。FaceNet的思想非常优雅——它不把人脸识别当成一个传统的分类任务而是通过训练让网络学会把一张人脸图像映射成一个固定维度的欧几里得空间向量。训练完成后人脸之间的相似度就转化成了向量之间的空间距离。具体来说FaceNet采用了Triplet Loss的训练策略。训练时构造三元组锚点样本Anchor、正样本Positive与锚点是同一人和负样本Negative与锚点不是同一人。损失函数的目标是让锚点与正样本的距离尽可能小同时让锚点与负样本的距离尽可能大并满足一个最小间隔margin。我用的预训练模型将人脸映射为512维的特征向量效果比较理想。普通人理解这个方法时最容易困惑的一点是模型不做分类怎么知道学到的是人脸的语义特征而不是某个具体人的像素模板关键在于训练数据的规模。预训练模型是在海量人脸数据上训练出来的见过数万甚至数十万个不同身份它被迫去学习那些能区分不同人脸的本质特征——五官结构、轮廓比例、肤色纹理而不是死记硬背某个人的像素信息。这就是泛化能力的来源。使用预训练模型做特征提取代码量几乎可以忽略from facenet_pytorch import InceptionResnetV1 model InceptionResnetV1(pretrainedvggface2).eval() embedding model(torch.from_numpy(aligned_face).permute(2, 0, 1).unsqueeze(0).float()) embedding embedding.detach().numpy().flatten() # 512维向量真正需要动脑筋的部分是特征向量的后续处理。原始embedding向量直接入库比对当然也能用但是我做了两次优化。第一次优化是向量标准化。FaceNet输出的embedding模长本身就接近1但直接存储归一化后的向量会更稳定。归一化之后欧几里得距离和余弦相似度在计算结果上会呈现单调对应关系这样后面无论用哪种距离度量结论都是一致的。第二次优化是特征向量池化。单人采集多张照片时每张照片都会生成一个512维向量。同一用户的多个向量取平均得到代表向量能有效抵消某张照片的表情波动、光照偏差带来的随机噪声。我做了个测试单张照片入库时同一用户的识别准确率在94%左右每人用5张照片的特征均值入库后准确率提升到了98%以上。这个提升幅度让我很意外强烈建议大家注册用户时至少采集3到5张不同条件下的照片取均值。3.3 人脸比对逻辑阈值怎么定才科学识别逻辑这块常见的表述是用余弦相似度本质是计算两个特征向量的夹角夹角越小向量越相似。数学表达是向量点积除以模长乘积。余弦相似度的优势在于它天然对向量长度不敏感即便两个向量的模长差异较大只要方向一致相似度依然高。对于归一化后的向量余弦相似度就简化成了点积。比对时最核心的环节是设定阈值。阈值设置得过高会把同一个人的不同照片误判为不同人阈值设置得过低会把不同人误判为同一个人。这个此消彼长的权衡在工程上就体现为查准率和查全率的拉锯。我当时在自己的测试集上做了完整的阈值扫描实验从0.5到0.85每隔0.05取一个值分别统计误识别率没认出本人False Reject和误接受率认错人False Accept。最终发现0.65这个阈值附近是错误率总和最小的区间。当然这个数值与模型和数据密切相关未必适用于所有场景但通过阈值扫描找到最优平衡点的思路是通用的。阈值扫描实验的核心代码如下def compute_best_threshold(embeddings, labels): thresholds np.arange(0.4, 0.95, 0.01) best_threshold 0.5 best_f1 0 for threshold in thresholds: # 遍历所有配对计算相似度 y_pred [] y_true [] for i in range(len(embeddings)): for j in range(i1, len(embeddings)): cos_sim np.dot(embeddings[i], embeddings[j]) y_pred.append(1 if cos_sim threshold else 0) y_true.append(1 if labels[i] labels[j] else 0) # 计算F1分数并追踪最优阈值 f1 f1_score(y_true, y_pred) if f1 best_f1: best_f1 f1 best_threshold threshold return best_threshold这里还涉及一个识别系统的关键设计是1:1验证还是1:N辨识。1:1验证是你报上名字系统只比对你这个人的注册特征判断是不是你。1:N辨识是不提供身份信息系统在整库中搜索返回你是库里的谁。后者在用户数量多的时候计算量直线上升需要做索引优化。我的毕设系统默认使用1:N辨识但所有比对逻辑都写在统一接口里1:1验证只是1:N在N1时的特例一套代码两种模式灵活度很高。4. 系统搭建与实操过程4.1 环境配置与踩坑记录环境配置是最容易劝退新手的环节。我拿到装机指南后以为照着敲命令就万事大吉结果光是跑通环境就用了一个多星期踩过的坑连起来可以绕实验室一圈。硬件方面训练阶段如果从零训模型一块显卡是常规操作常见消费级显卡如RTX系列即可胜任推理或轻量训练但毕设里我用的是预训练模型主要做推理CPU也能跑只是速度慢一些。MTCNN检测一帧图片在CPU上大约需要0.3秒FaceNet特征提取大约需要0.5秒识别一张图总耗时约0.8秒。换成GPU后总耗时能压到0.1秒以内体验完全不同。所以如果条件允许强烈建议直接用GPU跑推理。软件环境的坑主要在版本兼容。我一开始图省事直接装了最新版的PyTorch结果和某个依赖库的版本不兼容MTCNN初始化时直接报错。后来学乖了所有依赖都按官方文档的推荐版本去匹配。这里把最终的版本组合列出来新同学可以直接复用少走弯路pip install torch1.7.1 torchvision0.8.2 pip install facenet-pytorch2.5.3 pip install mtcnn0.1.0 pip install opencv-python4.4.0.46 pip install flask1.1.2 pip install numpy1.19.5还有一个隐蔽的问题facenet-pytorch这个库在安装时会自动拉取MTCNN的权重文件但某些网络环境下权重下载会失败导致调用时明明代码没错程序却卡在初始化。解决办法是手动下载权重文件放到指定缓存目录。这个问题在我当年困扰了我很久现在你们如果遇到类似情况直接顺藤摸瓜去库的源码里看权重加载路径手动把文件放到位即可。4.2 注册与识别全流程代码演示系统的主流程分成两条线注册流程和识别流程。注册流程完成人脸的录入识别流程完成人脸的查询。这两条线共享同一个预处理链路我把所有公共逻辑封装成了一个工具类避免代码重复。注册流程的完整逻辑是这样的def register_face(user_id, user_name, image_paths): embeddings [] model get_facenet_model() for image_path in image_paths: img cv2.imread(image_path) img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) faces detector.detect_faces(img_rgb) if not faces: print(f图像 {image_path} 未检测到人脸) continue aligned align_face(img_rgb, faces[0][keypoints]) embedding model(get_tensor(aligned)).detach().numpy().flatten() embeddings.append(embedding / np.linalg.norm(embedding)) if len(embeddings) 0: return -1, 注册失败没有有效人脸 avg_embedding np.mean(embeddings, axis0) avg_embedding avg_embedding / np.linalg.norm(avg_embedding) save_to_db(user_id, user_name, avg_embedding) return 0, 注册成功识别流程的代码结构也很清晰def recognize_face(frame): img_rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) faces detector.detect_faces(img_rgb) if not faces: return [], 未检测到人脸 recognized [] for face in faces: aligned align_face(img_rgb, face[keypoints]) query_emb get_embedding(aligned) # 从数据库中加载所有已注册的参考特征 best_match None best_score -1 for user in registered_users: score np.dot(query_emb, user[embedding]) if score best_score: best_score score best_match user if best_score SIMILARITY_THRESHOLD: recognized.append((best_match[name], float(best_score))) else: recognized.append((unknown, float(best_score))) return recognized这里有个值得说明的细节detect_faces返回的坐标是在RGB色彩空间下的原图坐标系中而align_face函数内部做了仿射变换这两个图像空间的坐标系转换容易搞混。我在调试阶段曾经因为忘记了这一步导致对齐后人脸被裁剪到了完全错误的位置识别率骤降到零。排查这个问题花了我整整一个下午最后是直接打印中间变量、逐像素验证才定位到问题。这种低级错误在视觉工程里非常常见大家一定要养成每步验证输出维度的好习惯。4.3 Web界面与交互设计毕设不能只有一个黑乎乎的终端窗口至少得有个像样的交互界面。我用Flask搭了一个极简Web应用前端用原生HTMLJavaScript后端提供三个接口注册接口、识别接口、用户管理接口。界面设计得很朴素左侧是上传区域右侧是识别结果展示区。上传一张图片后前端通过fetch把图片以Base64编码发送到后端后端调用识别流程返回识别出的人员姓名、相似度分数和检测到的人脸标注框前端把标注框画在图片预览上。这里有一个关键技术点图像数据在前后端之间的传输格式。原始图片直接传输耗流量又慢我用的是Base64编码后在JSON体中传输。对于毕设演示来说这个方案简单可靠不需要额外引入文件服务器。但如果图片分辨率很高Base64编码会让数据变大一些需要注意Flask的请求体大小限制我直接把它改成了10MB上限app Flask(__name__) app.config[MAX_CONTENT_LENGTH] 10 * 1024 * 1024对于部署层面的细节我想额外提一点Flask自带的开发服务器只适合本地调试如果要在答辩现场用局域网展示系统最好还是用waitress或者gunicorn这类WSGI服务器来跑。虽然毕设展示只涉及单机演示但提前用生产级服务器可以减少演示时的性能抖动。5. 常见问题与排查技巧实录5.1 识别准确率偏低怎么排查这是我被问得最多的问题也是自己踩过最深的坑。识别准确率提不起来的时候不要盲目去换模型、调参数先按下面这个顺序做系统性排查。第一步检查预处理链路。把测试图片的中间结果全部可视化输出看看对齐后人脸是否居中、有无裁切错误。这一步能排查掉大约三成的假性准确率问题。第二步检查数据分布。用t-SNE把所有注册用户和测试人脸的embedding向量降维可视化。如果发现同一用户的多个embedding在向量空间中分布极度分散说明预处理或者数据质量有问题如果不同用户的embedding大面积重叠说明特征本身的区分度不够。第三步检查阈值选择。很多时候准确率低只是因为阈值定得不合适。用前面提到的阈值扫描方法重新找最优阈值经常能凭空提升好几个百分点。第四步检查光线和图片质量。这个放在最后是因为它最容易被忽视其实影响最大。如果注册照片是室内弱光环境拍的而测试照片是强光下拍的特征向量的偏移会非常大。解决思路是注册时多采集不同光照条件下的照片或者提前用直方图均衡化做统一的亮度校正。不过直方图均衡化对人脸识别的帮助存在争议应用式使用时要小心因为它可能引入新的伪影。5.2 内存占用过高与推理速度优化用PyTorch做推理时有一个经典陷阱默认开启了梯度计算机制。训练时需要梯度来反向传播但推理时不需要。如果不手动关闭梯度模型会在前向传播过程中保留中间变量内存占用翻倍速度也变慢。正确的推理姿势是在模型推理时用torch.no_grad()上下文管理器with torch.no_grad(): embedding model(tensor)这个微小的改动能让推理速度提升将近30%内存占用下降一半。我经常看到新手同学跑推理时内存爆炸十有八九就是忘了关闭梯度。另一个优化点是MTCNN的批量检测能力。如果一次要处理多张图片比如注册流程批量导入照片可以把多张图片合并成一个batch喂给MTCNN它内部支持并行处理效率能提升好几倍。代价是代码复杂度上升需要手动管理batch的大小。5.3 关键技术难点的解决历程在毕设实验过程中最让我头疼的难点是相似脸误识别问题。实验室里有两位同学五官相似度比较高系统频繁把他俩混淆。刚开始我以为是模型不够强后来仔细分析才发现问题出在我的注册策略上——我只采了每人两张正面照片特征向量代表性不足。解决这个问题的过程让我学到了一个重要经验在有限的算力和数据条件下优化数据质量往往比换更强的模型更有效。我增加了一个环节注册时对采集的照片做质量筛选用人脸检测的置信度和图像的清晰度评分来淘汰模糊、遮挡、光线过暗的照片确保每次注册都使用高质量样本。另外我还注意到不同国家、不同地区的面孔特征分布差异较大而预训练模型在训练数据中本身对特定人群的代表性也会影响效果。虽然这个因素在毕设中基本无法改变但如果你要把这套系统部署到特定地区使用最好重新在本地人脸数据上做一轮微调或者至少验证一下当前模型在你所面向的人群上的表现别盲目相信基准测试的指标。5.4 常见问题速查表整理我把毕设期间收集到的典型问题整理了一张速查表方便你排查时直接对照。每个问题后面附带解决思路不一定对每个环境都完全适用但排查方向基本是通用的现象可能原因排查方式解决方案检测不到人脸MTCNN阈值过高降低阈值将置信度阈值从0.9调到0.8检测到大量误检MTCNN阈值过低查看输出提升阈值或增加IOU合并逻辑识别结果总是unknown阈值设置过高跑阈值扫描动态调整相似度阈值不同人的特征很接近预处理未对齐可视化中间结果强化人脸对齐流程同一人的特征差异大光照不一致查看训练集样本注册时多样化采集CPU推理极慢未关梯度计算查看GPU占用使用torch.no_grad()服务内存溢出图片未压缩查看请求体大小Base64传输前压缩图片Web上传大图报错Flask请求限制查看错误日志调整MAX_CONTENT_LENGTH6. 实操中的个人经验与扩展建议整套系统做完之后我实验下来最大的体会就是深度学习项目的难点往往不在某一个单独的模型而在于把所有环节串联成一个完整的、稳定可靠的处理流程。MTCNN、FaceNet这些模型本身是开箱即用的真正拉开差距的是你如何处理数据、如何设定阈值、如何应对各种边缘情况。如果让我重新做一遍这个毕设我会在以下三个方向上做更深一步的探索。第一个方向是轻量化部署。当前系统在GPU上可以做到实时识别但要跑在树莓派、手机这些嵌入式设备上还是太吃力。可以考虑用知识蒸馏或者模型剪枝技术把FaceNet压缩成一个参数量更小的网络。代价是识别精度会有小幅下降但对于低功耗设备来说这个取舍是值得的。第二个方向是活体检测。现在的系统本质上只能判断图片里有没有人脸不能判断这张脸是不是活人的脸。如果拿来做一个真实场景的门禁系统攻击者用一张照片或者一段视频就能轻易通过验证。加入活体检测模块——比如让用户眨眼、转头、张嘴或者分析图片的深度信息——可以让整套系统的安全性上升一个台阶。第三个方向是可增量学习。当前系统在注册新用户之后模型本身并不更新只是数据库里多了一条记录。如果用户的面部特征随时间发生了较大变化发型变了、胖了瘦了识别率就会下降。更优雅的方案是每成功识别一次就把当次的特征向量以一定的权重合并到用户的历史特征里实现特征的动态更新。这个方案在实验中有一定效果但要小心特征漂移的问题不能让长期积累的特征偏离真实人脸太远。最后再分享一个小技巧在答辩演示之前一定要提前准备好一组不同光照、不同角度甚至包括一张戴眼镜照片的测试集。现场演示的时候不要只用同一张照片反复试那会让评委觉得你的系统只是背答案。我当时的做法是准备了八个人共四十张测试照片随机抽取测试既能展示系统的稳健性也能在出现意外时从容应对。人脸识别系统最怕的就是演示时翻车而提前覆盖各种测试场景是最大程度降低翻车概率的手段。这个项目的意义对我来说远不止一个毕业设计的分数。它让我真正理解了从学术模型到工程系统的距离也让我在答辩时能够对每一个技术选型都说出背后的推理过程。如果你也正在做人脸识别方向的毕设希望这篇记录能帮你少走一些弯路。