MMPose 中的 RSN 骨干网络:残差步进网络与精细局部表示的多人体姿态估计实践
【免费下载链接】mmposeOpenMMLab Pose Estimation Toolbox and Benchmark.项目地址: https://gitcode.com/GitHub_Trending/mm/mmpose
导读
本文围绕 ECCV 2020 论文《Learning Delicate Local Representations for Multi-Person Pose Estimation》提出的Residual Steps Network(RSN,残差步进网络),结合 OpenMMLab MMPose 仓库中的真实源码与配置,系统讲解其核心思想(同尺寸层内特征聚合、Pose Refine Machine 注意力机制)、模块化实现(RSB / Downsample / Upsample / Multi-stage 结构)以及在 COCO 人体关键点任务上的完整训练、测试与调参方案。读完本文,你将掌握 RSN 的底层设计原理、如何在 MMPose 中复用其骨架与 MSPN 多头结构、如何理解并调整num_stages、num_blocks、kernel_sizes等关键超参数,并能直接复现仓库内置的 rsn18/rsn50/2xrsn50/3xrsn50 四套实验。
一、算法背景与论文核心思想
RSN 是字节跳动(ByteDance)团队在 2020 年发表于 ECCV 的多人姿态估计方法,论文作者为 Yuanhao Cai、Zhicheng Wang、Zhengxiong Luo 等人。该工作赢得了COCO Keypoint Challenge 2019 冠军,并在未使用额外训练数据和预训练模型的情况下,在 COCO 与 MPII 两个基准上取得了当时领先的结果。
论文原摘要指出(原文见 docs/src/papers/backbones/rsn.md):
In this paper, we propose a novel method called Residual Steps Network (RSN). RSN aggregates features with the same spatial size (Intra-level features) efficiently to obtain delicate local representations, which retain rich low-level spatial information and result in precise keypoint localization. Additionally, we observe the output features contribute differently to final performance. To tackle this problem, we propose an efficient attention mechanism - Pose Refine Machine (PRM) to make a trade-off between local and global representations in output features and further refine the keypoint locations.
其核心贡献可以归纳为两点:
精细局部表示(Delicate Local Representations):不同于常规 HRNet 式的"多分辨率并行"结构,RSN 着重于聚合空间尺寸相同的层内特征(Intra-level features)。通过多步残差聚合,保留丰富的低层空间信息,从而获得更精确的关键点定位能力。
Pose Refine Machine(PRM):论文观察到骨干输出的不同特征对最终性能的贡献不同,因此设计了一种轻量注意力机制,在输出特征的局部表示与全局表示之间做权衡(trade-off),进一步修正关键点位置。
论文报告的结果(来自原文档):单模型在 COCO test-dev 上达到78.6,在 MPII test 上达到93.0;集成模型在 COCO test-dev 上达到79.2,在 COCO test-challenge 数据集上达到77.1。
原始论文引用信息如下(源自 rsn.md,可供学术引用):
@misc{cai2020learning, title={Learning Delicate Local Representations for Multi-Person Pose Estimation}, author={Yuanhao Cai and Zhicheng Wang and Zhengxiong Luo and Binyi Yin and Angang Du and Haoqian Wang and Xinyu Zhou and Erjin Zhou and Xiangyu Zhang and Jian Sun}, year={2020}, eprint={2003.04030}, archivePrefix={arXiv}, primaryClass={cs.CV} }二、RSN 的模块化源码实现剖析
MMPose 将 RSN 完整实现为可注册的骨干网络,源码位于 mmpose/models/backbones/rsn.py(共 640 行),通过@MODELS.register_module()注册为'RSN',继承自BaseBackbone。整个实现由六个核心模块自底向上堆叠而成。
1. RSB(Residual Steps Block)——层内特征聚合的最小单元
RSB类定义在 rsn.py#L14-L125,是 RSN 与普通 ResNet 残差块最大的区别所在。其构造参数如下:
| 参数 | 默认值 | 说明 |
|---|---|---|
in_channels | 必填 | 输入通道数 |
out_channels | 必填 | 输出通道数 |
num_steps | 4 | RSB 内的步进数(step 数量),构造时断言> 1 |
stride | 1 | 残差块步长 |
downsample | None | 恒等分支上的降采样模块 |
with_cp | False | 是否启用 checkpoint(省显存) |
norm_cfg | dict(type='BN') | 归一化层配置 |
expand_times | 26 | 通道扩展倍数 |
res_top_channels | 64 | ResNet_top 输出的通道数,用于计算分支通道 |
其前向计算的核心逻辑(rsn.py#L93-L125)是"步进式"的层内聚合:
def forward(self, x): identity = x x = self.conv_bn_relu1(x) # 1x1 卷积将通道扩展为 num_steps * branch_channels spx = torch.split(x, self.branch_channels, 1) # 按步数切分为 num_steps 份 for i in range(self.num_steps): for j in range(i + 1): if j == 0: inputs = spx[i] else: inputs = outputs[i][j - 1] if i > j: inputs = inputs + outputs[i - 1][j] # 层内(同尺寸)特征逐步相加聚合 outputs[i].append(module_i_j(inputs)) outs.append(outputs[i][i]) out = torch.cat(tuple(outs), 1) # 各步输出拼接 out = self.conv_bn3(out) # 1x1 卷积还原通道 out = out + identity # 残差连接 out = self.relu(out) return out从源码结构可以推断:RSB 将输入在通道维切分成num_steps份,构建一个类似"阶梯"的计算图——第i步会聚合前i步的所有中间结果(同尺寸特征相加),最后将所有步的输出在通道维拼接并经1x1卷积融合。这正是论文中"聚合同空间尺寸特征以获得精细局部表示"的直接代码映射:多步内聚合让浅层空间细节得以保留并逐级细化。
注意branch_channels的计算方式:branch_channels = in_channels * expand_times // res_top_channels,默认expand_times=26、res_top_channels=64,当in_channels=64时,分支通道数为64 * 26 / 64 = 26,因此conv_bn_relu1输出num_steps * 26通道。
2. ResNet_top——输入降采样模块
ResNet_top(rsn.py#L499-L525)作为网络的入口,对输入图像做快速降采样:
self.top = nn.Sequential( ConvModule(3, channels, kernel_size=7, stride=2, padding=3, norm_cfg=norm_cfg, inplace=True), MaxPool2d(kernel_size=3, stride=2, padding=1))即一个7x7步长 2 的卷积加一个3x3步长 2 的 MaxPool,将输入分辨率降为原来的 1/4,并输出res_top_channels(默认 64)个通道。
3. Downsample_module——编码端(下采样单元组)
Downsample_module(rsn.py#L128-L239)由num_units(默认 4)个下采样单元组成,每个单元内部是若干RSB的堆叠(num_blocks列表控制每个单元内 RSB 数量),从第 2 个单元起步长设为 2 进行空间降采样,通道数按in_channels * pow(2, i)递增。
其前向(rsn.py#L228-L239)接收当前特征x以及上一级(上采样模块)回传的两组 skip 特征:
def forward(self, x, skip1, skip2): out = list() for i in range(self.num_units): x = module_i(x) if self.has_skip: x = x + skip1[i] + skip2[i] # 上一级上采样的跨级反馈 out.append(x) out.reverse() # 反转,供上采样端从小到大消费 return tuple(out)has_skip=True表示该阶段具备来自前一个上采样模块的跨阶段跳跃连接(即多阶段之间的粗到精细化信号),这正是 PRM 思想在结构上的体现之一。
4. Upsample_module / Upsample_unit——解码端(上采样单元组)
Upsample_module(rsn.py#L361-L432)由num_units个Upsample_unit(rsn.py#L242-L358)组成。每个Upsample_unit的关键行为(rsn.py#L335-L358):
in_skip:用1x1卷积对齐来自下采样端的通道数;- 当
ind > 0时,将上一个单元的输出up_x通过F.interpolate(..., mode='bilinear', align_corners=True)双线性上采样到当前尺寸并相加,形成逐级融合; - 当
gen_skip=True时,生成两组 skip 特征(out_skip1、out_skip2)回传给后续下采样模块; - 当
ind == num_units - 1且gen_cross_conv=True时,通过cross_conv生成跨阶段特征(供下一个阶段作为输入,同时也供头部使用)。
5. Single_stage_RSN——单阶段沙漏式结构
Single_stage_RSN(rsn.py#L435-L496)把 Downsample 与 Upsample 组装成一个完整的"下采样—上采样"阶段:
self.downsample = Downsample_module(RSB, num_blocks, num_steps, num_units, has_skip, norm_cfg, in_channels, expand_times) self.upsample = Upsample_module(unit_channels, num_units, gen_skip, gen_cross_conv, norm_cfg, in_channels)6. RSN——多阶段级联的整体骨架
顶层RSN类(rsn.py#L528-L640)的构造参数即配置文件可覆盖的全部口径:
| 参数 | 默认值 | 说明 |
|---|---|---|
unit_channels | 256 | 上采样单元内通道数 |
num_stages | 4 | 多阶段 RSN 的阶段数(断言> 0) |
num_units | 4 | 单个阶段内下/上采样单元数(断言> 1,且需等于len(num_blocks)) |
num_blocks | [2, 2, 2, 2] | 每个下采样单元内的 RSB 数量 |
num_steps | 4 | RSB 内步进数(断言> 1) |
norm_cfg | dict(type='BN') | 归一化配置 |
res_top_channels | 64 | ResNet_top 输出通道 |
expand_times | 26 | RSB 通道扩展倍数 |
init_cfg | Kaiming + Constant + Normal | 默认初始化策略(Conv2d 用 Kaiming,BN/GroupNorm 置 1,Linear 用 std=0.01 的 Normal) |
多阶段级联的关键逻辑在构造函数(rsn.py#L612-L628):
for i in range(self.num_stages): has_skip = (i != 0) # 首阶段无跨阶段 skip gen_skip = gen_cross_conv = (i != self.num_stages - 1) # 末阶段不再产生 skip / cross self.multi_stage_rsn.append(Single_stage_RSN(...))前向(rsn.py#L630-L640)将每个阶段的输出特征收集为out_feats列表返回,供MSPNHead消费:
def forward(self, x): out_feats = [] skip1 = skip2 = None x = self.top(x) for i in range(self.num_stages): out, skip1, skip2, x = self.multi_stage_rsni out_feats.append(out) return out_feats源码 docstring 给出了一个可验证的前向示例(rsn.py#L562-L576):RSN(num_stages=2, num_units=2, num_blocks=[2,2])对(1, 3, 511, 511)输入,会输出 4 组特征,形状分别为(1, 256, 64, 64)、(1, 256, 128, 128)、(1, 256, 64, 64)、(1, 256, 128, 128)——即每个阶段产生与num_units数量相同的多尺度特征。
三、配套头部:MSPNHead 与 PRM 注意力机制
RSN 骨架通常与MSPNHead(多阶段多单元热图头部,源自 MSPN 论文、被 RSN 复用)配合使用,实现在 mmpose/models/heads/heatmap_heads/mspn_head.py#L170。其类 docstring 明确指出该头部"introduced in Multi-Stage Pose estimation Network (MSPN) by Li et al (2019), and used by Residual Steps Networks (RSN) by Cai et al (2020)"。
MSPNHead 的关键参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
num_stages | 4 | 阶段数,需与 RSN 的num_stages一致 |
num_units | 4 | 每阶段单元数 |
out_shape | (64, 48) | 输出热图尺寸(H, W) |
unit_channels | 256 | 输入通道数 |
out_channels | 17 | 关键点类别数(COCO 人体为 17) |
use_prm | False | 是否启用 Pose Refine Machine(PRM) |
level_indices | [] | 每个 stage/unit 输出特征对应到哪一尺度的热图标签,长度须等于num_stages * num_units |
loss | KeypointMSELoss | 各 stage/unit 的损失配置(可为列表逐单元指定) |
decoder | None | 从网络输出解码关键点坐标的编解码器配置 |
其中use_prm正是论文中Pose Refine Machine注意力机制的开关(默认为False)。构造时会校验len(level_indices) != num_stages * num_units以及损失列表长度与num_stages * num_units的一致性(mspn_head.py#L225-L245),从源码结构可以推断:该头部为每个阶段、每个单元都维护独立的PredictHeatmap预测层与独立的损失模块,从而实现"多阶段多尺度监督"。
四、COCO 配置文件逐项精读
仓库为 RSN 提供了四套可直接运行的 COCO 配置,位于 configs/body_2d_keypoint/topdown_heatmap/coco/:
| 配置 | 骨架规模 | num_stages | num_blocks | 学习率 |
|---|---|---|---|---|
| td-hm_rsn18_8xb32-210e_coco-256x192.py | RSN-18 | 1 | [2, 2, 2, 2] | 2e-2 |
| td-hm_rsn50_8xb32-210e_coco-256x192.py | RSN-50 | 1 | [3, 4, 6, 3] | 5e-3 |
| td-hm_2xrsn50_8xb32-210e_coco-256x192.py | 2xRSN-50 | 2 | [3, 4, 6, 3] | 5e-3 |
| td-hm_3xrsn50_8xb32-210e_coco-256x192.py | 3xRSN-50 | 3 | [3, 4, 6, 3] | 5e-3 |
1. 训练运行时与优化器
所有配置统一继承_base_下的default_runtime.py,并约定max_epochs=210、val_interval=10;优化器统一使用 Adam,其中rsn18 的 lr 为2e-2,rsn50 及多阶段版本为5e-3(参数规模越大、学习率相应调小)。
学习率调度采用两段式(以 rsn18 为例):
param_scheduler = [ dict(type='LinearLR', begin=0, end=500, start_factor=0.001, by_epoch=False), # 前 500 iter 线性预热 dict(type='MultiStepLR', begin=0, end=210, milestones=[170, 190, 200], gamma=0.1, by_epoch=True) ]即先做 500 步迭代级线性 warm-up,再在 170/190/200 epoch(rsn50 系为 170/200)按gamma=0.1分步衰减。同时配置了auto_scale_lr = dict(base_batch_size=256),实际训练时框架会根据真实 batch size 自动等比缩放学习率;默认钩子default_hooks按coco/AP指标保存最优 checkpoint。
2. Megvii 多尺度热图编解码(codec)
RSN 系列使用 Megvii(旷视)式多尺度高斯热图编解码,对应实现为 mmpose/codecs/megvii_heatmap.py 中的MegviiHeatmap(通过KEYPOINT_CODECS注册)。该 codec 的关键点是kernel_size为多尺度高斯核列表:编码时对每个尺度热图执行cv2.GaussianBlur(heatmaps[k], kernel_size, 0)(megvii_heatmap.py#L100-L101),为不同层级的输出特征匹配不同粗细的高斯核:
- rsn18 / rsn50(单阶段):
kernel_sizes = [11, 9, 7, 5] - 2xrsn50 / 3xrsn50(多阶段):
kernel_sizes = [15, 11, 9, 7, 5](阶段变多、监督层级变多,多出一个更粗的15核)
统一配置为input_size=(192, 256)、heatmap_size=(48, 64),即输入 192x256,输出热图降采样 4 倍为 48x64。训练管线中通过dict(type='GenerateTarget', multilevel=True, encoder=codec)生成多层级监督目标,这正是 MSPNHead 中level_indices所索引的标签集合。
3. 骨架与头部配置(以 rsn50 单阶段为例)
model = dict( type='TopdownPoseEstimator', data_preprocessor=dict( type='PoseDataPreprocessor', mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], bgr_to_rgb=True), backbone=dict( type='RSN', unit_channels=256, num_stages=1, num_units=4, num_blocks=[3, 4, 6, 3], num_steps=4, norm_cfg=dict(type='BN'), ), head=dict( type='MSPNHead', out_shape=(64, 48), unit_channels=256, out_channels=17, num_stages=1, num_units=4, norm_cfg=dict(type='BN'), level_indices=[0, 1, 2, 3], loss=[ dict(type='KeypointMSELoss', use_target_weight=True, loss_weight=0.25) ] * 3 + [ dict(type='KeypointOHKMMSELoss', use_target_weight=True, loss_weight=1.) ], decoder=codec[-1]), test_cfg=dict( flip_test=True, flip_mode='heatmap', shift_heatmap=False, ))几个值得展开的细节:
level_indices的取值规律:单阶段(rsn18/rsn50)为[0, 1, 2, 3],对应 4 个单元的 4 个特征层;2 阶段为[0, 1, 2, 3] + [1, 2, 3, 4](第二个阶段的最深层输出对应第 5 级标签);3 阶段为[0, 1, 2, 3] * 2 + [1, 2, 3, 4]。- 混合损失设计:前 3 个单元使用
KeypointMSELoss(权重0.25),最后 1 个单元使用KeypointOHKMMSELoss(权重1.0)——即最终输出层用OHKM(在线难例挖掘 MSE)强化监督,中间层级用普通 MSE 辅助监督。多阶段版本把这一损失块整体乘以阶段数(* 2/* 3)。 - 测试增强:
flip_test=True、flip_mode='heatmap',即测试时对输入做水平翻转并融合两张热图;shift_heatmap=False。 - 精度优化:配置末尾统一开启
fp16 = dict(loss_scale='dynamic')动态损失缩放混合精度训练。
4. 数据管线与评测
数据部分为标准的 top-down 流程(data_mode='topdown'、CocoDataset、data_root='data/coco/')。训练管线依次为LoadImage→GetBBoxCenterScale→RandomFlip(horizontal)→RandomHalfBody→RandomBBoxTransform→TopdownAffine(input_size=192x256)→GenerateTarget(multilevel=True)→PackPoseInputs;验证/测试管线去掉增强并加入外部检测框文件COCO_val2017_detections_AP_H_56_person.json(人体检测器 AP 56.4 的检测结果)。评测器为CocoMetric,且因为该配置没有显式启用 OKS-NMS(nms_mode='none'),在复现指标时需保持该设置与结果表一致。
五、训练、测试与推理实战
1. 训练
使用仓库根目录的 tools/train.py 启动训练(单卡或多卡--launcher方式见仓库说明;8 卡环境可直接用8xb32语义复现):
python tools/train.py configs/body_2d_keypoint/topdown_heatmap/coco/td-hm_rsn50_8xb32-210e_coco-256x192.py多卡训练可参考 tools/dist_train.sh:
bash tools/dist_train.sh configs/body_2d_keypoint/topdown_heatmap/coco/td-hm_rsn50_8xb32-210e_coco-256x192.py 8训练前请确保已按 docs/zh_cn/user_guides/prepare_datasets.md 准备好 COCO 数据集(data/coco/下含annotations/person_keypoints_train2017.json等),并在data/coco/person_detection_results/放置验证用检测框文件。
2. 测试
使用 tools/test.py 在验证集上评测:
python tools/test.py configs/body_2d_keypoint/topdown_heatmap/coco/td-hm_rsn50_8xb32-210e_coco-256x192.py \ <checkpoint.pth> --out result.pkl训练过程中default_hooks.checkpoint会按save_best='coco/AP'自动保存最优权重;离线测试时传入该权重即可复现下表指标。CocoMetric会输出 AP / AP50 / AP75 / AR 等完整指标。
3. 推理
最快捷的方式是使用 MMPose 的 Inferencer 加载任一 RSN 配置与权重做单人图片推理,例如 demo/inferencer_demo.py 配合 COCO 全身/人体姿态模型;若需"检测器 + top-down 姿态估计"的完整多人流程,可参考 demo/topdown_demo_with_mmdet.py 与 demo/mmdetection_cfg/ 下的人体检测器配置。
六、COCO 基准结果与模型选型建议
仓库在 configs/body_2d_keypoint/topdown_heatmap/coco/rsn_coco.md 中公布了 COCO val2017(检测器人体 AP 56.4)上的复现结果,原文数值如下:
| Arch | Input Size | AP | AP50 | AP75 | AR | AR50 |
|---|---|---|---|---|---|---|
| rsn_18 | 256x192 | 0.704 | 0.887 | 0.781 | 0.773 | 0.927 |
| rsn_50 | 256x192 | 0.724 | 0.894 | 0.799 | 0.790 | 0.935 |
| 2xrsn_50 | 256x192 | 0.748 | 0.900 | 0.821 | 0.810 | 0.939 |
| 3xrsn_50 | 256x192 | 0.750 | 0.900 | 0.824 | 0.814 | 0.941 |
选型建议(基于上述实测数据的合理推断):
- 追求性价比:单阶段 RSN-18 即可达到 AP 0.704,显存占用和训练成本最低,适合快速验证与轻量部署;
- 单模型精度优先:RSN-50 比 RSN-18 提升约 2.0 AP(0.704 → 0.724),是精度/成本平衡点;
- 多阶段堆叠收益递减:2 阶段 RSN-50 提升约 2.4 AP(0.724 → 0.748)幅度最大,3 阶段仅再提升 0.2 AP(0.748 → 0.750),而参数量与显存继续翻倍——若算力受限,2 阶段是性价比较高的配置。这恰好与论文"输出特征对最终性能贡献不同"的观察一致,也是 PRM 机制要解决的问题。
七、RSN 的使用限制与调参注意事项
- 阶段一致性约束:
RSN.num_stages、MSPNHead.num_stages必须保持一致,且num_units == len(num_blocks);MSPNHead.level_indices的长度必须等于num_stages * num_units(源码中有显式校验,见 mspn_head.py#L225-L228),修改阶段数时务必同步更新这两处。 - 多阶段监督标签:阶段数增加时,
MegviiHeatmap的kernel_sizes建议同步扩展(仓库在 2/3 阶段配置中加入了15的粗高斯核),并为每个阶段在loss列表中复制对应损失块。 - 显存与学习率:多阶段 RSN 显存开销接近线性增长,若显存不足可优先尝试降低
batch_size并依靠auto_scale_lr自动缩放学习率,或探索启用with_cp(checkpoint 重计算)节省显存。 - 编解码器依赖:RSN 系列依赖
MegviiHeatmap多尺度热图编解码与MSPNHead的多层级监督设计,直接替换为普通MSRAHeatmap+HeatmapHead会丢失其设计意图;如需完整复现论文设定,建议保留仓库配置的组合。 - PRM 开关:
MSPNHead.use_prm参数(默认False)提供了论文中 Pose Refine Machine 的实现入口,仓库内置的四套 COCO 配置未显式开启,读者可在自定义实验中将其置为True进行消融对比。
八、延伸阅读
- 骨架完整实现:mmpose/models/backbones/rsn.py
- 配套头部实现:mmpose/models/heads/heatmap_heads/mspn_head.py
- 编解码器实现:mmpose/codecs/megvii_heatmap.py
- 全部 RSN COCO 配置与结果:configs/body_2d_keypoint/topdown_heatmap/coco/
- 训练与测试入口:tools/train.py、tools/test.py、tools/dist_train.sh
- 官方结果表:configs/body_2d_keypoint/topdown_heatmap/coco/rsn_coco.md
【免费下载链接】mmposeOpenMMLab Pose Estimation Toolbox and Benchmark.项目地址: https://gitcode.com/GitHub_Trending/mm/mmpose
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考