4、深度学习检测器(上):MTCNN网络结构详解
聊到人脸检测,MTCNN 绝对是个绕不开的名字。我记得 2016 年刚接触这个算法时,就被它的精巧设计打动了——用三个小网络级联,就把检测和对齐一起搞定。说实话,直到今天,很多工业级项目里依然能看到它的身影。
这一章,咱们就把它拆开揉碎了讲。从 P-Net、R-Net 到 O-Net,每个网络负责什么、怎么训练的,我都会结合自己的踩坑经历跟你聊透。
4.1 为什么是三个网络?
你想想看,如果直接用一个超大网络去检测人脸,计算量会爆炸。尤其早期设备算力有限,根本跑不动。
MTCNN 的思路很聪明:先粗筛,再细挑,最后精修。
- P-Net(Proposal Network):快速扫描全图,找出可能有人脸的候选框。说白了就是「宁可错杀一千,不可放过一个」。
- R-Net(Refine Network):对候选框做精细筛选,去掉大部分误检。
- O-Net(Output Network):最终输出人脸框和关键点坐标。
这种级联架构的好处很明显:计算量逐级递减,精度逐级提升。我在项目中遇到过类似场景,如果只用单阶段检测器,小脸很容易漏掉。但 MTCNN 这种多阶段设计,对小目标的召回率确实高不少。
4.2 P-Net:全卷积的快速扫描器
P-Net 的结构非常轻量。它本质上是一个全卷积网络(FCN),输入是 12×12 的图像块。
为什么是 12×12?嗯,这是经过实验验证的——既能保留足够的人脸特征,又不会让计算量太大。
网络结构
输入: 12×12×3
↓
Conv: 3×3, 10 filters, stride=1, PReLU
↓
MaxPool: 2×2, stride=2
↓
Conv: 3×3, 16 filters, stride=1, PReLU
↓
Conv: 3×3, 32 filters, stride=1, PReLU
↓
Conv: 1×1, 2 filters (face classification)
Conv: 1×1, 4 filters (bounding box regression)
Conv: 1×1, 10 filters (facial landmark localization)
注意最后三层是并行的。它们分别输出:
- 分类分支:2 个值,表示是人脸/非人脸的概率
- 回归分支:4 个值,候选框的偏移量
- 关键点分支:10 个值,5 个关键点的坐标
我个人习惯把 P-Net 看作一个「滑动窗口加速器」。它通过全卷积结构,一次前向就能处理整张图片的所有 12×12 窗口,效率极高。
4.3 R-Net:精筛器的担当
P-Net 输出的候选框大概有几千个,但大部分是误检。R-Net 的任务就是把这些候选框进一步筛选。
R-Net 的输入是 24×24 的图像块。相比 P-Net,它多了全连接层,所以判别能力更强。
网络结构
输入: 24×24×3
↓
Conv: 3×3, 28 filters, stride=1, PReLU
↓
MaxPool: 3×3, stride=2
↓
Conv: 3×3, 48 filters, stride=1, PReLU
↓
MaxPool: 3×3, stride=2
↓
Conv: 2×2, 64 filters, stride=1, PReLU
↓
FC: 128 units, PReLU
↓
FC: 2 (classification)
FC: 4 (bbox regression)
FC: 10 (landmark localization)
R-Net 的参数量大约是 P-Net 的 5 倍。它会把 P-Net 输出的候选框缩放到 24×24,然后判断「这到底是不是人脸」。
我在项目中遇到过一个问题:R-Net 的阈值设得太高,导致很多正脸被误删。后来我调低了阈值,配合 NMS(非极大值抑制)来平衡召回率和精度。具体来说,R-Net 的阈值建议设在 0.6~0.7 之间。
4.4 O-Net:最后的把关人
O-Net 是三个网络中最重的。它的输入是 48×48,参数量是 R-Net 的 3 倍左右。
网络结构
输入: 48×48×3
↓
Conv: 3×3, 32 filters, stride=1, PReLU
↓
MaxPool: 3×3, stride=2
↓
Conv: 3×3, 64 filters, stride=1, PReLU
↓
MaxPool: 3×3, stride=2
↓
Conv: 3×3, 64 filters, stride=1, PReLU
↓
MaxPool: 2×2, stride=2
↓
Conv: 2×2, 128 filters, stride=1, PReLU
↓
FC: 256 units, PReLU
↓
FC: 2 (classification)
FC: 4 (bbox regression)
FC: 10 (landmark localization)
O-Net 不仅输出人脸框,还会输出5 个关键点:左眼、右眼、鼻子、左嘴角、右嘴角。这些关键点在做人脸对齐时非常有用。
你想想看,如果只有人脸框,你很难判断人脸是正脸还是侧脸。但有了关键点,你就能做仿射变换,把人脸摆正。这在后续的人脸识别中至关重要。
4.5 训练数据准备与标注格式
聊完网络结构,咱们看看训练数据怎么准备。MTCNN 的训练需要三类数据:
| 数据类型 | 标注内容 | 数据来源 |
|---|---|---|
| 人脸/非人脸 | 0 或 1 | WIDER Face + 随机裁剪 |
| 边界框回归 | 4 个偏移量 (dx, dy, dw, dh) | WIDER Face |
| 关键点定位 | 10 个坐标值 (x1,y1,...,x5,y5) | CelebA / 私有数据集 |
标注格式详解
MTCNN 的标注格式其实很简单。每个样本对应一个文本行:
# 格式:图片路径 类别 框坐标 关键点坐标
/path/to/image.jpg 1 100 120 80 90 45.2 48.1 50.3 52.0 48.5 50.1 45.8 48.9 46.2 49.5
其中:
- 类别:1 表示人脸,0 表示非人脸
- 框坐标:x1, y1, w, h(左上角坐标 + 宽高)
- 关键点坐标:依次为左眼、右眼、鼻子、左嘴角、右嘴角的 x,y 坐标
这里有个细节:坐标都是归一化后的值。归一化到 [0,1] 区间,这样训练更稳定。
数据增强策略
我在项目中总结了一套数据增强流程,效果不错:
- 随机裁剪:从原图中随机裁剪出不同大小的块,与真实人脸框计算 IoU。IoU > 0.65 的算正样本,IoU < 0.3 的算负样本。
- 颜色抖动:调整亮度、对比度、饱和度。模拟不同光照条件。
- 水平翻转:注意关键点也要对应翻转。左眼变右眼,右眼变左眼。
- 旋转:±20° 以内的随机旋转。旋转后人脸框需要重新计算。
4.6 训练策略:多任务学习
MTCNN 的训练是典型的多任务学习。三个分支共享底层特征,但各自有独立的损失函数。
总损失函数为:
L = α * L_cls + β * L_box + γ * L_landmark
其中:
- L_cls:交叉熵损失,用于人脸分类
- L_box:欧氏距离损失,用于边界框回归
- L_landmark:欧氏距离损失,用于关键点定位
权重 α、β、γ 的取值很关键。原论文建议:
- P-Net:α=1.0, β=0.5, γ=0.5
- R-Net:α=1.0, β=0.5, γ=0.5
- O-Net:α=1.0, β=0.5, γ=1.0
我个人习惯把 γ 调高一些,因为关键点定位的精度直接影响后续人脸对齐的效果。尤其是 O-Net,γ 设为 1.5 时,关键点误差能降低 10% 左右。
4.7 小结
这一章咱们把 MTCNN 的三个网络结构、训练数据和标注格式都聊透了。总结几个要点:
- P-Net:全卷积,快速生成候选框。输入 12×12,输出人脸概率、框偏移和关键点。
- R-Net:带全连接层,精筛候选框。输入 24×24,参数量适中。
- O-Net:最重的网络,输出最终结果。输入 48×48,关键点精度最高。
- 训练数据:需要三类标注,正负样本比例 1:3,数据增强必不可少。
- 多任务学习:三个分支共享特征,损失函数加权求和。
下一章,咱们会深入 MTCNN 的推理流程,包括图像金字塔构建、NMS 策略和关键点对齐。到时候我会分享一个我在实际部署中遇到的性能优化案例,很有意思。