简介围绕人脸识别、YoloV5、ArcFace与活体检测整合的学习实践包面向希望从理论走向项目实操的算法初学者和开发者重点解决目标检测、人脸特征提取与真伪判别如何在同一流程中衔接的问题。压缩包共54个文件以26个Python源码为主辅以9个pyc、7个yaml配置、2个pth权重、4张示例人脸图片及txt说明等整体约3.4MB内容覆盖模型定义、训练测试、数据生成、推理入口与多模型权重便于按模块阅读和逐步复现。已有179人学习适合用作课程设计、毕业项目预研或算法入门的参考样例。代码与配置相互配套读者可据此理解YoloV5负责人脸检测、ArcFace提取区分度更高的特征、活体检测模型判别照片视频等攻击样本的协作方式并借助训练、测试和预测脚本完成从数据准备到结果验证的闭环积累可迁移的工程排错、参数调整与模型调试经验。1. 需求拆解人脸识别项目为什么不能只做一个模型带课程设计时见过一种典型情况A 同学把一个人脸识别模型跑通准确率表也填好了现场摄像头一开就翻车——脸没框准、侧脸特征飘、照片贴在屏幕前也能过。问题不在识别模型本身而在这类项目天然是“检测 对齐 特征比对 活体判别”的组合题。这个压缩包围绕 YOLOv5 做人脸检测、ArcFace 做特征空间、活体检测做防伪把三段能力串成一条可演示、可验收的流水线。它适合正在做人脸识别课程设计、毕设或入门实践的人也适合需要快速搭一套可改可测的工程骨架的开发者。先别急着调识别阈值先把整条链路的输入输出边界定清楚后面才有的放矢。2. 总体架构设计检测、对齐、识别、活体四段流水线人脸识别项目最容易写成“一个模型包打天下”结果代码越堆越乱。更稳妥的做法是把整条链路拆成四段检测负责人脸在哪里对齐负责人脸朝向标准化识别负责输出特征并比对底库活体负责判断当前人脸是不是真实在场。四段之间用固定格式的数据结构传递结果任何一段替换模型都不影响其他段。这样做的直接好处是排查问题时有边界框不准看检测特征不像看对齐和识别照片能过看活体而不是所有问题都归咎于“模型不行”。2.1 四段式流水线的职责边界检测段输入原始帧输出人脸框和置信度对齐段输入人脸框和关键点输出 112×112 的标准人脸识别段输入标准人脸输出归一化特征向量再与底库做余弦相似度比对活体段输入连续多帧的人脸区域输出真伪分数或动作通过标记。四段之间不要互相“猜”检测段不负责判断是谁识别段不负责判断真假活体段也不负责纠正人脸框。很多现场翻车来自职责越界比如识别模型顺手做了检测结果小脸漏检活体模块顺手拿了未对齐的人脸纹理分数被背景干扰。模块输入输出主要失败表现人脸检测原始帧人脸框、置信度、五点关键点漏检、框偏、侧脸框不全人脸对齐人脸框、关键点112×112 对齐人脸眼睛不水平、嘴巴偏移特征识别对齐人脸512 维归一化特征同人相似度低、不同人相似度高活体判别连续帧人脸区域真伪分数、动作通过标记照片通过、真人误杀这张表可以直接作为答辩时的模块说明。每个模块只回答一个问题验收时也按这个边界拆指标。检测看召回率和框的 IoU对齐看关键点误差识别看同人/不同人相似度分布活体看攻击样本通过率和真人误杀率。不要用一个“综合准确率”糊住所有问题那样后期改一个阈值就会牵一发动全身。2.2 模型选型与输入输出约定YOLOv5 在人脸检测上的优势是工程资料多、导出 ONNX 后部署简单适合课程设计这种需要“能跑起来、能改得动”的场景。它的输出格式在不同导出方式下会有差异常见的是[1, N, 5cls]其中前四个是中心点宽高第五个是目标置信度。实际落地时不要直接在代码里假设输出维度先打印一次输出形状再写后处理。ArcFace 这边通常用 112×112 输入、512 维输出特征是归一化后的向量比对用余弦相似度。活体检测不要一上来就上大模型先用多帧动作配合纹理分数能把“照片贴在镜头前”这类低成本攻击挡掉再考虑更复杂的方案。输入输出约定要写进配置而不是散落在代码里。下面这份配置把检测阈值、识别阈值、活体阈值和输入尺寸集中管理现场调参时只改配置不改业务代码。# config/pipeline.yaml detector: model_path: models/face_det.onnx input_size: 640 conf_thres: 0.35 iou_thres: 0.45 align: output_size: 112 landmark_num: 5 recognizer: model_path: models/arcface.onnx input_size: 112 embedding_dim: 512 match_thres: 0.45 liveness: mode: blink_and_texture ear_thres: 0.19 ear_consec_frames: 2 texture_thres: 0.55 timeout_sec: 5 runtime: video_source: 0 frame_skip: 0 draw_result: trueconf_thres控制检测框的最低置信度调低会提高召回但引入误框match_thres是余弦相似度阈值不是百分比别和准确率混在一起讲ear_thres是眼睛纵横比阈值和摄像头距离、分辨率相关换设备要重新标timeout_sec是活体动作的超时时间太短会误杀配合慢的人太长会让演示节奏拖沓。配置集中之后答辩现场换一台机器只需要改video_source和少量阈值不用翻代码找魔法数字。2.3 工程目录与配置项目录结构按“数据、模型、配置、业务、脚本”分开答辩时打开目录就能说清工程组织。下面这套结构不依赖具体框架换成 PyQt、Flask 或命令行演示都能套。face_pipeline/ ├── config/ │ └── pipeline.yaml ├── data/ │ ├── raw/ │ ├── aligned/ │ └── gallery/ ├── models/ │ ├── face_det.onnx │ └── arcface.onnx ├── src/ │ ├── detector.py │ ├── aligner.py │ ├── recognizer.py │ ├── liveness.py │ └── pipeline.py ├── scripts/ │ ├── export_onnx.py │ ├── build_gallery.py │ └── demo.py └── tests/ └── test_pipeline.pydata/gallery放底库人脸每个人一个子目录目录名就是标签data/aligned放对齐后的人脸训练和测试都从这里取src/pipeline.py只做流程编排不写模型细节scripts/build_gallery.py负责把底库图片批量转成特征文件。这里有个血泪经验底库特征不要每次启动都重新算离线生成gallery.npz启动时直接加载否则演示时摄像头还没开程序先卡在特征提取上。目录和配置定好之后后面加活体动作、换检测模型、改比对策略都只是局部改动。3. 数据与训练方案从人脸检测标注到 ArcFace 特征空间人脸识别项目的效果上限很大程度上由数据决定但课程设计不需要一上来就搞大规模采集。更现实的做法是先把检测数据和识别数据分开准备检测数据重标注质量和难样本覆盖识别数据重身份数量和类内变化。ArcFace 的训练目标不是“分类到某个人”而是让同身份特征靠拢、不同身份特征拉开。这个目标决定了数据构造方式每个人要有足够多的角度、光照和表情变化否则模型学到的只是“这张图长这样”换一个摄像头就失效。3.1 检测数据标注与难样本补充检测标注用矩形框加五点关键点框要贴紧人脸边缘不要把脖子和背景框进去。关键点顺序固定为左眼、右眼、鼻尖、左嘴角、右嘴角后续对齐代码按这个顺序取点。难样本重点补四类侧脸、遮挡、小脸、逆光。侧脸样本不够模型会把正脸框得很紧侧脸框一半遮挡样本不够口罩或手势一挡就漏检小脸样本不够远距离人脸召回率会明显掉逆光样本不够暗部人脸置信度会低到阈值以下。补充时不要只加图片要同步检查标注框是否仍然贴边。下面这段脚本用来检查标注框尺寸分布提前发现“全是大人脸”的问题。width_ratio和height_ratio是框宽高相对图像宽高的比例小脸样本通常低于 0.08。统计出来如果小脸占比太低就要补采或做随机缩放增强。import json from pathlib import Path from collections import Counter def check_face_scale(label_dir, bins(0.02, 0.05, 0.08, 0.15, 0.30, 1.0)): counter Counter() for txt in Path(label_dir).glob(*.txt): for line in txt.read_text().splitlines(): parts line.strip().split() if len(parts) 5: continue # YOLO 格式cls cx cy w h这里取宽高比例 w, h float(parts[3]), float(parts[4]) scale max(w, h) for i in range(len(bins) - 1): if bins[i] scale bins[i 1]: counter[f{bins[i]}-{bins[i1]}] 1 break return counter print(check_face_scale(data/raw/labels))bins按人脸框最大边占图像边长的比例分档0.02-0.05和0.05-0.08基本对应远距离小脸。统计结果如果集中在0.15以上模型在远距离场景就会吃力。补充样本时优先补小脸和侧脸不要重复补正脸大头照那样指标好看但现场没用。标注完成后随机抽 30 张画框检查重点看框有没有切掉下巴、有没有把耳朵框丢这些细节会直接影响对齐关键点的稳定性。3.2 人脸对齐与识别数据集构造识别数据按身份组织每个人一个文件夹文件夹内放不同角度、光照、表情的图片。数量不需要追求特别大但类内变化要够。课程设计常见问题是每个人只放两三张正脸照训练集和测试集又来自同一批图指标虚高。更稳妥的划分是每个人取一部分做训练留一部分做测试测试集里至少包含一张没参与训练的角度或光照图。底库和测试集也要分开底库用注册照测试用现场抓拍风格图这样比对阈值才有参考价值。对齐用五点仿射变换把眼睛、鼻尖、嘴角映射到固定坐标。对齐后的图统一成 112×112人脸尽量居中眼睛大致在同一水平线。对齐代码不要用整张图直接缩放要先裁人脸框并留一点边距再估计变换矩阵。边距太小会切掉下巴太大又会让背景占比过高。通常的做法是人脸框向外扩 10% 到 20%再做关键点对齐。import cv2 import numpy as np # ArcFace 常用的 112x112 五点目标坐标 SRC_POINTS np.array([ [38.2946, 51.6963], # 左眼 [73.5318, 51.5014], # 右眼 [56.0252, 71.7366], # 鼻尖 [41.5493, 92.3655], # 左嘴角 [70.7299, 92.2041], # 右嘴角 ], dtypenp.float32) def align_face(image, landmarks, output_size112, margin0.15): h, w image.shape[:2] pts np.array(landmarks, dtypenp.float32) x1, y1 pts.min(axis0) x2, y2 pts.max(axis0) bw, bh x2 - x1, y2 - y1 # 根据关键点范围扩边避免裁掉下巴和额头 x1 max(0, int(x1 - bw * margin)) y1 max(0, int(y1 - bh * margin)) x2 min(w, int(x2 bw * margin)) y2 min(h, int(y2 bh * margin)) crop image[y1:y2, x1:x2].copy() pts[:, 0] - x1 pts[:, 1] - y1 # 用部分仿射估计降低个别关键点抖动的影响 M, _ cv2.estimateAffinePartial2D(pts, SRC_POINTS, methodcv2.LMEDS) aligned cv2.warpAffine(crop, M, (output_size, output_size), borderValue0) return alignedmargin控制扩边比例侧脸或遮挡时关键点范围偏小可以适当调大到 0.2estimateAffinePartial2D只估计旋转、缩放和平移不引入透视形变适合稳定场景。对齐后的图要抽查重点看眼睛是否水平、嘴巴是否居中。如果对齐图里人脸明显偏左或偏右先查关键点顺序再查扩边是否把一侧裁掉了。识别模型对对齐很敏感对齐歪了后面调阈值都是玄学。3.3 ArcFace 训练参数与损失函数ArcFace 的核心是在分类损失里加入角度间隔让同类特征更紧凑。训练时 backbone 输出特征向量接一个带权重的全连接层对权重和特征做归一化再加入角度间隔m。课程设计不需要从头训一个大 backbone可以用轻量网络加预训练权重冻结前几层只训最后阶段。学习率不要设太大ArcFace 对学习率比较敏感太大容易震荡太小收敛慢。批量大小受显存限制时可以用梯度累积凑等效批量。参数建议范围说明输入尺寸112×112与对齐输出保持一致特征维度256 或 512课程设计 256 足够底库大再升角度间隔 m0.3 到 0.5太大难收敛太小区分度不足学习率0.01 到 0.1配合 warmup 和余弦衰减批量大小32 到 128不够时用梯度累积训练轮次20 到 60看损失和验证集相似度分布训练循环里要打印同人相似度和不同人相似度的均值不要只看分类损失。分类损失下降但相似度分布重叠说明间隔没起到作用。下面这段是训练关键片段arcface_head内部做权重归一化和角度间隔ce_loss是交叉熵。实际实现时注意数值稳定性余弦值要 clamp 到合法范围。import torch import torch.nn.functional as F def train_one_epoch(backbone, arcface_head, loader, optimizer, device): backbone.train() arcface_head.train() total_loss 0.0 for images, labels in loader: images images.to(device) labels labels.to(device) embeddings backbone(images) # arcface_head 内部完成归一化、角度间隔和 logits 计算 logits arcface_head(embeddings, labels) loss F.cross_entropy(logits, labels) optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() * images.size(0) return total_loss / len(loader.dataset)backbone输出的是未归一化特征arcface_head里再做 L2 归一化labels是身份编号不是检测框类别loss只反映分类间隔验收时还要单独算同人/不同人相似度。训练集身份数和每类图片数要平衡某个身份图片特别多会把特征空间往那一类拉。验证集按身份划分不要随机打散图片否则同一个人同时出现在训练和验证里指标会虚高。训练完成后把 backbone 导出成 ONNX只保留特征提取部分分类头不带走。4. 工程实现视频流推理、特征比对与活体判别工程实现阶段最容易出现“训练代码能跑演示代码一堆硬编码”的问题。更稳的写法是把检测、对齐、识别、活体各自封装成类pipeline.py只负责按帧调度。视频流读取用 OpenCV检测结果先做 NMS再按面积和置信度排序取最大人脸做识别小脸或低置信度框直接跳过。底库特征离线生成运行时只做矩阵乘法比对。活体判别不要单帧下结论用连续帧的动作配合和纹理分数融合减少单帧噪声导致的误判。4.1 YOLOv5 检测封装与预处理YOLOv5 导出 ONNX 后预处理要保持和训练一致letterbox 缩放、填充、BGR 转 RGB、归一化、HWC 转 CHW。letterbox 的作用是保持长宽比避免人脸被拉伸。后处理要把框映射回原图坐标再做 NMS。不同导出方式的输出维度不一样有的带 objectness有的已经合并代码里先看output.shape再决定解析方式。下面这段是检测封装的核心postprocess按常见[1, N, 5cls]写实际模型输出不同时只改这一段。import cv2 import numpy as np import onnxruntime as ort class FaceDetector: def __init__(self, model_path, input_size640, conf_thres0.35, iou_thres0.45): self.session ort.InferenceSession(model_path, providers[CPUExecutionProvider]) self.input_name self.session.get_inputs()[0].name self.input_size input_size self.conf_thres conf_thres self.iou_thres iou_thres def preprocess(self, frame): h, w frame.shape[:2] scale min(self.input_size / w, self.input_size / h) nw, nh int(w * scale), int(h * scale) resized cv2.resize(frame, (nw, nh)) canvas np.full((self.input_size, self.input_size, 3), 114, dtypenp.uint8) x_off (self.input_size - nw) // 2 y_off (self.input_size - nh) // 2 canvas[y_off:y_off nh, x_off:x_off nw] resized blob canvas[:, :, ::-1].transpose(2, 0, 1).astype(np.float32) / 255.0 return np.expand_dims(blob, 0), scale, x_off, y_off def postprocess(self, output, scale, x_off, y_off, orig_shape): preds output[0] boxes [] for p in preds: conf float(p[4]) if conf self.conf_thres: continue cx, cy, bw, bh map(float, p[:4]) x1 (cx - bw / 2 - x_off) / scale y1 (cy - bh / 2 - y_off) / scale x2 (cx bw / 2 - x_off) / scale y2 (cy bh / 2 - y_off) / scale h, w orig_shape boxes.append([max(0, x1), max(0, y1), min(w, x2), min(h, y2), conf]) return boxesinput_size通常用 640显存或 CPU 吃紧可以降到 416但小脸召回会下降conf_thres调低会保留更多框后面按面积取最大人脸时要防小框干扰x_off和y_off是 letterbox 的填充偏移映射回原图时必须减掉。检测结果不要直接送识别先按框面积过滤掉过小脸再按置信度排序。侧脸或遮挡时关键点可能落在框外对齐前要把关键点裁剪到图像范围内。4.2 ArcFace 特征提取与相似度比对识别段只做两件事把对齐人脸变成归一化特征和底库特征做余弦相似度。底库用gallery.npz存两个数组features和labels。启动时加载运行时算features emb取最大值和对应标签。相似度阈值不要拍脑袋定用验证集画同人/不同人分布取等错误率附近的值再根据业务偏保守或偏宽松微调。下面这段是特征提取和比对的核心encode输出单位向量cosine_sim直接点积。import numpy as np import onnxruntime as ort class ArcFaceEncoder: def __init__(self, model_path, input_size112): self.session ort.InferenceSession(model_path, providers[CPUExecutionProvider]) self.input_name self.session.get_inputs()[0].name self.input_size input_size def encode(self, aligned_face): # aligned_face 为 BGRArcFace 常用 RGB 输入 blob aligned_face[:, :, ::-1].astype(np.float32) blob (blob - 127.5) / 127.5 blob blob.transpose(2, 0, 1)[None] emb self.session.run(None, {self.input_name: blob})[0][0] return emb / (np.linalg.norm(emb) 1e-12) def match_gallery(emb, gallery_features, gallery_labels, thres0.45): sims gallery_features emb idx int(np.argmax(sims)) best_sim float(sims[idx]) if best_sim thres: return unknown, best_sim return str(gallery_labels[idx]), best_simencode里的归一化参数要和训练时一致训练用(x-127.5)/127.5推理就不能改成(x/255-mean)/stdgallery_features必须是归一化后的矩阵否则点积不是余弦相似度thres是余弦阈值0.45 只是课程设计常见起点实际要用验证集重新定。底库标签建议用字符串编号不要用真实姓名演示时更安全。比对结果同时保留相似度分数答辩时能解释“为什么判成这个人”而不是只给一个标签。4.3 活体检测的多帧策略活体检测分两层动作配合层判断“有没有按要求动”纹理层判断“人脸区域像不像真实皮肤”。动作层用眼睛纵横比做眨眼检测连续帧低于阈值再恢复记一次眨眼纹理层可以用简单的手工特征或轻量分类模型输出真伪分数。两层都过才判真人单层过容易误判。多帧策略的关键是时间窗口太短抓不到动作太长演示卡顿。通常 5 秒窗口、每 2 帧检测一次足够覆盖一次眨眼。import numpy as np def eye_aspect_ratio(landmarks): # landmarks 为 68 点36:42 左眼42:48 右眼 left np.array(landmarks[36:42], dtypenp.float32) right np.array(landmarks[42:48], dtypenp.float32) def ear(eye): v1 np.linalg.norm(eye[1] - eye[5]) v2 np.linalg.norm(eye[2] - eye[4]) h np.linalg.norm(eye[0] - eye[3]) return (v1 v2) / (2.0 * h 1e-6) return (ear(left) ear(right)) / 2.0 def liveness_by_blink(ear_values, ear_thres0.19, consec2): closed 0 blinked False for ear in ear_values: if ear ear_thres: closed 1 elif closed consec: blinked True closed 0 else: closed 0 return blinkedear_thres和摄像头距离强相关远距离人脸眼睛小EAR 整体偏低阈值要相应下调consec是连续闭眼帧数太小会被噪声触发太大可能漏掉快速眨眼。纹理分数要和动作分数融合不要简单相加建议动作通过后再看纹理分动作不通过直接拒绝。活体检测不要只依赖一个动作照片可以打印眼睛睁开图但连续眨眼和纹理分数同时满足就难得多。演示时先让用户正视镜头再提示眨眼成功和失败都要有明确界面反馈否则现场不知道卡在哪一步。5. 避坑与排查活体误杀、阈值漂移和跨场景泛化这条链路真正难的不是把模型跑通而是换一台机器、换一个光照、换一个人结果还可信。下面四类问题在课程设计和入门项目里出现频率很高每类都按“现象、原因、解决”拆开写。排查时先定位属于哪一段不要一上来就重训模型。很多时候只是预处理不一致、阈值没重标、底库特征没归一化改几行代码比重新训练有效得多。5.1 活体误杀与光照干扰现象真人正对镜头动作也做了系统仍然提示“非活体”换到背光或侧光位置更明显。原因通常有两个眼睛纵横比阈值按某个固定距离标定换摄像头或用户离得远EAR 整体偏低眨眼检测不到纹理分数在暗光下偏低被误判成攻击。另一个常见原因是动作超时太短用户还没完成眨眼窗口已经关闭。解决时先把 EAR 阈值按当前设备重新标一遍让用户正常睁眼和闭眼各采一段数据取闭眼上限和睁眼下限的中间值纹理分数不要用固定阈值按光照均值做分段阈值动作窗口从 5 秒放宽到 8 秒并给出倒计时提示。提示活体阈值和摄像头强相关换设备必须重新标不要直接复制上一台机器的配置。排查时把每帧的 EAR、纹理分数、动作状态打印到日志复现一次失败过程。如果 EAR 曲线根本没有下降说明关键点或眼睛区域取错了如果 EAR 下降了但没被计数看连续帧数阈值如果动作通过但纹理分低看光照和图像质量。活体误杀比漏放更影响演示体验课程设计里宁可稍微放宽纹理阈值也要保证真人能过但要在报告里说明攻击样本通过率不能只报真人通过率。5.2 识别阈值漂移与底库管理现象同一个人今天能识别明天相似度掉到阈值以下或者不同人之间相似度异常高。原因常见于底库特征没有归一化、底库图片和现场图质量差异大、阈值在不同测试集上反复手调。还有一种情况是底库标签重复同一个人注册了两条特征比对时取到较差的那条。解决时统一在入库前做归一化底库特征保存前检查norm是否接近 1每个人只保留一条主特征多张注册照取平均后再归一化阈值用验证集分布定不要用演示现场临时试出来的值。import numpy as np def build_gallery(encoder, image_paths, labels): features, valid_labels [], [] for path, label in zip(image_paths, labels): face load_and_align(path) # 外部对齐函数输出 112x112 emb encoder.encode(face) norm np.linalg.norm(emb) if norm 1e-6: continue features.append(emb / norm) valid_labels.append(label) features np.stack(features).astype(np.float32) return {features: features, labels: np.array(valid_labels)}build_gallery里做二次归一化防止编码器输出异常向量valid_labels和features必须一一对应顺序错了会把 A 判成 B底库更新后要重新评估阈值不能沿用旧阈值。底库规模变大时余弦相似度的最大值可能来自错误身份建议加一个“第二名差距”检查第一名和第二名相似度太接近时判 unknown减少误认。底库不要放真实姓名用编号或昵称报告和演示都更安全。5.3 检测漏检与遮挡场景现象正脸能检测侧脸、低头、口罩、手势遮挡时框不出来或者框出来了但关键点偏移对齐图歪。原因通常是训练数据里难样本不足检测阈值又设得偏高另一种是 letterbox 后小脸被缩得太小置信度掉到阈值以下。解决时先补侧脸、遮挡、小脸样本再适当降低conf_thres同时用面积过滤去掉过小误框关键点偏移要检查标注顺序和扩边比例侧脸时关键点范围小扩边比例可以调到 0.2。检测漏检不要只看整体召回率要按人脸尺寸和姿态分桶统计。小脸桶召回低就补小脸数据或提高输入尺寸侧脸桶低就补侧脸数据。遮挡场景可以加一个“可见关键点置信度”过滤关键点置信度太低时不做识别直接提示“请正对镜头”。现场演示时用户低头看屏幕导致漏检很常见界面要给明确引导而不是让用户猜。5.4 推理性能与资源占用现象摄像头帧率低、画面卡顿检测和识别串行执行CPU 占用高。原因常见于每帧都跑检测和识别、模型输入尺寸过大、ONNX Runtime 线程数没配、底库比对用了 Python 循环。解决时先做帧跳过检测每 2 帧一次识别只在检测到稳定人脸后触发输入尺寸从 640 降到 416 或 320看召回是否可接受ONNX Runtime 设置intra_op_num_threads不要让它占满所有核底库比对用矩阵乘法不要逐条算余弦。现象先查什么常见处理帧率低检测输入尺寸、是否每帧识别跳帧、降尺寸、识别降频CPU 占满线程数、是否重复加载模型限制线程、模型单例内存上涨是否每帧创建 sessionsession 初始化一次首帧慢底库是否在线提取离线生成 gallery.npz性能排查先看时间花在哪一段检测、对齐、识别、活体各打一个时间戳。通常检测占大头识别次之对齐和活体很轻。不要为了帧率把识别也跳掉识别可以每 3 到 5 帧一次检测不能完全停否则人脸移动后框跟不上。课程设计演示机性能有限提前把模型导出成 ONNX 并开好线程比现场装环境可靠得多。6. 验收与答辩指标表、演示脚本和现场问答验收不要只报一个“识别准确率”要把四段链路的指标拆开。检测看召回率和框的 IoU对齐看关键点误差和肉眼抽查识别看同人/不同人相似度分布与等错误率活体看真人通过率和照片攻击拒绝率。测试集按身份划分底库和测试集不重叠现场抓拍风格图要单独留一组。答辩时先讲架构图再讲每段指标最后现场跑一遍成功和失败案例。失败案例比成功案例更能说明你理解边界。验收项指标课程设计可接受范围人脸检测召回率 / IoU0.5召回 0.9 以上IoU 0.5 以上人脸对齐关键点肉眼抽查眼睛水平嘴巴居中特征识别同人相似度均值明显高于不同人均值识别阈值验证集等错误率附近按业务偏保守取值活体检测真人通过率 / 照片拒绝率真人 0.9 以上照片拒绝 0.8 以上演示脚本提前写成固定流程启动后先加载底库显示“请正对镜头”检测到人脸后画框提示“请眨眼”活体通过后显示识别结果和相似度。准备两张测试图一张底库内的人一张底库外的人再加一张手机屏幕照片做攻击样本。每步失败都要有提示不要只黑屏。答辩问答常问“阈值怎么定的”“换摄像头还行不行”“照片能不能过”回答时把测试集和阈值来源讲清楚比堆模型名称有用。从那以后我每次带这类项目都强制先跑一遍“换设备、换光照、换人”的三换测试再谈模型优不优化。这套压缩包里的目录、配置和脚本可以按第 2 章的模块划分对照看先跑通检测和对齐再加识别和活体顺序别反。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?