MMPose 中的 CPM 卷积姿态机:COCO 自上而下热图模型配置、训练与实现原理全解析
【免费下载链接】mmposeOpenMMLab Pose Estimation Toolbox and Benchmark.项目地址: https://gitcode.com/GitHub_Trending/mm/mmpose
导读
本文围绕 MMPose 仓库中 CPM(Convolutional Pose Machines,卷积姿态机)在 COCO 人体关键点数据集上的自上而下(Top-Down)热图配置展开,完整介绍configs/body_2d_keypoint/topdown_heatmap/coco/cpm_coco.md收录的两套官方训练配置、其 COCO val2017 上的评估结果,以及CPM骨干网络与CPMHead多阶段热图头的源码级实现原理。读完本文,你将掌握如何在 MMPose 中复现 CPM 的 COCO 训练、读懂其每层网络结构、理解多阶段中间监督与 MSRA 热图编解码机制,并学会独立修改配置以获得不同输入分辨率下的行为。
一、CPM 配置文档速览:官方成果表与引用信息
1.1 算法与数据集出处
configs/body_2d_keypoint/topdown_heatmap/coco/cpm_coco.md在头部以<details>折叠块的形式引用了两个文献:
- 算法:CPM 论文 "Convolutional Pose Machines",Wei, Shih-En 等人,发表于 CVPR 2016;
- 数据集:COCO "Microsoft COCO: Common Objects in Context",Lin 等人,发表于 ECCV 2014。
两个条目都附带了可直接复制的 BibTeX 引用,方便你在论文中正确引用该模型与数据来源。该 md 文件同时是 模型索引 的元数据来源,后者登记了两套配置的 Architecture、Training Data、Task、各项指标与权重下载地址。
1.2 COCO val2017 官方评测结果
文档记录了在检测器人体 AP 为 56.4的前提下(即使用COCO_val2017_detections_AP_H_56_person.json提供的检测框),CPM 在 COCO val2017 上的评估指标,两套配置分别对应 256×192 与 384×288 两种输入尺寸:
| Arch | Input Size | AP | AP^50 | AP^75 | AR | AR^50 | ckpt | log |
|---|---|---|---|---|---|---|---|---|
| cpm | 256x192 | 0.627 | 0.862 | 0.709 | 0.689 | 0.906 | ckpt | log |
| cpm | 384x288 | 0.652 | 0.865 | 0.730 | 0.710 | 0.907 | ckpt | log |
对比可见,输入分辨率从 256×192 提升到 384×288 后,AP 由 0.627 提升至 0.652(+2.5 AP),但训练批次从 8×64 调整为 8×32,以换取更大的显存开销。这也是选择配置时最核心的权衡点:分辨率越高精度越好,但显存与训练成本也越高。
二、配置文件逐段解析:td-hm_cpm_8xb64-210e_coco-256x192.py
CPM 在 COCO 上的训练配置位于configs/body_2d_keypoint/topdown_heatmap/coco/td-hm_cpm_8xb64-210e_coco-256x192.py(另一套 384×288 配置为configs/body_2d_keypoint/topdown_heatmap/coco/td-hm_cpm_8xb32-210e_coco-384x288.py,两者除分辨率、批大小与sigma外完全一致)。下面以 256×192 版本为线索逐段拆解。
2.1 基础运行时与训练总览
_base_ = ['../../../_base_/default_runtime.py'] # runtime train_cfg = dict(max_epochs=210, val_interval=10)- 继承自 configs/base/default_runtime.py,其中包含了默认钩子(LoggerHook、CheckpointHook 每 10 个 epoch 保存一次、PoseVisualizationHook 默认关闭、BadCaseAnalysisHook 默认关闭)、
SyncBuffersHook、本地可视化后端LocalVisBackend、env_cfg(mp_start_method='fork')等公共运行时配置。 - 训练总时长210 个 epoch,每 10 个 epoch 做一次验证。
2.2 优化器与学习率调度
# optimizer optim_wrapper = dict(optimizer=dict( type='Adam', lr=5e-4, )) # learning policy param_scheduler = [ dict( type='LinearLR', begin=0, end=500, start_factor=0.001, by_epoch=False), # warm-up dict( type='MultiStepLR', begin=0, end=210, milestones=[170, 200], gamma=0.1, by_epoch=True) ] # automatically scaling LR based on the actual training batch size auto_scale_lr = dict(base_batch_size=512)- 使用Adam 优化器,初始学习率5e-4。
- 学习率策略分为两段:前500 个 iteration使用
LinearLR做 warm-up,起始学习率为 0.001 倍;之后切换为MultiStepLR,在第 170 与 200 个 epoch处各衰减 0.1 倍。 auto_scale_lr = dict(base_batch_size=512)表示基准批大小为 512,实际训练时若批大小与 512 不同,MMEngine 会自动按比例缩放学习率,便于在单卡/多卡环境下复现官方指标。
2.3 关键点编解码器:MSRAHeatmap
# codec settings codec = dict( type='MSRAHeatmap', input_size=(192, 256), heatmap_size=(24, 32), sigma=2)CPM 配置采用MSRA 高斯热图编解码器,其实现位于 mmpose/codecs/msra_heatmap.py:
input_size=(192, 256):模型输入图像尺寸(宽 192,高 256);heatmap_size=(24, 32):输出热图尺寸,为输入的 1/8;sigma=2:高斯核标准差。
在编码阶段(encode),MSRAHeatmap将关键点坐标除以scale_factor(输入尺寸与热图尺寸之比)后,通过generate_gaussian_heatmaps生成 (K, H, W) 的高斯热图与 (N, K) 的 keypoint_weights(源码);在解码阶段(decode),则对热图取最大值位置,再经refine_keypoints(一阶梯度精修)得到亚像素坐标,最后乘以scale_factor还原到输入图像空间(源码)。
细节:当
unbiased=True时该编解码器会走 DarkPose 的generate_unbiased_gaussian_heatmaps与refine_keypoints_dark分支;CPM 的官方配置未开启该选项,保持unbiased=False的经典 MSRA 方式。
2.4 模型组装:TopdownPoseEstimator + CPM + CPMHead
# model settings 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='CPM', in_channels=3, out_channels=17, feat_channels=128, num_stages=6), head=dict( type='CPMHead', in_channels=17, out_channels=17, num_stages=6, deconv_out_channels=None, final_layer=None, loss=dict(type='KeypointMSELoss', use_target_weight=True), decoder=codec), test_cfg=dict( flip_test=True, flip_mode='heatmap', shift_heatmap=True, ))关键字段说明:
TopdownPoseEstimator:自上而下姿态估计器,先由检测器给出人体框,再对每个框内的人体做关键点回归;PoseDataPreprocessor:ImageNet 均值和标准差归一化,bgr_to_rgb=True表示输入图像按 BGR 读取、送入网络前转换为 RGB;backbone:CPM,输入 3 通道、输出 17 通道(COCO 17 个关键点)、特征通道 128、共6 个 stage;head:CPMHead,in_channels=17、out_channels=17、num_stages=6,deconv_out_channels=None与final_layer=None表示不额外添加反卷积上采样层与最后的 1×1 卷积层(因为 CPM 骨干自身已直接输出热图),每个 stage 均使用KeypointMSELoss(use_target_weight=True)计算损失,decoder直接复用上文的codec;test_cfg:开启flip test(水平翻转测试增强),flip_mode='heatmap'表示对热图做翻转融合,shift_heatmap=True表示翻转后按关键点对称索引进行偏移校正(实现见 CPMHead.predict)。
2.5 数据流水线(pipeline)
# pipelines train_pipeline = [ dict(type='LoadImage'), dict(type='GetBBoxCenterScale'), dict(type='RandomFlip', direction='horizontal'), dict(type='RandomHalfBody'), dict(type='RandomBBoxTransform'), dict(type='TopdownAffine', input_size=codec['input_size']), dict(type='GenerateTarget', encoder=codec), dict(type='PackPoseInputs') ] val_pipeline = [ dict(type='LoadImage'), dict(type='GetBBoxCenterScale'), dict(type='TopdownAffine', input_size=codec['input_size']), dict(type='PackPoseInputs') ]- 训练流水线在
LoadImage与GetBBoxCenterScale之后依次应用:水平RandomFlip、RandomHalfBody(有概率只保留一半身体的关键点,增强局部遮挡鲁棒性)、RandomBBoxTransform(对检测框做随机缩放/平移/旋转)、TopdownAffine(按输入尺寸做仿射变换)、GenerateTarget(用codec生成热图标签)、PackPoseInputs(打包为模型输入)。 - 验证流水线不含任何随机增强,且
GenerateTarget由PackPoseInputs取代——标签只在训练时需要。
2.6 数据加载器与评测器
train_dataloader = dict( batch_size=64, num_workers=2, persistent_workers=True, sampler=dict(type='DefaultSampler', shuffle=True), dataset=dict( type=dataset_type, data_root=data_root, data_mode=data_mode, ann_file='annotations/person_keypoints_train2017.json', data_prefix=dict(img='train2017/'), pipeline=train_pipeline, )) val_dataloader = dict( batch_size=32, num_workers=2, persistent_workers=True, drop_last=False, sampler=dict(type='DefaultSampler', shuffle=False, round_up=False), dataset=dict( type=dataset_type, data_root=data_root, data_mode=data_mode, ann_file='annotations/person_keypoints_val2017.json', bbox_file='data/coco/person_detection_results/' 'COCO_val2017_detections_AP_H_56_person.json', data_prefix=dict(img='val2017/'), test_mode=True, pipeline=val_pipeline, )) test_dataloader = val_dataloader val_evaluator = dict( type='CocoMetric', ann_file=data_root + 'annotations/person_keypoints_val2017.json') test_evaluator = val_evaluator- 数据根目录为
data/coco/,使用CocoDataset、data_mode='topdown'; - 训练集批大小 64(8 卡即 8×64),验证集批大小 32;
- 验证/测试使用
COCO_val2017_detections_AP_H_56_person.json作为人体检测框来源——这正是文档中"检测器人体 AP 为 56.4"那句说明的出处; - 指标由
CocoMetric计算,输出 COCO 的 AP/AR 系列指标;配置文件顶部还设置了default_hooks = dict(checkpoint=dict(save_best='coco/AP', rule='greater')),即以验证集 AP 为准保存最优权重。
三、源码级原理:CPM 骨干网络的 6 阶段结构
CPM骨干网络实现在 mmpose/models/backbones/cpm.py,由三部分组成:
3.1 Stem(第一阶段网络)
self.stem = nn.Sequential( ConvModule(in_channels, 128, 9, padding=4, norm_cfg=norm_cfg), nn.MaxPool2d(kernel_size=3, stride=2, padding=1), ConvModule(128, 128, 9, padding=4, norm_cfg=norm_cfg), nn.MaxPool2d(kernel_size=3, stride=2, padding=1), ConvModule(128, 128, 9, padding=4, norm_cfg=norm_cfg), nn.MaxPool2d(kernel_size=3, stride=2, padding=1), ConvModule(128, 32, 5, padding=2, norm_cfg=norm_cfg), ConvModule(32, 512, 9, padding=4, norm_cfg=norm_cfg), ConvModule(512, 512, 1, padding=0, norm_cfg=norm_cfg), ConvModule(512, out_channels, 1, padding=0, act_cfg=None))- 以 9×9/5×5/1×1 卷积配合三次 3×3 stride=2 的 MaxPooling 下采样,最终在
stem末尾直接输出第一个 stage 的 17 通道热图(act_cfg=None表示最后一层无激活); - 网络结构与论文一致:初始阶段即可给出一个粗糙的关键点预测。
3.2 Middle 分支(中继特征提取)
self.middle = nn.Sequential( ConvModule(in_channels, 128, 9, padding=4, norm_cfg=norm_cfg), nn.MaxPool2d(kernel_size=3, stride=2, padding=1), ConvModule(128, 128, 9, padding=4, norm_cfg=norm_cfg), nn.MaxPool2d(kernel_size=3, stride=2, padding=1), ConvModule(128, 128, 9, padding=4, norm_cfg=norm_cfg), nn.MaxPool2d(kernel_size=3, stride=2, padding=1))middle分支从原始输入单独提取与stem同空间分辨率的特征(同样三次下采样),用于在后续 stage 中与前一 stage 的热图拼接,为网络补充原始图像信息。
3.3 多阶段循环(Stage 2 ~ 6)
self.cpm_stages = nn.ModuleList([ CpmBlock( middle_channels + out_channels, channels=[feat_channels, feat_channels, feat_channels], kernels=[11, 11, 11], norm_cfg=norm_cfg) for _ in range(num_stages - 1) ]) self.middle_conv = nn.ModuleList([ nn.Sequential(ConvModule(128, middle_channels, 5, padding=2, norm_cfg=norm_cfg)) for _ in range(num_stages - 1) ]) self.out_convs = nn.ModuleList([ nn.Sequential( ConvModule(feat_channels, feat_channels, 1, padding=0, norm_cfg=norm_cfg), ConvModule(feat_channels, out_channels, 1, act_cfg=None)) for _ in range(num_stages - 1) ])forward中每个 stage 的执行逻辑为(源码):
inp_feat = torch.cat([out_feats[-1], self.middle_convind], 1) cpm_feat = single_stage(inp_feat) out_feat = out_conv(cpm_feat) out_feats.append(out_feat)即:把上一个 stage 的输出热图与降维后的middle特征拼接(维度为middle_channels + out_channels),经CpmBlock(3 层 11×11 卷积,源码)与out_convs(1×1 卷积)输出新的 17 通道热图。每个 stage 的输入都显式包含上一阶段的热图,这正是"姿态机"(Pose Machine)通过结构化上下文逐步精修预测的核心思想,也是它对相邻关键点空间关系建模的基础。
最终forward返回一个包含6 张热图的列表(shape 均为 (1, 17, H/8, W/8)),每个 stage 一张,全部送入CPMHead参与损失计算。
四、源码级原理:CPMHead 的多阶段中间监督
CPMHead实现在 mmpose/models/heads/heatmap_heads/cpm_head.py,它接收骨干网络输出的多张 stage 热图并完成两件事:训练时的多阶段损失计算与测试时的关键点解码。
4.1 前向与测试时的翻转增强
forward断言特征数量等于num_stages,并对每个 stage 依次施加可选的反卷积层与最终卷积层(本配置两者均为None,退化为恒等映射,直接透传骨干输出)。测试阶段predict的核心逻辑为:
- 开启
flip_test时,将原图与水平翻转图的热图按flip_indices对称翻转后取平均(shift_heatmap=True校正偏移); - 只取最后一个 stage的热图
multi_stage_heatmaps[-1]进行decode(早期 stage 只参与训练监督,不参与最终预测); - 若
output_heatmaps=True,还会以PixelData形式返回热图。
4.2 多阶段损失叠加
loss方法对每个 stage 的热图分别与同一个 GT 热图计算KeypointMSELoss并累加(源码):
for i in range(self.num_stages): loss_i = loss_func(multi_stage_pred_heatmaps[i], gt_heatmaps, keypoint_weights) if 'loss_kpt' not in losses: losses['loss_kpt'] = loss_i else: losses['loss_kpt'] += loss_i- 这种多阶段中间监督(Intermediate Supervision)是 CPM 的训练关键:即使前几个 stage 预测不够准,其热图也会被强制对齐到 GT,避免深层网络梯度消失并加速收敛;
- 每个 stage 可共享同一个损失模块(默认),也可通过传入长度等于
num_stages的loss列表为不同 stage 配置不同损失(源码中显式校验了长度一致性); - 损失函数为 KeypointMSELoss,
use_target_weight=True表示不同关键点按其可见性权重加权,遮挡/未标注关键点的梯度贡献被压低; - 损失之外,
loss方法还会基于最后一个 stage 的热图与 GT 计算pose_pck_accuracy作为训练中的acc_pose指标。
4.3 关于 deconv 与 final_layer 的灵活设计
CPMHead的构造函数支持:
deconv_out_channels为None时各 stage 使用nn.Identity(),不做反卷积上采样(本配置即如此,因为 CPM 骨干的输出分辨率已是 1/8 且空间信息足够);传元组(如(32, 32))时则为每个 stage 构建kernel=4/3/2、stride=2 的反卷积栈(源码支持 kernel 4→padding 1、kernel 3→output_padding 1、kernel 2→padding 0 三种映射);final_layer为None时同样退化为恒等;为 dict(如dict(kernel_size=1))时构建 1×1 卷积映射到out_channels。
单元测试 tests/test_models/test_heads/test_heatmap_heads/test_cpm_head.py 分别验证了 w/o deconv、w/ deconv、w/o final layer、w/ decoder 及多 stage 损失叠加等分支,可作为理解这些选项行为的最小示例。
五、COCO 数据准备与训练、测试实战
5.1 数据准备
训练需要按 COCO 官方目录结构放置数据:
data/coco/ ├── annotations/ │ ├── person_keypoints_train2017.json │ └── person_keypoints_val2017.json ├── train2017/ ├── val2017/ └── person_detection_results/ └── COCO_val2017_detections_AP_H_56_person.json- 关键点标注来自
annotations/,图像来自train2017/与val2017/; - 验证/测试必需的人体检测框结果文件
COCO_val2017_detections_AP_H_56_person.json(对应文档中"detector having human AP of 56.4")需要按 数据准备指南(中文见 docs/zh_cn/user_guides/prepare_datasets.md)预先准备。
5.2 训练
单机多卡训练使用 tools/dist_train.sh:
bash tools/dist_train.sh configs/body_2d_keypoint/topdown_heatmap/coco/td-hm_cpm_8xb64-210e_coco-256x192.py 8如需 384×288 配置,将配置路径替换为configs/body_2d_keypoint/topdown_heatmap/coco/td-hm_cpm_8xb32-210e_coco-384x288.py。单卡可用 tools/train.py,以 Slurm 集群运行时则用 tools/slurm_train.sh。训练时auto_scale_lr会按实际批大小相对 512 自动缩放学习率,因此单卡(batch=64)训练时学习率会被相应调小。
5.3 测试与指标复现
bash tools/dist_test.sh configs/body_2d_keypoint/topdown_heatmap/coco/td-hm_cpm_8xb64-210e_coco-256x192.py \ work_dirs/td-hm_cpm_8xb64-210e_coco-256x192/best_coco_AP_epoch_210.pth 8- 测试默认在 COCO val2017 上计算
CocoMetric的 AP/AR 指标; - 由于配置了
save_best='coco/AP',训练后最优权重会以best_coco_AP_epoch_*.pth形式保存在 work_dirs 下,用它进行测试即可复现文档表格中的指标。
5.4 推理与部署
下载文档表格中提供的 ckpt 后,可使用 demo/inferencer_demo.py 快速推理:
python demo/inferencer_demo.py tests/data/coco/000000000785.jpg \ td-hm_cpm_8xb64-210e_coco-256x192 \ --draw-heatmap --pred-out-dir vis_results其中td-hm_cpm_8xb64-210e_coco-256x192是 模型索引 中登记的模型名,推理器会自动解析配置并加载对应权重;--draw-heatmap可同时可视化最后 stage 的热图。端到端的人体检测+关键点流程可参考 demo/docs/en/2d_human_pose_demo.md。
六、两种分辨率配置对比与调参建议
| 配置 | 输入尺寸 | 批大小 | sigma | AP | AP^50 | AP^75 |
|---|---|---|---|---|---|---|
| td-hm_cpm_8xb64-210e_coco-256x192.py | 256×192 | 64 | 2 | 0.627 | 0.862 | 0.709 |
| td-hm_cpm_8xb32-210e_coco-384x288.py | 384×288 | 32 | 3 | 0.652 | 0.865 | 0.730 |
值得注意的是两套配置中sigma并非不变:256×192 对应sigma=2,384×288 对应sigma=3。这是因为高斯核的绝对尺寸应随热图分辨率(24×32 vs 36×48)等比放大,编码器在 mmpose/codecs/msra_heatmap.py 中按scale_factor = input_size / heatmap_size缩放坐标,而 sigma 需要手工按分辨率匹配。修改输入分辨率时,应遵循以下经验:
input_size与heatmap_size保持 8:1 的比例(如 256×192→32×24,384×288→48×36);- 按分辨率增大适度调大
sigma(官方 256×192→2、384×288→3 即为基准); - 分辨率提升后显存需求显著上升,需按显存余量下调 batch size 或卡数;
- 保持
flip_test、shift_heatmap、RandomHalfBody等数据与推理策略不变,可稳定获得与官方一致的精度收益。
七、从源码结构看 CPM 与同类算法的定位
从仓库结构看,CPM 属于 configs/body_2d_keypoint/topdown_heatmap/coco 目录下自上而下热图法家族的一员,与 Hourglass、HRNet、MSPN 等并列。它的特点可以概括为:
- 多阶段串行精修:6 个 stage 逐级细化热图,每一级都显式拼接上一级预测,梯度由中间监督直达浅层(backbone);
- 无需反卷积上采样:骨干末尾直接输出 1/8 分辨率热图,
CPMHead在本配置中保持恒等映射,整体结构简洁、参数量少; - 轻量实用:以 256×192 输入即取得 COCO val2017 AP 0.627 的可用精度,适合作为基线模型验证数据增强、损失设计等改进手段(仓库内 hrnet_augmentation_coco、resnet_dark_coco 等即展示了同框架下其他算法的同类实验)。
如需在生产环境中追求更高精度,可横向对比仓库内 HRNet(hrnet_coco)、Lite-HRNet、ViTPose 等方案;如需在边缘设备部署,可参考 部署指南(中文见 docs/zh_cn/user_guides/how_to_deploy.md)对 CPM 这类轻量模型做转换加速。
结语
本文完整还原了 configs/body_2d_keypoint/topdown_heatmap/coco/cpm_coco.md 中 CPM 在 COCO 上的官方成果,并从配置、骨干网络、多阶段头、编解码器、训练测试全链路给出源码级解析。CPM 虽然诞生于 2016 年,但其多阶段姿态机思想在 MMPose 的现代工程化实现中依旧清晰可读,是理解自上而下热图法、中间监督训练与翻转测试增强的上手模型。基于上述配置与源码路径,你可以直接复现官方指标,并在此基础上进一步扩展实验。
【免费下载链接】mmposeOpenMMLab Pose Estimation Toolbox and Benchmark.项目地址: https://gitcode.com/GitHub_Trending/mm/mmpose
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考