这几年在项目里只要跟姿态估计打交道“Stacked Hourglass Networks” 就绕不开。这篇 ECCV 2016 的论文我是先看的开源实现再回头读的原文说实话第一眼看到结构图的时候有点意外不就是把编码器-解码器串起来了吗为什么效果能好那么多后来真正把网络拆开把中间的监督信号加进自己的关键点模型里才明白它的巧妙之处不在于发明了什么新奇算子而是重新回答了姿态估计这类任务的输出形态问题——你用一张图去描述一个点还是用一个坐标去描述一个点差别远比想象中大。这篇笔记不打算把论文逐段翻译一遍而是按我理解的主线去拆为什么沙漏结构符合姿态估计的天然需求沙漏模块内部是怎么设计的堆叠多个沙漏到底多了什么能力以及训练和推理阶段有哪些容易被忽略的细节。适合刚接触人体姿态估计、想搞懂经典网络原理的读者也适合已经在做关键点检测、想从经典思路里找优化灵感的人。先放一段论文速览省得后面读迷糊项目内容出处ECCV 2016任务2D 人体姿态估计单人多人走自上而下流程核心结构串联堆叠的沙漏模块 中继监督输出表示每类关键点一张热力图概率图损失预测热力图与高斯目标热力图的均方误差常用训练分辨率256×256部分复现使用 384×384主要数据MPII、FLIC 等公开人体姿态数据集1. 为什么用“沙漏”从坐标回归到热力图的转变1.1 直接回归坐标听起来很美用起来处处是坑人体姿态估计要做的事简单说就是给定一张图找出肩膀、手肘、膝盖等关键点的位置。很多人第一次接触这个任务时的第一反应是这不就是一个回归问题吗让网络输出每个关键点的 (x, y) 坐标不就行了吗这个思路在早期工作里确实被试过直接把卷积网络提取的特征接到全连接层上输出一组坐标。理论上合理实际上问题不少。全连接层会把空间信息压成一个很短的向量相当于把一个二维图卷成一个没有位置概念的数字列表网络很难再去利用“旁边的像素长什么样”这个信息。关键点本身又具有很强的空间关联性比如左手肘大概率出现在左肩和左手腕连线的附近这种结构信息在坐标回归的框架里只能靠网络自己硬记一旦遇到遮挡、夸张姿态、非常规视角回归出来的结果就很容易漂。我自己的体验是坐标回归对小规模数据、简单姿态还行但只要数据里出现一点遮挡或者集外姿态错误就非常离谱经常出现手肘飞到画面外的情况。原因倒不是因为网络笨而是这个输出形式本身丢掉了空间一致性。1.2 一张热力图能装下多少信息论文选择了一个看上去更“重”的方案不回归坐标而是让网络输出一张和输入分辨率相当的热力图每个关键点对应一个通道通道上的高亮区域表示该关键点可能出现的位置。这其实是在预测“每个像素上是这个关键点的概率有多大”。热力图这个表示方式有两个直观好处。第一网络不需要把空间信息压扁再拉直它可以在保留图像拓扑结构的前提下做预测局部感受野里的纹理、边缘、遮挡关系都能直接被用来修正响应位置。第二多个关键点可以共享同一套底层特征不需要每个关键点单独训练一条分支通道之间还能隐式地建模关节之间的空间关系。这事我用一个生活类比去理解坐标回归像是让你闭着眼报出房间里桌子的坐标而热力图是让你睁着眼把桌子在户型图上涂出来。后者看起来费劲但对网络来说反而更自然因为它天然就是在图像这种栅格结构上做推理的。1.3 多尺度处理为什么是姿态估计的刚需还有一个问题被很多初学者忽略人的关节在不同尺度下呈现出的特征完全不同。看一张全身照脚踝可能只有十几个像素是典型的低分辨率小目标而脸部五官、手指这些位置需要足够大的分辨率才能看出细节。如果网络只在单一尺度上做特征提取必然会有顾此失彼的问题。过去的方法往往靠图像金字塔来处理尺度变化把图片缩放到多个尺寸分别跑网络再融合结果。这么做不仅计算量大而且不同尺度之间的信息没有交互网络没能形成全局的尺度感知能力。沙漏网络的做法是用一个模块把图像逐步压到低分辨率提取全局语义信息再逐步恢复到高分辨率同时在恢复过程中不断引入高分辨率细节。这样一个模块处理完后输出的特征图同时包含了小尺度和大尺度的信息。可以说多尺度融合不再是网络的外部开关而是结构内生的一部分。2. 沙漏模块的内部构造编码、解码与跳跃连接2.1 一个沙漏的前向过程其实很线性以 256×256 输入为例单个沙漏模块做的事情可以概括为四步。第一步通过卷积和池化把分辨率逐级降低例如从 256 降到 128、64、32、16、8。第二步在最低分辨率8×8 附近的位置用一组残差块对特征做深度加工让网络在这个“最懂全局语义”的尺度上想清楚整体结构。第三步从低分辨率开始逐级上采样每次把特征图放大一倍逐步恢复空间细节。第四步在恢复到 256×256 后经过一层输出卷积得到关键点的热力图。这个过程很像先把画面缩成一张草稿在草稿上想明白人体的大致构图然后再把细节一点一点填回去。严格说它就是一个带跳跃连接的编码器-解码器结构。2.2 残差块承担了“下采样”和“特征提炼”的双重任务沙漏模块里没有用什么花哨的卷积变体最基本的积木是残差块。残差块的好处不多说了核心就是让梯度能有一条高速公路从深层传回浅层网络加深之后不会劣化。在沙漏内部下采样不是一次性大跨度完成的而是逐步进行的每降一次分辨率都会经过若干残差块做特征提炼。这样每一层级都能学到该尺度下的模式例如浅层偏纹理和边缘深层偏语义和部件结构。低分辨率层的特征最终成为整张图全局布局判断的主要依据而高分辨率层特征则负责把细节精确度补上。2.3 上采样路径为什么不用转置卷积读这篇论文时我发现一个容易忽略的设计细节上采样时没有采用转置卷积而是用最近邻插值配合卷积来放大特征图。这一点放到今天看仍然很有讲究。转置卷积虽然可以学习上采样但它容易在特征图里制造不均匀的重叠痕迹也就是常说的棋盘效应而且参数量更大。对一个姿态估计任务来说我们最终关心的是关键点热力图的空间精度任何不自然的伪影都会干扰峰值位置的判断。最近邻插值本身不引入可学习的伪影放大后再接一个卷积层做特征变换既能学到上采样所需的语义信息又能保持空间上的干净。我第一次复现时还担心最近邻插值会不会太“粗”后来对比了一下发现至少在关键点定位这类逐像素精度敏感的任务里最近邻上采样加卷积的方案稳定且好用。2.4 跳跃连接不是什么新发明但位置很讲究沙漏模块的另一个关键是跳跃连接。网络在下采样过程中每一级都保存一份特征上采样时把对应的特征图拿回来和当前特征逐元素相加。这样上采样路径就不是单纯依赖低分辨率信息去“猜”细节而是直接参考了下采样那一层已有的高分辨率特征。这种设计在语义分割网络里已经很常见但放在姿态估计里有额外的意义。关键点定位既需要全局视野来判断“这是左手还是右手”“这条腿有没有被遮挡”又需要局部精度来锁定“手腕到底在哪一格像素”。跳跃连接让这两类信息在同一份特征里共存后续的卷积可以自主决定在多大程度上依赖局部还是全局证据。如果只从输出热力图的效果看去掉跳跃连接后模型不会立刻崩但关键点的边缘会模糊膝盖、手腕这类小关节的定位会明显变差。这是我在实际实验中反复验证过的事。3. 堆叠多级沙漏重复精化与中继监督的配合3.1 一个沙漏不够就多堆几个单个沙漏模块已经能输出不错的热力图但作者直接把多个沙漏串联起来使用论文里最常用的配置是堆叠 8 个沙漏。这个思路可以看作对“逐步精化”这一直觉的最直接执行。前一个沙漏输出的不是最终热力图而是作为后一个沙漏的输入特征。后面的沙漏可以在前一个结果的基础上继续修正错误比如第一次预测把左手腕偏到了右上角第二个沙漏看到整张图上下文后可能会把响应拉回到左手腕真正应该在的位置。这种串行结构让网络有机会对姿态估计做多次迭代而不是一次性拍板。我理解堆叠的真正意义在于网络不再需要在一个模块里解决所有问题每个沙漏可以分工前面的阶段负责找到大致的响应位置后面的阶段负责在已有响应附近精修。相当于两个人接力画素描第一个人画轮廓第二个人在轮廓基础上补细节第三个人再检查比例。3.2 中继监督解决的是深层网络的训练问题串了 8 个沙漏之后网络深度已经相当可观。如果只靠最终的损失来反向传播前面几个沙漏得到的梯度会非常微弱训练很难收敛这是很实际的工程问题。论文的解法是在每个沙漏的输出位置都计算一次热力图损失也就是中继监督。每个沙漏都被明确要求产出一份“现在我已经能初步猜测关键点在哪”的热力图。这样每一段网络都能直接从损失函数拿到梯度信号不需要等 8 个沙漏全部算完才能学到东西。这种设计还有一个训练上的好处中间监督相当于给网络提供了大量约束约束多了搜索空间就小了。损失函数不再只是一个终点目标而是遍布在网络的多个位置就像路标一样引导每一段网络都学习有意义中间表示。3.3 中间热力图会造成“过度自信”吗有人可能会问每个沙漏都给中间监督会不会让前面的沙漏过早锁定答案反而抑制后期修正能力从训练表现看这个问题并不严重。前面的沙漏在感受野有限的情况下也会产生合理的粗略预测但置信度通常较低、峰值较宽给后面留出了修正空间。我自己训练时观察到的现象是前几个沙漏的中间损失下降得很快但热力图还带着大量模糊高亮区域到第 5、6 个沙漏热力图开始变得尖锐峰值逐渐逼近真实位置最后一个沙漏的热力图已经是“干净的单峰”状态。这种从模糊到清晰的过程说明网络确实在逐级做空间上的精化而不是把前面的结果简单复制。如果去掉中继监督只保留最终损失堆叠深度超过 4 个沙漏时训练就会明显不稳定。这也是为什么今天很多姿态估计网络即便不采用完整沙漏结构也会在网络中段加上辅助监督层。4. 训练与推理阶段的实用细节4.1 目标热力图怎么生成训练数据给的是关键点的坐标但网络要学的是热力图。所以训练前需要把离散坐标转换成分布式的目标。标准的做法是以每个关键点为中心生成一张二维高斯分布图中心响应值最大越往外衰减越平缓。高斯核的半径需要根据任务调整半径太大热力图显得很糊网络学会的是模糊响应半径太小峰值太尖锐网络很难精准命中训练也容易震荡。经典配置里通常把高斯标准差设为几个像素量级具体值要看输入分辨率和标注噪声情况。我给新手一个实用的建议不要盲目照搬别人代码里的高斯半径先在训练集上画几张可视化热力图看目标峰值有没有过度重叠、背景区域是否干净。一个好的目标热力图应当主体清晰、分布平滑、峰值位置无歧义。4.2 网络输出与损失函数网络最终输出的通道数等于关键点数量加一个背景通道背景通道负责建模“这里不是任何关键点”的概率。如果只看关键点通道而不显式训练背景通道模型在非关键点部位也容易产生杂乱响应。论文的损失函数选用的是逐像素均方误差也就是把预测热力图和目标热力图当作两张图来计算差异。MSE 对像素级误差比较敏感且梯度计算简单配合热力图这种平滑多峰的监督信号可以稳定收敛。训练过程中建议看一眼各个通道的损失数值是否平衡。有些关节训练样本本身就少比如遮挡严重的脚踝、腕部它们的热力图通道损失会居高不下。如果发现某个关键点始终收不拢可以考虑在该通道上适当加大损失权重而不是盲目调小学习率。4.3 常用数据增强与优化配置姿态估计的数据增强几乎可以决定模型的天花板。论文和后来的复现实践中常用的增强手段包括随机旋转、随机缩放、水平翻转、颜色抖动等。旋转和缩放可以直接改变人体姿态出现的角度与尺度分布模拟真实场景里的变化。水平翻转尤其重要因为人体天然左右对称。翻转后关键点标签也要做对应映射例如左手腕变成右手腕的监督信号。理论上这样做等于把训练数据翻了一倍而且能让网络对左右侧的判别更加健壮。不过要注意关节的左右顺序映射不能写错这种低级错误在代码里非常容易埋雷。优化器方面经典配置多用 RMSProp。训练姿态估计这类输出为稠密热力图的网络调整学习率时不要看整体 loss 降得有多快要专门看小关节通道的 loss。如果小关节 loss 长期保持不变多半是学习率过大导致响应区域在“震荡”这时候降学习率比加迭代次数更有效。4.4 推理阶段的关键点提取与多人场景训练好模型后推理时拿到的是热力图还需要从热力图还原回关键点坐标。最简单的方式是找每个通道的最大值位置。如果追求亚像素精度可以在最大值附近做加权质心计算或者结合热力图峰值周围几个像素的响应做一个二次插值估计这样得到的坐标比直接取整更平滑。多人场景下沙漏网络本身是针对单人设计的不能直接对整张图输出所有关键点。标准的做法是自上而下的两阶段流程先用一个行人检测器框出每个人然后把每个检测框裁剪出来单独缩放后送入沙漏网络最后把预测出的关键点坐标映射回原图。这个流程在工程上非常常见优点是可以复用精细的单人姿态模型缺点是人多时推理次数会线性增长速度压力比较大。部署时你会发现输入分辨率对精度和速度的影响非常大。从 256×256 换成 384×384小关节精度会有明显提升但计算量可能翻一倍以上。我的习惯是先固定 256×256 跑通整个流程确认没有逻辑错误后再利用多尺度融合评估精度收益。5. 实验内容回顾效果验证与短板观察5.1 从论文实验里能看到什么论文在公开的人体姿态基准上验证了堆叠沙漏的效果主要的实验结论可以归纳成三点。第一即使只用一个沙漏模块热力图监督下的精度就已经超过同时期不少基于坐标回归的方法第二堆叠 8 个沙漏并配合中继监督后精度提升非常显著尤其在手肘、膝盖这类容易混淆的关节上第三中间监督带来的收益在浅层网络上不明显网络越深、沙漏数量越多中继监督的增益越大。我印象最深的实验是对中间热力图的可视化。训练完成后取出第 1、2 个沙漏的输出你会看到这些“早期预测”已经在正确区域附近出现响应甚至不需要额外训练一个分支就能得到粗糙的姿态估计。这说明中继监督让每段网络都真正学会了独立的姿态感知能力而不只是把特征传递到后段。5.2 这篇论文没有直接解决哪些问题再经典的论文也有边界。沙漏网络对单人裁剪场景表现很强但离开检测器它无法独立完成多人场景而且检测框质量会直接影响最终姿态精度检测框偏了关键点预测也跟着偏。沙漏网络的计算开销也是明显的短板。堆叠 8 个沙漏带来的是巨大的参数量和推理耗时在低功耗设备上基本不具备实时性。如果只看骨干网络的设计理念它的确很优雅放到应用场景里多数人还是会选择它的一众轻量化变体。另外对于严重的遮挡和自遮挡问题沙漏网络和其他视觉方法一样会失效。它本质上还是依赖图像外观线索没有显式建模人体骨骼的物理约束如果看不到腿就很难凭想象把膝盖定位准确。5.3 复现时最容易踩的坑复现这篇论文时我踩过比较多的坑集中在两个方面。一个是目标热力图的坐标变换训练时经常对图像做了随机旋转和缩放但标签坐标没有同步做仿射变换导致网络看到的热图目标和图像内容不一致损失看起来在降实际精度一直上不去。另一个是训练时的 batch size堆叠 8 个沙漏显存开销非常大如果卡显存有限不少人会选择调小 batch但 batch 一变小BatchNorm 的统计量就不稳定精度明显下降后来改成固定 batch 并配合梯度累积才稳住。中继监督权重也要注意。每个沙漏的损失权重默认相等是一种常见设置但如果前几层损失降得太快可能会让前段网络过于追求中间结果而忽略为后段提供更丰富特征。我试过给前几个沙漏设稍小的权重后几个沙漏保持完整权重整体精度会略好一点点但它属于调优细节不建议一开始就花太多精力。6. 今天重读这篇论文我带走的三样东西6.1 监督信号的“形状”比想象中重要这篇论文给我最深的启发是同样一个任务把输出从坐标改成热力图整个优化难度会发生质变。坐标回归丢掉了空间结构热力图保留了空间分布损失函数也能更自然地衡量“哪里错了”。今天很多关键点任务包括人脸关键点、手部关键点都已经默认使用热力图输出这个习惯就是从这里开始被大家广泛接受的。6.2 “串行精化”是一类通用方法论堆叠沙漏本质上是在做一个事情不指望一步到位而是把难任务拆成多段接力。这个思路可以迁移到很多任务里。比如在分割任务中后续模块可以逐步修正边界在目标检测里级联检测器不断筛掉假正例。很多时候我们觉得模型效果不够好不是网络结构不够深而是没有给网络“反复修正”的机会。6.3 简单结构的上限取决于监督信息的密度沙漏模块里的每个积木都不复杂残差块、最近邻上采样、跳跃连接、MSE 损失单独拿出来都算不上新颖。但作者通过堆叠多个模块并在每个模块都施加监督把简单的积木变成了一个高效的精化系统。这提醒我在设计模型时不要总想着发明新算子先想清楚监督信号能不能到达网络的每一处关键位置信号密度可能比网络宽度更影响最终效果。最后再分享一个实践上的小建议如果你自己负责的姿态估计项目还在起步阶段不要一上来就堆 8 个沙漏。先搭一个单沙漏模型把数据管线、热力图生成、坐标还原这些环节全部验证通再逐步加沙漏数量和中间监督。我见过太多项目是在“模型花里胡哨数据处理一塌糊涂”的状态下反复试错的。经典论文提供的是可以反复借鉴的方法论真正让它在业务里发挥作用的一定是数据和工程细节足够扎实。