SCRFD 人脸检测:0.67M 参数、4.2ms,InsightFace 里这个轻量检测器怎么做到的
【免费下载链接】insightfaceState-of-the-art 2D and 3D Face Analysis Project项目地址: https://gitcode.com/GitHub_Trending/in/insightface
SCRFD 是 InsightFace 仓库中最实用的轻量级人脸检测模块,基于 ONNX 推理单张 VGA 图像只要 4.2ms,0.67M 参数的 2.5GF 版本在 WIDER Face 三个子集上全面超过 RetinaFace。如果你要落地实时人脸检测,这篇文章讲清楚它凭什么快、最小推理流程怎么写、哪个规格适合你,以及生产环境里几个真实的坑。
先回答"够不够用":WIDER Face 上的精度-速度权衡
SCRFD 的评测数据都写在 detection/scrfd/README.md 里,精度、FLOPs、推理耗时统一在 VGA 分辨率下测得,这是对比各检测器时最可信的口径。预训练模型部分给出的规格如下:
| 规格 | Params(M) | FLOPs | WIDER Easy | Medium | Hard | Infer(ms) |
|---|---|---|---|---|---|---|
| SCRFD_500M | 0.57 | 0.5G | 90.57 | 88.12 | 68.51 | 3.6 |
| SCRFD_2.5G | 0.67 | 2.5G | 93.78 | 92.16 | 77.87 | 4.2 |
| SCRFD_10G | 3.86 | 10G | 95.16 | 93.87 | 83.05 | 4.9 |
两个值得注意的信号:
- Hard 子集才是分水岭。RetinaFace(MobileNet 0.25)在 Hard 上只有 47.32,SCRFD_500M 拉到 68.51,小脸和遮挡场景的提升全部体现在这里。所以如果你的业务涉及多人合影、远距离监控,至少用 2.5G 版本。
- 速度差异主要看 FLOPs 而不是参数量。500M 到 10G 参数量涨了 6 倍多,耗时只从 3.6ms 到 4.9ms,说明计算量被压得很低,瓶颈在 CPU 访存而非模型容量。README 还给了 0.5G 版在 Ryzen 9 3950X 上单线程(OMP_NUM_THREADS=1、无 mkldnn)的实测:640x480 输入 28.3ms,Original-Size 输入时 Hard 能到 82.03——原始分辨率输入对小脸更友好,代价是耗时,这个取舍后面会再提。
多人室内合影正是 Hard 子集那类场景:人脸尺度差异大、有低头侧脸。这类图片是检验一个检测器小脸召回率最直接的素材。
最小可运行推理流程
推理参考实现在 detection/scrfd/tools/scrfd.py,它用 onnxruntime 加载 ONNX 模型,自己实现了 blob 归一化、距离解码(distance2bbox)和 NMS。跑通的最短路径如下,模型从 README 的 Pretrained-Models 表下载(2.5G 带关键点版 SCRFD_2.5G_KPS):
# 来源: detection/scrfd/tools/scrfd.py import cv2, onnxruntime from detection.scrfd.tools.scrfd import SCRFD det = SCRFD(model_file='scrfd_2.5gkps.onnx') det.prepare(-1) # ctx_id<0 表示 CPUExecutionProvider img = cv2.imread('group.jpg') bboxes, kpss = det.detect(img, 0.5, input_size=(640, 640)) # bboxes: [x1,y1,x2,y2,score]; kpss: 5 个关键点(眼睛/鼻尖/嘴角), 无 KPS 版为 None调用链只有四步:构造 session、选 provider、读图、detect。输入图像内部会自动做 keep-ratio 缩放并 pad 到input_size,输出的框已经映射回原图坐标,不需要自己算逆变换。
几个容易踩的坑,都是从这个脚本的实现细节里看出来的:
- 动态输入要显式给
input_size。tools/scrfd2onnx.py导出的 ONNX 默认是动态输入,此时必须传input_size,否则detect里的断言直接失败。想省一次网络往返,可以用--shape 640 640导出固定尺寸模型再交给 onnx-simplifier 优化。 - pad 区域会引入边界假框。
detect把缩放后的图放进左上角、右下角留黑边,黑边区域偶尔会出低分框。thresh传 0.5 基本可以滤掉;如果追求高召回把阈值压到 0.2 以下,记得把边界框丢弃或收紧nms_thresh。 - 选对 KPS 版本。输出张量个数(6/9/10/15)决定模型是否带关键点、走 3 层还是 5 层 FPN。后续要做人脸对齐、注册识别,直接选
_KPS版,2.5G_KPS 比 2.5G 多 0.15M 参数,Hard 精度 77.13 vs 77.87,几乎无损。 - 阈值口径别混。
test_widerface.py评测用score_thr=0.02,demo 用 0.5,nms的 IoU 阈值默认 0.45。同一张图两种阈值结果差异很大,对账时先确认口径。
0.67MB 模型怎么塞进端侧
先澄清一个常见的单位混淆:README 里 0.67 是参数量(M),不是文件大小。2.5G 版 ONNX(fp32)实际下载下来约 2.7MB,量化后更小。这个量级意味着它可以直接打进手机 APP 的安装包,也可以放进 Web 端 WASM 推理——仓库 README 的 Demo 一节列了 ncnn、MNN、TNN、ONNXRuntime 的 C++ 移植,端侧部署基本不用自己写前处理。
端侧的两个实操建议:
- 输入尺寸按业务选。
prepare之后的detect接受任意input_size。小脸密集场景(监控、街景)用 640x640 甚至更高;单人脸刷脸场景 320x320 足够,耗时近似按面积缩放,0.5G 版在 320x240 下单线程 11.4ms(README 实测)。 - GPU 服务侧直接转 TensorRT。
trtexec --onnx=scrfd_2.5g.onnx --saveEngine=scrfd_2.5g.trt --fp16一条命令,fp16 对精度影响很小,延迟还能再压一截。多实例并发时建议每线程一个InferenceSession,onnxruntime 的 session 本身不是为高频多线程共享设计的。
为什么它能"又快又准":三个设计点
这部分点到为止,细节看 configs/scrfd/scrfd_2.5g.py:
- 多尺度锚点:
AnchorGenerator配base_sizes=[16,64,256]、strides=[8,16,32]、每层 2 个 scale,相当于给不同焦段各配一台镜头——stride 8 那层负责几十像素的小脸,stride 32 那层负责大脸,不用一套锚框硬扛全尺度。 - PAFPN 颈部:
in_channels=[24,48,48,80]压到out_channels=24,通道数小,特征融合开销低,这也是 FLOPs 能压到 2.5G 的关键。 - ATSS 样本分配:
assigner=dict(type='ATSSAssigner', topk=9),按局部统计量动态挑正样本,替代固定 IoU 阈值,训练配置里allowed_border=-1、pos_weight=-1就是配套写法。
值得一提的是这些规格不是调出来的,是搜出来的:仓库search_tools/里保留了两阶段网络搜索的完整流程(先生成 64 个候选 backbone 配置训练 80 轮,按 WIDER Hard mAP 选优,再以最优者为模板做整体搜索),detection/scrfd/search_tools/ 可以直接照着复现。这解释了为什么参数量和 FLOPs 能卡得这么"整"。
上图展示了检测框之上的完整分析链路:关键点、眼开合、活体、口罩、年龄性别。SCRFD 输出的 bbox 加 KPS 就是这一整条链路的入口——python-package/insightface/ 里的app/face_analysis.py把检测、对齐、识别、属性串在一起,做完检测之后的事不用自己接。
最后收一个判断:单人实时场景选 0.5G(3.6ms),多人/小脸密集选 2.5G,离线高精度再上 10G;对 Hard 子集精度有硬指标的话,记得优先验证原始分辨率输入的效果,README 里 82.03 vs 68.51 的差距说明输入尺寸本身就是你手里最大的调优旋钮。
【免费下载链接】insightfaceState-of-the-art 2D and 3D Face Analysis Project项目地址: https://gitcode.com/GitHub_Trending/in/insightface
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考