3、人脸检测算法:MTCNN、RetinaFace原理对比、检测模型选型与部署

人脸检测是人脸库管理的第一步,也是最重要的一步。你想想看,如果连人脸都找不到,后面的特征提取、比对检索就全是空谈。这一章,我结合自己踩过的坑,聊聊MTCNN和RetinaFace这两个主流检测器,以及怎么选型、怎么部署。

3.1 MTCNN:经典的三级级联架构

MTCNN(Multi-task Cascaded Convolutional Networks)是2016年的工作,但直到今天,很多轻量级场景还在用。它的核心思路很简单:先粗后精,逐步筛选。

整个流程分三步走:

  1. P-Net(Proposal Network):快速生成候选框。用一个小网络在全图滑动,找出可能有人脸的区域。这一步速度快,但框很粗糙,会有大量误检。
  2. R-Net(Refine Network):精炼候选框。把P-Net输出的候选框缩放到24x24,用稍大的网络做二分类和边框回归,去掉大部分非人脸。
  3. 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 检测模型选型:四个关键问题

选型不是拍脑袋,我一般问自己四个问题:

  1. 部署平台是什么? CPU还是GPU?ARM还是x86?有没有NPU加速?
  2. 最小人脸多大? 如果场景里人脸只有20x20像素,MTCNN基本废了,必须上RetinaFace。
  3. 实时性要求? 视频流需要30fps,还是图片库离线处理?
  4. 精度底线? 漏检和误检哪个更不能接受?安防场景漏检是致命问题。

嗯,这里要注意:不要盲目追求高精度。我见过有人把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的核心流程、选型依据、部署链路串起来了。你可以对照着看,理解更直观。

人脸检测算法选型与部署全景图 MTCNN 级联架构 P-Net R-Net O-Net 输出:人脸框 + 5关键点 RetinaFace 单阶段 Backbone FPN+SSH Detection Head 输出:人脸框 + 5/106关键点 选型依据 平台算力 → 最小人脸尺寸 → 实时性要求 → 精度底线 训练模型 导出ONNX ONNX Runtime推理 后处理+NMS CPU部署(OpenVINO/TNN) GPU部署(TensorRT) NPU部署(RKNN/SNPE) 💡 我的经验 • 小脸场景(人脸<30px)直接选RetinaFace,MTCNN基本检不到 • 嵌入式设备优先MTCNN,模型小、速度快、部署简单 • 导出ONNX时固定输入尺寸,避免动态shape带来的兼容性问题

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占用率直接降一半。