3、人脸检测算法:MTCNN、RetinaFace原理对比、检测模型选型与部署
人脸检测是人脸库管理的第一步,也是最重要的一步。你想想看,如果连人脸都找不到,后面的特征提取、比对检索就全是空谈。这一章,我结合自己踩过的坑,聊聊MTCNN和RetinaFace这两个主流检测器,以及怎么选型、怎么部署。
3.1 MTCNN:经典的三级级联架构
MTCNN(Multi-task Cascaded Convolutional Networks)是2016年的工作,但直到今天,很多轻量级场景还在用。它的核心思路很简单:先粗后精,逐步筛选。
整个流程分三步走:
- P-Net(Proposal Network):快速生成候选框。用一个小网络在全图滑动,找出可能有人脸的区域。这一步速度快,但框很粗糙,会有大量误检。
- R-Net(Refine Network):精炼候选框。把P-Net输出的候选框缩放到24x24,用稍大的网络做二分类和边框回归,去掉大部分非人脸。
- O-Net(Output Network):输出最终结果。缩放到48x48,用最大的网络做精细分类、边框回归,同时输出5个关键点(左眼、右眼、鼻子、左嘴角、右嘴角)。
我个人习惯把MTCNN比作「漏斗」——每一级筛掉一批,最后留下的就是高质量的人脸框。
关键点:MTCNN同时输出人脸框和关键点,这对后续的人脸对齐非常友好。我在做门禁系统时,直接用O-Net输出的5点做仿射变换,省去了单独训练关键点检测器的麻烦。
3.2 RetinaFace:单阶段+特征金字塔的革新
RetinaFace是2019年InsightFace团队提出的。它跟MTCNN的思路完全不同——单阶段、端到端,不需要级联。
它的核心创新点有三个:
- 特征金字塔(FPN):在不同尺度的特征图上做检测,解决小脸问题。MTCNN对小人脸效果差,RetinaFace就好很多。
- 上下文模块(SSH):在检测头里加入更大的感受野,提升遮挡场景的召回率。
- 额外监督分支:除了人脸框和关键点,还预测3D网格和密集关键点。不过实际部署时,我一般只取前两个分支。
说白了,RetinaFace就是「用更深的网络、更复杂的结构,换取更高的精度」。我在安防场景的测试中,RetinaFace在WIDER Face的Hard子集上比MTCNN高了将近10个点。
3.3 核心对比:一张表说清楚
| 维度 | MTCNN | RetinaFace |
|---|---|---|
| 架构 | 三级级联(P→R→O) | 单阶段+FPN+SSH |
| 输入尺寸 | 12/24/48 | 640/512/256(可配置) |
| 推理速度(CPU) | 快(~10ms/图) | 慢(~50ms/图) |
| 小脸检测 | 弱 | 强 |
| 关键点输出 | 5点 | 5点/106点/密集点 |
| 模型大小 | ~1.5MB | ~10MB(MobileNet版)~100MB(ResNet版) |
| 开源框架 | Caffe/PyTorch/ONNX | PyTorch/MXNet/ONNX |
我的建议:如果跑在嵌入式设备上(树莓派、RK3399),选MTCNN;如果跑在服务器或高端GPU上,且对召回率要求高,选RetinaFace。我曾经在RK3588上部署MTCNN,单帧只要3ms,效果完全够用。
3.4 检测模型选型:四个关键问题
选型不是拍脑袋,我一般问自己四个问题:
- 部署平台是什么? CPU还是GPU?ARM还是x86?有没有NPU加速?
- 最小人脸多大? 如果场景里人脸只有20x20像素,MTCNN基本废了,必须上RetinaFace。
- 实时性要求? 视频流需要30fps,还是图片库离线处理?
- 精度底线? 漏检和误检哪个更不能接受?安防场景漏检是致命问题。
嗯,这里要注意:不要盲目追求高精度。我见过有人把ResNet-50的RetinaFace部署到Jetson Nano上,结果只有2fps,根本没法用。选型一定要结合硬件算力做权衡。
3.5 部署实战:从训练到ONNX推理
我以RetinaFace为例,讲一下部署流程。MTCNN的部署更简单,思路是一样的。
第一步:模型导出
训练好的PyTorch模型,先转成ONNX。注意要固定输入尺寸,否则动态shape会导致推理框架报错。
import torch
import torch.onnx
model = RetinaFace(backbone='mobilenet0.25')
model.load_state_dict(torch.load('retinaface.pth'))
model.eval()
dummy_input = torch.randn(1, 3, 640, 640)
torch.onnx.export(model, dummy_input, 'retinaface.onnx',
input_names=['input'],
output_names=['bbox', 'landmark', 'score'],
dynamic_axes=None) # 固定尺寸
第二步:ONNX推理
用ONNX Runtime做推理,速度比PyTorch快不少,而且不依赖训练框架。
import onnxruntime as ort
import numpy as np
session = ort.InferenceSession('retinaface.onnx')
input_name = session.get_inputs()[0].name
img = preprocess(image) # 缩放到640x640,归一化
outputs = session.run(None, {input_name: img})
bboxes, landmarks, scores = outputs
# 后处理:NMS、阈值过滤
避坑指南:我曾经在导出ONNX时忘了设置opset_version,默认用了老版本,结果某些算子不支持,推理直接崩了。建议显式设置opset_version=11或12,兼容性最好。
第三步:后处理优化
NMS(非极大值抑制)是后处理的瓶颈。我建议用Fast NMS或者Cluster NMS,比传统NMS快3-5倍。如果对精度要求不高,还可以用MaxPool NMS,速度更快。
3.6 本章知识结构图
下面这张SVG图,把MTCNN和RetinaFace的核心流程、选型依据、部署链路串起来了。你可以对照着看,理解更直观。
3.7 避坑指南:我踩过的三个坑
做检测模型部署,坑不少。我分享三个亲身经历:
- 坑一:MTCNN的P-Net阈值设太低。我曾经设成0.3,结果P-Net输出了几千个候选框,R-Net和O-Net直接跑崩了。建议P-Net阈值设0.6以上,宁可漏检也不要让后面两级爆炸。
- 坑二:RetinaFace的输入尺寸不匹配。训练时用640x640,部署时为了提速改成320x320,结果小脸全丢了。记住:输入尺寸决定了最小可检测人脸,部署时不要随意改。
- 坑三:NMS的IoU阈值。默认0.5,但密集场景(比如地铁站)容易把相邻人脸误删。我一般调到0.3-0.4,宁可多几个重复框,也不要漏检。
小技巧:如果你用MTCNN做视频流检测,可以加上跟踪逻辑——检测到人脸后,用KCF或CSRT跟踪器跟几帧,隔几帧再检测一次。这样能省掉大量重复检测,CPU占用率直接降一半。