首页/新闻资讯/正文详情

人脸识别三段式流水线:YOLOv5检测、ArcFace识别与活体门控

发布时间:2026/11/2 19:11:45 来源:云帆数科 栏目:资讯中心
人脸识别三段式流水线:YOLOv5检测、ArcFace识别与活体门控
简介这份压缩包围绕人脸识别学习实践展开面向希望掌握目标检测、特征提取与活体防伪的开发者与学习者将YoloV5的人脸检测、ArcFace的特征向量学习以及活体检测的判别思路整合到可运行的案例中帮助读者从理论认知过渡到项目实操。包内共54个文件以26个Python源码、9个pyc、7个YAML配置、2个pth模型权重、4张示例图片及说明文档为主整体约3.4MB目录覆盖检测、识别、训练测试与数据处理等环节。已有179人学习说明其适合作为课程设计、毕业项目或算法入门的参考。完整代码示例与项目架构便于按流程理解系统搭建读者可据此梳理人脸检测到特征比对再到活体防伪的实现路径并参考训练、测试与数据生成脚本完成动手实践积累可复用的工程经验。1. 一份把检测、识别、活体串成流水线的工程适合谁拿来练手压缩包解出来是三个模型权重加一堆脚本第一眼看不出从哪下手这大概是很多人拿到这份人脸识别学习实践工程时的真实感受。它刻意没做成装完就能调的库检测、对齐、识别、活体四条链路全摊在明面上YOLOv5 负责在一帧画面里找出人脸在哪五点关键点把脸摆正ArcFace 把脸压成 512 维向量活体模型判断这张脸是真人还是屏幕里的照片。适合已经把 PyTorch 跑通过、想弄清工业级人脸识别流水线到底长什么样的人只想调个接口出结果的话这包东西反而绕远了。把它当成一条可以逐段打断点、逐段换模型的链路来读价值比跑通一个 demo 大得多。2. 架构拆解YOLOv5 出框、ArcFace 出特征、活体出真伪2.1 三段式流水线为什么比端到端更适合落地人脸识别在真实场景里从来不是单一模型能解决的事。端到端思路是把整张图丢进一个大网络直接回归出身份听起来优雅实际一到多人、小脸、侧脸就崩网络要同时学“在哪”和“是谁”两个任务的梯度互相打架小脸的像素信息在深层特征图里早就被下采样磨没了。这份工程走的是经典三段式检测器只回答“人脸在哪个矩形里、五个关键点在哪”识别模型只回答“这张 112×112 的脸是谁”活体模型只回答“这张脸是活的还是翻拍的”。拆开的好处是每段都能单独评估、单独替换。检测召回不够换 RetinaFace 或 SCRFD识别跨域差换更大的 ArcFace backbone活体误拒高单独拿一批正负样本重训。坏处是误差会累积检测框偏一点五点关键点就偏对齐矩阵就偏最后特征就飘。所以工程里没有用检测框直接 resize 当识别输入而是老老实实做了关键点对齐这个细节决定了识别精度上限。另一个实际考虑是算力分配。三个模型串起来如果每帧都全量跑1080p 摄像头 30fps 在单卡上很容易掉帧。分段之后就有取舍空间检测每帧跑识别按需跑活体只在人脸面积足够大时才跑。这种“按价值分配算力”的设计比堆一个巨型端到端网络更贴近真实项目。2.2 三个模型的输入输出契约链路要跑通先得把每个模块的输入输出对齐清楚。这份工程里三个模型各自独立加载靠 numpy 数组交接边界非常清晰模块输入输出关键参数人脸检测 YOLOv5-faceletterbox 到 640×640 的 BGR 图N×(x1,y1,x2,y2)、置信度、5 组关键点conf 0.5、NMS IoU 0.45人脸对齐原图 5 个关键点坐标112×112 RGB 人脸相似变换、ArcFace 标准模板身份识别 ArcFace112×112 对齐人脸512 维 L2 归一化特征归一化 (x-127.5)/128静默活体 MiniFASNet扩边 1.5 倍的人脸 crop3 类 softmax 概率真人概率阈值 0.85这张表看着简单实际每个格子都是踩过坑的地方。检测输出的关键点坐标是相对 640×640 输入图的必须做反 letterbox 变换才能映射回原图对齐用的五点模板是 ArcFace 训练时的固定坐标换一套模板就等于把脸摆到了模型没见过的位置识别的归一化参数写死成 127.5 和 128换成常见的 /255 会掉好几个点。这些后面第四章都会落到代码上。活体那块特别要提醒一句MiniFASNet 的输出是三分类类别顺序跟训练脚本强绑定工程里默认是“打印攻击、真人、屏幕翻拍”这个顺序索引取错就会把真人全判成假脸而且概率还特别高看着特别像“模型很自信”。2.3 单卡调度谁每帧跑谁降频跑三个模型如果都挂在一个循环里顺序执行单帧耗时是三者之和。YOLOv5s 在 640 输入下大概 812msArcFace R100 在 112 输入下 610msMiniFASNet 因为输入只有 80×802ms 左右。理论上一帧 20ms 出头30fps 勉强够但一旦有多个脸识别和活体的次数就按人数翻倍立刻掉帧。工程里的做法是给识别加缓存每个检测框用 IoU 跟上一帧的结果做匹配匹配上且人脸位置变化不大就复用上一帧的特征不重跑识别。人脸在画面里通常不会一秒变好几次这个近似几乎无感但省下来的算力很可观。活体则相反它对时序敏感翻拍视频恰恰是逐帧变化的所以只要有脸就每帧跑只是会先判断人脸框面积小于 60×60 的直接跳过因为太小裁出来的纹理根本判不了。线程模型上用的是生产者消费者采集线程只管cap.read()把最新帧塞进一个maxsize1的队列队列满了就丢旧帧推理线程从队列取处理完推给显示线程。maxsize1这个细节很关键设大了会导致延迟越积越高画面里的人抬手三秒后屏幕才动体验直接崩。这套结构在后面的避坑章节还会再提一次因为它是最容易翻车的地方之一。3. 目录结构与环境搭建把工程从压缩包跑到出画面3.1 目录长什么样哪些是代码哪些是数据解压之后先别急着装依赖花两分钟把目录看一遍能省掉后面一半的麻烦。工程结构大致是这样face_project/ ├── weights/ # 三个模型的权重需要单独下载或转换 │ ├── yolov5s_face.pt # 检测PyTorch 格式 │ ├── arcface_r100.onnx # 识别ONNX 格式 │ └── minifasnet_v2.onnx # 活体ONNX 格式 ├── models/ # 每个模型一个封装类 │ ├── yolo_face.py # 检测预处理 推理 后处理 │ ├── arcface.py # 识别对齐 特征提取 │ └── anti_spoof.py # 活体crop 扩边 分类 ├── utils/ │ ├── face_align.py # 五点相似变换 │ ├── nms.py # 纯 numpy 实现的 NMS │ └── draw.py # 画框、画关键点、写名字 ├── data/ │ ├── gallery/ # 注册照一人一个子目录 │ └── test_images/ # 单图测试用 ├── configs/ │ └── default.yaml # 阈值、路径、输入尺寸都在这 ├── detect_face.py # 统一入口图片 / 视频 / 摄像头 ├── enroll.py # 批量建库产出 features.npz └── requirements.txt判断哪些是“代码”、哪些是“数据”很重要。models/和utils/是要读要改的weights/和data/是运行时依赖configs/default.yaml是唯一应该频繁改的地方。我在第一次跑通之前习惯先把所有阈值集中到配置文件里避免在代码里到处找魔数这份工程基本做到了只有少量阈值还硬编码在函数默认参数里后面二次开发时可以顺手收拢。data/gallery/的约定是一人一个目录目录名就是人名或工号enroll.py会遍历这个目录把每个人的照片逐张提特征最后写成一个features.npz里面两个数组featsN×512和namesN。识别时直接加载这个文件不再碰原始图片启动速度会快很多。3.2 依赖装法与权重摆放环境建议单独建别和主环境混。Python 3.8 是这套组合最稳的版本3.10 以上在 onnxruntime 和旧版 torch 上偶尔会出玄学问题conda create -n face python3.8 -y conda activate face # torch 按自己的 CUDA 版本选这里以 cu113 为例 pip install torch1.12.1cu113 torchvision0.13.1cu113 \ --extra-index-url https://download.pytorch.org/whl/cu113 pip install opencv-python numpy scikit-image onnxruntime-gpu pyyaml tqdm几个点值得单独说。opencv-python和opencv-contrib-python不要同时装会互相覆盖症状是昨天还好好的cv2.dnn今天突然报属性缺失。onnxruntime-gpu的版本必须和本机 CUDA、cuDNN 对上装错了不会报错只会静默回落到 CPU速度差十倍日志里只有一行 warning 很容易被刷过去。scikit-image看着可有可无实际SimilarityTransform就靠它缺了会在对齐那步直接 ImportError。权重摆放按 3.1 的目录来三个文件名字要和代码里读的路径一致。检测权重是.pt用 torch 加载识别和活体是.onnx用 onnxruntime 加载。这种混搭不是随便定的检测模型需要后处理灵活改动留在 torch 里方便识别和活体推理图固定转 ONNX 后速度快、显存占用低。如果你机器上没有 ONNX 版本工程里一般也留了 PyTorch 版本的转换脚本torch.onnx.export一把梭注意 dynamic_axes 只开 batch 维别把 H/W 也开成动态的否则某些算子会退化。3.3 三条入口图片、视频、摄像头detect_face.py用--source一个参数统一了三种输入靠字符串判断能转成整数的当摄像头索引以.mp4/.avi结尾的当视频文件其余当图片路径。第一次跑千万别直接上摄像头先用单张图确认整条链路是通的# 第一步单图看框、关键点、名字对不对 python detect_face.py --source data/test_images/demo.jpg \ --weights weights/ --config configs/default.yaml --save out/ # 第二步视频文件看时序上跟不跟得住 python detect_face.py --source data/test_images/test.mp4 --save out/ # 第三步摄像头索引 0 一般是内置摄像头 python detect_face.py --source 0 --show第一步的输出最有信息量。如果框画上去了但位置整体偏移说明反 letterbox 变换写错了如果框对但名字全是 unknown说明 gallery 没建或者阈值太高如果名字对但同一个人时有时无说明特征归一化或者阈值卡在边界上。这一步能把 80% 的问题定位掉比直接看摄像头画面里人来人往有效得多。--show会开一个cv2.imshow窗口按 q 退出。无头服务器上跑记得去掉这个参数改用--save落盘否则imshow会抛 GTK 相关的错误这个错跟算法一点关系没有但第一次遇到很容易误判成环境装崩了。建库用enroll.py它是独立于检测入口的python enroll.py --gallery data/gallery --out data/features.npz跑完会打印每个人注册了几张、哪几张因为质量不过关被跳过。这一步的日志要仔细看如果某个人只注册进去一张后面识别他大概率不稳不如补拍几张正脸、光照均匀的。注册质量直接决定识别上限模型再好也救不回来一堆糊图。4. 核心链路代码从一帧 BGR 到一条带分数的识别结果4.1 检测输出解析与五点关键点YOLOv5-face 的输出张量形状是(1, N, 16)16 个通道依次是 4 个框偏移、1 个目标置信度、1 个类别分数、10 个关键点坐标。很多人第一次接会卡在这里因为标准的 YOLOv5 只有 5类别数 个通道多出来的 10 个就是为人脸关键点专门加的回归头。import numpy as np import cv2 def letterbox(img, new_size640, color114): 等比缩放 灰边填充返回填充图、缩放比、左上角偏移 h, w img.shape[:2] r min(new_size / h, new_size / w) nw, nh int(round(w * r)), int(round(h * r)) resized cv2.resize(img, (nw, nh), interpolationcv2.INTER_LINEAR) canvas np.full((new_size, new_size, 3), color, dtypenp.uint8) top, left (new_size - nh) // 2, (new_size - nw) // 2 canvas[top:top nh, left:left nw] resized return canvas, r, left, top def xywh2xyxy(x): y np.empty_like(x) y[:, 0] x[:, 0] - x[:, 2] / 2 # x1 y[:, 1] x[:, 1] - x[:, 3] / 2 # y1 y[:, 2] x[:, 0] x[:, 2] / 2 # x2 y[:, 3] x[:, 1] x[:, 3] / 2 # y2 return y def decode_yolov5face(out, conf_thres0.5, iou_thres0.45): out: (1, N, 16) - boxes(N,4), scores(N,), landmarks(N,5,2) pred out[0] scores pred[:, 4] * pred[:, 5] # obj * cls单类别时等价于置信度 keep scores conf_thres pred, scores pred[keep], scores[keep] if len(pred) 0: return np.zeros((0, 4)), np.zeros((0,)), np.zeros((0, 5, 2)) boxes xywh2xyxy(pred[:, :4]) # 5 个关键点每个点 (x, y)坐标都在 640 输入图的尺度下 landmarks pred[:, 6:16].reshape(-1, 5, 2) idx nms(boxes, scores, iou_thres) # 纯 numpy NMS见 utils/nms.py return boxes[idx], scores[idx], landmarks[idx] def unletterbox(boxes, landmarks, r, dx, dy): 把 640 尺度下的坐标还原到原图漏了这步框会整体偏移 boxes (boxes - np.array([dx, dy, dx, dy])) / r landmarks (landmarks - np.array([dx, dy])) / r return boxes, landmarksconf_thres0.5是检测的召回/误检平衡点。人脸检测场景下我一般会把它压到 0.350.45因为漏掉一张脸的代价识别不到人比多检一个框后面活体和识别会过滤掉大得多。iou_thres0.45控制 NMS 的合并强度人脸框重叠度天然不高除非画面里有人贴脸0.45 足够。最关键的是unletterbox。letterbox 做了两步变换先按比例r缩放再在左上角补了(dx, dy)的灰边。反变换必须先减偏移再除缩放比顺序反了结果会差出一整块。这个函数写错的话框会稳定地偏向左上或右下很多人第一反应是“模型不准”实际上是坐标变换错了。4.2 相似变换对齐到 112×112拿到五个点之后不要直接拿检测框 resize。检测框是水平的人脸要是歪的resize 完还是歪的ArcFace 对输入姿态非常敏感歪 15 度相似度能掉 0.1 以上。正确做法是用五个点估计一个相似变换把脸“摆正”到标准位置import cv2 import numpy as np from skimage import transform as trans # ArcFace 训练时使用的 112x112 五点标准模板 ARC_TEMPLATE 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(img, landmarks, image_size112): landmarks: 原图坐标系下的 5 个点shape (5, 2) tform trans.SimilarityTransform() tform.estimate(landmarks, ARC_TEMPLATE) # 从实际点估计到模板的变换 M tform.params[0:2, :] # 取 2x3 仿射矩阵 aligned cv2.warpAffine( img, M, (image_size, image_size), flagscv2.INTER_LINEAR, borderValue0.0) return aligned相似变换只有四个自由度旋转、缩放、平移不包含剪切和拉伸。这正好对应“把人脸摆正”这件事该有的自由度不会把胖脸压瘦、把长脸拉宽。如果你用cv2.getAffineTransform直接从三个点算虽然也是仿射但三点估计没有最小二乘平差一个关键点抖一下整张脸就跟着歪。用五点做estimate本质是超定方程求最小二乘解鲁棒性高一个档次。模板坐标不能随便改。这五个数是 ArcFace 训练集里的人脸平均位置模型见到的所有脸都被摆到这个位置。你换成自己随便量的一组坐标识别精度会莫名其妙地掉而代码看起来完全正常属于最难查的那类问题。还有个小细节warpAffine之后得到的是 BGR而识别模型要 RGB转换放在后面blobFromImage的swapRB参数里做别在这里就cvtColor来回转容易漏。4.3 ArcFace 推理与余弦比对识别这一步的代码反而最短因为所有复杂度都在训练阶段。推理只需要前向一次拿到 512 维向量再做一次 L2 归一化import cv2 import numpy as np def get_feature(sess, aligned_bgr): aligned_bgr: align_face 的输出112x112 BGR blob cv2.dnn.blobFromImage( aligned_bgr, scalefactor1.0 / 128, # 注意是 128不是 255 size(112, 112), mean(127.5, 127.5, 127.5), swapRBTrue, # BGR - RGB cropFalse) out sess.run(None, {sess.get_inputs()[0].name: blob})[0] feat out.ravel().astype(np.float32) return feat / np.linalg.norm(feat) # L2 归一化之后只用余弦 def match(feat, gallery_feats, gallery_names, thres0.38): gallery_feats 必须是已归一化的 (N, 512) sims gallery_feats feat # 归一化后点积即余弦相似度 i int(np.argmax(sims)) if sims[i] thres: return unknown, float(sims[i]) return gallery_names[i], float(sims[i])scalefactor1/128配mean127.5合起来等价于(x - 127.5) / 128把像素映射到 [-1, 1]。这是 ArcFace 训练时的标准归一化换成常见的/255会让输入分布整体偏移相似度分布跟着平移原来 0.4 的阈值可能就要调到 0.6 才勉强能用但排序关系已经被破坏了。这个坑我在不止一个项目里见过。归一化的位置也有讲究。L2 归一化必须在模型输出之后单独做一次不能指望模型内部做了。归一化之后余弦相似度退化成点积一次矩阵乘法就能算完整个库的比对一万个人也就几毫秒。阈值0.38是个偏保守的默认值。同一人在不同光照、不同角度下的余弦相似度通常在 0.50.75不同人之间普遍低于 0.30.38 落在中间偏安全的一侧。真实项目里应该拿自己的数据扫一遍 ROC 再定后面第六章会给方法。4.4 活体门控与最终决策活体模型要解决的是另一类问题屏幕上放一张照片检测器照样能检出脸ArcFace 照样能认出这个人只有活体模型能识破。所以它在链路里的位置是在识别之后、结果输出之前做一次门控import cv2 import numpy as np def anti_spoof(sess, frame, box, scale1.5, thres0.85): 返回 (是否真人, 三类概率) x1, y1, x2, y2 box.astype(int) cx, cy (x1 x2) // 2, (y1 y2) // 2 bw int((x2 - x1) * scale) bh int((y2 - y1) * scale) h, w frame.shape[:2] crop frame[max(0, cy - bh // 2):min(h, cy bh // 2), max(0, cx - bw // 2):min(w, cx bw // 2)] if crop.size 0: return False, np.zeros(3, dtypenp.float32) blob cv2.dnn.blobFromImage( crop, scalefactor1.0, size(80, 80), mean(0, 0, 0), swapRBFalse, cropFalse) prob sess.run(None, {sess.get_inputs()[0].name: blob})[0].ravel() # 类别顺序跟训练脚本绑定常见是 [print, real, replay] return bool(prob[1] thres), probscale1.5是这类静默活体模型的惯例把人脸框往外扩 50%。原因是翻拍痕迹、摩尔纹、屏幕边框这些线索大量分布在人脸边缘之外裁得太紧等于把最有判别力的区域切掉了。但也不能扩太多扩到 2.5 倍就把背景全带进来了背景变化会干扰判断。prob[1]这个索引是最容易翻车的一个数字。三类模型输出顺序由训练时的标签映射决定不看训练脚本根本猜不出来。判断方法很简单拿一张确定的真人照片跑一次打印完整的prob看哪一维最大那一维就是 real。别用“看起来合理”去猜猜错了症状是所有人全被判假而概率还接近 1.0特别有迷惑性。最后把四段串起来def pipeline(frame, det_sess, rec_sess, spoof_sess, gallery, det_thres0.5, sim_thres0.38, live_thres0.85): canvas, r, dx, dy letterbox(frame) inp canvas[:, :, ::-1].transpose(2, 0, 1)[None].astype(np.float32) / 255.0 out det_sess.run(None, {det_sess.get_inputs()[0].name: inp})[0] boxes, scores, landmarks decode_yolov5face(out, det_thres) boxes, landmarks unletterbox(boxes, landmarks, r, dx, dy) results [] for box, lm in zip(boxes, landmarks): aligned align_face(frame, lm) # 对齐 feat get_feature(rec_sess, aligned) # 提特征 name, sim match(feat, gallery[feats], gallery[names], sim_thres) # 比对 is_live, prob anti_spoof(spoof_sess, frame, box, threslive_thres) # 活体 if not is_live: name, sim fake, float(prob.max()) results.append({box: box, name: name, sim: sim, live_prob: prob}) return results注意检测输入这里用的是/255.0跟识别的/128不一样。检测模型YOLOv5 系列训练时就是/255识别模型是(x-127.5)/128两套归一化不能混用。这种“同一个工程里两套标准”的情况在真实项目里非常常见写代码时最好在每个模型封装类里把预处理写死不要抽成公共函数抽了迟早出事。5. 避坑与排查五个把人卡到怀疑人生的现场5.1 环境与坐标类现象框画上去整体偏向左上人脸在框外。原因漏了unletterbox或者只除了缩放比r没减填充偏移(dx, dy)。letterbox 是“先缩放后填充”反变换必须“先减填充后除缩放”顺序反了或者只做一半都会偏。 解决拿一张已知人脸位置的图把检测输出的原始坐标和反变换后的坐标都打印出来对着原图量一下左上角像素差差值应该正好等于(dx, dy)除以r。这个办法比盯着屏幕猜快得多。现象onnxruntime-gpu装了但推理速度跟 CPU 一样慢。原因CUDA 版本或 cuDNN 版本跟 onnxruntime 的编译版本不匹配session 初始化时静默回落到 CPUExecutionProvider只打一行 warning很容易被后面的日志刷过去。 解决初始化后显式打印sess.get_providers()第一项必须是CUDAExecutionProvider。如果不对先pip show onnxruntime-gpu看版本再去对照官方版本矩阵别硬试。现象摄像头跑几秒后画面越来越卡延迟累积到几秒。原因采集和推理解耦时队列没有上限或者cv2.VideoCapture的内部缓冲在堆积。摄像头采集快、推理慢帧全堵在队列里。 解决队列设成maxsize1满了直接丢旧帧cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)把驱动缓冲也压到最小。实时场景里丢掉旧帧永远比堆积延迟正确。5.2 特征与阈值类现象同一个人两张照片相似度只有 0.2不同人反而 0.6。原因特征没做 L2 归一化或者归一化做了但建库和查询用了两套预处理一个/255、一个/128向量根本不在同一个空间里。 解决enroll.py写完后立刻做一次自检——把库里每个人的特征和它自己比对相似度必须约等于 1.0再把同一个人的两张不同照片拿来比正常应该在 0.5 以上。这一步跑不过后面所有阈值调优都是白费。现象所有人全被判成 fake活体概率稳定在 0.99。原因活体模型的类别索引取错了。三分类的输出顺序由训练脚本决定凭直觉写prob[0]或prob[2]都很容易踩中“翻拍”或“打印”那一类。 解决拿一张手机翻拍的假脸和一张真人正脸各跑一次打印完整的概率向量对比两次哪一维变化最大那一维就是 real。确认后把索引写进配置文件别散在代码里。现象注册了五个人识别结果全指向同一个人。原因建库时混进了模糊图、侧脸图或者干脆不是人脸的图这类特征离群在余弦空间里变成了“吸引子”把很多查询都吸过去了。 解决enroll.py里加一道质量门——拉普拉斯方差低于阈值糊图、人脸框小于 80×80太远、五点明显不对称大角度侧脸的直接跳过并打日志。宁可少注册几张也别往里塞垃圾。6. 二次开发换检测器、建人脸库、按自己的数据重定阈值这套工程最容易改的地方是检测器。models/yolo_face.py对外只暴露一个detect(img) - boxes, scores, landmarks你只要保证新模型返回同样结构的 numpy 数组上层pipeline一行都不用动。换 RetinaFace 或 SCRFD 时唯一要注意的是关键点顺序各家定义的五点排列不一样有的左眼在前有的右眼在前顺序错了对齐出来的脸是镜像的识别率会断崖式下跌。我一般的做法是换完之后拿同一张图分别用新旧检测器对齐把两张 112×112 的图并排看一眼肉眼能过再谈精度。建人脸库这块注册质量比模型重要得多。同一个人注册 35 张不同光照、不同角度的正脸取特征平均后重新 L2 归一化比只注册一张稳得多。平均之前要先去异常值简单做法是两两算余弦相似度把跟其他人平均相似度最低的那张剔掉import numpy as np def quality_filter(feats, laplacian_vars, min_var60.0): feats: (N, 512) 已 L2 归一化laplacian_vars: (N,) 清晰度 keep laplacian_vars min_var feats feats[keep] if len(feats) 2: return feats sim feats feats.T # 两两余弦 mean_sim (sim.sum(axis1) - 1) / (len(feats) - 1) return feats[mean_sim mean_sim.mean() * 0.9] def build_gallery(feats_list): 一个人多张特征 - 一条平均特征 avg np.mean(np.stack(feats_list, axis0), axis0) return avg / np.linalg.norm(avg) # 平均后必须重新归一化min_var60是拉普拉斯方差的常用下限低于这个值基本是糊的。平均之后那次归一化千万别漏平均会让向量长度小于 1不归一化的话余弦相似度的绝对值会整体偏低阈值就得跟着重调等于白干。阈值调优别拍脑袋。拿 20 个人、每人 5 张图构一个正样本对同一人和负样本对不同人的小验证集扫一遍相似度阈值算各点的误拒率和误受率选一个误受率接近 0 而误拒率还能接受的点。人脸识别场景下我通常把误受压得比误拒狠因为放错人进来的代价远大于让人多刷一次。参数默认值建议范围调大的影响det_thres0.50.350.6误检增多小脸召回提高iou_thres0.450.40.5密集人脸合并不足sim_thres0.380.320.5误受上升unknown 变少live_thres0.850.750.95真人误拒上升最后说个习惯。这套链路我踩过最贵的一次坑是换了识别模型之后忘了确认归一化参数新模型用的是(x-127.5)/127.5而我沿用了旧的代码跑得通、日志也正常就是识别率掉了十几个点查了三天才定位到一行blobFromImage。从那以后我每次换模型都强制走一遍“自己跟自己比必须大于 0.99、同人不同图必须大于 0.5”的自检两条过不了就不往下走。希望帮到你。本文还有配套的精品资源点击获取

相关推荐

MCP 面试高频考点:控制返回内容大小与上下文消耗,你能答到第几层?
MCP 面试高频考点:控制返回内容大小与上下文消耗,你能答到第几层?

MCP 面试高频考点:控制返回内容大小与上下文消耗,你能答到第几层? 面试场景 某团队用 MCP 把内部知识库接入 AI 应用:Server 运行在 Docker 容器中,暴露 Tools 与 Resources;知识检索走 RAG 链路&#xff0… · 2026/11/2 19:11:45

YOLOv5+ArcFace+活体检测:人脸识别四段式流水线工程实践
YOLOv5+ArcFace+活体检测:人脸识别四段式流水线工程实践

简介:这份压缩包面向人脸识别入门与进阶实践者,围绕YoloV5人脸检测、ArcFace特征提取和活体检测三条技术线组织学习素材,适合希望从算法原理走向系统搭建的开发者参考。包内共54个文件,以26个Python源码为主体,辅以YAM… · 2026/11/2 19:11:45

JavaWeb一对一网页聊天系统:WebSocket实时通信与消息持久化方案
JavaWeb一对一网页聊天系统:WebSocket实时通信与消息持久化方案

简介:这是一套基于JSPJavaScriptAjaxServletTomcatMySQL实现的一对一网页聊天系统源码,面向正在学习JavaWeb技术栈、需要完成课程设计或练手项目的学生与初级开发者,重点解决如何用Ajax轮询机制实现消息实时收发这一核心问题。压缩包共54个文… · 2026/11/2 19:10:40

麒麟系统远程桌面密码安全增强配置指南
麒麟系统远程桌面密码安全增强配置指南

1. 麒麟系统远程桌面密码设置背景解析作为国产操作系统的代表之一,麒麟系统在企业办公和政务领域有着广泛应用。其远程桌面功能(通常基于VNC或RDP协议实现)是日常运维的重要工具。但在实际使用中,许多管理员发现系统默认对远程桌面… · 2026/11/3 7:12:54

Drawgent:Excalidraw画布直连Claude Code/Codex的实时代码生成智能体
Drawgent:Excalidraw画布直连Claude Code/Codex的实时代码生成智能体

1. 项目概述:一个让代码生成器“看见画布”的实时协同智能体Drawgent 这个名字乍一听像某个开源库的缩写,但实际它指向一个非常具体、非常务实的技术缝合点——把 Claude Code、Codex 或 opencode 这类大模型驱动的代码生成能力,直接嵌入到 E… · 2026/11/3 7:12:54

智能楼宇温湿度监测系统优化与实战部署
智能楼宇温湿度监测系统优化与实战部署

1. 项目背景与行业痛点现代智能楼宇对环境参数的精准监测需求日益增长。我们团队最近在某商业综合体项目中,遇到一个典型场景:传统温湿度监测系统存在数据延迟大(平均3-5分钟)、多点位部署成本高、各品牌设备协议不兼容三大痛点。… · 2026/11/3 7:12:54

UTF-8 BOM 与 U+FEFF:不可见字符引发解析故障的排查与治理
UTF-8 BOM 与 U+FEFF:不可见字符引发解析故障的排查与治理

做配置导入功能的时候踩过一次特别邪门的坑:本地跑得好好的解析逻辑,到了联调环境,文件里第一行配置永远读不出来。日志把 key 原样打印出来,肉眼对比跟预期字符串一模一样,长度、字符看着都正常,可丢进 Ma… · 2026/11/3 7:12:54

零基础入门空气动力学:从飞行原理到工程实践
零基础入门空气动力学:从飞行原理到工程实践

简介:这份PDF讲义源自北京航空航天大学北京市精品课《空气动力学》,面向航空航天类专业学生及需要补强流体力学基础的工程技术人员,系统讲解空气动力学的基本概念、原理与研究方法。内容从绪论中的物质形态与流体力学定义、研究对象、发展进程… · 2026/11/3 7:12:54

VL805数据手册精读:PCIe转USB3.0硬件设计与寄存器调试实战
VL805数据手册精读:PCIe转USB3.0硬件设计与寄存器调试实战

简介:这份PDF是VIA Labs设计的VL805四端口USB 3.0主控制器芯片手册,面向硬件工程师、PCB设计人员及PC外设扩展方案开发者,用于解决从PCI-E接口到USB3.0高效转换的设计参考问题。资源包仅含1个PDF文件,约936KB,内容涵盖… · 2026/11/3 7:11:54

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46

STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战
STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59

ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题
ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59

基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践
基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码