最近在复现人体姿态估计的经典模型时,绕不开这篇ECCV 2016的论文《Stacked Hourglass Networks for Human Pose Estimation》。哪怕放到现在,stacked hourglass的思路依然在影响很多关键点检测方法——从单人姿态估计到人脸关键点、手部骨架,甚至一些视觉自监督任务都会借鉴这套“先降维再升维、反复迭代修正”的结构。如果你准备在单张图像上做人体关键点检测,或者想搞明白为什么很多姿态估计网络都自带一个“沙漏形状”,这篇笔记值得你花几分钟看完。
我当时是带着三个疑问去读的:为什么非要把图像缩得很小再放大,而不是各尺度分开跑?为什么同一个沙漏模块要堆好几次,一次不行吗?中间监督到底是怎么帮网络收敛的?论文本身写得不算长,但很多关键设计藏在实验和补充材料里,自己上手复现时也踩了不少坑。这篇文章就把我的论文笔记和实操心得一起梳理清楚,重点看结构设计、训练细节、评价指标,以及复现过程中真正容易出问题的几个环节。
1. 论文到底解决了什么问题
1.1 单人姿态估计的任务与难点
单人姿态估计的任务是:给定一张已经框好一个人的图像,输出这个人身体上若干个关键点的位置,常见的有头部、肩膀、肘部、手腕、髋部、膝盖、脚踝等。论文里的标准做法是预测每个关键点对应的热图(heatmap),而不是直接回归坐标。
为什么用热图而不是直接输出多个数值?因为空间位置信息在卷积网络里是以“空间分布”的形式保留下来的。直接回归坐标相当于把所有空间信息压缩成两个数字,网络很难学准;而热图把每个关键点的位置建模成一个高斯峰,网络只需要判断这个峰最可能落在哪个区域,训练起来稳定得多,后续要做多尺度融合、遮挡推理也更自然。
难点的核心在于“上下文信息”。一个典型例子:手臂和躯干颜色相近、或者手被身体挡住时,单看局部区域根本分不清哪块是手肘,必须看整条手臂的走向、肩和腕的相对位置,甚至结合对面半身的对称关系才能推断。而为了精确到像素级,网络又需要保留原始分辨率上的细节。既要全局视野,又要局部精度,这就是姿态估计最本质的矛盾。
1.2 沙漏形状的动机:多尺度空间信息的融合
沙漏模块的设计目标,就是让网络同时拥有“全局视野”和“局部精度”。看看沙漏的形状,一个完整的沙漏模块先通过一系列卷积和下采样把特征图分辨率逐步降低,过程中通道数逐渐增加,让网络在低分辨率层面看到整个人的全局结构;然后再通过上采样把分辨率逐步恢复,最终回到输入分辨率附近的尺寸。
这个形状看起来像一个沙漏,论文标题里的Hourglass就是这么来的。下采样阶段感受野不断变大,网络能看到的人和场景范围越来越广;上采样阶段再把高层语义逐步传回每个局部位置。还有一点容易被忽略:沙漏模块中间的跨层连接(skip connection)。在下采样路径上,每一层分辨率都保留了一份特征图,上采样时把对应分辨率的特征图直接加回来。这样高分辨率特征和低分辨率语义特征在每一级都做了融合,而不是等到最后才一次性混合。
1.3 堆叠与中间监督:让网络反复“检查修正”
单个沙漏模块已经能在一定程度上输出关键点热图了,那为什么要堆好几个?论文给出的思路是:姿态推理本来就是一个迭代过程。第一次沙漏得到的预测可能只是粗略的初步估计,把这一步的热图结果和特征一起送到下一个沙漏里,网络就能在上一轮结果的基础上继续修正,比如发现左右肩膀的位置关系不合理,就根据全局姿势约束把偏差纠正过来。可以类比成干细活:先整体看一遍布局,再上前修正局部错误,再退远看一遍整体,反复几次,最后的结果才靠谱。
但堆叠更深,梯度在长路径里衰减会很明显。论文引入了中间监督,也就是在每一个沙漏模块的输出位置都接一次损失计算,让每个模块都受到真实热图的监督信号。这条路从“只在最后监督一次”变成“每一步都有人告诉你对错”,梯度可以更直接地传到每个模块内部,训练明显更稳。我之后复现时也对比过,去掉中间监督,网络并不是完全训不动,而是收敛更慢、最终精度掉一截,特别是堆到四个以上沙漏时差别非常明显。
2. 模型结构拆解与关键参数
2.1 基础单元:Residual模块
沙漏内部的基础卷积单元是类似ResNet的残差块。常见的结构是:1x1卷积先降通道,3x3卷积做空间特征变换,再用1x1卷积恢复通道,中间接BatchNorm和ReLU,最后把输入加到输出上。论文里的实现基本上就是这个套路,只是针对不同分辨率做了适配。
残差结构的好处是让深层网络更容易优化,梯度可以跨层短路传播。放在沙漏结构里还有一个好处:反复堆叠多个沙漏时,整个主干非常深,如果没有残差连接,训练时前层的更新信号会非常弱。虽然代码里不一定写的名字是“Residual”,但几乎所有开源复现都是拿残差块当作基本积木,我在自己搭的时候也沿用了这一套,训练稳定性比直接用普通卷积+ReLU好很多。
2.2 单沙漏模块的自底向上与自顶向下路径
单沙漏模块的工作过程可以分为三个阶段。
第一阶段是自底向上(下采样路径)。输入特征图经过若干残差块,让通道数翻倍、分辨率减半。论文里比较常见的做法是下采样到四个不同尺度,最低分辨率约为输入尺寸的1/4到1/8左右。这样做网络不断获得更大感受野,但特征图越来越小、越来越抽象。
第二阶段是自顶向下(上采样路径)。把最低分辨率的特征图逐步上采样回原始分辨率,每上采样一次,就把下采样路径中保存的同一分辨率特征图通过残差块处理后相加。这一步非常关键,如果不加跳层连接,直接在上采样基础上生成热图,高分辨率空间细节会丢很多,关键点的定位精度会明显变差。用了跳层连接之后,每一级上采样都同时拿到了“语义清晰的低分辨率特征”和“细节丰富的高分辨率特征”。
第三阶段是在沙漏模块末尾再接几个残差块处理融合后的特征,然后用1x1卷积把特征图映射成热图输出。热图的通道数等于关键点数量,每个通道对应一个关键点的预测分布。到这里,一个沙漏模块的输出就成形了。
2.3 中间热图与下一个沙漏的输入
堆叠式设计里,沙漏模块不是仅仅输出最终热图就算完,而是要把当前模块的热图预测也拼到特征里,作为下一个模块的输入条件之一。实际操作上,很多开源实现会先做一次1x1卷积把热图转成与特征图匹配的通道数,再和主特征逐元素相加或拼接。
中间监督的热图比较特殊:它也会继续往上传递到下一个沙漏,但不会简单地把真实值直接塞进去,网络自己预测出来的热图会更自然。因为预测热图里的高斯峰位置不一定完全准确,峰周围还有响应扩散,这种“带噪声”的中间状态反而给下一轮推理提供了修正线索。最开始我理解成“把GT热图作为下一级输入”,复现出来精度并不好;后面读代码才发现是预测热图参与循环,这才明白为什么中间监督既训练了网络,又给级联提供了上下文。
2.4 分辨率、通道数与堆叠层数的选择
论文里默认输入是固定尺寸的单人图像,通常以人为中心裁剪后缩放到256x256左右,输出热图的分辨率常用64x64。也就是说,网络整体做了4倍下采样,这个分辨率对关键点定位来说够用,又不会让热图计算量和内存爆炸。如果输出分辨率设置成原始256,显存开销会大好几倍,收益却非常有限。
关于堆叠数量,作者在实验中对 1、2、4、8 个沙漏做了对比,结果很明显:沙漏越多精度越高,但提升幅度越来越小,训练时间也越长。一般复现时选2到4个沙漏就够刷出接近论文结果的水平,8个沙漏需要更充分的训练和更大的显存。通道数方面,网络最早阶段通常是64或128通道,越靠近低分辨率路径通道越多,这样能保证深层语义表达足够丰富。
下面是复现时特别值得关注的几个结构参数:
| 参数 | 常见选择 | 说明 |
|---|---|---|
| 输入图像尺寸 | 256x256 | 以人为中心裁剪并缩放 |
| 热图输出尺寸 | 64x64 | 4倍下采样,对应关键点定位精度 |
| 沙漏数量 | 2~8 | 越多越好,但训练成本和显存更高 |
| 基础通道数 | 64/128 | 控制模型容量,显存紧张时优先缩减 |
| Residual块结构 | 1x1 -> 3x3 -> 1x1 | 通道降升,加BN与ReLU |
3. 训练细节与评价指标
3.1 数据集与预处理
论文实验主要用了两个数据集:FLIC和MPII。FLIC关键点相对少一些,图像多来自电影截图;MPII覆盖面更广,包含日常活动、运动场景,单人和多人都有。日常复现时MPII是最常用的基准之一,因为标注更规范,评测协议也统一。
预处理阶段,最简单的做法是把图像按检测框中心裁剪成正方形,再缩放到256x256。但不建议直接拉伸,因为拉伸会改变身体比例,影响关键点的空间位置关系,更好的做法是等比例缩放并填充边缘。数据增强方面,论文和常见的复现都会用随机旋转、随机缩放、水平翻转。旋转范围一般在[-30度, 30度]之间,缩放范围在[0.75, 1.25]左右。水平翻转相当于把训练样本翻倍,并且天然增加了对称部位的学习样本量。
还有一个细节是归一化。图像输入到网络之前要做减均值除方差,特征的尺度才稳定。这步很多人会漏掉,导致前期损失波动很大。
3.2 高斯热图的生成与损失计算
生成每个关键点的真值热图时,图里对应位置放置一个二维高斯峰,峰值在关键点中心,向周围平滑衰减。高斯半径(sigma)是复现时一个很关键的参数,如果设置太大,真值热图变得模糊,网络学的目标本身就不精确;如果设置太小,正样本区域太窄,网络很难学到稳定的响应。经验上,sigma取输出热图尺寸的百分之二到百分之四比较好,比如64x64的输出,sigma取1到2.5之间比较常用。
损失函数用的是预测热图和真值热图之间的均方误差(MSE),等价于对每个关键点单独计算L2损失再累加。没有用复杂的加权策略,这个简单损失就能让网络训得很好,因为热图分布的峰值信息本身就隐含了“关键点在哪”的强监督。训练时通常一个batch里聚合所有关键点的损失回传。
3.3 评价指标PCK与PCKh
姿态估计常用的评测指标是PCK(Percentage of Correct Keypoints),意思是预测关键点落在真实关键点一定范围内的比例。关键在于“范围怎么定义”。如果固定像素范围,对不同尺度的人不公平,所以论文用了与人体尺寸相关的阈值。MPII上用到的是PCKh,阈值取“头部尺寸的一定比例”,PCKh@0.5表示预测点与真实点的距离不超过头部连接线长度的50%。
这个细节一定不能搞错。很多人复现时把阈值写错,导致跑出来的分数明显偏低或偏高。计算PCKh时,需要先读取每个人物的头部边界框或头部长宽信息,再算对应距离阈值。测试时的最终输出也要经过坐标转换:从热图预测峰值位置,再把坐标映射回原始图像尺寸。
3.4 优化器与训练配置经验
论文训练阶段使用了RMSProp这类自适应学习率优化器,初始学习率一般在2.5e-4左右,并配合逐步衰减。我在复现时测试过Adam和RMSProp,结论是RMSProP在这类热图回归任务上更容易稳定收敛,因为它的梯度累积方式适合稠密回归任务。
Batch size受显存影响很大。输入256x256,输出64x64,堆叠4个沙漏时,单卡训练通常只能放8到16个样本,需要梯度累积来模拟更大batch。训练轮数方面,论文通常训练到100多轮,具体看数据集大小。如果训练过程出现损失不降,可以先检查学习率是否过高、热图sigma是否合理,不要一上来就改网络结构。
4. 复现过程中的常见问题与排查
4.1 高斯峰定位不准的根源
最典型的问题:训练好久,损失降到一个水平就卡住,测试时关键点总是偏移几个像素。我排查下来,最常见原因有两个,一个是热图sigma设得过大,另一个是输入图像裁剪导致关键点坐标与热图坐标没对齐。
坐标对齐的坑很隐蔽。训练阶段把原图裁剪缩放后,关键点坐标也要跟着做同样的变换;很多人在写数据加载时只对图像做变换,忘了对坐标做一样的归一化,或者旋转中心没对齐,导致真值热图错位。遇到这种情况,可以在训练前可视化几张样本的热图,把关键点画在图像上看看是否对应准确,基本一眼就能发现问题。
4.2 中间监督的权重与堆叠深度怎么配
中间监督不是加得越多就越好,权重也要调。我的经验是每个沙漏模块的损失权重保持相同或逐渐递减都行,但不要出现中间权重远大于最终输出的情况。如果发现前几个沙漏收敛很快、最后一个沙漏输出反而不准,通常就是最终输出的梯度被中间监督稀释了,可以把最终输出的损失权重适当调大一点。
堆叠深度增加后,另一个不稳定因素是批归一化在训练和推理时的差异。堆叠模块多时,每一模块都会引入新的BN参数,如果训练时每个batch的统计量波动大,推理时结果会抖。解决办法是训练时用足够大的batch或累积BN统计量,或者在推理前固定BN参数。
4.3 测试时增强与输出处理
测试时只拿原始图像跑一遍,往往不是精度最优的做法。论文和多数开源实现都会做测试时增强,典型操作是水平翻转和多尺度预测。具体做法是把原图和水平翻转后的图分别输入网络,得到两组热图,翻转热图后再按通道对应位置取平均,尺度方面可以用多个分辨率输入再平均结果。
有一点必须注意:翻转后关键点的左右顺序是反的,直接平均会把错误对应起来。正确做法是先把翻转热图按通道重排,把左肘变成右肘、右肘变成左肘,再与原始热图平均。这个bug我踩过不止一次,最开始测试分数一直上不去,检查半天发现是通道没重排。
4.4 常见问题速查表
| 现象 | 可能原因 | 解决建议 |
|---|---|---|
| 损失不降 | 学习率过高或过低 | 尝试梯度裁剪或把学习率下调一个量级 |
| 关键点整体偏移 | 坐标变换未对齐 | 可视化训练样本热图与图像叠加检查 |
| 左右关键点混淆 | 翻转增强后未重排通道 | 翻转热图按关键点顺序重排后再平均 |
| 显存不足 | 堆叠沙漏过多 | 减少沙漏数或降低基础通道数 |
| 预测热图响应模糊 | sigma过大或中间监督权重过低 | 缩小高斯半径或调整监督权重 |
| 测试分数偏低 | 阈值计算或归一化方式错误 | 对照评估协议重新检查PCKh阈值定义 |
4.5 调试时的独家技巧
我调试这类模型时习惯把热图预测直接可视化,不是只看loss曲线。在训练过程中周期性保存几张原图和叠加了预测热图的输出,这样能直观看到定位误差是整体性偏移、某一关节特差,还是峰值散乱。整体偏移通常是坐标变换问题;某一关节特差往往是该关键点的标注噪声大或训练样本不均衡;峰值散乱则说明感受野还不够,可以考虑增加堆叠次数。
另外,刚开始跑小规模实验时,没必要直接上8个沙漏。先用2个沙漏配上较小输入尺寸把整个流程跑通,确认训练管线没有问题,再逐步加大模型规模和分辨率。这样调试效率高很多,也避免因为一个参数写错浪费一整天训练时间。
5. 论文的后续影响与应用落地参考
5.1 后续的姿态估计方法如何借鉴沙漏
这篇论文提出后,“沙漏模块+中间监督”的组合在很多后续工作里反复出现,哪怕是近几年基于Transformer的姿态估计方法,在CNN backbone阶段也经常借鉴这种多尺度融合思路。一些高分辨率网络(High-Resolution Net)其实也是在保持高分辨率分支的同时,加并行低分辨率分支,本质上都是想解决“既要全局又要局部”的问题。
对我个人来说,最值得借鉴的不是具体的模块代码,而是设计思路:多尺度特征不是简单把所有分辨率拼接起来,而是通过逐级上采样与跳层连接,让网络每一层都能在完整的多尺度语义下做决策。这个思想后来也被我用到了别的关键点任务上,包括人脸关键点检测和手指关键点估计,效果都非常直接。
5.2 做实际项目时该怎么选模型
如果项目场景是单人的姿态估计,对实时性要求不高,比如离线动作分析、姿态教学质量评估,直接用沙漏网络或者其轻量变体是很稳妥的选择,训练代码成熟、各种开源实现多、问题排查资料也多。MC如果需要在移动端或嵌入式设备上跑,就要考虑剪枝、量化或者替换成轻量级网络,因为沙漏堆叠的参数量和计算量都不算小。
在工程落地时还有一个经验:不要只盯着PCKh看,要在实际业务数据上测试。论文数据集比较干净,人物基本完整、背景相对简单;真实业务里可能有遮挡、多人交错、画面畸变等情况。可以先在目标场景采集一批贴标注的数据做微调,测试时结合目标跟踪或目标检测框来裁剪输入区域,这样比直接在公开模型上硬跑要靠谱得多。
5.3 后续可以尝试的扩展方向
堆叠沙漏的理念稍微扩展一下就能做很多事情。比如,把沙漏结构用于视频姿态估计时,可以额外引入时序模块,让网络参考前后帧的信息,抑制单帧抖动。也可以把中间监督换成更丰富的任务,比如同时预测部位分割图或骨骼连接图,通过多任务学习进一步提升关键点定位精度。
另外一个我试过比较有意思的做法是把沙漏特征用于注意力建模:把沙漏模块中间层的多尺度特征作为自注意力机制的输入条件,帮助Transformer类模型更快关注到人物关键区域。这种组合在一些数据集上比单独用沙漏或单独用Transformer效果更好,代价是训练时多一份显存开销;不过思路是通的,值得一试。
结尾:个人几点真实体会
反复读这篇论文和自己复现之后,我最大的感受是:论文的核心创新点拆开看都不复杂,难的是“堆叠+中间监督+多尺度融合”这三个东西合在一起形成的系统效应。单看任何一个模块,换掉好像也差不多;但真到训练深模型时,每一个设计都有它存在的理由。尤其是中间监督,一开始我觉得只是工程技巧,后来才发现它实际上是整个深沙漏结构能否训练起来的必要条件。
最后再分享一个小技巧:如果你打算在校验集上快速验证不同沙漏数量的效果,可以先用一个小数据集训练并固定一组超参,只改变堆叠层数,观察PCKh变化趋势。这样能帮你判断当前任务对迭代式推理的依赖有多强,也更容易找到精度和资源消耗的平衡点。若某个任务用2个沙漏就能达到要求,就完全没必要追求8个沙漏的复杂度。毕竟实际落地时,稳定、可控、可复现,比盲目刷高一个点更有价值。
套用一句我自己常和身边的人说的话:复现经典论文不是背代码,而是把作者踩过的坑重新踩一遍,然后变成自己的直觉。这篇Stacked Hourglass论文,值得每一个做关键点检测的人这样去读一遍。