MMPose 人脸关键点检测实战:Hourglass 堆叠沙漏网络在 COCO-WholeBody-Face 数据集上的训练与评测
【免费下载链接】mmposeOpenMMLab Pose Estimation Toolbox and Benchmark.项目地址: https://gitcode.com/GitHub_Trending/mm/mmpose
本文以 MMPose 仓库中 td-hm_hourglass52_8xb32-60e_coco-wholebody-face-256x256.py 这一官方配置为线索,系统讲解如何用 Stacked Hourglass(堆叠沙漏)网络完成 COCO-WholeBody-Face 人脸 68 点关键点检测:从配置文件的逐项拆解、数据流水线与编解码器原理,到 NME 评测指标、训练与测试命令,一应俱全。读完本文,你将能够独立复现该模型在验证集上 NME 0.0587 的结果,并掌握基于 MMPose 自顶向下(Top-Down)人脸关键点任务的完整开发链路。
一、任务背景:COCO-WholeBody-Face 上的 68 点人脸关键点检测
1.1 数据集:COCO-WholeBody-Face
COCO-WholeBody(ECCV'2020,论文Whole-Body Human Pose Estimation in the Wild)在原始 COCO 数据集基础上补充了人体全身各部位的密集标注,其中人脸关键点采用 68 点标注协议。MMPose 从中抽取面部标注,构成了独立的COCO-WholeBody-Face人脸关键点数据集。
在仓库中,该数据集的元信息(METAINFO)由 coco_wholebody_face.py 定义,包括dataset_name、68 个关键点的名称(face_kpt_names)、骨骼连接关系以及关键点颜色等信息;数据集的加载逻辑实现在 coco_wholebody_face_dataset.py 中:
@DATASETS.register_module() class CocoWholeBodyFaceDataset(BaseCocoStyleDataset): METAINFO: dict = dict( from_file='configs/_base_/datasets/coco_wholebody_face.py')从源码的parse_data_info可以看到几个对训练影响关键的处理细节:
- 通过
face_valid标志过滤掉无有效人脸标注的实例; - 从
face_box读取人脸框(xywh 格式),并裁剪到图像边界内,转换成[1, 4]的 xyxy 格式 bbox; - 将
face_kpts的(x, y, visible)三元组拆分为关键点坐标keypoints与可见性keypoints_visible(可见性阈值取min(1, v),即只要标注为非 0 就视为可见)。
这些细节决定了本配置中ann_file、data_prefix等字段的写法,也是理解后续数据管线的前提。
1.2 算法:Hourglass(ECCV'2016)
Hourglass 网络由 Newell 等人于 2016 年提出(论文Stacked Hourglass Networks for Human Pose Estimation),其核心思想是通过重复的下采样-上采样沙漏结构,同时捕获人脸的全局上下文信息与局部精细特征:先逐步池化降低分辨率以获取整体语义,再通过上采样恢复空间分辨率,并利用跳跃连接(skip connection)将各尺度的特征相加,从而实现对关键点的精确定位。堆叠多个沙漏模块(Stacked)并对每个阶段的输出分别计算损失,可以进一步细化预测。
在本配置中,使用的num_stacks=1对应论文中的Hourglass-52(单沙漏、52 层),而num_stacks=2则是 Hourglass-104。
二、配置全景:从零拆解官方训练配置
官方配置 td-hm_hourglass52_8xb32-60e_coco-wholebody-face-256x256.py 的名字已经透露了关键信息:td-hm(Top-Down Heatmap 自顶向下热图方法)、hourglass52(骨干网络)、8xb32(8 卡 × 每卡 32 样本,共 256 批大小)、60e(60 个 epoch)、输入尺寸256x256。下面逐段拆解。
2.1 运行时与优化器设置
_base_ = ['../../../_base_/default_runtime.py'] # runtime train_cfg = dict(max_epochs=60, val_interval=1) # optimizer optim_wrapper = dict(optimizer=dict( type='Adam', lr=2e-3, ))配置以 default_runtime.py 为基础,继承其中的 hooks、可视化后端、日志处理器、环境配置等默认项,再覆盖训练轮数与优化器:
train_cfg:训练 60 个 epoch,每个 epoch 结束后执行一次验证(val_interval=1);optimizer:采用Adam优化器,初始学习率lr=2e-3,未额外指定 weight decay,因此使用默认值。
default_runtime.py 中还内置了BadCaseAnalysisHook(默认关闭,enable=False)、SyncBuffersHook等组件,以及cudnn_benchmark=False、dist_cfg=dict(backend='nccl')等分布式训练环境配置。
2.2 学习率调度:线性热身 + 多步衰减
# 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=[40, 55], gamma=0.1, by_epoch=True) ]学习率采用两段式调度:
- 线性热身(warm-up):前 500 个 iteration 内,学习率从初始值的
start_factor=0.001(即 2e-6)线性攀升到 2e-3,by_epoch=False表示按 iteration 计; - 多步衰减(MultiStepLR):在第 40 与第 55 个 epoch,学习率按
gamma=0.1衰减,即 2e-3 → 2e-4 → 2e-5。
2.3 批大小自适应缩放学习率
# automatically scaling LR based on the actual training batch size auto_scale_lr = dict(base_batch_size=256)auto_scale_lr是 MMPose/MMEngine 提供的学习率自动缩放机制:base_batch_size=256表示学习率 2e-3 是在总批大小 256(8 卡 × 32)下调参得到的;当实际训练的批大小改变时,框架会依据批大小比例自动缩放学习率,保证训练行为的等价性。
2.4 模型结构:HourglassNet 骨干 + 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='HourglassNet', num_stacks=1, ), head=dict( type='CPMHead', in_channels=256, out_channels=68, num_stages=1, deconv_out_channels=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 统计的mean/std做标准化,bgr_to_rgb=True说明输入图像按 BGR 读入后需转为 RGB;HourglassNet:骨干网络,仅指定num_stacks=1,其余参数(downsample_times=5、stage_channels、stage_blocks等)使用源码默认值;CPMHead:多阶段热图头(源自 Convolutional Pose Machines,为 Stacked Hourglass 配套使用),in_channels=256对应骨干输出特征通道数,out_channels=68对应 68 个面部关键点,num_stages=1与num_stacks=1对应,deconv_out_channels=None表示该阶段不额外堆叠反卷积上采样层;- 损失函数:
KeypointMSELoss(关键点 MSE 损失),use_target_weight=True表示按关键点可见性加权; test_cfg:测试时启用水平翻转测试(TTA),flip_mode='heatmap'表示对热图翻转并shift_heatmap=True做像素偏移补偿(因为翻转前后热图坐标存在半像素错位)。
2.5 编解码器:MSRAHeatmap
# codec settings codec = dict( type='MSRAHeatmap', input_size=(256, 256), heatmap_size=(64, 64), sigma=2)MSRAHeatmap是经典的热图编解码器(源自Simple Baselines for Human Pose Estimation,Xiao et al. 2018),其核心参数为:
| 参数 | 取值 | 含义 |
|---|---|---|
input_size | (256, 256) | 网络输入图像尺寸 |
heatmap_size | (64, 64) | 生成的热图尺寸,为输入的 1/4 |
sigma | 2 | 高斯热图的标准差(像素),决定每个关键点峰的弥散范围 |
unbiased | False(默认) | 是否使用 DarkPose 无偏编码,本配置未开启 |
从 msra_heatmap.py 源码可见其编码过程:关键点坐标除以scale_factor = input_size / heatmap_size(即 4)缩放到热图坐标系后,通过generate_gaussian_heatmaps生成(K, H, W)的高斯热图(K=68 个关键点,H=W=64),并返回对应的keypoint_weights。解码时则对热图取最大值位置(get_heatmap_maximum)得到整数像素坐标,再用refine_keypoints结合周围像素做亚像素精度的坐标细化,最后乘以scale_factor还原到输入图像坐标系。
MSRAHeatmap支持unbiased=True切换到 DarkPose 方案(对应仓库中同目录下的 td-hm_hrnetv2-w18_dark-8xb32-60e_coco-wholebody-face-256x256.py 配置),可在不换模型结构的前提下进一步提升精度。
2.6 数据流水线与 DataLoader
# base dataset settings dataset_type = 'CocoWholeBodyFaceDataset' data_mode = 'topdown' data_root = 'data/coco/' # pipelines train_pipeline = [ dict(type='LoadImage'), dict(type='GetBBoxCenterScale'), dict(type='RandomFlip', direction='horizontal'), dict( type='RandomBBoxTransform', rotate_factor=60, scale_factor=(0.75, 1.25)), 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') ]训练流水线依次完成:加载图像 → 由 bbox 计算中心点与尺度 → 以 0.5 概率水平翻转 → 随机 bbox 变换(旋转 ±60°、尺度缩放 0.75~1.25 倍)→ 仿射变换裁剪到 256×256 → 调用MSRAHeatmap编解码器生成高斯热图标签 → 打包为模型输入。验证流水线则去掉所有随机增强,只保留仿射对齐。
train_dataloader = dict( batch_size=32, 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/coco_wholebody_train_v1.0.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/coco_wholebody_val_v1.0.json', data_prefix=dict(img='val2017/'), test_mode=True, pipeline=val_pipeline, )) test_dataloader = val_dataloader数据组织要点:
- 训练集与验证集分别使用
coco_wholebody_train_v1.0.json与coco_wholebody_val_v1.0.json标注文件,图片分别位于data/coco/train2017/与data/coco/val2017/(data_root='data/coco/'下); - 训练集
shuffle=True随机打乱;验证集shuffle=False, round_up=False,且test_mode=True表示进入评测模式; test_dataloader = val_dataloader直接复用验证集数据;- 8 卡训练时,单卡 batch_size=32,总批大小 256(与
auto_scale_lr的base_batch_size一致)。
2.7 评测配置:NME 指标
# evaluators val_evaluator = dict( type='NME', norm_mode='keypoint_distance', ) test_evaluator = val_evaluator人脸关键点领域使用NME(Normalized Mean Error,归一化平均误差)作为标准评测指标。从 keypoint_2d_metrics.py 中NME类的源码可以看到其两种归一化模式:
'use_norm_item':使用数据信息中某一项(如bbox_size)作为归一化因子;'keypoint_distance':使用一对指定关键点之间的距离作为归一化因子。
本配置使用后者。源码中DEFAULT_KEYPOINT_INDICES为coco_wholebody_face定义了默认关键点索引对[36, 45]——即左右眼外眼角(right-most和left-mosteye keypoints)。NME 即所有关键点的平均欧氏距离除以双眼间距,值越小精度越高,这一设计使人脸尺寸差异不会直接影响误差读数,便于跨尺度公平比较。
三、源码级原理:骨干网络与检测头的内部实现
3.1 HourglassNet:递归构建的沙漏模块
骨干网络实现在 hourglass.py 中。HourglassModule采用递归定义构建沙漏结构:
class HourglassModule(BaseModule): def __init__(self, depth, stage_channels, stage_blocks, ...): self.up1 = ResLayer(BasicBlock, cur_block, cur_channel, cur_channel, ...) # 保持分辨率分支 self.low1 = ResLayer(..., stride=2, ...) # 下采样分支 if self.depth > 1: self.low2 = HourglassModule(depth - 1, stage_channels[1:], stage_blocks[1:]) # 递归 else: self.low2 = ResLayer(...) self.low3 = ResLayer(..., downsample_first=False) # 上采样前的特征处理 self.up2 = nn.Upsample(scale_factor=2) # 最近邻上采样 def forward(self, x): up1 = self.up1(x) low1 = self.low1(x) low2 = self.low2(low1) low3 = self.low3(low2) up2 = self.up2(low3) return up1 + up2 # 跳跃连接相加关键设计一目了然:up1与up2的逐元素相加实现跳跃连接,使高分辨率细节信息绕过深层瓶颈直接传递给输出;low1用 stride=2 卷积下采样,up2用nn.Upsample恢复分辨率,形成完整的"先降后升"沙漏。
HourglassNet的默认结构参数为:
downsample_times=5:每个沙漏模块内下采样 5 次;num_stacks=2默认(本配置改为 1,即 Hourglass-52);stage_channels=(256, 256, 384, 384, 384, 512)、stage_blocks=(2, 2, 2, 2, 2, 4);- 网络先经 stem(7×7 卷积 + 残差层,两次 stride=2 下采样)提取初始特征,再进入沙漏模块;多栈时通过 1×1 卷积与中间残差层(
inters)将前一栈输出反馈到下一栈,实现多阶段级联细化。
3.2 CPMHead:多阶段热图头与翻转测试
检测头实现在 cpm_head.py 中,CPMHead支持多阶段输出。前向过程为:对每个阶段的特征依次经过反卷积层(multi_deconv_layers,本配置deconv_out_channels=None时退化为nn.Identity)与 1×1 卷积(multi_final_layers)输出(B, 68, 64, 64)的热图。
两个重要实现细节值得注意:
- 多阶段损失:
loss方法对每个阶段的热图分别计算 MSE 损失并累加(本配置只有 1 个阶段,因此loss_kpt只计算一次),同时通过pose_pck_accuracy计算平均关键点准确率acc_pose用于训练日志监控; - 翻转测试(TTA):
predict方法中,当test_cfg['flip_test']=True时,对原始图像与水平翻转图像分别前向,将翻转热图通过flip_heatmaps(含flip_indices关键点索引重排与shift_heatmap半像素偏移校正)后与原热图求平均,再统一解码出关键点坐标。这是验证集 NME 0.0587 能稳定复现的关键设置之一。
四、模型基准与结果
官方在 COCO-WholeBody-Face 验证集上的评测结果如下(来源:hourglass_coco_wholebody_face.md 及配套 hourglass_coco_wholebody_face.yml):
| 架构 | 输入尺寸 | NME | 配置文件 |
|---|---|---|---|
| pose_hourglass_52 | 256×256 | 0.0587 | td-hm_hourglass52_8xb32-60e_coco-wholebody-face-256x256.py |
模型权重(checkpoint)与训练日志的官方下载地址记录在同目录的 hourglass_coco_wholebody_face.yml 的Weights字段中,权重由 OpenMMLab 官方发布(hourglass52_coco_wholebody_face_256x256-6994cf2e_20210909.pth),可直接用于离线推理或继续训练。
作为对照,该目录下还提供了HRNet-w18(含 DarkPose 变体)、ResNet-50、SCNet-50、MobileNetV2等同任务配置(见 coco_wholebody_face 目录),便于在精度与速度之间横向权衡。
五、数据准备与训练测试
5.1 数据集准备
按data_root='data/coco/'的约定,需要将 COCO-WholeBody 数据集组织为如下目录结构:
data/coco/ ├── annotations/ │ ├── coco_wholebody_train_v1.0.json │ └── coco_wholebody_val_v1.0.json ├── train2017/ └── val2017/完整的数据集下载与格式说明请参考 prepare_datasets.md(英文版见 prepare_datasets.md)。
5.2 训练
使用仓库根目录下的 train.py 启动训练:
# 单卡训练 python tools/train.py configs/face_2d_keypoint/topdown_heatmap/coco_wholebody_face/td-hm_hourglass52_8xb32-60e_coco-wholebody-face-256x256.py # 多卡(8 卡)分布式训练 bash tools/dist_train.sh configs/face_2d_keypoint/topdown_heatmap/coco_wholebody_face/td-hm_hourglass52_8xb32-60e_coco-wholebody-face-256x256.py 8关键行为说明:
- 训练过程中每 1 个 epoch 在验证集上评测一次(
val_interval=1),并使用default_hooks.checkpoint的save_best='NME', rule='less'设置(见配置第 30 行)保存 NME 最低的模型为最佳权重; - 若实际可用 GPU 数量导致总批大小不是 256,建议开启
--auto-scale-lr(train.py 支持该参数)触发auto_scale_lr的学习率自动缩放; - 训练配置中
default_runtime.py的CheckpointHook默认interval=10,本配置覆盖为interval=1,即每个 epoch 都会保存 checkpoint; - 若以较小的 GPU 显存训练,可下调
batch_size并配合--auto-scale-lr保持训练动力学近似。
5.3 测试与指标复现
使用 test.py 在验证集上评测:
python tools/test.py configs/face_2d_keypoint/topdown_heatmap/coco_wholebody_face/td-hm_hourglass52_8xb32-60e_coco-wholebody-face-256x256.py \ <checkpoint.pth> --out <输出结果文件>评测过程将启用配置中的翻转测试(flip_test=True),最终输出NME指标。若使用官方发布的预训练权重,可复现 NME ≈ 0.0587 的基准结果。
5.4 端到端推理
由于自顶向下方法需要先定位人脸框,实际部署通常配合人脸检测器一起使用。MMPose 提供了 topdown_demo_with_mmdet.py 演示脚本,将 MMDetection 的人脸检测器与本模型串联完成"检测 + 关键点"端到端推理;此外 inferencer_demo.py 展示了基于MMPoseInferencer的统一推理接口用法,更多单图/多图/视频推理示例可参考 2d_face_demo.md 与 2d_face_demo.md。
六、结语与进一步探索
通过本配置,我们完整走通了 MMPose 中"Hourglass 骨干 + CPMHead 热图头 + MSRAHeatmap 编解码 + NME 评测"这一经典人脸关键点方案的每一个环节:CocoWholeBodyFaceDataset负责数据解析与 68 点标注加载,HourglassNet用递归沙漏与跳跃连接捕获多尺度人脸结构,CPMHead输出 64×64 高斯热图并支持翻转 TTA,NME以双眼间距归一化误差实现尺度无关的精度度量。这一套组件的组合方式对其他数据集(如 wflw、aflw、300w)同样适用,只需替换数据集与关键点元信息即可迁移。
若想进一步理解框架层面的编排机制,可以继续阅读 guide_to_framework.md(英文版见 guide_to_framework.md);如果想对比更现代的人脸关键点方案(如基于 SimCC 表示与 RTMPose 的轻量级模型),可以浏览 face_2d_keypoint/rtmpose 下的配置。
【免费下载链接】mmposeOpenMMLab Pose Estimation Toolbox and Benchmark.项目地址: https://gitcode.com/GitHub_Trending/mm/mmpose
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考