MTCNN(Multi-task Cascaded Convolutional Networks)是2016年提出的一种人脸检测算法,一经发布就成为了人脸检测领域的标杆方案。它的核心思想是用三个结构不同、职责不同的卷积网络串联起来,先粗筛再精调,像漏斗一样把海量的候选窗口逐层过滤,最终输出高精度的人脸边界框和五个关键点位置。这种级联设计在精度和速度之间取得了很好的平衡,即使放到今天,很多嵌入式设备和移动端的人脸相关应用仍然在使用它或它的变种。

一、MTCNN整体架构:为什么需要三级级联
人脸检测本质上是在一张图中回答两个问题:哪里有人脸,人脸的精确边界在哪里。如果只用一个网络完成所有工作,要么网络太轻导致漏检误检严重,要么网络太重导致速度无法接受。MTCNN的解法是把任务拆解成三个阶段,每个阶段只处理上一阶段筛选后的少量候选,这样重网络只跑在少数窗口上,整体计算量被大幅压缩。
三个网络分别是P-Net(Proposal Network)、R-Net(Refine Network)和O-Net(Output Network)。P-Net是一个全卷积网络,直接在缩放后的图像上滑窗生成候选框;R-Net在网络结构上多了全连接层,负责过滤掉P-Net产生的大量假阳性候选,并对边框做第一次回归修正;O-Net结构更深,除了进一步精筛和边框回归外,还多输出一个关键点定位分支,可以标出左右眼、鼻尖和左右嘴角五个关键点。
这种设计的巧妙之处在于计算量的分配。假设一张图经过P-Net后产生一万个候选框,经过NMS去重后可能只剩下一千个送入R-Net,R-Net再筛掉九成,最终只有一两百个框进入O-Net。最重的O-Net只处理极少量样本,整体耗时被控制在可接受范围内,这就是级联架构的核心价值。
二、图像金字塔与P-Net:候选框是怎么来的
MTCNN处理的第一步是构建图像金字塔。因为卷积网络对尺度敏感,固定大小的卷积核只能检测与其感受野相近的人脸尺寸。做法是将原图按照一个缩放系数(通常是0.709,即根号二分之一)不断缩小,生成一系列不同尺度的图像,直到最小边小于12像素为止。P-Net的输入尺寸是12x12,因此在每个尺度的图上滑动时,天然就能检测出不同大小的人脸。
P-Net是一个只有五个卷积层的轻量网络,结构大致为:输入12x12x3,经过10x10卷积(16通道)、池化、16x16卷积、池化、32x32卷积后,输出两个分支。一个分支输出每个位置是人脸的概率,另一个分支输出边框回归的偏移量。由于它是全卷积结构,不需要真正裁剪出12x12的小图逐个输入,直接对整张缩放图做一次前向传播,就能得到所有位置的检测结果,效率远高于传统滑窗。
import cv2
import numpy as np
def build_image_pyramid(img, min_face_size=20, factor=0.709):
"""构建图像金字塔,用于多尺度人脸检测"""
height, width = img.shape[:2]
min_side = min(height, width)
# 保证最小人脸尺度不小于12像素(P-Net输入尺寸)
scale = 12.0 / min_face_size
min_side = min_side * scale
scales = []
while min_side >= 12:
scales.append(scale)
scale = scale * factor
min_side = min_side * factor
return scales
img = cv2.imread('test.jpg')
scales = build_image_pyramid(img, min_face_size=24, factor=0.709)
print('金字塔尺度数量:', len(scales))拿到P-Net的输出后,系统会将概率低于阈值(一般取0.6左右)的候选直接丢弃,再对剩下的人脸框进行边框回归修正,把框调整得更贴合人脸,最后做一次NMS去重,把高度重叠的冗余框合并掉,输出给下一阶段。
三、NMS与边框回归:级联之间不可忽视的细节
NMS(非极大值抑制)的作用是去除重叠的重复检测框。基本流程是:把所有候选框按置信度从高到低排序,取出置信度最高的框,计算它与剩余所有框的IoU(交并比),凡是IoU超过阈值(通常0.5或0.7)的框都视为重复而被删除;然后对剩下的框重复上述过程,直到处理完毕。MTCNN在每个阶段输出后都会做NMS,且阈值逐级收紧,P-Net阶段用较宽松的0.5,O-Net阶段用0.7,确保层层过滤。
def iou(box_a, box_b):
"""计算两个框的交并比"""
x1 = max(box_a[0], box_b[0])
y1 = max(box_a[1], box_b[1])
x2 = min(box_a[2], box_b[2])
y2 = min(box_a[3], box_b[3])
inter = max(0, x2 - x1) * max(0, y2 - y1)
area_a = (box_a[2] - box_a[0]) * (box_a[3] - box_a[1])
area_b = (box_b[2] - box_b[0]) * (box_b[3] - box_b[1])
return inter / (area_a + area_b - inter + 1e-9)
def nms(boxes, scores, threshold=0.5):
"""非极大值抑制,返回保留框的索引"""
order = scores.argsort()[::-1]
keep = []
while order.size > 0:
i = order[0]
keep.append(i)
ious = np.array([iou(boxes[i], boxes[j]) for j in order[1:]])
order = order[1:][ious < threshold]
return keep边框回归是另一个关键机制。卷积网络初始预测的框往往不够准,网络额外输出四个偏移量,分别表示框中心点的平移和宽高的缩放比例。用这四个值对原始框做仿射修正,可以让框更贴合真实人脸。由于偏移预测是在原图尺度下学习到的,所以还要根据图像金字塔的缩放比例把候选框坐标映射回原图坐标系,这是实现时容易出错的细节,务必小心处理。
四、R-Net与O-Net:从粗筛到精修的关键点定位
R-Net的输入是P-Net输出的候选框裁剪并缩放到24x24的图像块。它的结构包含卷积层加一个128维的全连接层,最后同样输出人脸概率和边框偏移。相比P-Net,全连接层赋予了它更强的判断能力,能够识别出大量P-Net阶段漏掉的假阳性,比如把纹理复杂的背景误判为人脸的情况。经过R-Net后,候选框数量通常减少一个数量级。
O-Net是三个网络中最深的一个,输入为48x48的图像块。它的独特之处在于多任务输出:除了人脸分类和边框回归,还有第三个分支输出五个关键点的坐标。这正是MTCNN名字中多任务的含义,同一个网络同时学习分类、回归和定位三个任务,共享底层特征,不仅节省计算,关键点信息反过来还能帮助网络学到更鲁棒的人脸特征表达。
import torch.nn as nn
class ONet(nn.Module):
"""O-Net简化结构定义,三个分支分别对应分类、边框回归、关键点定位"""
def __init__(self):
super(ONet, self).__init__()
self.backbone = nn.Sequential(
nn.Conv2d(3, 32, 3), nn.BatchNorm2d(32), nn.PReLU(),
nn.MaxPool2d(3, 2),
nn.Conv2d(32, 64, 3), nn.BatchNorm2d(64), nn.PReLU(),
nn.MaxPool2d(3, 2),
nn.Conv2d(64, 64, 3), nn.BatchNorm2d(64), nn.PReLU(),
nn.MaxPool2d(2, 2),
nn.Conv2d(64, 128, 3), nn.BatchNorm2d(128), nn.PReLU(),
)
self.fc = nn.Sequential(nn.Linear(128 * 3 * 3, 256), nn.PReLU())
self.cls = nn.Linear(256, 2) # 人脸概率
self.box = nn.Linear(256, 4) # 边框偏移
self.landmark = nn.Linear(256, 10) # 五个关键点坐标
def forward(self, x):
x = self.fc(self.backbone(x).view(x.size(0), -1))
return self.cls(x), self.box(x), self.landmark(x)训练时三个任务的损失函数通过加权求和的方式联合优化,分类用交叉熵损失,边框回归和关键点定位用平方和损失。关键点损失只在正样本上计算,边框损失在正样本和部分样本上计算,这种样本分级的策略让训练信号更加干净。O-Net输出经过最终的NMS处理后,得到的就是最终的人脸框和关键点结果。
五、工程实践与替代方案对比
在实际项目中使用MTCNN,最省事的方式是直接用facenet-pytorch这类成熟库,它提供了预训练好的三个网络权重,一行代码即可完成检测。也可以用原版的Caffe实现或OpenCV的DNN模块加载社区提供的模型。需要注意图像金字塔构建是整个流程中比较耗时的环节,可以在检测前先做一次粗略的缩放,或者根据业务场景限定最大最小人脸尺寸来减少金字塔层数。
MTCNN并非没有短板。它的小人脸检测能力一般,极端姿态下容易漏检,三个网络串行执行也限制了极限速度。如果追求更高的精度和速度,可以考虑RetinaFace、SCRFD等基于锚框改进或NAS搜索的方案,它们在WIDER FACE等公开数据集上的指标已经明显超越MTCNN。但MTCNN的模型极小、依赖简单、CPU上也能实时运行的特点,使它在资源受限的场景和对关键点精度要求不高的应用中依然有很强的生命力。理解MTCNN的级联思想和多任务学习设计,对学习后续更先进的人脸检测算法也大有裨益。