CPU上的实时2D多人姿势估计:轻量级OpenPose
原始OpenPose分析
与所有自下而上的方法类似,OpenPose 由两部分组成:
- 神经网络推理提供两个张量:关键点热图及其成对关系(部分关联区域,pafs)。此输出被下采样 8 次。
- 按人的实例分割结果对关键点进行分组。 它包括将张量上采样到原始图像大小、热力图峰值的关键点提取以及它们按实例分组。
网络首先提取特征,然后执行热力图和 pafs 的初始估计,之后执行 5 个细化阶段。它能够找到 18 种类型的关键点。然后分组程序从预定义的关键点对列表中搜索每个关键点的最佳对(按相关性),例如:左肘和左手腕,右臀部和右膝盖,左眼和左耳,等等,总共19对。流程如图 1 所示。在推理过程中,输入图像的大小被调整以匹配网络输入大小的高度,宽度被缩放以保持图像纵横比,然后填充为 8 的倍数。
优化部分
Refinement Stage的更改
如表可见,后期阶段对 GFLOP 的改进较少,因此对于优化版本,我们将仅保留前两个阶段:初始阶段和单个细化阶段。
具体到代码中:
class PoseEstimationWithMobileNet(nn.Module):
def __init__(self, num_refinement_stages=1, num_channels=128, num_heatmaps=19, num_pafs=38):
在初始化的时候,直接将堆叠次数num_refinement_stages = 1,具体后面会讲。
Backbone的更改
文章评估了 MobileNet 系列的网络以替换 VGG 特征提取器,并从 MobileNet v1开始。
以一种天真的方式,如果我们将所有层保持到最深,以匹配输出张量分辨率,则会导致精度显着下降。 这可能是由于浅层和弱特征表示。为了节省空间分辨率和重用主干权重,我们使用空洞卷积。
轻量化RefinementStage
为了产生对关键点热图和 pafs的新估计,细化阶段从主干中获取特征,并与之前对关键点热图和 pafs 的估计相连接。 受这一事实的启发,我们决定在热图和 pafs 之间共享大部分计算,并在初始和细化阶段使用单个预测分支。 我们共享除了最后两个层之外的所有层,它们直接产生关键点热图和 pafs。
在代码中的InitialStage体现:
可以看到在trunk中共享了三层Conv2d卷积bn加relu层。
7*7卷积的替换
每个具有 7x7 内核大小的卷积都被一个具有相同感受野的卷积块替换,以捕获远程空间依赖关系。我们用这个块设计进行了一系列实验,并观察到有1x1、3x3和3x3内核大小的三个连续卷积就足够了,后者的膨胀参数等于2,以保留初始感受野。因为网络变得更深,我们为每个这样的块添加了残差连接。
在代码中具体体现为:
使用深度可分离卷积的复杂度比使用 7x7 内核的卷积低 2.5 倍。
代码加原理讲解
整体结构
原图3 * 368 * 368输入进来,首先通过MobileNet结构与Cpm结构获得backbonefeatures,然后通过InitialStage获得heatmaps和pafs。然后backbonefeatures与heatmaps和pafs共同cat拼接,通道数相加,送入RefinementStage中(默认为1),得到heatmaps和pafs。如果指定了堆叠次数,那么backbonefeatures要分别与heatmaps和pafs进行堆叠然后重复细化阶段的操作。
卷积部分
注意,conv_dw_no_bn使用的是Elu激活函数,关于Elu激活函数可以查看这篇博文
from torch import nn
def conv(in_channels, out_channels, kernel_size=3, padding=1, bn=True, dilation=1, stride=1, relu=True, bias=True):
modules = [nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding, dilation, bias=bias)]
if bn:
modules.append(nn.BatchNorm2d(out_channels))
if relu:
modules.append(nn.ReLU(inplace=True))
return nn.Sequential(*modules)
def conv_dw(in_channels, out_channels, kernel_size=3, padding=1, stride=1, dilation=1):
return nn.Sequential(
nn.Conv2d(in_channels, in_channels, kernel_size, stride, padding, dilation=dilation, groups=in_channels, bias=False