大家好!此前我们介绍了二维人体姿态估计(2D Human Pose Estimation,以下简称2D HPE)的基本概念、常用数据集,以及自顶向下的2D HPE算法。本文将结合MMPose对自底向上的2D HPE算法做一些更详细的分析。
2D HPE旨在从图像或者视频中预测人体关节点(或称关键点,比如头,左手,右脚等)的二维空间位置坐标。应用场景包括:动作识别,动画生成,增强现实等。传统的2D HPE算法,设计手工特征提取图像信息,从而进行关键点的检测。
近年来随着深度学习的快速发展,基于深度学习的2D HPE算法取得了重大突破,算法精度得到了大幅提升。
当前主流的2D HPE方法主要可以分为自顶向下(top down)和自底向上(bottom up)两种方式。
自顶向下的方法主要有如下两方面的问题:
1)依赖人体框检测的效果。如果检测器没有检测到某个人体,或者一个框内框出了多个人体(在密集人群场景中,会经常遇到),自顶向下的方法会失效。
2)自顶向下方法的计算复杂度随着图片中总人数的增加而线性上升。在人数比较多的场景,耗时难以满足实时性要求。而自底向上的算法,能够有效解决这两方面的问题,因此也受到了研究者的广泛关注。
那么,让我们来学习下如何使用自底向上的二维人体姿态估计方法吧~
本文内容
主流算法
MMPose 中的 2D HPE 算法实现
模型、数据、训练配置
1. 主流算法
基于 Part Affinity Fields 的方法
OpenPose
OpenPose [1] 算法基于 CPM(Convolutional Pose Machines)[5]模型结构设计了一个多阶段(multi-stage)的卷积神经网络,每个stage的输入是上一个stage输出和图像特征的融合,其网络结构如下图所示。
OpenPose用神经网络同时预测多人关节点热度图(Heatmap 或 Confidence Map) 和关节点亲和场(Part Affinity Fields, PAFs)。其中,关节点热度图代表了关节点的位置;而 PAFs 表示不同关节点之间的骨架连接关系。PAFs是一组二维向量图,其每个像素表征该位置的骨架连接方向信息。
OpenPose首先使用NMS算法,从关键点热度图中得到一系列候选关节点,再利用关节点亲和场来匹配关节点。
然而,将候选关键点进行关节匹配并得到完整的人体的过程,是一个NP-Hard问题。OpenPose采用了greedy inference算法,将关节点作为图的顶点,把关节点之间的PAFs向量看作两点间的边的权重,将多人检测问题转换成了一个二分图匹配问题,并用匈牙利算法求得局部最优匹配。
基于 Associative Embedding 的方法
Associative Embedding
AE(Associative Embedding) [2]算法采用了基于堆叠沙漏网络(Stacked Hourglass [6])的网络结构,连续进行上采样和下采样,融合多尺度特征。
该算法提出了关联嵌入特征图(Associative Embedding),以实现关键点聚类。
如下图所示,网络会预测 N 张关键点热度图,用于多人关键点定位;同时预测 N 张关联嵌入特征图,用于关键点聚类。
在训练过程中,要求同一个人的各个关键点的关联嵌入特征尽可能相似(Pull loss),不同人的关联嵌入特征尽可能不同(Push loss)。在预测阶段,我们只需要对关联嵌入特征的取值进行聚类,即可将关键点匹配成人体姿态。
下图对网络预测的关联嵌入特征图进行了可视化。由图中可以看出,同一个人不同关键点的关联嵌入特征图的取值非常接近,且不同人的关联嵌入特征图的取值各不相同。
HigherHRNet
该算法整体沿用了 AE 的思想,设计了 HigherHRNet 网络结构,致力于解决自底向上的人体姿态估计方法的尺度变化问题,有利于同时预测图片中不同尺度大小的人体姿态。
HigherHRNet特征金字塔由 1)HRNet [7]的特征图输出和 2)通过转置卷积上采样的高分辨率输出组成。该模型采用多分辨率监督训练和多分辨率聚合推理,能够更精确地定位关键点。
基于中心点回归的方法
SPM
近几年,单阶段人体姿态检测器逐渐流行。而SPM(Single-Stage Multi-Person Pose Machines)[4]是其中的代表性方法,也是最早的单阶段多人姿态估计方法之一。
该算法同样采用堆叠沙漏(Stacked Hourglass [6])网络结构。SPM抛弃了以往的”先检测所有关键点,再进行聚合”的策略。它提出首先检测人体中心点,再回归从人体中心到各个关键点的偏移向量。
如上图所示,SPM算法在预测一张人体根节点的热度图(Root Conf. Map)的同时,预测了N*2张关键点偏移向量图(Joint Disp. Map)。在预测阶段,首先根据根节点热度图,定位各个人体的中心点坐标。中心点坐标再加上各个关键点的偏移向量,最终得到了每个人的姿态动作。
2. MMPose 中的 2D HPE 算法实现
MMPose现已支持 Associative Embedding, HigherHRNet 等不同的算法模型。我们也提供了基于图像和视频的 demo ,感兴趣的话不妨先尝试一下~
demo 链接:
https://mmpose.readthedocs.io/en/latest/demo.html#d-human-pose-bottom-up-image-demo
接下来,我们以HigherHRNet为例介绍自底向上的2D HPE算法在MMPose中的实现。
模型
Backbone
模型的骨干网络(Backbone)为 HRNet 模型(具体实现见hrnet.py),输入为3通道的图片,输出为num_branches=4 个不同分辨率下的特征。特征分辨率越小的branch,其特征通道数越多 num_channels=(32, 64, 128, 256)。
backbone=dict( type='HRNet', # 骨干网络的类型 in_channels=3, # 输入通道数 extra=dict( stage1=dict( num_modules=1, # 串行module数 num_branches=1, # 并行branch数 block='BOTTLENECK', # 使用block的类型 num_blocks=(4, ), # 使用block的数量 num_channels=(64, )), # 特征通道数 stage2=dict( num_modules=1, # 串行module数 num_branches=2, # 并行branch数 block='BASIC', # 使用block的类型 num_blocks=(4, 4), # 使用block的数量 num_channels=(32, 64)), # 特征通道数 stage3=dict( num_modules=4, # 串行module数 num_branches=3, # 并行branch数 block='BASIC', # 使用block的类型 num_blocks=(4, 4, 4), # 使用block的数量 num_channels=(32, 64, 128)), # 特征通道数 stage4=dict( num_modules=3, # 串行module数 num_branches=4, # 并行branch数 block='BASIC', # 使用block的类型 num_blocks=(4, 4, 4, 4), # 使用block的数量 num_channels=(32, 64, 128, 256))), # 特征通道数 )
Head
模型的Head部分,采用了AEHigherResolutionHead,具体实现见ae_higher_resolution_head.py。该网络头用于产生多分辨率的输出结果。模型训练的损失类型为MultiLossFactory,来分别监督多人关键点热度图和关联嵌入特征图。
keypoint_head=dict( type='AEHigherResolutionHead', # 网络头的类型 in_channels=32, # 输入通道数 num_joints=17, # 输出关节点数 tag_per_joint=True, # 是否每个关节点使用单独的关联嵌入特征图 extra=dict(final_conv_kernel=1, ), # final_conv 为1x1的卷积 num_deconv_layers=1, # 反卷积层数量 num_deconv_filters=[32], # 反卷积层的输出通道数 num_deconv_kernels=[4], # 反卷积层的卷积核大小 num_basic_blocks=4, # BasicBlock的数量 cat_output=[True], # 是否级联输出 with_ae_loss=[True, False], # 是否使用AE损失 loss_keypoint=dict( type='MultiLossFactory', # 损失函数类别 num_joints=17, # 关节点数量 num_stages=2, # 网络头阶段数 ae_loss_type='exp', # AE损失类型 with_ae_loss=[True, False], # 是否使用AE损失 push_loss_factor=[0.001, 0.001], # push损失的权重 pull_loss_factor=[0.001, 0.001], # pull损失的权重 with_heatmaps_loss=[True, True], # 是否使用热度图损失 heatmaps_loss_factor=[1.0, 1.0])) # 热度图损失的权重
数据
Data config
以下是与训练数据相关的一些配置,具体实现见bottom_up_coco.py 。
data_cfg = dict( image_size=512, # 输入图片尺寸 base_size=256, # 基准尺寸 base_sigma=2, # 热度图高斯分布的标准差 heatmap_size=[128, 256], # 输出多分辨率热度图的尺寸 num_joints=channel_cfg['dataset_joints'], # 关键点个数 dataset_channel=channel_cfg['dataset_channel'], # 数据集所对应的通道列表 inference_channel=channel_cfg['inference_channel'], # 模型输出的通道列表 num_scales=2, # 输出分辨率的个数 scale_aware_sigma=False, # 高斯分布的标准差是否随尺度变化 ) data_root = 'data/coco' # 数据集路径 data = dict( samples_per_gpu=24, # 训练阶段,每张GPU的批处理大小 workers_per_gpu=2, # 每个GPU的数据读取线程数量 train=dict( type='BottomUpCocoDataset', # 训练数据集名称 ann_file=f'{data_root}/annotations/person_keypoints_train2017.json', # 训练数据集标签路径 img_prefix=f'{data_root}/train2017/', # 训练数据集图片路径前缀 data_cfg=data_cfg, pipeline=train_pipeline), val=dict( type='BottomUpCocoDataset', # 验证数据集名称 ann_file=f'{data_root}/annotations/person_keypoints_val2017.json', # 验证数据集标签路径 img_prefix=f'{data_root}/val2017/', # 验证数据集图片路径前缀 data_cfg=data_cfg, pipeline=val_pipeline), test=dict( type='BottomUpCocoDataset', # 测试数据集名称 ann_file=f'{data_root}/annotations/person_keypoints_val2017.json', # 测试数据集标签路径 img_prefix=f'{data_root}/val2017/', # 测试数据集图片路径前缀 data_cfg=data_cfg, pipeline=val_pipeline), )
Pipeline
数据预处理的 pipeline 主要由以下几个步骤组成:
2. LoadImageFromFile:读取图片。
3. BottomUpRandomAffine:随机仿射变换。
4. BottomUpRandomFlip:随机水平翻转 。
5. ToTensor:图片转换为Tensor。
6. NormalizeTensor:输入Tensor归一化,减均值,除方差。
7. BottomUpGenerateTarget:根据标签,生成所需GT。
8. Collect:整合训练中需要用到的数据。
train_pipeline = [ dict(type='LoadImageFromFile'), # 读取图片 dict( type='BottomUpRandomAffine', # 数据增强:随机仿射变换 rot_factor=30, # 最大旋转幅度 scale_factor=[0.75, 1.5], # 随机缩放范围 scale_type='short', # 以图像的长边或短边进行缩放 trans_factor=40), # 最大中心点偏移 dict( type='BottomUpRandomFlip', # 数据增强:随机翻转 flip_prob=0.5), # 翻转概率 dict(type='ToTensor'), # 数据格式转换为 Tensor dict( type='NormalizeTensor', # 输入Tensor归一化 mean=[0.485, 0.456, 0.406], # 归一化均值 std=[0.229, 0.224, 0.225]), # 归一化方差 dict( type='BottomUpGenerateTarget', # 根据标签,生成GT sigma=2, # 热度图高斯分布的标准差 max_num_people=30, # 最大人数 ), dict( type='Collect', # 整合训练中所需要的数据 keys=['img', 'joints', 'targets', 'masks'], # 保留的key meta_keys=[]), # 保留的meta-key ]
训练配置
Optimizer
采用Adam作为优化器,并设置初始学习率为0.0015。
optimizer = dict( type='Adam', # 优化器的类型 lr=0.0015, # 基础学习率 ) optimizer_config = dict(grad_clip=None) # 不进行梯度裁剪
Learning rate policy
学习率采用阶梯状衰减,分别在第200和第260个epoch,降低学习率(默认每次降低到 0.1倍),总计训练300个epoch。
我们采用了预热启动(warmup)策略,初始设置非常小的学习率(0.001倍的初始学习率lr),并在前500个迭代轮次中,线性增加为 lr=0.0015。
lr_config = dict( policy='step', # 学习率调整策略 warmup='linear', # 预热启动策略 warmup_iters=500, # 预热启动的训练迭代数 warmup_ratio=0.001, # 预热启动阶段初始学习率为 warmup_ratio * lr step=[200, 260]) # 学习率下降的轮次,分别在第200和第260个epoch,降低学习率 total_epochs = 300 # 总迭代轮数
本文介绍了2D HPE一些自底向上的算法,并以HigherHRNet为例,介绍了MMPose中的具体算法实现。
希望大家通过本文的阅读能够对自底向上的2D HPE有一个初步的认识,也欢迎大家使用MMPose来支持相关的研究与应用~
文章来源:公众号【OpenMMLab】
2021-09-14 18:13