资讯详情

资讯详情

MTCNN人脸检测实战:三阶段级联网络原理与工程调优指南

1. 为什么我至今还在用MTCNN做人脸预处理人脸检测这个方向模型迭代速度非常快从早期的Viola-Jones到后来的HOGSVM再到深度学习时代的各种单阶段、双阶段检测器每隔一两年就会冒出新东西。但如果你问我在资源受限、需要快速落地、又不想在数据标注上砸太多成本的项目里我会选什么做人脸检测和对齐答案依然是MTCNN。这个模型全称是Multi-task Cascaded Convolutional Networks翻译过来就是“多任务级联卷积网络”名字听着挺唬人拆开看其实逻辑非常朴素用三个阶段的卷积网络由粗到细地把人脸框和五个关键点左右眼、鼻尖、左右嘴角一起回归出来。我第一次接触MTCNN是在做一个门禁打卡的Demo当时手头只有几千张标注质量参差不齐的人脸图训练集里大量侧脸、遮挡、模糊的样本。试过直接用通用目标检测框架去训人脸结果小脸漏检严重关键点回归也不稳定。后来换成MTCNN在同样的数据量下召回率和关键点定位精度都有明显提升而且推理速度在CPU上也能接受。从那以后MTCNN就成了我做人脸相关项目时的“第一道工序”——不管后面接的是人脸识别、活体检测还是表情分析前面都先过一遍MTCNN把脸框和关键点拿到手后面的模型只需要在裁剪对齐后的人脸图上做文章整个pipeline的稳定性会好很多。这篇文章适合谁看如果你正在做人脸相关的应用开发手头数据量不大、算力有限或者你刚入门计算机视觉想找一个结构清晰、原理不复杂、又能真正跑起来的模型来练手MTCNN都是一个很好的切入点。我会从整体设计思路讲起把三个阶段各自的职责、网络结构、损失函数设计拆开揉碎然后给出完整的实操流程和参数配置最后分享一些我在实际项目中踩过的坑和排查技巧。文章里涉及的代码和参数都是可以直接参考复现的不需要你有很深的深度学习背景只要会Python、装过PyTorch或TensorFlow就能跟上。2. MTCNN整体设计思路拆解2.1 为什么是“级联”而不是“一步到位”人脸检测的核心难点在于人脸在图像中的尺度变化极大从几十个像素的小脸到占据半个画面的大脸都有可能同时人脸还面临姿态、光照、遮挡、模糊等各种干扰。如果用一个单一的网络去直接回归所有人脸框网络容量需要非常大训练难度也高而且小脸的召回率往往很差。MTCNN的解决思路是分而治之。它把检测过程拆成三个阶段P-Net、R-Net、O-Net。每个阶段都是一个独立的卷积网络前一个阶段的输出经过非极大值抑制NMS和边框回归校正后作为后一个阶段的输入。这种设计的好处在于P-Net负责快速筛选用很小的计算量把大量背景窗口过滤掉R-Net在P-Net的基础上进一步过滤和精修O-Net最后输出精确的边框和五个关键点。整个流程像流水线一样每一级只做自己最擅长的事整体计算效率比单一大网络要高得多。我打个比方这就像招聘流程。P-Net是简历初筛快速把明显不合适的候选人剔除R-Net是笔试进一步考察基本能力O-Net是终面给出最终录用决定和具体岗位匹配度。每一轮都比上一轮更精细但处理的候选数量也大幅减少整体成本就控制住了。2.2 多任务学习检测、回归、关键点一起做MTCNN的另一个核心设计是多任务学习。每个阶段的网络都不只做一件事而是同时输出三个分支人脸分类是不是人脸、边框回归框的位置怎么调、关键点回归五个点的坐标。这三个任务共享前面的卷积特征只在最后的全连接层或卷积层分叉。这样做的好处很明显。首先特征共享让网络可以从同一个底层表示中同时学习“哪里有人脸”和“人脸长什么样”关键点回归任务会迫使网络关注眼睛、鼻子、嘴巴这些局部结构反过来提升了分类和边框回归的准确性。其次多任务学习相当于一种正则化减少了过拟合的风险尤其是在训练数据不够大的时候。我在实际训练中发现如果去掉关键点回归分支只做分类和边框回归模型在侧脸和遮挡场景下的表现会明显下降因为网络缺少了对局部结构的约束。2.3 图像金字塔解决尺度问题的经典手段MTCNN处理尺度变化的方式是图像金字塔。在P-Net阶段输入图像会被缩放到多个不同的尺寸每个尺寸都过一遍P-Net这样不同大小的人脸都能在某个尺度上被检测到。比如原始图像是640×480缩放因子是0.709那么会生成一系列缩放后的图像直到最短边小于12像素为止。每个尺度上P-Net输出的候选框最后统一映射回原图坐标再做NMS合并。这个策略虽然简单但非常有效。它的代价是计算量随尺度数量线性增长所以缩放因子的选择很关键。太小会导致尺度太多、速度慢太大会导致某些尺度的人脸被漏掉。根据我的经验0.709是一个比较平衡的值这也是原论文推荐的设置。如果你的人脸尺寸分布比较集中比如监控场景下人脸大小相对固定可以适当调大缩放因子来提速。3. 三个阶段网络结构深度解析3.1 P-Net全卷积的快速筛选器P-Net是一个全卷积网络输入是任意尺寸的RGB图像输出是三张特征图一张是人脸分类得分一张是边框回归偏移量一张是关键点坐标。因为是全卷积结构P-Net可以接受任意尺寸的输入输出特征图上的每个位置对应原图上的一个感受野相当于在原始图像上滑动窗口。P-Net的结构非常轻量三个卷积层卷积核大小分别是3×3、3×3、2×2通道数分别是10、16、32。最后通过1×1卷积输出三个分支。整个网络参数量很小在CPU上也能跑得飞快。它的任务是“宁可错杀一千不可放过一个”所以分类阈值设得比较低通常0.6左右保证高召回率后续阶段再来精筛。这里有个细节值得注意P-Net的边框回归输出的是相对于锚点的偏移量。锚点就是特征图每个位置对应到原图上的中心点配合不同的尺度和长宽比生成一组先验框。不过MTCNN的锚点机制比Faster R-CNN简单很多它只用正方形锚点边长根据当前金字塔尺度确定。这种简化在人脸检测场景下是合理的因为人脸框的长宽比通常接近1:1。3.2 R-Net进一步过滤与精修R-Net的输入是P-Net输出的候选框裁剪出来的图像块统一缩放到24×24大小。网络结构比P-Net深一些三个卷积层加两个全连接层。卷积部分负责提取特征全连接层负责分类和回归。R-Net的作用是过滤掉大量误检。P-Net因为阈值低会输出很多假阳性比如窗户、圆形物体、纹理复杂的区域都可能被误判为人脸。R-Net通过更深的网络和更大的输入尺寸能够看到更多的上下文信息从而把这些误检剔除。同时R-Net也会对边框做进一步的回归校正让框的位置更准确。在实际调参时R-Net的分类阈值通常设得比P-Net高比如0.7。这个阈值需要根据你的应用场景来调如果后续是人脸识别漏检的代价很高可以适当降低如果是人脸统计误检的代价更高可以适当提高。3.3 O-Net最终输出与关键点定位O-Net是最后一个阶段输入是R-Net输出的候选框裁剪并缩放到48×48的图像块。网络结构又比R-Net深了一层四个卷积层加两个全连接层。O-Net的输出除了分类得分和边框回归还有五个关键点的坐标。O-Net的任务是给出最终的人脸框和关键点。因为输入尺寸最大48×48网络最深所以它的判断最准确但计算量也最大。好在经过前两个阶段的过滤进入O-Net的候选框数量已经很少了通常只有几十个所以整体耗时可控。关键点回归是O-Net区别于前两个阶段的重要输出。这五个点左眼、右眼、鼻尖、左嘴角、右嘴角对于后续的人脸对齐至关重要。有了这五个点就可以通过相似变换把人脸旋转、缩放到标准姿态消除姿态对后续识别模型的影响。我在实际项目中发现经过关键点对齐后的人脸识别准确率能提升好几个百分点尤其是侧脸和俯仰角较大的样本。4. 损失函数设计与训练策略4.1 三个任务的损失如何加权MTCNN的训练损失由三部分组成人脸分类损失、边框回归损失、关键点回归损失。分类损失用的是交叉熵损失边框回归和关键点回归用的是均方误差损失。总损失是这三项的加权和。权重的设置很关键。原论文中P-Net和R-Net阶段的关键点损失权重设为0.5O-Net阶段设为1.0。这是因为P-Net和R-Net的主要任务是筛选和粗定位关键点回归只是辅助而O-Net需要输出精确的关键点所以权重更高。边框回归损失的权重通常设为0.5。我在实际训练中调整过这些权重。如果发现关键点定位不准可以适当提高关键点损失的权重如果边框抖动厉害可以提高边框回归的权重。但要注意权重调整会影响训练稳定性建议每次只调一个观察验证集上的表现。4.2 在线困难样本挖掘人脸检测的一个突出问题是正负样本极度不平衡。一张640×480的图像中人脸可能只占几十个像素而背景窗口有几十万个。如果直接用所有样本训练负样本会淹没正样本导致模型偏向于预测“不是人脸”。MTCNN采用了在线困难样本挖掘策略。在每个batch中先计算所有样本的损失然后只保留损失最大的前70%作为困难样本参与梯度回传。这样模型会专注于那些容易被误判的样本训练效率更高。具体实现时可以把分类损失和回归损失分开做困难样本挖掘也可以合并做根据实际情况选择。这个策略我在训练小数据集时感受特别明显。不做困难样本挖掘的话模型很快就把所有框都预测成背景召回率极低加上之后模型会逐渐学会区分那些模棱两可的区域召回率和准确率都能稳步提升。4.3 数据增强与训练集构建MTCNN的训练数据来自WIDER FACE和CelebA两个数据集。WIDER FACE提供人脸框标注CelebA提供关键点标注。训练时从WIDER FACE中裁剪出正样本、负样本和部分样本IoU介于0.4到0.65之间从CelebA中裁剪出关键点样本。数据增强方面常用的手段包括随机裁剪、水平翻转、颜色抖动、尺度变换等。需要注意的是水平翻转时关键点坐标也要相应翻转否则关键点回归会学错。另外MTCNN对输入图像的尺寸没有严格要求但训练时每个阶段都固定了输入尺寸12×12、24×24、48×48所以裁剪和缩放是必须的。我在构建训练集时的一个经验是负样本的质量比数量更重要。如果负样本都是从纯背景区域随机裁剪的模型学到的判别边界会很粗糙。更好的做法是加入一些“困难负样本”比如纹理复杂的区域、部分遮挡的人脸、类似人脸的物体等。这些样本能让模型学到更精细的判别能力。5. 完整实操流程与参数配置5.1 环境准备与依赖安装先说一下我常用的环境配置。Python 3.8以上PyTorch 1.10以上或者TensorFlow 2.x都可以。如果追求推理速度建议用PyTorch因为可以方便地导出ONNX或者用TorchScript加速。依赖库方面除了深度学习框架还需要OpenCV用于图像读写和显示NumPy用于数值计算Pillow用于图像处理。pip install torch torchvision opencv-python numpy pillow如果你打算从头训练还需要准备WIDER FACE和CelebA数据集。WIDER FACE的标注文件是MAT格式需要自己写解析脚本转成训练用的格式。CelebA的标注是文本文件相对好处理。我建议先把数据预处理脚本写好把正样本、负样本、部分样本、关键点样本分别生成好存成numpy数组或者LMDB数据库训练时直接读取避免每次epoch都重新解析标注。5.2 数据预处理与样本生成样本生成是MTCNN训练中最繁琐但也最关键的一步。以P-Net为例输入是12×12的图像块需要生成对应的分类标签、边框回归目标和关键点回归目标。正样本的选取标准是IoU大于0.65负样本是IoU小于0.3部分样本是IoU在0.4到0.65之间。正样本和部分样本参与边框回归正样本参与关键点回归。每个样本的边框回归目标是相对于候选框的偏移量计算公式是tx (x_gt - x_anchor) / w_anchor ty (y_gt - y_anchor) / h_anchor tw log(w_gt / w_anchor) th log(h_gt / h_anchor)关键点回归目标类似也是相对于候选框中心点的偏移量再除以候选框的宽高做归一化。这里有个容易踩的坑样本比例要控制好。正样本、负样本、部分样本的比例建议在1:3:1左右。如果负样本太多模型会偏向预测背景如果正样本太多模型会过拟合。我通常会在生成样本时做随机采样保证每个batch里三类样本的比例大致均衡。5.3 三阶段网络训练细节P-Net的训练相对简单因为输入尺寸小batch size可以设大一些比如512。学习率初始设为0.001用Adam优化器每10个epoch衰减一次。训练时要注意P-Net的输入是12×12的图像块但实际推理时输入是整张图所以训练和推理存在一定的尺度不一致。为了缓解这个问题可以在训练时随机裁剪不同尺度的图像块让网络适应尺度变化。R-Net和O-Net的训练类似只是输入尺寸更大网络更深。R-Net的batch size可以设256O-Net设128。学习率同样用0.001起步但衰减策略可以更激进一些因为这两个阶段的训练数据更少容易过拟合。我在训练O-Net时遇到过一个典型问题关键点损失下降很慢边框回归已经收敛了关键点误差还是很大。后来发现是学习率太高关键点回归的梯度被分类和边框回归的梯度淹没了。把学习率降到0.0005并且给关键点损失更高的权重问题就解决了。5.4 推理流程与参数调优推理流程就是前面说的三阶段级联。先构建图像金字塔对每个尺度跑P-Net收集所有候选框做NMS然后裁剪候选框送R-Net再做NMS最后送O-Net输出最终结果。参数调优方面有几个关键阈值需要根据场景调整参数默认值调整建议P-Net分类阈值0.6漏检多则降低误检多则提高R-Net分类阈值0.7同上但调整幅度要小O-Net分类阈值0.7通常保持默认NMS IoU阈值0.5人脸密集时降低稀疏时提高缩放因子0.709人脸尺寸集中时调大提速最小人脸尺寸20像素根据实际需求调整NMS阈值在人脸密集场景下特别重要。如果阈值设得太高重叠的人脸会被合并成一个框设得太低同一个人脸会出现多个框。我一般会在验证集上画PR曲线找到召回率和准确率的平衡点。6. 常见问题与排查技巧实录6.1 小脸漏检严重怎么办小脸漏检是MTCNN最常见的抱怨之一。原因通常有两个一是图像金字塔的缩放因子太大导致小脸对应的尺度没有被覆盖二是P-Net的分类阈值太高小脸的特征响应弱得分低被过滤掉了。解决办法先把缩放因子调小比如从0.709降到0.5这样金字塔层数更多小脸更容易被检测到。如果速度还能接受再把P-Net的分类阈值降到0.5甚至0.4。另外可以在P-Net阶段对输入图像做上采样比如把原图放大2倍再跑金字塔这样小脸在某个尺度上就变成了“大脸”更容易被检测到。代价是计算量增加需要权衡。6.2 关键点定位不准的排查思路关键点不准通常表现为眼睛、嘴角的位置偏移导致对齐后的人脸歪斜。排查时先看训练数据CelebA的关键点标注是否准确有没有大量侧脸、遮挡的样本如果训练数据没问题再看损失权重关键点损失的权重是不是太低了可以试着提高到1.0甚至2.0。还有一个容易被忽略的点O-Net的输入尺寸。如果输入图像块在裁剪时没有严格按照关键点中心对齐网络学到的关键点位置就会有偏差。我通常会在裁剪时以人脸框中心为基准向外扩展1.5倍保证关键点落在图像块内部同时保留足够的上下文信息。6.3 推理速度慢的优化手段MTCNN在CPU上的推理速度取决于图像分辨率和金字塔层数。如果速度不达标可以从这几个方面优化第一限制最小人脸尺寸比如把20像素提高到40像素这样金字塔层数会减少第二用ONNX Runtime或者TensorRT加速推理第三把P-Net的卷积层用深度可分离卷积替换参数量和计算量都能降下来但需要重新训练。我在一个嵌入式项目里做过测试原始MTCNN在ARM CPU上处理1080P图像需要200毫秒左右把最小人脸尺寸从20调到40耗时降到120毫秒再用ONNX Runtime量化推理降到80毫秒。如果对精度要求不高还可以把R-Net和O-Net合并成一个网络进一步减少级联开销。6.4 常见问题速查表问题现象可能原因排查方向解决方案大量误检P-Net阈值过低检查P-Net输出得分分布提高P-Net阈值或R-Net阈值人脸框抖动边框回归不稳定检查回归损失权重提高边框回归损失权重关键点偏移训练数据标注不准可视化关键点标注清洗数据或重新标注推理速度慢金字塔层数过多统计金字塔层数调大缩放因子或最小人脸尺寸侧脸漏检训练集侧脸样本少统计训练集姿态分布增加侧脸样本或做姿态增强密集人脸合并NMS阈值过高可视化NMS前后框数量降低NMS IoU阈值7. 我在实际项目中的几点体会MTCNN不是一个“新”模型但它是一个工程上非常成熟的模型。它的三阶段级联设计、多任务学习框架、在线困难样本挖掘策略放到今天依然有很多值得借鉴的地方。我在实际项目中使用MTCNN时最大的体会是不要把它当成一个黑盒。很多人直接调用现成的库参数全部用默认值结果在某些场景下效果不好就放弃了。其实只要理解了每个阶段的作用针对性地调整阈值和缩放因子MTCNN能适应非常多的场景。另一个体会是数据质量决定上限。MTCNN的网络结构并不复杂参数量也不大它的性能很大程度上取决于训练数据的质量和多样性。如果你手头的数据集人脸姿态单一、光照条件好训练出来的模型在真实场景中就会表现很差。我通常会在训练前花大量时间做数据清洗和增强确保训练集覆盖各种姿态、光照、遮挡、模糊的情况。最后分享一个小技巧如果你的人脸检测场景比较固定比如都是正面人脸、光照均匀可以考虑只保留P-Net和R-Net跳过O-Net用R-Net的输出做最终结果。这样速度能提升一倍以上精度损失很小。但如果需要关键点做对齐O-Net就不能省。这个取舍需要根据你的实际需求来定。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →