MMPose 手部关键点检测实践:HRNetv2-w18 + UDP 在 OneHand10K 上的配置与原理全解析
【免费下载链接】mmposeOpenMMLab Pose Estimation Toolbox and Benchmark.项目地址: https://gitcode.com/GitHub_Trending/mm/mmpose
本篇文章围绕 MMPose 仓库中configs/hand_2d_keypoint/topdown_heatmap/onehand10k/hrnetv2_udp_onehand10k.md这一模型卡片展开,完整解析 HRNetv2-w18 主干网络结合 UDP 无偏数据编解码的 2D 手部姿态估计方案,并给出在 OneHand10K 数据集上的验证结果、完整可运行的训练配置,以及底层 codec 的源码级原理。读完本文,你将掌握该 topdown 热力图方案的配置结构、UDP 编解码细节与训练评估方法,可直接在 MMPose 中复现该模型。
一、模型卡片概览:一次读懂"算法 + 数据集 + 配置"三要素
该模型卡片位于 configs/hand_2d_keypoint/topdown_heatmap/onehand10k/hrnetv2_udp_onehand10k.md,属于 MMPose 中 top-down heatmap 系列的手部关键点模型。它明确引用了三篇核心工作:
| 组成部分 | 论文 | 出处 |
|---|---|---|
| 主干网络 | HRNetv2(Deep High-Resolution Representation Learning for Visual Recognition) | TPAMI'2019 |
| 编解码 | UDP(The Devil is in the Details: Delving Into Unbiased Data Processing for Human Pose Estimation) | CVPR'2020 |
| 数据集 | OneHand10K(Mask-pose Cascaded CNN for 2D Hand Pose Estimation from Single Color Image) | TCSVT'2019 |
该配置对应的模型是td-hm_hrnetv2-w18_udp-8xb64-210e_onehand10k-256x256,完整模型定义与训练参数位于 td-hm_hrnetv2-w18_udp-8xb64-210e_onehand10k-256x256.py。从文件命名即可读出核心要素:td-hm(top-down heatmap 范式)、hrnetv2-w18(18 通道宽度的 HRNetv2)、udp(UDP 编解码)、8xb64(8 卡 × batch size 64)、210e(210 个 epoch)、256x256(输入分辨率)。
OneHand10K 验证集上的表现
原模型卡片给出的 OneHand10K val 集结果如下:
| Arch | Input Size | PCK@0.2 | AUC | EPE | ckpt | log |
|---|---|---|---|---|---|---|
| pose_hrnetv2_w18_udp | 256x256 | 0.990 | 0.571 | 23.88 | 见 hrnetv2_udp_onehand10k.yml | 见 hrnetv2_udp_onehand10k.yml |
三个指标的含义如下:
- PCK@0.2:以关键点所在图像区域尺寸的 20% 为阈值,预测点与真值距离小于阈值的比例,取值 0~1,越高越好。此处 0.990 表示 99% 的关键点预测误差落在该阈值内;
- AUC:不同 PCK 阈值下正确率的曲线下面积,越高越好,此处 0.571;
- EPE(End Point Error):所有关键点预测位置与真值的平均欧氏距离(像素),越低越好,此处 23.88。
作为横向参照,同一数据集上其他配置的对比记录在 configs/hand_2d_keypoint/topdown_heatmap/README.md:HRNetv2-w18 + Dark 为 PCK@0.2=0.990 / AUC=0.572 / EPE=23.96,纯 HRNetv2-w18 为 0.990 / 0.567 / 24.26,ResNet-50 为 0.989 / 0.555 / 25.16,MobileNet-v2 为 0.986 / 0.537 / 28.56。UDP 相对基础 MSRA 编解码在 AUC 与 EPE 上均有改善,印证了"无偏数据处理"带来的坐标精度收益。
二、UDP 无偏编解码原理:从配置文件到源码实现
UDP 的核心思想是消除传统 heatmap 方法中由坐标取整、仿射变换与量化引入的系统性偏差。在 MMPose 中它由 codec 模块实现,配置文件里只声明一行:
# codec settings codec = dict( type='UDPHeatmap', input_size=(256, 256), heatmap_size=(64, 64), sigma=2)2.1 关键参数说明
| 参数 | 配置值 | 含义 |
|---|---|---|
type | UDPHeatmap | codec 类型,在 mmpose/codecs/udp_heatmap.py 中注册 |
input_size | (256, 256) | 网络输入图像尺寸[w, h] |
heatmap_size | (64, 64) | 热力图分辨率[W, H],即 4 倍下采样 |
sigma | 2 | 高斯热力图标准差,默认 2.0 |
heatmap_type | 'gaussian'(默认) | 编码方式,可选'gaussian'或'combined'(偏移图 + 二值标签图) |
radius_factor | 0.0546875(默认) | 'combined'模式下正样本区域半径因子 |
blur_kernel_size | 11(默认) | DarkPose 式热力图调制的高斯模糊核大小 |
2.2 编码(encode):无偏高斯热力图生成
UDPHeatmap.encode接收输入图像坐标系下的关键点坐标,先按下式将坐标从输入空间映射到热力图空间:
self.scale_factor = ((np.array(input_size) - 1) / (np.array(heatmap_size) - 1)).astype(np.float32)注意这里使用的是(input_size - 1) / (heatmap_size - 1),即保留了边界坐标与像素中心对齐关系,这正是 UDP 与普通线性映射的关键差异之一。随后调用 generate_udp_gaussian_heatmaps 生成高斯热力图。与朴素实现相比,UDP 的高斯生成在整数网格上保留了亚像素偏移信息:
mu = (keypoints[n, k] + 0.5).astype(np.int64) # 整数化后的中心 ... mu_ac = keypoints[n, k] # 亚像素精确中心 x0 = y0 = gaussian_size // 2 x0 += mu_ac[0] - mu[0] # 高斯核内偏移量 y0 += mu_ac[1] - mu[1] gaussian = np.exp(-((x - x0)**2 + (y - y0)**2) / (2 * sigma**2))即高斯核的中心不是被四舍五入到整数像素,而是在核内以亚像素精度偏移,从而消除"坐标取整"引入的训练目标偏差。超出边界的点会将其keypoint_weights置 0,避免无意义监督。
2.3 解码(decode):最大响应定位 + 分布感知精修
UDPHeatmap.decode完成推理时的坐标恢复:
- 通过 get_heatmap_maximum 在每张热力图上取最大响应位置作为初始预测;
- 调用 refine_keypoints_dark_udp 进行分布感知(distribution-aware)精修:先对热力图做高斯模糊调制并取对数,再在峰值附近用数值差分构造一阶导数与 Hessian 矩阵,通过
keypoints -= inv(H) @ grad一步定位到连续坐标下的真实极值,将输出精度推进到亚像素级别; - 最后按
keypoints / [W-1, H-1] * input_size将坐标映射回输入图像空间。
这一解码过程在实现上与 DarkPose 的refine_keypoints_dark同源,但针对 UDP 的像素中心对齐约定做了适配,且在实现细节上采用了 edge padding 与向量化的einsum求逆,兼顾了数值稳定性与效率。
三、数据集与标注:OneHand10K 的 21 点手部骨架
OneHand10K 是一个大规模单目 2D 手部关键点数据集。MMPose 中其加载逻辑由 mmpose/datasets/datasets/hand/onehand10k_dataset.py 中的OneHand10KDataset类实现,它继承自BaseCocoStyleDataset,即标注采用 COCO 风格 JSON,并通过METAINFO = dict(from_file='configs/_base_/datasets/onehand10k.py')引用数据集元信息。
数据集元信息定义在 configs/base/datasets/onehand10k.py,包含完整的 21 点骨架定义(索引从 0 到 20):
| 索引 | 关键点 | 索引 | 关键点 |
|---|---|---|---|
| 0 | wrist(手腕) | 11 | middle_finger3(中指第 3 节) |
| 1 | thumb1(拇指第 1 节) | 12 | middle_finger4(中指指尖) |
| 2 | thumb2 | 13 | ring_finger1(无名指第 1 节) |
| 3 | thumb3 | 14 | ring_finger2 |
| 4 | thumb4(拇指指尖) | 15 | ring_finger3 |
| 5 | forefinger1(食指第 1 节) | 16 | ring_finger4(无名指指尖) |
| 6 | forefinger2 | 17 | pinky_finger1(小指第 1 节) |
| 7 | forefinger3 | 18 | pinky_finger2 |
| 8 | forefinger4(食指指尖) | 19 | pinky_finger3 |
| 9 | middle_finger1(中指第 1 节) | 20 | pinky_finger4(小指指尖) |
| 10 | middle_finger2 | — | — |
该文件同时定义了 5 条手指链的skeleton_info(腕部连接各指第 1 节、指节之间依次连接)以及joint_weights=[1.] * 21(全部关键点等权参与损失计算)。这也是配置文件中head.out_channels=21的直接依据。
四、完整配置文件逐段精讲
下面按配置文件的逻辑分区,逐一说明 td-hm_hrnetv2-w18_udp-8xb64-210e_onehand10k-256x256.py 各段的作用。
4.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)) # 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) # hooks default_hooks = dict(checkpoint=dict(save_best='AUC', rule='greater'))- 训练轮数:210 个 epoch,每 10 个 epoch 做一次验证(
val_interval=10); - 优化器:Adam,初始学习率 5e-4;
- 学习率调度:前 500 次迭代线性 warm-up(从 0.001 倍初始 LR 爬升),之后在 170/200 epoch 处按 0.1 倍阶梯衰减(MultiStepLR);
- 自动缩放 LR:
auto_scale_lr声明了 512 的基准 batch size,当实际训练的 batch size 与之不同时,MMPose 会自动按比例调整学习率; - 模型选择:
save_best='AUC'表示按验证集 AUC 选择最优 checkpoint 保存,rule='greater'表示指标越大越好。
default_runtime.py的基础运行时(来自 configs/base/default_runtime.py)提供日志、checkpoint、可视化、BadCase 分析等默认 hooks,并设定default_scope='mmpose'以启用 MMEngine 的注册表机制。
4.2 模型结构:HRNetv2-w18 + FeatureMapProcessor + HeatmapHead
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='HRNet', in_channels=3, extra=dict( stage1=dict(num_modules=1, num_branches=1, block='BOTTLENECK', num_blocks=(4, ), num_channels=(64, )), stage2=dict(num_modules=1, num_branches=2, block='BASIC', num_blocks=(4, 4), num_channels=(18, 36)), stage3=dict(num_modules=4, num_branches=3, block='BASIC', num_blocks=(4, 4, 4), num_channels=(18, 36, 72)), stage4=dict(num_modules=3, num_branches=4, block='BASIC', num_blocks=(4, 4, 4, 4), num_channels=(18, 36, 72, 144), multiscale_output=True), upsample=dict(mode='bilinear', align_corners=False)), init_cfg=dict(type='Pretrained', checkpoint='open-mmlab://msra/hrnetv2_w18')), neck=dict(type='FeatureMapProcessor', concat=True), head=dict( type='HeatmapHead', in_channels=270, out_channels=21, deconv_out_channels=None, conv_out_channels=(270, ), conv_kernel_sizes=(1, ), loss=dict(type='KeypointMSELoss', use_target_weight=True), decoder=codec), test_cfg=dict( flip_test=True, flip_mode='heatmap', shift_heatmap=False, ))关键设计点:
- HRNetv2-w18 结构:4 个 stage 并行分辨率分支数依次为 1→2→3→4,通道数为 18/36/72/144,各 stage 均保持高分辨率分支与低分辨率分支并行,最后以
multiscale_output=True输出多尺度特征;upsample使用bilinear + align_corners=False上采样到统一分辨率; - 初始化:主干网络使用 ImageNet 预训练权重
open-mmlab://msra/hrnetv2_w18; - 颈部聚合:
FeatureMapProcessor将 HRNet 输出的多尺度特征concat拼接(18+36+72+144=270 通道),与head.in_channels=270对应; - 检测头:
HeatmapHead输出 21 通道热力图,用 1×1 卷积(conv_out_channels=(270, )、conv_kernel_sizes=(1, ))压缩通道,不使用反卷积(deconv_out_channels=None); - 损失:
KeypointMSELoss(均方误差),use_target_weight=True表示按 codec 生成的关键点权重加权,遮挡或出界点的权重为 0; - 测试策略:
flip_test=True开启水平翻转测试,flip_mode='heatmap'表示对热力图做翻转融合,shift_heatmap=False表示不额外做 0.5 像素偏移修正——这与 UDP 的像素中心对齐约定一致,是 UDP 配置区别于普通 MSRA 配置(该字段为shift_heatmap=True)的重要差异,也侧面印证了 UDP 无偏设计的目标。
4.3 数据流水线:topdown 范式下的训练/验证流程
dataset_type = 'OneHand10KDataset' data_mode = 'topdown' data_root = 'data/onehand10k/' 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') ]topdown模式意味着"检测 + 单人姿态估计"两阶段范式:本配置输入的是已经由检测器给出的手部 bbox;- 训练时依次执行:读图 → 由 bbox 计算中心点与尺度 → 水平随机翻转 → 随机 bbox 变换(旋转幅度最高 180°、尺度缩放 0.7~1.3 倍,对手部大范围旋转具有很好的鲁棒性)→ topdown 仿射变换到 256×256 → 用
UDPHeatmapcodec 生成监督目标 → 打包输入; - 验证时不做数据增强,仅做仿射变换。
数据加载部分:训练集使用annotations/onehand10k_train.json、batch size 64、2 个 worker 并开启persistent_workers=True;验证集使用annotations/onehand10k_test.json、batch size 32,test_mode=True。
4.4 评估指标
val_evaluator = [ dict(type='PCKAccuracy', thr=0.2), dict(type='AUC'), dict(type='EPE'), ] test_evaluator = val_evaluator验证与测试阶段同时计算 PCK@0.2、AUC、EPE 三项指标,与模型卡片结果表一一对应。
五、从零训练与测试:命令行实操
MMPose 基于 MMEngine 提供统一的训练入口 tools/train.py 与测试入口 tools/test.py,配合仓库根目录的分布式脚本 tools/dist_train.sh 和 tools/dist_test.sh。以下命令均在仓库根目录下执行。
5.1 单卡训练
python tools/train.py configs/hand_2d_keypoint/topdown_heatmap/onehand10k/td-hm_hrnetv2-w18_udp-8xb64-210e_onehand10k-256x256.py5.2 多卡(8 卡)训练
bash tools/dist_train.sh configs/hand_2d_keypoint/topdown_heatmap/onehand10k/td-hm_hrnetv2-w18_udp-8xb64-210e_onehand10k-256x256.py 88xb64命名即对应 8 卡 × 单卡 batch size 64 = 512 的全局 batch size,正好与auto_scale_lr.base_batch_size=512对齐。
5.3 测试与指标复现
python tools/test.py configs/hand_2d_keypoint/topdown_heatmap/onehand10k/td-hm_hrnetv2-w18_udp-8xb64-210e_onehand10k-256x256.py <checkpoint路径>运行前需按 docs/zh_cn/user_guides/prepare_datasets.md 的指引将 OneHand10K 数据放置到data/onehand10k/目录下,并确认目录结构符合配置文件中的ann_file与data_prefix约定。训练完成后默认按 AUC 择优保存 checkpoint,日志与权重输出位置遵循 configs/base/default_runtime.py 中的默认工作目录设置。
六、模型系列的延伸阅读
该配置在仓库中不是孤例,同目录下还提供了同一主干网络在不同编解码方案下的对照模型:
- td-hm_hrnetv2-w18_8xb64-210e_onehand10k-256x256.py:使用经典 MSRAHeatmap 编解码,是 UDP 的基线对照;
- td-hm_hrnetv2-w18_dark-8xb64-210e_onehand10k-256x256.py:使用 DarkPose 编解码(
MSRAHeatmap+ dark 精修); - td-hm_res50_8xb32-210e_onehand10k-256x256.py 与 td-hm_mobilenetv2_8xb64-210e_onehand10k-256x256.py:不同主干网络(ResNet-50、MobileNet-v2)的轻量/经典对照。
对比各模型在 OneHand10K 上的 EPE(UDP 23.88 < Dark 23.96 < 基线 24.26 < ResNet-50 25.16 < MobileNet-v2 28.56),可以直观看到编解码方案与主干容量对定位精度的共同影响。若需深入了解 UDP 在更多数据集(如 COCO-WholeBody-Hand、RHD)上的表现,可参考 configs/hand_2d_keypoint/topdown_heatmap/README.md 中的完整模型清单;codec 的完整参数说明与扩展指南见 docs/en/advanced_guides/codecs.md(中文版 docs/zh_cn/advanced_guides/codecs.md)。
七、总结
HRNetv2-w18 + UDP 在 OneHand10K 上取得了 PCK@0.2=0.990、AUC=0.571、EPE=23.88 的成绩,其精度优势主要来自两个层面的"无偏"设计:编码时高斯核在整数网格内保留亚像素偏移、解码时通过分布感知精修将坐标推进到连续域。通过本文对配置文件逐段的拆解与对 mmpose/codecs/udp_heatmap.py、mmpose/codecs/utils/gaussian_heatmap.py、mmpose/codecs/utils/refinement.py 等源码的对照,你可以据此复现训练、验证结果,并以此为模板迁移到其他 topdown 手部姿态估计任务中。
【免费下载链接】mmposeOpenMMLab Pose Estimation Toolbox and Benchmark.项目地址: https://gitcode.com/GitHub_Trending/mm/mmpose
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考