☰
RetinaFace深度拆解:多任务监督如何攻克小尺度人脸检测
2026/10/16 8:10:48 网站建设 项目流程

如果让我选一个在工业界和学术界都吃得开的人脸检测模型,RetinaFace一定在我的前三名里。这篇发布于2019年的论文,全称是 RetinaFace: Single-stage Dense Face Localisation in the Wild,它把一个看似走到瓶颈的方向又往前顶了一大截。做安防抓拍、做移动端人像、做人脸底库清洗的工程师,基本绕不开它以及围绕它生长的各种变体。这篇文章我不按论文顺序原样复述,而是从设计决策的角度重新拆一遍,讲清楚它为什么能在 WIDER FACE 的 Hard 子集上把精度拉上去,同时还能保持单阶段检测器的高吞吐。

如果你是刚入门人脸检测的学生,或者正在做相关项目但一直对"多任务学习"具体怎么帮助检测有疑问,这篇解读会适合你。我会把网络结构、SSH 模块、额外标注、损失函数、训练细节和部署经验都讲透,尽量用工程人之间的语言,少绕弯子。

1. 为什么需要RetinaFace:人脸检测在"野外"的困境

1.1 人脸检测任务的发展脉络

人脸检测是最典型的"看起来简单、做起来难受"的视觉任务之一。早期从手工特征的 Viola-Jones 开始,后来进入 CNN 时代,所有人脸相关的系统都要先回答一个问题:人在哪?这个问题的难点不在于标准证件照,而在于"in the wild",也就是野外环境。

野外的情况非常残忍:人的姿态可能是仰头、低头、侧脸,表情可能是大笑、痛哭、皱眉,光照可以是逆光、强侧光、夜间补光,脸部可以被口罩、墨镜、手、头发遮挡。更麻烦的是尺度问题,一张 1920x1080 的监控画面里,人脸可能只有 20x20 像素。这还不是最极端的,最极端的是密集人群,几十上百个人头挤在一起,目标之间互相重叠,模型既要找出所有人,又不能漏掉被压住的半张脸。

这样的任务性质决定了它不能只靠"图像分类"那套思路。分类只需要回答"这是什么",检测需要回答"在哪"以及"是不是一张完整的脸"。后来从 R-CNN 到 Faster R-CNN,检测器从"两阶段"走向"单阶段",人脸检测也随之分化成两条路线。两阶段精度高但速度慢,单阶段速度快但精度容易掉,尤其是在 Hard 子集这种人脸又小又模糊的评测上。

RetinaFace 的选择很有意思:它踩在 RetinaNet 的框架上,但靠多任务监督、额外标注和上下文模块把精度补了回来。它不是第一个单阶段人脸检测器,却是第一个在 WIDER FACE 上把 Hard 子集 AP 刷到接近当时两阶段 SOTA 的单阶段方案。这个点很重要,它证明了"速度"和"精度"之间的墙并不是不可打破的。

1.2 单阶段与两阶段的路线之争

两阶段检测器,比如 Faster R-CNN,先由区域提议网络产出若干候选框,再做第二阶段的分类和回归。它的优点是每个候选框都有机会被精细化调整,对小目标、遮挡目标更友好;缺点是"两顿做两个模型"的时间开销在那里摆着,一帧图像很难做到实时。

单阶段检测器,比如 SSD、YOLO、RetinaNet,直接在特征图每个位置预测分类和边界框偏移。它省掉了提议阶段,所以前向推理非常快,但问题也明显:正负样本极度不平衡。图像里大多数地方都是背景,没有人脸,如果所有位置都去参与分类,模型会被背景淹没。

RetinaNet 之前,单阶段检测器在精度上普遍矮两阶段一截,大家觉得这是路线本身的问题。后来 RetinaNet 用 Focal Loss 解决了样本不平衡,单阶段才真正追上两阶段。RetinaFace 把这一整套思路搬到人脸检测上,但它不只是移植,还额外塞进了关键点回归和密集分割两个任务,让模型在训练时能接触更多结构信息。

我的理解是,作者团队并不认为检测头越简单越好,而是认为检测任务本身太抽象,如果再加一点"人的面部结构"做监督,检测器会学得更稳。后面消融实验也证明了这一点:仅仅加关键点分支,Hard 子集的 AP 就有明显提升。这就是典型的"用任务养任务"思路。

2. RetinaFace的设计思路:多任务是一条"以战养战"的路

2.1 整网结构:一个anchor-based的密集检测器

RetinaFace 的主干用的是 ResNet50,输入图像被缩放成固定分辨率后,经过骨干网络提取特征,再接 FPN 做特征金字塔。FPN 的路径从 P2 到 P6,P2 对应原图下采样 4 倍的层,分辨率最高,对小人脸特别重要;P6 是下采样 64 倍,负责大尺度目标。

为什么人脸检测把金字塔拉这么低?人脸的尺度变化实在太大了。一张标准监控画面里,远处的人脸往往只有十几个像素,如果金字塔从 P3 开始,也就是下采样 8 倍,那 16 像素的人脸在下采样后只剩 2 个像素,分类信息基本消失。把 P2 纳入进来,相当于给小人脸多留了一格"原生分辨率"的通道。这是 RetinaFace 跟通用物体检测的一个关键区别,很多复现跑不出论文效果,就是因为把 P2 砍了或者输入分辨率缩水了。

金字塔每一层上会放一组 anchor,沿用了 RetinaNet 的密集采样策略:每个位置有几个不同尺度和宽高比的锚框,负责匹配不同大小的人脸。锚框的设置和匹配策略对最终 AP 影响极大,这个我在后面训练部分细说。

骨干网络可以直接换 MobileNet 等轻量主干,论文里也给出了移动端版本,这是它能在工业界流行的重要原因。训练时用大网络挣精度,部署时换小网络换速度,而且整个检测头不需要改,代码路径一致。

2.2 SSH模块:感受野与上下文信息怎么加

SSH 模块,全称是 Single Stage Headless,它解决的问题非常朴素:人脸检测不能只看脸那一小块区域,还要看肩膀、周围的人和背景,才能判断"这到底是一张脸还是别的什么圆形物体"。也就是说,模型需要更大的感受野。

SSH 的设计方式很巧妙,它不用一堆 5x5、7x7 的大卷积核去硬扩感受野,而是用多个小卷积核并联,再拼接结果。具体来说,一个分支用普通的 3x3 卷积,另一个分支用膨胀率 2 的 3x3 卷积,这个等效感受野等于 5x5,再一个分支用膨胀率 3 的 3x3 卷积,等效感受野等于 7x7。三个分支输出按通道拼接,再经过后续卷积融合。

这样做的优势显而易见:参数量比直接上大卷积核小,同时通过不同膨胀率捕捉了多个尺度范围的上下文。我没有在文本里仔细对比过参数量,但直观感受是,这种"并联小膨胀卷积"比"串联大卷积"对训练更友好,梯度更稳,内存占用也更低。

在 RetinaFace 的具体实现中,SSH 模块放在特征金字塔的头部,会进一步处理 FPN 输出的特征图。上下文信息的注入主要就是通过它完成的。

2.3 多分支输出:框、分类、关键点、密集网格回归

RetinaFace 的检测头不是单一输出,而是四胞胎:

第一个分支是分类,输出每个 anchor 属于"人脸"还是"背景"的置信度;第二个分支是框回归,输出边界框的中心点偏移和宽高缩放;第三个分支是关键点回归,输出左眼、右眼、鼻子、左嘴角、右嘴角五个点的坐标;第四个分支是密集回归,输出像素级的人脸区域分割图,也就是一个跟特征图尺寸相关的 mask,帮助模型学习"脸到底长在哪个范围里"。

这四个分支共享前面所有骨干和 FPN 的特征,只是在最后几层分开。这样做的意义在于,只要有一个任务做得好,其他任务就能跟着受益。关键点回归逼着模型去定位眼睛、鼻子、嘴巴这些具有明确语义的点,等于给模型提供了一份"内部结构说明书"。

我记得自己第一次看这个结构时有个疑问:关键点任务是辅助检测的,那测试时能不能直接把关键点头砍掉?答案是能。实际部署中,有些人会保留关键点头做后续的活体比对或者人脸对齐,有些人为了极致速度会把密集回归分支去掉。这不会影响检测精度,因为关键点分支的作用发生在训练阶段。

密集回归分支其实还可以理解成"用 3D 模型渲染出的人脸 mask 来监督 2D 特征"。作者团队借助 3D 人脸重建手段,为训练数据生成密集的人脸网格标注,再通过这个分支强迫网络输出人脸轮廓和人脸内部区域。这个分支会让人脸特征的响应更加聚焦,不容易被类似肤色的背景干扰到。

3. 额外标注与自监督:把数据用到位

3.1 数据与标注瓶颈

人脸检测最常用的评测集是 WIDER FACE,它提供了大量带有人脸边界框的图片,覆盖各种野外场景。但框标注只告诉模型"脸在哪一块区域",并没有告诉模型"眼睛鼻子嘴在哪"。如果想要训练一个带关键点的人脸检测器,必须先有关键点标注。

但关键点标注的成本远高于框标注。给一张图标注四个角和一个中心点容易,标注双眼中心、鼻尖、嘴角却需要人眼仔细核对,尤其当人脸模糊、角度夸张时,标注员自己都分不清。做大几千张还好,做几万几十万张就是一场人力灾难。

所以作者团队的思路很直接:既然手工标注太贵,那就用机器自动生成一份"足够好"的标注,先让模型有东西可学。这个思路放在今天看很平常,但在当时的检测社区里算是把"weakly-supervised"的理念用到极致了。

3.2 用3D人脸重建自动生成关键点标注

具体做法是,拿一个现成的、已经能较好工作的 3D 人脸重建模型,对 WIDER FACE 的所有训练图像跑一遍,得到每个人脸的 5 个关键点坐标。这些坐标不一定精确到像素级,但对于大多数清晰人脸来说足够可靠。

这样做有一个很微妙的好处:自动生成的标注带有一定的噪声,但这些噪声是均匀分布的,不会系统性地偏向某一类姿态。模型在大量略带噪声的监督信号下学习,反而比只靠一小撮手工精确标注学得更鲁棒。这跟"数据增强里加一点随机扰动"是同一个道理,只不过扰动加在了标签上。

用这套方法,原本只有边界框标注的数据集,一下子就变成了"框+关键点"的数据集。论文中把这一步称为额外的标注方法,训练时不需要额外的人工成本。

不过这里有个陷阱:如果 3D 重建模型本身有系统偏差,比如对侧脸特别容易飘,那生成的标签也会带同样的偏差,模型最终会继承这个坏习惯。所以在实际复现时,我倾向于对自动生成的关键点做一次清洗,把置信度低的样本过滤掉,只保留重建模型"很自信"的那部分。

3.3 密集回归与人脸分割监督

除了关键点,作者团队还利用 3D 重建模型生成像素级的人脸区域 mask。简单说,就是渲染出每个目标人脸覆盖的像素范围,让网络学会一个"分割任务"。

这个分支的定位很微妙。它不参与最后的检测框输出,唯一的使命是帮助骨干网络学到更丰富的人脸结构特征。实验表明,引入密集回归分支后,Hard 子集上的 AP 又有提升,说明结构监督的确让特征表达更紧致了。

我自己的理解是,密集回归相当于给了网络一层"注意力约束":模型不仅要知道脸在哪一块框里,还要精确知道脸皮覆盖到哪些像素。当一张小脸只有十几个像素时,这种像素级的监督信号能让特征响应更加集中,减少误把背景纹理当成脸的错误。

测试阶段,这个分支可以直接丢弃。因为它已经完成了自己的历史使命,就像脚手架一样,楼盖完了就可以拆。

4. 损失函数设计与训练细节逐个看

4.1 分类损失与anchor采样

训练的时候,第一个要解决的是正负样本不平衡。一张训练图里有成千上万个 anchor,其中只有极少一部分落在真实人脸框附近,如果全部参与分类,模型很容易被背景带偏。RetinaFace 沿用了 Focal Loss,公式核心是给分类损失加上一个调制因子:

FL(pt) = -(1 - pt)^γ * log(pt)

其中 γ 一般取 2。当某个 anchor 的预测概率 pt 接近 1(也就是分对了)时,(1-pt)^γ 趋近于 0,这个样本的损失被压到很低;相反,当模型把一个背景 anchor 误判成人脸、或者把一个真实人脸漏掉时,pt 会很小,损失权重反而大。这相当于在梯度层面自动聚焦困难样本。

anchor 的匹配策略也很关键。通常来说,与真实人脸框 IOU 大于 0.5 的 anchor 被标记为正样本,IOU 小于 0.3 的被标记为负样本,介于二者之间的直接忽略。这个阈值如果定得太严,正样本数量过少,模型在训练初期根本找不到学习方向;定得太松,又会让大量半脸框参与回归,导致检测框边界模糊。我复现时通常先用 0.5 跑,如果发现 loss 不稳再往 0.45 调。

4.2 框回归损失与关键点回归损失

框回归分支预测的是 anchor 到真实框的偏移量,也就是中心坐标偏移和宽高缩放。损失函数用的是 Smooth L1。Smooth L1 的好处是它对离群点不敏感,误差太大时梯度不会爆炸,误差很小时梯度又平滑,非常适合边界框这种需要精调的回归任务。

关键点回归同样用 Smooth L1,目标是让模型输出的十个数值(5 个点,每个点 x 和 y)尽量接近自动生成的关键点标注。值得注意的是,关键点回归只在正样本上计算。如果一个 anchor 被判定为背景,它没有对应的人脸,自然也没有关键点可以学。

密集回归分支用的是逐像素的二分类损失,类别是人脸和非人脸。这一层的设计有点像 FCN 语义分割,只不过输出通道数很少,而且只在正样本所在区域计算。整体损失就是分类损失、框回归损失、关键点损失、密集回归损失的加权和,不同权重对结果的影响并不完全相同。

我在实践中发现,关键点损失的权重如果太高,模型会把大量注意力放在"把点挪准"上面,导致框的定位稍微变差;如果太低,关键点又学不到东西,检测精度同样上不去。论文给出的比例是一个不错的起点,但自己复现时最好在验证集上扫一遍权重。

4.3 训练策略与数据采样

训练数据主要来自 WIDER FACE 的训练集,但作者团队还会额外挑选一些包含清晰人脸的图像数据补充进去,尤其是有侧脸、遮挡、极端姿态的图像。训练时输入图像会先经过随机裁剪、缩放、水平翻转、颜色抖动等常规增广。随机裁剪特别重要,因为它可以人为制造出各种尺度的正样本,让模型见过"只有半张脸在画面里"的情况。

优化器一般用 SGD,配合动量、weight decay 和学习率 warm-up。这里有一个经验:FPN 和检测头部分的学习率可以比骨干网络略高一点点,因为骨干用 ImageNet 预训练权重初始化,已经有一个不错的起点,而检测头是从零开始的。

这部分的细节看起来琐碎,但直接决定训练能不能收敛。我记得第一次用单卡跑类似架构时,因为 batch size 太小、学习率没有跟着 scale,前 50 轮 loss 一直在高位震荡,后来把学习率按 batch 比例调小才解决。如果你是刚入门,建议先在小规模数据上把过拟合跑出来,再上全量数据。

5. 实验数据与工程落地:从WIDER FACE到移动端

5.1 WIDER FACE上的性能对比

WIDER FACE 的验证集和测试集分成 Easy、Medium、Hard 三档,难度逐级增加。Easy 子集人脸大、清晰、遮挡少;Hard 子集人脸小、模糊、遮挡严重、密集程度高。大部分模型的差距就拉开在 Hard 子集上。

论文里基于 ResNet50 骨干的 RetinaFace,在 WIDER FACE 验证集上大致取得了 Easy 95.5%、Medium 94.0%、Hard 91.7% 的 AP。这个数字在当时已经超过了绝大多数两阶段检测器,尤其 Hard 子集上相比同类单阶段方案有数个百分点提升。别小看这几个点,在安全场景里,多检出一个人脸可能就是多一条有效线索。

我通常建议做工程选型的人同时看 AP 和推理延迟,不要被单指标迷惑。WIDER FACE 的 Easy 子集上大家都接近 95% 以上,区分度很低,重点看 Hard。如果某个模型只报了 Easy 分数,基本说明它自己也知道 Hard 拿不出手。

5.2 消融实验:哪一块贡献最大

论文里最有说服力的部分其实是消融实验。逐步加入不同模块后,可以看到各自的边际收益。

第一个阶段是纯 FPN + Focal Loss 的基线检测器,Hard 子集 AP 大概在 87% 左右;加入关键点分支后,Hard 子集 AP 上升超过 3 个百分点;再加入密集回归分支,又有约 1 个百分点的提升;而使用额外标注数据之后,Hard 子集又往上走了一截。这一串数字说明,关键点分支的贡献最大,是 RetinaFace 能拉开差距的核心原因。

为什么关键点分支这么重要?因为它给模型提供了一个比"框内是不是有人脸"更精细的监督信号。模型要输出五个点,就必须学会分辨眼睛、鼻子、嘴巴的位置,这比单纯判断"这块区域有脸"要难得多。为了完成这个更难的任务,模型被迫学到更细致、更稳定的特征,而这些特征恰好也是检测小脸所需要的。

密集回归分支的贡献看似不大,但它在训练早期能稳定特征学习。你可以把它理解成给模型加了一道"面部形状正则",防止模型只通过学习肤色纹理这种浅层特征来偷懒。

5.3 移动端与实时推理

论文没有止步于 ResNet50,还给出了基于 MobileNet 骨干的移动端版本。把主干换成轻量网络后,模型体量和计算量大幅下降,在GPU 上推理一张图只需要几十毫秒量级,移动设备上也能达到实时帧率,代价是 Hard 子集上的 AP 相对回落。

工程部署时,我通常不会直接使用论文里那个最小的骨干。移动端内存和算力允许的话,尽量选 MobileNet 的 0.5 或 0.75 宽度版本,精度和速度更平衡。另一个容易被忽略的点是:检测头占的计算量比想象中大,尤其 SSH 模块在 P2 高分辨率特征图上做 3x3 卷积,开销并不小。你要是做嵌入式部署,需要仔细分析每个算子的耗时,不能只看骨干网络的 FLOPs。

如果你的场景是视频流,还要考虑 NMS 之后的时间平滑。单帧关键点会有轻微抖动,直接拿去做人脸对齐或活体会感觉不够稳定。常用做法是加一个轻量卡尔曼滤波或指数移动平均,让输出更平顺。

6. 复现与调参经验:想跑出论文的效果要注意什么

6.1 数据预处理与增广

复现 RetinaFace 的第一步是处理数据。WIDER FACE 原始标注里有一些非常小的、只有几个像素的人脸,这些目标在缩放之后很容易直接消失。所以训练时输入图像的尺寸不能太小,我常用的是 640x640 或者保持短边 600 以上。

另一种非常有效的增广是随机裁剪,裁剪出来的区域会给模型不断提供尺度变化和部分遮挡样本。有些目标被裁掉 30%,模型仍然要把它们框出来,这对提升遮挡鲁棒性很有帮助。颜色抖动可以做,但幅度别太大,人脸检测对颜色信息有一点依赖,过度扰动会导致训练不稳定。

硬负样本挖掘也是训练人脸检测的重要技巧。把最容易误判为人的背景图像挑出来,继续参与训练,能显著降低误检。如果只跑标准数据,模型很容易对衣服纹理、轮胎花纹这类图案产生响应。

6.2 后处理与NMS细节

推理时,多个尺度的检测结果会合并到一起,然后用 NMS 去掉重叠框。NMS 阈值一般设置在 0.4 到 0.5 之间。阈值调太高,重叠的人脸会输出两个框,误检率上升;调太低,密集人群中互相遮挡的人脸又会被去掉。我自己的经验是,在密集场景先试 0.4,人脸稀疏场景可以放宽到 0.45。

还有一个很多人忽略的点:多尺度测试。把原图放大或缩小后分别输入模型,再把结果合并,能稳定提升 Hard 子集的表现。代价是推理时间成倍增加。所以实际工程里,我通常只在离线评测或者图片检索场景用多尺度,视频流实时检测只用单尺度。

6.3 踩坑记录:训练不收敛、小脸漏检

第一个坑是训练初期 loss 不降。排查方向先看 anchor 匹配的正样本数量,如果一批里几乎都是负样本,模型学不到任何有用信息。解决办法是调整 anchor 大小范围,或者放宽正样本匹配阈值。

第二个坑是小脸漏检严重。先确认 P2 层是否真的参与检测,很多简化版本的实现会丢 P2,导致小脸能力大幅退化。其次是输入分辨率,如果训练和推理输入都只给 320x320,小脸基本没救。还有一种情况,模型把大量注意力放到了背景分类上,Focal Loss 的 γ 调制过头,困难正样本被压得太狠,需要检查 γ 和 α 参数的平衡。

第三个坑是关键点回归震荡。训练曲线看起来关键点 loss 一直抖,多半是自动标注里的噪声太大。建议先把关键点损失权重降一点,让模型先稳住框,再逐步增加权重;或者预清洗标注数据。

最后还有个部署相关的坑:导出到移动端或推理引擎时,SSH 模块里的膨胀卷积算子可能没有高效实现,导致推理速度比预期慢很多。遇到这种情况,要么选择支持膨胀卷积的推理框架,要么在损失精度可控的前提下把膨胀卷积分支替换为标准卷积或剪枝掉。

我个人在使用 RetinaFace 做工程时最大的体会是:它的成功不单是某一个模块的胜利,而是"多任务监督、特征金字塔、上下文模块、额外标注"这一整套组合拳的结果。单独拆出任何一个部分,都未必出彩,但把它们放在一起,线程就盘活了。后续如果你要开发自己的检测器,完全可以把 RetinaFace 当成一个模板:先搭一个稳定可靠的检测基线,再用额外的结构监督去提升难例性能。这比一上来就设计复杂的新网络要靠谱得多。

希望这篇深度解读能帮你在读论文或者做项目时省一点时间。如果你在自己的数据集上也跑了 RetinaFace,欢迎拿实验结果来对一对,尤其是在 Hard 子集的提升幅度上,咱们可以聊聊各自的调参经验。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询