资讯中心

人脸识别门禁系统实战:FaceNet+RetinaFace检测与特征提取全解析

📅 2026/9/28 17:03:04
人脸识别门禁系统实战:FaceNet+RetinaFace检测与特征提取全解析
简介这是一份面向毕业设计、课程设计场景的FaceNetRetinaFace人脸识别管理系统完整项目包采用PythonJavaVue技术栈适合计算机相关专业学生、Python开发者以及想要快速搭建人脸识别原型的研究者系统覆盖人脸检测、特征提取、特征库存储、身份比对等闭环流程可用于门禁管理、课堂签到、安防监控等实际场景。资源包为ZIP格式共646个文件大小57.39MB其中242张PNG图片与22张JPG图片构成界面素材30个GIF动图为演示截图172个Java、64个Vue、34个JS及7个CSS文件组成前后端工程18个Python脚本用于模型加载与识别流程另有XML配置、JSON数据、SQL数据库脚本、PTH模型权重、MP4演示视频等目录按功能模块划分便于定位源码、样式、文档与数据集。已有448人学习下载压缩包内提供可运行的完整源码包含前端页面、后端接口、人脸检测与识别算法、模型参数以及数据库建表脚本通过GIF和MP4演示可直观了解运行流程结合代码注释还能学习锚框策略、三元组损失、欧氏距离阈值设定等细节便于二次开发快速完成毕设或课设。1. 门禁机背后那套 FaceNetRetinaFace检测要快、识别要稳去楼下看一眼现在的人脸识别门禁机镜头里人脸一靠近屏幕闪一下门就开了。这个动作背后不是一个人脸识别模型套到底而是两个网络各管一段RetinaFace 管人在哪、眼睛鼻子在哪FaceNet 管把对齐后的脸压成 128 维向量再跟数据库里的特征比远近。做人脸识别门禁系统设计或课程设计时最容易翻车的地方就是把检测和识别混成同一个模型去调参。这套笔记按门禁流程把两个网络拆开讲怎么跑通、参数怎么设、管理系统的表怎么建以及答辩前一定要补的验证实验。2. RetinaFace 做检测为什么门禁要用专门的检测网络做人脸识别系统第一步不是“认人”而是“找人”。RetinaFace 在整套 pipeline 里负责把画面里的人脸先框出来同时给出眼睛、鼻子、嘴角五个关键点的坐标。这两个输出一个给后面的 FaceNet 当输入一个给门禁系统做“人是否正对镜头”的判断依据。2.1 为什么检测和识别必须分开而不是一个模型全包很多初学者会问能不能用一个网络同时完成“检测 识别”技术上可行但工程上没人这么干。原因很简单检测目标和识别目标冲突。检测要的是“不管是谁只要是脸就给我框出来”识别要的是“把这张脸和库里某个人区分开”。硬绑在一个模型里训练数据既要框的标注又要身份的标注数据量翻几倍训练难度也翻几倍最后效果还未必比两个模型串联好。另一个更实际的原因是维护。系统上线后发现某个角度检不出来你要单独换检测模型发现双胞胎分不清你要单独换特征模型。两个模块独立升级改一个不用动另一个排查问题也容易定位——比对结果不对先查特征检测结果不对先查 RetinaFace互不甩锅。我用过一段时间端到端方案后来换回检测识别的两段式维护成本低得多。所以这个系统的架构很朴素摄像头采集画面RetinaFace 逐帧检测并输出关键点关键点把脸“摆正”摆正后的图送入 FaceNet 生成向量向量去和数据库里注册过的特征算距离距离小于阈值就开门。看清楚这条链子后面每一步踩坑你都能知道该怪谁。2.2 RetinaFace 的网络设计多任务头怎么提升准召RetinaFace 是 2019 年提出来的检测网络它的核心思路是在 RetinaNet 的基础上加了一组人脸专属的多任务头。主干网络可以用 ResNet也可以用 MobileNet 这样的轻量骨干然后接特征金字塔把不同尺度的特征都拿来做预测。人脸有大有小站在门禁机前 30 厘米和站 1 米脸在画面里的尺寸差很多特征金字塔能保证大小脸都有对应的特征层去检测。它对每个候选框预测三样东西人脸置信度、边框位置、五个关键点位置。有的变体还会额外预测密集人脸像素信息用来提高难例的召回率。五个关键点这个输出很关键它比单纯一个框有用得多。因为框只能告诉你“这里有一团像脸的东西”但两眼之间的距离和角度能给你“这张脸是正着还是歪着的”这个几何信息。FaceNet 对输入图的姿态很敏感脸歪着送进去特征会明显偏离注册时的样子。在工程里我一般把多任务头理解为四个输出分支实际推理时取前三类就够用任务头输出维度在门禁系统里的作用人脸分类2人脸/背景筛掉误检决定是否触发识别边框回归4x, y, w, h画出人脸区域供显示和后续裁切五点回归10两眼的坐标等用于仿射对齐把歪脸转正密集回归可选逐像素分类回归提升困难样本召回部署可裁剪掉这个设计让 RetinaFace 在 CPU 上也能跑得动因为不是所有任务头都要参与最终推理。做课程设计时你完全可以直接用公开权重不必从零训练后面会讲怎么加载。2.3 跑通 RetinaFace 的最小推理代码ONNX Runtime 推理与五点对齐我从不在训练上折腾 RetinaFace而是直接拿公开的 ONNX 权重做推理。下面这段代码只依赖 onnxruntime、OpenCV 和 NumPy写清楚每一步在干什么。# face_detector.py import cv2 import numpy as np import onnxruntime as ort class RetinaFaceDetector: def __init__(self, onnx_pathretinaface.onnx, input_size(640, 640)): self.sess ort.InferenceSession(onnx_path, providers[CPUExecutionProvider]) self.input_size input_size self.input_name self.sess.get_inputs()[0].name def preprocess(self, img_bgr): # 保持宽高比缩放后填充到 input_size避免把脸拉变形 h, w img_bgr.shape[:2] scale min(self.input_size[0] / h, self.input_size[1] / w) new_h, new_w int(h * scale), int(w * scale) resized cv2.resize(img_bgr, (new_w, new_h)) canvas np.zeros((self.input_size[0], self.input_size[1], 3), dtypenp.uint8) canvas[:new_h, :new_w] resized # RetinaFace 训练时图像归一化到 [-1, 1] tensor canvas.astype(np.float32) tensor (tensor - 127.5) / 127.5 return tensor.transpose(2, 0, 1)[None, ...], scale def detect(self, img_bgr, box_threshold0.5): tensor, scale self.preprocess(img_bgr) outs self.sess.run(None, {self.input_name: tensor}) # outs 通常是 [bboxes, landmarks, scores]顺序随模型导出不同而变先打印 shape 确认 bboxes, landmarks, scores outs[0], outs[1], outs[2] valid scores[0, :, 0] box_threshold boxes_abs bboxes[0, valid] / scale lms landmarks[0, valid].reshape(-1, 10) / scale return boxes_abs, lms代码逻辑说明preprocess 里先算缩放比例再把图像等比缩放后填充到方形画布这一步是为了让 RetinaFace 输入尺寸固定同时不破坏人脸比例。直接暴力 resize 到正方形会把圆脸拉成长脸后面 FaceNet 提取的特征会漂。detect 里的阈值参数 box_threshold 控制的是“什么样算一张脸”0.5 是保守值门禁场景建议提到 0.8宁可少检一次也不要拿个背景板去刷脸。参数说明input_size(640, 640) 是效果和速度的平衡点。输出坐标之所以要除以 scale是因为网络看到的是填充后的 640×640 图像检测出的坐标要还原回原始图像分辨率才准确。如果发现输出 shape 不一致常见原因是导出的模型把 landmark 和 score 的顺序调换了跑一次打印各输出的 shape 就能确认。2.4 门禁场景的输入尺寸和 CPU 取舍做课程设计你大概率没有 GPU推理要跑在 CPU 上。RetinaFace 的输入尺寸直接决定 CPU 能不能扛得住。640×640 在四核 CPU 上单帧大约 40~80 毫秒只做检测还能接受加上后面 FaceNet 的耗时整体帧率会掉到 10 帧以下。320×320 分辨率能把检测压到 15~30 毫秒小脸召回率会掉一点但门禁场景人脸本来就靠近镜头半米到一米距离内脸不会太小所以损失不明显。输入尺寸单帧推理耗时参考CPU适用场景640×64040~80ms追求召回率后台服务器处理320×32015~30ms树莓派、PC 上的门禁原型160×1605~10ms移动端或扩展板误检明显增多如果你看到的是基于 stm32 的人脸识别门禁系统设计那要提前说明白RetinaFace 在单片机上跑不起来这类方案通常外接串口摄像头模组由模组内部完成检测和识别。用 k10 行空板这类能跑 Python 的开发板320 分辨率还能试一试再低就基本检不准了。选型时先确认自己手头的硬件属于哪一类再决定用不用 RetinaFace 原始权重。3. FaceNet 做特征把一张脸变成一组可比的数字检测做完接下来才是人脸识别系统的重头戏特征提取。FaceNet 拿一张对齐后的人脸图输出一个固定长度的向量。这里的关键不是“生成了什么”而是“怎么让同一个人的向量始终相近、不同人的向量始终远离”。这背后是三元组损失。3.1 三元组损失到底训练的是什么FaceNet 的训练数据由三元组构成一张锚图Anchor、一张同人的图Positive、一张不同人的图Negative。训练目标是让锚图和正样本的距离小于锚图和负样本的距离并且留出一个间隔 margin。公式上就是让 d(A, P) margin d(A, N)。这个 margin 控制的是“不同人之间要靠得多远才算合格”太大了训练难收敛太小了特征会挤在一起。真正影响训练效果的是三元组的采样策略。随机抽样拿到的三元组大部分里面负样本离锚图很远模型根本不需要学就能满足条件loss 很快就变成 0这就是常说的模型“躺平”。半难样本挖掘的做法是在每个 batch 里找那些“虽然比正样本远但远得不多”的负样本去训练让模型始终在面对难区分的人脸。这个细节是做课程设计时经常被忽略的答辩老师如果问到训练策略你能答出半难样本挖掘就说明真看懂了 FaceNet 的原理。FaceNet 原始论文用的是 128 维输出最后接了一个 L2 归一化层让向量都落在单位球面上。这样算欧氏距离时数值范围是固定的不会因为某张图太亮或太暗把向量拉得特别长。实际部署时我一般保留 L2 归一化这一步后面所有阈值调试都是在这个前提下进行的。3.2 用对齐后的脸生成 128 维向量从关键点到特征RetinaFace 给出的五个关键点在这里派上用场。直接把人脸框裁下来送进 FaceNet 是常见错误因为框里总带点头发背景头稍微歪一点裁出来的东西差别很大。正确做法是用关键点做仿射变换把两只眼转到水平再裁出固定大小的区域。这个操作决定了特征质量值不值得做看一组数据就知道不正的脸送进去同一个人两次识别的向量距离可能超过 0.8而正脸对齐后往往在 0.5 以内。# face_embedder.py import cv2 import numpy as np import onnxruntime as ort class FaceNetEmbedder: def __init__(self, onnx_pathfacenet.onnx, input_size(160, 160)): self.sess ort.InferenceSession(onnx_path, providers[CPUExecutionProvider]) self.input_size input_size def align_crop(self, img_bgr, landmarks): # landmarks: [left_eye_x, left_eye_y, right_eye_x, right_eye_y, ...] left_eye landmarks[0:2] right_eye landmarks[2:4] dx, dy right_eye - left_eye angle np.degrees(np.arctan2(dy, dx)) M cv2.getRotationMatrix2D(tuple(left_eye.astype(float)), angle, 1.0) rotated cv2.warpAffine(img_bgr, M, (img_bgr.shape[1], img_bgr.shape[0])) # 以两眼中心为基准裁 2 倍眼距的正方形区域 eye_center (left_eye right_eye) / 2 distance np.linalg.norm(right_eye - left_eye) size int(distance * 2.0) x0 int(eye_center[0] - size / 2) y0 int(eye_center[1] - size / 2) crop rotated[max(y0, 0): y0 size, max(x0, 0): x0 size] crop cv2.resize(crop, self.input_size) tensor crop.astype(np.float32) tensor (tensor - 127.5) / 127.5 return tensor.transpose(2, 0, 1)[None, ...] def embed(self, img_bgr, landmarks): aligned self.align_crop(img_bgr, landmarks) outputs self.sess.run(None, {input: aligned}) emb outputs[0][0] norm np.linalg.norm(emb) return emb / norm # 保留 L2 归一化逻辑说明align_crop 先用两眼坐标算出旋转角度把整帧图转正再以眼距的固定倍数裁出人脸区域。这样 FaceNet 看到的图里眼睛始终在同一水平线脸的大小也基本固定输入分布的波动就小很多。参数 size 取 2 倍眼距能罩住眉毛到下巴的大致范围太大容易带进耳朵和头发太小会切掉额头。参数说明FaceNet 对输入尺寸有严格约定如果权重是 160×160 训练的喂 112×112 特征会乱。embed 方法里最后除以范数是为了让向量落在单位球面上。所有预训练权重都有对应的预处理要求加载之前先确认模型文档里的尺寸、通道顺序和归一化方式别省这一步。3.3 欧氏距离和余弦相似度阈值到底该怎么选FaceNet 官方语义里用的是欧氏距离距离越小越像。余弦相似度则相反越大越像。两者在向量已经 L2 归一化的情况下是等价的但工程习惯不同。度量方式官方推荐门禁初值容易踩的坑欧氏距离越小越像1.0~1.2忘记归一化时阈值完全失效余弦相似度越大越像0.80~0.95阈值偏高导致拒识率飙升我做门禁系统时偏好欧氏距离原因只有一个距离的物理意义直观1.0 就是“差了多少”的直接度量。但无论用哪种都不能直接抄论文里的阈值。不同光照、不同摄像头拍出来的特征分布不同必须在自己的数据集上校准。后面有一章专门讲怎么用 ROC 曲线去定阈值这里先记住一个原则宁可阈值紧一点多拦几次熟人也别让陌生人随便刷开。误拒可以手动处理误识就是安全事故。4. 把两个网络接成系统注册、比对、数据库与记录模型能跑通只是第一步这套标题叫“人脸识别管理系统”重点在“管理”两个字。你要能注册新人、查识别记录、导出考勤哪怕界面丑一点逻辑必须完整。这一章直接给数据流、注册代码、识别代码和数据库表设计。4.1 从摄像头到开门的完整链路每一帧都干了什么先明确流程再写代码。整套系统按模块拆成四段摄像头取流OpenCV 打开摄像头不断读取画面帧。检测与筛选RetinaFace 跑检测保留置信度高于阈值的人脸过滤掉太小的人脸区域。特征提取与匹配FaceNet 生成当前脸的向量和内存中的注册特征库算距离取最小值判断是否低于阈值。动作记录匹配成功后写入一条开门记录包含用户 ID 和事件时间。这里面有个工程细节不是每一帧都要跑全流程。CPU 上检测加特征提取一帧可能要吃 50 毫秒以上如果摄像头是 30 帧队列里很快就会积压。常见做法是跳帧比如每隔 3 帧才做一次完整推理同时结果还要做去抖连续两三次匹配到同一个人才真正触发开门。单帧误检很常见不做去抖的话路过的人会让门不停地开。4.2 注册模块把“张三”变成一条数据库记录注册的本质是把某个人的人脸特征向量存进数据库。操作界面可以是 PyQt也可以是命令行核心逻辑都一样。下面的代码演示怎么把特征写入 SQLite注意我选择用 BLOB 而不是文本格式存向量。# register.py import sqlite3 import numpy as np def register_user(db_path, user_name, img_bgr, landmarks, embedder): embedding embedder.embed(img_bgr, landmarks) # 已 L2 归一化shape (128,) feature_blob embedding.astype(np.float32).tobytes() # 512 字节 conn sqlite3.connect(db_path) try: conn.execute( INSERT INTO users (name, feature_blob) VALUES (?, ?) ON CONFLICT(name) DO UPDATE SET feature_blobexcluded.feature_blob, (user_name, feature_blob), ) conn.commit() finally: conn.close()逻辑说明embedder.embed 返回的是已经归一化的 128 维 NumPy 数组。tobytes 把它转成二进制字节串这一步是关键。有人喜欢把向量转成 JSON 列表或者用逗号拼接字符串再存读取的时候 float 精度已经损失了而且每次比对都要做字符串解析又慢又容易出错。参数说明ON CONFLICT(name) 子句解决重复注册的问题——同一个人录第二次直接更新特征不用先删再插。这里假定 name 是唯一键如果系统里允许同名用户可以改用身份证号或工号做唯一键只需把字段换掉即可。4.3 实时识别用队列避免摄像头帧堆积实时识别最容易犯的错是摄像头线程里直接跑模型一帧还没算完下一帧就来了画面越来越卡最终看起来像死机。我一般用两个线程加两个队列来解耦。# recognize.py import queue import threading import numpy as np frame_queue queue.Queue(maxsize2) # 帧队列存不下就丢 result_queue queue.Queue(maxsize1) # 识别结果队列 def camera_loop(cap): while True: ok, frame cap.read() if not ok: break if frame_queue.full(): try: frame_queue.get_nowait() # 丢掉旧帧保证及时处理新帧 except queue.Empty: pass frame_queue.put(frame) def recognize_loop(detector, embedder, known_embs, names, threshold, frame_counter): while True: frame frame_queue.get() frame_counter 1 if frame_counter % 3 ! 0: # 跳 2 帧做一次完整推理 continue boxes, lms detector.detect(frame, box_threshold0.8) for box, lm in zip(boxes, lms): if (box[2] - box[0]) 40: # 人脸过小检出来也认不准 continue query_emb embedder.embed(frame, lm) dists np.linalg.norm(known_embs - query_emb, axis1) idx int(np.argmin(dists)) if dists[idx] threshold: result_queue.put((open, names[idx], float(dists[idx])))逻辑说明camera_loop 只负责把帧塞进队列塞不进去就丢旧帧保证队列里永远是刚拍的画面。recognize_loop 从队列取帧推理frame_counter 做跳帧控制。maxsize2 是刻意的——队列太长没意义模型处理速度跟不上堆再多帧也只有被丢掉的命。检测阈值在这里提到 0.8比基础演示的 0.5 更严格因为门禁场景下误开门的代价远大于漏检一次。参数说明box[2] - box[0] 是检测框宽度小于 40 像素的人脸在 160×160 的 FaceNet 输入里会被放大 4 倍以上插值噪声会严重影响特征质量所以直接跳过。known_embs 是启动时从数据库加载的注册特征矩阵形状是 (N, 128)这样对比时 numpy 广播一次算出所有距离比 for 循环快得多。4.4 数据库表设计两张表搞定人员与记录门禁系统的数据量不大SQLite 足够可靠而且零配置适合课程设计。真正要设计好的是表结构两张表分工明确users 存注册用户records 存每次开门的记录。CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, feature_blob BLOB NOT NULL, -- 128 * 4 512 字节的浮点向量 avatar_path TEXT, -- 登记照片路径界面展示用 group_id INTEGER DEFAULT 0, -- 分组比如“学生”“教师” created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE records ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, event_type TEXT DEFAULT open, event_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY(user_id) REFERENCES users(id) );字段说明feature_blob 是 512 字节的二进制数据从 NumPy 数组 tobytes 而来。读取时用 np.frombuffer(blob, dtypenp.float32) 还原成 128 维向量。avatar_path 建议存相对路径不要把图片二进制塞进数据库后期备份和读取都会变慢。records 表的 user_id 关联 users 表查某个人的开门记录时一条 JOIN 就解决。这里有个常见认知偏差觉得特征比对应该用数据库的 SQL 去查。实际上 128 维向量的 k 近邻查询用 SQL 写非常别扭而且门禁场景注册的人可能就几百个内存里放一个 (N, 128) 的矩阵做暴力比对耗时在毫秒级。所以识别时把 users 表全部加载进内存比对完再写记录不需要数据库参与计算。5. 避坑人脸识别门禁系统最常见的六个翻车点模型能跑、系统能开这只是起点。真正让课程设计从“能演示”变成“能验收”的是你踩没踩过下面这些坑。我把它们分成三组每一组都是真实场景里反复出现的。5.1 检测阶段的两个坑报名照检不出小脸截出来糊坑一录入照片用证件照结果系统检测不到人脸。现象是纯色背景的登记照送入 RetinaFace返回空结果。原因是这类照片人脸占比太大而且边缘锐度高网络训练数据里这种极近距离的“大头照”占比不高加上预处理时如果直接缩放会把原本清晰的五官磨糊。解决方法是先不要做任何缩放把整张图放进检测网络等检测框出来后再按框裁切如果原图分辨率太大比如 2000×3000先用最近邻插值缩到 1200 宽再检测避免压缩得太狠。坑二检测框裁出来的图送进 FaceNet特征和注册时对不上。现象是同一个人现场刷脸连续三次被拒。原因是直接用框的坐标去裁原图框边缘通常带着头发和耳朵背景一变特征也跟着变。解决方法是别用框裁剪用 2.3 节里的五点关键点做仿射对齐后再裁。同一个道理注册时也要走同样的对齐流程保证训练和推理的数据分布一致。5.2 特征与阈值的两个坑阈值拿生活常识设向量存成文本丢了精度坑三相似度阈值随手填了 0.5结果系统对所有人都放行。这个坑在校验时表现得很隐蔽因为演示时只有几个人测试每次距离都在 0.3 以下看不出问题。原因是没有区分度量的方向余弦相似度是越大越像你填 0.5 相当于“长得有三分像就放行”。解决方法是先跑一批正负样本对打印出距离分布的直方图再选一个能把两类明显分开的值。欧氏距离初值从 1.0 往上试余弦相似度从 0.9 往下试别拍脑袋。坑四向量存成 JSON 字符串入库再读出来标准差了好大一截。现象是同一个人注册两次两次特征的距离比对出来有 0.8几乎认成两个人。原因是 JSON 序列化时 float32 被转成 float64小数点后面多位被四舍五入128 个维度累积起来误差就放大了。解决方法是原则上一律用 BLOB 存二进制读出来用 np.frombuffer 还原全程不经过字符串转换。这条血泪经验只要你用 MySQL 或 SQLite 做毕设早晚会遇到一次。5.3 部署环境的两个坑CPU 推理顶不住打印照片刷开门禁坑五RetinaFace 加 FaceNet 的完整链路在 CPU 上跑不到 10 帧演示时画面明显卡顿。原因很简单两个 FP32 模型串行推理单帧耗时超 100 毫秒还没有任何跳帧控制。解决方法是先加跳帧每 3 帧做一次推理然后把 RetinaFace 输入从 640 降到 320最后把 OpenCV 的帧率限制在 15 帧画面反而看起来更流畅。如果你的硬件是 k10 行空板这类带 NPU 的板卡优先把 FaceNet 放到 NPU 上跑检测保留在 CPU因为检测的输出是稀疏的不受实时帧率约束。坑六拿一张打印的照片对着摄像头门开了。这是人脸识别门禁系统设计里最致命的问题。原因是纯 2D 人脸特征只依赖纹理和几何打印照片完全具备这些信息。解决方法的完整方案是加活体检测模块最基础的做法是检测连续帧里眼睛是否眨眼或者头部是否转动再高级一点用红外摄像头排除屏幕和纸张。做课程设计时如果做不了红外至少做一个简单的眨眼检测在 RetinaFace 关键点基础上连续几帧记录上下眼睑距离的变化有明显波动才判定为活体。哪怕简单答辩时也能讲清楚设计考量。6. 让系统通过答辩的收尾实验用 ROC 曲线定阈值答辩老师最爱问的问题就是“这个 1.0 的阈值怎么来的”如果你回答“试出来的”这题就悬了。正确做法是提前做一个阈值标定实验把结果画成 ROC 曲线。具体操作分三步第一步构造测试样本对。从注册库里挑 20 个人每人拍 3 张不同角度的照片两两组合得到同一人的正样本对再从外部找 20 个未注册的人拍照片和库里的每个人组合得到负样本对。正负样本对各至少 100 组不用多够画趋势就行。第二步把所有样本对的距离算出来。提前写一个脚本把同一对图片两次送入 RetinaFace FaceNet 提特征记录欧氏距离输出到 CSV。第三步遍历候选阈值。比如从 0.6 到 1.4步长 0.05对每个阈值统计误识率 FPR 和拒识率 TPR画一条曲线。门禁系统关心的指标是误识率也就是陌生人被放行的概率一般要求低于 1%。在 ROC 曲线上找到 FPR 低于 1% 时对应的最大阈值这个值就是你的最终参数。我一般会把这个过程固定在验收脚本里每次换摄像头或换光照环境就重跑一遍。阈值不是玄学它是一张可以写进论文附录的对照表。用数据说话而不是用“感觉”说话是整套系统能不能让人信服的分界线。做完阈值标定再补一个活体检测最小实现这套 FaceNetRetinaFace 的人脸识别管理系统基本就完整了。我自己做这类项目时一直保留一个习惯把每个阶段的阈值、帧率和识别距离记录在文档里而不是只记住“能跑”。这份记录在后续换模型、调硬件时就是你最后的后悔药。希望帮到你。本文还有配套的精品资源点击获取

看完文章,想为自己的企业也做一次专业网站诊断?

尧图顾问免费为您评估现有网站,并给出建站/改版建议与报价方案。

免费获取方案