MMPose 手部关键点回归实战:RHD 数据集上基于 DeepPose + ResNet-50 的 Top-down 2D 姿态估计全解析

原创2026-09-16 23:27:121,141 阅读
文章标签:计算机视觉人工智能深度学习

MMPose 手部关键点回归实战:RHD 数据集上基于 DeepPose + ResNet-50 的 Top-down 2D 姿态估计全解析

MMPose 在 configs/hand_2d_keypoint/topdown_regression/ 目录下提供了基于回归范式的 Top-down 手部 2D 关键点检测方案,其中 RHD(Rendered Handpose Dataset)上的 ResNet-50 配置是理解"直接回归坐标"这一范式的最佳入口。本文以 resnet_rhd2d.md 为核心骨架,逐段拆解其完整训练配置、数据管线、模型结构与评估指标,并结合 regression_head.py 与 regression_label.py 等源码揭示底层实现原理。读完本文,你将能够独立复现 RHD 上手部关键点回归模型的训练与测试,并理解如何将同一配置迁移到 OneHand10K 等其他手部数据集。

一、文档背景:三种技术栈的组合

resnet_rhd2d.md 的第一部分通过三段 <details> 折叠块引用了支撑该配置的三篇基础工作,这也是该模型在 MMPose Model Zoo 中归属的三大技术来源:

  1. DeepPose(CVPR'2014)——由 Toshev 等人提出的"基于深度神经网络的人体姿态估计",开创了直接用全连接网络回归关键点坐标的范式。该配置中的 RegressionHead 正是这一范式的现代实现。
  2. ResNet(CVPR'2016)——He 等人提出的深度残差学习,作为特征提取主干。本配置使用 50 层版本(ResNet-50),并加载 torchvision 预训练权重。
  3. RHD(ICCV'2017)——Zimmermann 与 Brox 提出的"从单张 RGB 图像估计 3D 手部姿态"数据集,是评估手部关键点检测的标准化基准,提供 2D 与 3D 标注。

这三篇工作的 BibTeX 引用均保留在文档中,若在论文中复用该模型或数据集,可直接引用。该配置在 MMPose Model Zoo 中被归类于 "DeepPose" 集合(见 resnet_rhd2d.yml 中的 In Collection: DeepPose 字段)。

二、RHD 测试集上的核心结果

文档正文给出该模型在 RHD 测试集上的量化表现,这是理解配置效果的核心数据,完整继承如下:

Arch Input Size PCK@0.2 AUC EPE ckpt log
deeppose_resnet_50 256x256 0.988 0.865 3.32 ckpt log

对应说明:

  • 模型命名 td-reg_res50_8xb64-210e_rhd2d-256x256 中,"td-reg" 表示 Top-down 回归(topdown + regression),"8xb64" 表示 8 卡、每卡 batch size 64,"210e" 表示训练 210 个 epoch,"rhd2d" 表示数据集,"256x256" 为输入分辨率。
  • 三个评估指标分别衡量定位精度(PCK@0.2)、整体相似度(AUC)与平均像素误差(EPE),其具体含义见本文第六节。其中 AUC 还是 训练配置 中 checkpoint 保存的依据(save_best='AUC')。

三、训练配置逐段精解

该模型的完整配置位于 td-reg_res50_8xb64-210e_rhd2d-256x256.py,下面按功能块逐一解析。

3.1 运行期与优化器设置

_base_ = ['../../../_base_/default_runtime.py']

# runtime
train_cfg = dict(max_epochs=210, val_interval=10)

# optimizer
optim_wrapper = dict(optimizer=dict(
    type='Adam',
    lr=5e-4,
))
  • 配置继承自仓库基础的 default_runtime.py,复用其中默认的日志、checkpoint、visualizer 等运行期组件。
  • 训练 210 个 epoch,每 10 个 epoch 在验证集上评估一次。
  • 优化器使用 Adam,初始学习率 5e-4。手部关键点任务数据规模适中,Adam 配合较低学习率可稳定收敛。

3.2 学习率调度

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)
]

采用两段式调度:

  • 预热(warm-up):前 500 个 iteration(by_epoch=False 表示按 iteration 计),学习率从初始值的 0.1% 线性上升到目标值,避免训练初期不稳定。
  • 阶梯下降:在 epoch 170 与 200 处分别将学习率乘以 0.1(gamma),用于收敛后期精细调整。

此外配置了 auto_scale_lr = dict(base_batch_size=512),当实际训练总 batch size 与基准值 512 不同时,MMEngine 会自动按比例缩放学习率。

3.3 关键点编解码器(Codec)设置

# codec settings
codec = dict(type='RegressionLabel', input_size=(256, 256))

MMPose 3.x 将标签生成与结果解码抽象为 Codec 组件。本配置使用 RegressionLabel,其核心逻辑位于 mmpose/codecs/regression_label.py:

  • 编码(encode):将关键点坐标除以输入尺寸 [w, h],归一化到 [0, 1] 区间,得到 shape 为 (N, K, D)(N 为实例数、K 为关键点数、D=2 为坐标维度)的 keypoint_labels;同时依据坐标是否落在图像范围内且可见性大于 0.5,生成 0/1 的 keypoint_weights。
  • 解码(decode):将网络输出的归一化坐标乘以 [w, h] 还原到原图坐标空间,并返回默认全 1 的置信度分数。

这一"归一化—回归—反归一化"的闭环,是回归范式区别于热图范式(输出空间为热图)的本质特征。

3.4 模型结构

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='ResNet',
        depth=50,
        init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50'),
    ),
    neck=dict(type='GlobalAveragePooling'),
    head=dict(
        type='RegressionHead',
        in_channels=2048,
        num_joints=21,
        loss=dict(type='SmoothL1Loss', use_target_weight=True),
        decoder=codec),
    test_cfg=dict(
        flip_test=True,
        flip_mode='heatmap',
        shift_heatmap=True,
    ))

各部分职责:

  • TopdownPoseEstimator:Top-down 姿态估计器,接收检测框裁剪出的单实例图像块,输出该实例的关键点。
  • PoseDataPreprocessor:图像归一化,mean/std 使用 ImageNet 统计值,bgr_to_rgb=True 匹配 OpenCV 读取的 BGR 输入。
  • ResNet-50 主干:加载 torchvision://resnet50 预训练权重,在 ImageNet 分类任务上预训练的特征对手部外观具有良好迁移性。
  • GlobalAveragePooling 颈部:将 (B, 2048, 8, 8) 的特征图池化为 (B, 2048) 的全局特征向量,为全连接回归做准备。这是回归范式与热图范式的关键差异——热图范式需要保留空间分辨率,回归范式则压缩为全局向量。
  • RegressionHead:输入 2048 维特征,输出 num_joints × 2 = 42 维坐标。其 forward 实现极为精简(见 regression_head.py):x = torch.flatten(x, 1); x = self.fc(x); return x.reshape(-1, self.num_joints, 2),即单层全连接 nn.Linear(in_channels, num_joints * 2) 直接输出归一化坐标。
  • SmoothL1Loss:使用带目标权重的平滑 L1 损失,对离群值更鲁棒,use_target_weight=True 表示对不可见关键点的损失进行加权屏蔽。
  • flip_test 测试时增强:推理时同时前向原图与水平翻转图,将翻转结果的坐标按 flip_indices 映射回原图空间后取平均,提升预测稳定性。

3.5 RHD 数据集定义与 21 点关键点顺序

dataset_type = 'Rhd2DDataset'
data_mode = 'topdown'
data_root = 'data/rhd/'

Rhd2DDataset 实现于 mmpose/datasets/datasets/hand/rhd2d_dataset.py,继承自 BaseCocoStyleDataset,其元信息指向 configs/base/datasets/rhd2d.py。RHD 手部关键点共 21 个,定义如下:

0: 'wrist',         1: 'thumb4',      2: 'thumb3',
3: 'thumb2',        4: 'thumb1',      5: 'forefinger4',
6: 'forefinger3',   7: 'forefinger2', 8: 'forefinger1',
9: 'middle_finger4', 10: 'middle_finger3', 11: 'middle_finger2',
12: 'middle_finger1', 13: 'ring_finger4', 14: 'ring_finger3',
15: 'ring_finger2', 16: 'ring_finger1', 17: 'pinky_finger4',
18: 'pinky_finger3', 19: 'pinky_finger2', 20: 'pinky_finger1'

特别注意事项(元信息中明确标注):RHD 的手指关键点顺序是从指尖到手掌(tip to palm),而 COCO-WholeBody-Hand、FreiHand、CMU Panoptic HandDB 等数据集的顺序恰好相反。若要将 RHD 与其他手部数据集混合训练,必须先统一关键点顺序,否则会导致对应关系错乱。

3.6 数据管线(Pipeline)

训练与验证阶段使用不同管线:

train_pipeline = [
    dict(type='LoadImage'),
    dict(type='GetBBoxCenterScale'),
    dict(type='RandomFlip', direction='horizontal'),
    dict(
        type='RandomBBoxTransform', rotate_factor=180,
        scale_factor=(0.7, 1.3)),
    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')
]
  • 训练增强:水平随机翻转(RandomFlip)+ 旋转与缩放(RandomBBoxTransform,旋转范围 ±180°、尺度因子 0.7~1.3)。±180° 的旋转范围对手部这类可任意旋转的物体非常关键,也是手部任务与人体任务的重要差异。
  • TopdownAffine:根据 bbox 中心与尺度将图像仿射变换到 256×256 输入尺寸。
  • GenerateTarget:调用 encoder=codec(即 RegressionLabel)生成归一化坐标标签。
  • PackPoseInputs:打包为模型输入格式。

3.7 数据加载器与评估器

训练加载 annotations/rhd_train.json(batch size 64、打乱采样),验证与测试加载 annotations/rhd_test.json(batch size 32、不打乱),均使用 2 个 worker 与 persistent_workers=True。

评估器同时配置三个指标:

val_evaluator = [
    dict(type='PCKAccuracy', thr=0.2),
    dict(type='AUC'),
    dict(type='EPE'),
]
test_evaluator = val_evaluator

四、回归范式在源码中的落点

理解该配置最好的方式是在源码中追踪"回归"的完整链路,MMPose 的测试代码可帮助你验证理解:

  • 头(Head):regression_head.py 定义了 RegressionHead,其 docstring 明确说明"该头由全连接层组成,直接预测坐标,源自 DeepPose (Toshev et al., 2014)"。前向过程仅含 flatten + Linear + reshape 三步。
  • Codec:regression_label.py 的 encode/decode 实现坐标的归一化与还原,是训练标签与推理输出之间的转换枢纽。
  • 测试验证:tests/test_models/test_heads/test_regression_heads/test_regression_head.py 中 test_init 断言 head.fc.weight.shape == (17 * 2, 1024)(验证全连接层输出维度),test_predict 验证 keypoints 输出与 GT 形状一致,test_tta 验证 flip_test=True 时的测试时增强逻辑——这些单测直接印证了上文对结构与 TTA 行为的分析。
  • 标签编码测试:tests/test_codecs/test_regression_label.py 覆盖了 RegressionLabel 的归一化编码与解码还原逻辑。

五、训练与测试命令

数据准备:将 RHD 数据集按官方结构放置于 data/rhd/ 下,包含 annotations/rhd_train.json 与 annotations/rhd_test.json(具体下载与转换步骤参考 prepare_datasets.md)。

训练(完整命令语法参见 train_and_test.md):

python tools/train.py configs/hand_2d_keypoint/topdown_regression/rhd2d/td-reg_res50_8xb64-210e_rhd2d-256x256.py

多卡分布式训练:

bash tools/dist_train.sh configs/hand_2d_keypoint/topdown_regression/rhd2d/td-reg_res50_8xb64-210e_rhd2d-256x256.py 8

测试(需传入 checkpoint 路径):

python tools/test.py configs/hand_2d_keypoint/topdown_regression/rhd2d/td-reg_res50_8xb64-210e_rhd2d-256x256.py ${CHECKPOINT_FILE} --work-dir work_dirs/rhd2d

测试将输出 PCK@0.2、AUC、EPE 三项指标,与文档结果表对照即可验证复现效果。

六、三项评估指标解读

指标 全称 含义 数值说明
PCK@0.2 Percentage of Correct Keypoints 预测点与 GT 的距离小于阈值(此处为 0.2 × 手部尺寸)的关键点比例 0.988 表示约 98.8% 的关键点预测正确
AUC Area Under Curve 在归一化误差阈值序列上累积的 PCK 曲线下面积,综合反映不同精度下的表现 0.865,越高越好
EPE End-Point Error 所有关键点预测坐标与 GT 坐标的平均欧氏距离(像素) 3.32 像素,越低越好

三者从"达标率、整体曲线、绝对误差"三个角度刻画模型精度。由于 checkpoint 保存以 AUC 为准(save_best='AUC', rule='greater'),训练过程中会自动保留验证 AUC 最优的权重。

七、跨数据集迁移:与 OneHand10K 配置对照

同一套回归范式还提供了 OneHand10K 版本 td-reg_res50_8xb64-210e_onehand10k-256x256.py,与 RHD 配置相比仅有三处差异:

  1. dataset_type 改为 'OneHand10KDataset',data_root 改为 'data/onehand10k/',标注文件换为 onehand10k_train.json / onehand10k_test.json;
  2. 模型、优化器、调度器、管线、评估器完全相同——这正是"配置即模板"的体现;
  3. 其文档 resnet_onehand10k.md 记录的结果为 PCK@0.2 = 0.990、AUC = 0.485、EPE = 34.21。

两个数据集的 AUC 与 EPE 差异巨大(RHD 为 0.865 / 3.32,OneHand10K 为 0.485 / 34.21),但这不代表模型在 OneHand10K 上更差,而是数据集规模、难度与 bbox 尺寸定义不同所致——这也提醒读者在跨数据集对比指标时必须先对齐评估协议。两者结果汇总见 topdown_regression/README.md 中的 "Results and Models" 章节。

八、总结

resnet_rhd2d.md 看似只是一张结果表,但其背后是一套完整的、可复现的 Top-down 回归式手部关键点方案:

  • 范式层面:回归范式(DeepPose 路线)以 RegressionHead 的单层全连接直接输出归一化坐标,配以 RegressionLabel Codec 完成归一化编码与解码,相比热图范式省去了热图后处理,结构更简洁。
  • 配置层面:210 个 epoch、Adam + 5e-4、预热 + 阶梯下降、±180° 旋转增强、SmoothL1Loss、flip_test TTA,共同构成了手部回归任务的一套成熟超参数模板,可无缝迁移到 OneHand10K 等其他手部数据集。
  • 验证层面:文档结果表(PCK@0.2 = 0.988、AUC = 0.865、EPE = 3.32)与配置、源码、单测相互印证,保证了从理论到复现的闭环一致性。
登录后查看全文
mmpose