Body estimation 论文阅读笔记(1):全网最详细的 body estimation 经典文章分析

2016CPM——convolutional pose machine

论文思路

  • 采用了 C N N CNN CNN 网络作为前端的特征提取
  • 采用了多个 s t a g e stage stage,这样 s t a g e stage stage 堆叠越深越有利于得到感受野更大的特征
  • 每一个 s t a g e stage stage 的输入 = 特征图( i m a g e   f e a t u r e image~feature image feature) + 上一个 s t a g e stage stage 产生的 b e l i e f   m a p belief~map belief map
  • 采用 g a u s s i a n   k e r n e l gaussian~kernel gaussian kernel k e y p o i n t keypoint keypoint 变成 h e a t m a p heatmap heatmap 然后再进行回归操作
  • 采用 n + 1 n+1 n+1 个通道进行训练, n n n 个通道每个负责一个 k e y p o i n t keypoint keypoint 的回归训练,剩下一个通道是背景
  • 为了防止反向更新权重过程中出现的梯度消失问题,让每个 s t a g e stage stage 都收到 i n t e r m e d i a t e   s u p e r v i s i o n intermediate ~supervision intermediate supervision(即在每个 s t a g e stage stage 后面都加 l o s s loss loss 进行约束),不断补充梯度从而随着 s t a g e stage stage 加深, h e a t m a p heatmap heatmap 的位置会越来越准确

主要贡献

  • 使用结构化的 CNN 网络和系统的方法来学习图片的特征,不需要任何图形模型风格推理(graphical model style inference)
  • 达到了 stage-of-the-art 的精度

网络结构

在这里插入图片描述

实现细节

  • 分为很多 stage, s t a g e 1 − s t a g e T stage_1-stage_T stage1stageT ,stage 分为两种 s t a g e 1 , s t a g e t , w h e r e   t ∈ { 2 , 3 , . . T } stage_1,stage_t,where ~t\in\{2,3,..T\} stage1,staget,where t{2,3,..T}
  • s t a g e 1 stage_1 stage1 中:
    • 通过固定的卷积结构 X X X 来获得特征图 image feature F F F(这里是自己引入的符号,不是原文中的);第一个 stage 的特征图的感受野普遍很小(是特征图的感受野小,而不是特征图自身尺寸小),因此能够捕获 keypoint 的信息
    • 然后将得到的特征图进行 1 × 1 1×1 1×1 卷积,不改变 w , h w,h w,h 尺寸,只压缩通道尺寸减少参数量
    • 1 × 1 1×1 1×1 卷积的最后 conv layer 的时候,通道数变成 n + 1 n+1 n+1
    • 最后通过 mse loss 为 s t a g e 1 stage_1 stage1 提供监督信息
  • s t a g e t , t ∈ { 2 , . . . T } stage_t,t\in \{2,...T\} staget,t{2,...T} 中:
    • 拿到已有的特征图 F F F,还有上一个 s t a g e t − 1 stage_{t-1} staget1 的输出进行特征融合(采用concate的方式融合),这样网络可以获得不同 scale(resolution) 的特征;其实还有一个通道的信息也在这个过程中被融合进来了,那就是 center map 的信息,从 stage2 往后每一个 stage 的网络都有这个部分,因为感受野变大了,变成全图的感受野了,这个时候要注重整体的信息了,而 center map 就是多个 person 的时候,每个 person 所处的中心位置信息。

center map 实例

在这里插入图片描述

网络结构


import torch.nn as nn
import torch.nn.functional as F
import torch


class CPM(nn.Module):
    def __init__(self, k):
        super(CPM, self).__init__()
        self.k = k
        self.pool_center = nn.AvgPool2d(kernel_size=9, stride=8, padding=1)
        self.conv1_stage1 = nn.Conv2d(3, 128, kernel_size=9, padding=4)
        self.pool1_stage1 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        self.conv2_stage1 = nn.Conv2d(128, 128, kernel_size=9, padding=4)
        self.pool2_stage1 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        self.conv3_stage1 = nn.Conv2d(128, 128, kernel_size=9, padding=4)
        self.pool3_stage1 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        self.conv4_stage1 = nn.Conv2d(128, 32, kernel_size=5, padding=2)
        self.conv5_stage1 = nn.Conv2d(32, 512, kernel_size=9, padding=4)
        self.conv6_stage1 = nn.Conv2d(512, 512, kernel_size=1)
        self.conv7_stage1 = nn.Conv2d(512, self.k + 1, kernel_size=1)

        ######################################################
        ## 这个部分就是从 stage_2 到 stage_T 使用的卷积网络
        self.conv1_stage2 = nn.Conv2d(3, 128, kernel_size=9, padding=4)
        self.pool1_stage2 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        self.conv2_stage2 = nn.Conv2d(128, 128, kernel_size=9, padding=4)
        self.pool2_stage2 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        self.conv3_stage2 = nn.Conv2d(128, 128, kernel_size=9, padding=4)
        self.pool3_stage2 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        self.conv4_stage2 = nn.Conv2d(128, 32, kernel_size=5, padding=2)
        ######################################################
        
        self.Mconv1_stage2 = nn.Conv2d(32 + self.k + 2, 128, kernel_size=11, padding=5)
        self.Mconv2_stage2 = nn.Conv2d(128, 128, kernel_size=11, padding=5)
        self.Mconv3_stage2 = nn.Conv2d(128, 128, kernel_size=11, padding=5)
        self.Mconv4_stage2 = nn.Conv2d(128, 128, kernel_size=1, padding=0)
        self.Mconv5_stage2 = nn.Conv2d(128, self.k + 1, kernel_size=1, padding=0)

        self.conv1_stage3 = nn.Conv2d(128, 32, kernel_size=5, padding=2)

        self.Mconv1_stage3 = nn.Conv2d(32 + self.k + 2, 128, kernel_size=11, padding=5)
        self.Mconv2_stage3 = nn.Conv2d(128, 128, kernel_size=11, padding=5)
        self.Mconv3_stage3 = nn.Conv2d(128, 128, kernel_size=11, padding=5)
        self.Mconv4_stage3 = nn.Conv2d(128, 128, kernel_size=1, padding=0)
        self.Mconv5_stage3 = nn.Conv2d(128, self.k + 1, kernel_size=1, padding=0)

        self.conv1_stage4 = nn.Conv2d(128, 32, kernel_size=5, padding=2)

        self.Mconv1_stage4 = nn.Conv2d(32 + self.k + 2, 128, kernel_size=11, padding=5)
        self.Mconv2_stage4 = nn.Conv2d(128, 128, kernel_size=11, padding=5)
        self.Mconv3_stage4 = nn.Conv2d(128, 128, kernel_size=11, padding=5)
        self.Mconv4_stage4 = nn.Conv2d(128, 128, kernel_size=1, padding=0)
        self.Mconv5_stage4 = nn.Conv2d(128, self.k + 1, kernel_size=1, padding=0)

        self.conv1_stage5 = nn.Conv2d(128, 32, kernel_size=5, padding=2)

        self.Mconv1_stage5 = nn.Conv2d(32 + self.k + 2, 128, kernel_size=11, padding=5)
        self.Mconv2_stage5 = nn.Conv2d(128, 128, kernel_size=11, padding=5)
        self.Mconv3_stage5 = nn.Conv2d(128, 128, kernel_size=11, padding=5)
        self.Mconv4_stage5 = nn.Conv2d(128, 128, kernel_size=1, padding=0)
        self.Mconv5_stage5 = nn.Conv2d(128, self.k + 1, kernel_size=1, padding=0)

        self.conv1_stage6 = nn.Conv2d(128, 32, kernel_size=5, padding=2)

        self.Mconv1_stage6 = nn.Conv2d(32 + self.k + 2, 128, kernel_size=11, padding=5)
        self.Mconv2_stage6 = nn.Conv2d(128, 128, kernel_size=11, padding=5)
        self.Mconv3_stage6 = nn.Conv2d(128, 128, kernel_size=11, padding=5)
        self.Mconv4_stage6 = nn.Conv2d(128, 128, kernel_size=1, padding=0)
        self.Mconv5_stage6 = nn.Conv2d(128, self.k + 1, kernel_size=1, padding=0)

    def _stage1(self, image):

        x = self.pool1_stage1(F.relu(self.conv1_stage1(image)))
        x = self.pool2_stage1(F.relu(self.conv2_stage1(x)))
        x = self.pool3_stage1(F.relu(self.conv3_stage1(x)))
        x = F.relu(self.conv4_stage1(x))
        x = F.relu(self.conv5_stage1(x))
        x = F.relu(self.conv6_stage1(x))
        x = self.conv7_stage1(x)

        return x

    def _middle(self, image):

        x = self.pool1_stage2(F.relu(self.conv1_stage2(image)))
        x = self.pool2_stage2(F.relu(self.conv2_stage2(x)))
        x = self.pool3_stage2(F.relu(self.conv3_stage2(x)))

        return x

    def _stage2(self, pool3_stage2_map, conv7_stage1_map, pool_center_map):

        x = F.relu(self.conv4_stage2(pool3_stage2_map))
        x = torch.cat([x, conv7_stage1_map, pool_center_map], dim=1)
        x = F.relu(self.Mconv1_stage2(x))
        x = F.relu(self.Mconv2_stage2(x))
        x = F.relu(self.Mconv3_stage2(x))
        x = F.relu(self.Mconv4_stage2(x))
        x = self.Mconv5_stage2(x)

        return x

    def _stage3(self, pool3_stage2_map, Mconv5_stage2_map, pool_center_map):

        x = F.relu(self.conv1_stage3(pool3_stage2_map))
        x = torch.cat([x, Mconv5_stage2_map, pool_center_map], dim=1)
        x = F.relu(self.Mconv1_stage3(x))
        x = F.relu(self.Mconv2_stage3(x))
        x = F.relu(self.Mconv3_stage3(x))
        x = F.relu(self.Mconv4_stage3(x))
        x = self.Mconv5_stage3(x)

        return x

    def _stage4(self, pool3_stage2_map, Mconv5_stage3_map, pool_center_map):

        x = F.relu(self.conv1_stage4(pool3_stage2_map))
        x = torch.cat([x, Mconv5_stage3_map, pool_center_map], dim=1)
        x = F.relu(self.Mconv1_stage4(x))
        x = F.relu(self.Mconv2_stage4(x))
        x = F.relu(self.Mconv3_stage4(x))
        x = F.relu(self.Mconv4_stage4(x))
        x = self.Mconv5_stage4(x)

        return x

    def _stage5(self, pool3_stage2_map, Mconv5_stage4_map, pool_center_map):

        x = F.relu(self.conv1_stage5(pool3_stage2_map))
        x = torch.cat([x, Mconv5_stage4_map, pool_center_map], dim=1)
        x = F.relu(self.Mconv1_stage5(x))
        x = F.relu(self.Mconv2_stage5(x))
        x = F.relu(self.Mconv3_stage5(x))
        x = F.relu(self.Mconv4_stage5(x))
        x = self.Mconv5_stage5(x)

        return x

    def _stage6(self, pool3_stage2_map, Mconv5_stage5_map, pool_center_map):
        
        x = F.relu(self.conv1_stage6(pool3_stage2_map))
        x = torch.cat([x, Mconv5_stage5_map, pool_center_map], dim=1)
        x = F.relu(self.Mconv1_stage6(x))
        x = F.relu(self.Mconv2_stage6(x))
        x = F.relu(self.Mconv3_stage6(x))
        x = F.relu(self.Mconv4_stage6(x))
        x = self.Mconv5_stage6(x)

        return x



    def forward(self, image, center_map):


        pool_center_map = self.pool_center(center_map)

        conv7_stage1_map = self._stage1(image)

        pool3_stage2_map = self._middle(image)

        Mconv5_stage2_map = self._stage2(pool3_stage2_map, conv7_stage1_map,
                                         pool_center_map)
        Mconv5_stage3_map = self._stage3(pool3_stage2_map, Mconv5_stage2_map,
                                         pool_center_map)
        Mconv5_stage4_map = self._stage4(pool3_stage2_map, Mconv5_stage3_map,
                                         pool_center_map)
        Mconv5_stage5_map = self._stage5(pool3_stage2_map, Mconv5_stage4_map,
                                         pool_center_map)
        Mconv5_stage6_map = self._stage6(pool3_stage2_map, Mconv5_stage5_map,
                                         pool_center_map)

        return conv7_stage1_map, Mconv5_stage2_map, Mconv5_stage3_map, Mconv5_stage4_map, Mconv5_stage5_map, Mconv5_stage6_map

2016 Stacked Hourglass Networks for Human Pose Estimation

动机

  • 传统的卷积网络是从浅层到深层,浅层的感受野比较小,因此是一个感受野不断增大的过程,即:bottom-up,但是传统的 conv 很少有 top-down 的过程,因此这个文章提出了 hourglass 这种模式
  • 对于部分的检测:人脸和手部检测,局部信息是很重要的,但是对于 body estimation 整体信息也是非常重要的

论文思路

  • 结合不同尺度的 features,能够捕获 body 不同部分的空间关系
  • 使用中间监督 intermediate supervision
  • 提出了 stacked hourglass 网络结构
  • 通过连续堆叠的 hourglass 结构而不是单个的 hourglass 结构;因为这样可以迫使网络在不同的 scale 上进行不断地 “bottom-up”,“top-down”推理;同时结合中间层的监督进行 bi-direction 的 inference
  • 数据集:FLIC,MPII

重要相关工作

  • DeepPose 首先使用 deep network 来完成 human pose estimation;首先采用回归坐标点的方式来完成 joints 坐标点的预测。

  • Tompson 的工作:Joint training of a convolutional network and a graphical model for human pose estimation 开始使用热图替代单纯的 joints 的坐标,并且这个 heatmaps 通过多分辨率的特征生成的;本文借鉴了他们多尺度特征的工作。除此之外,他们还提出了使用 graphical model 来学习 joints 之间特定的空间特征。借鉴 graphical model 的相关工作还有

    • 2015 Deepcut: joint subset partition and labeling for multi person pose estimation.
    • 2015 Combining local appearance and holistic view: dual-source deep neural networks for human pose estimation
    • 2016 Bottom-up and top-down reasoning with hierarchical rectified gaussians.
  • 2015 Learning deconvolution network for semantic segmentation 采用 conv-deconv 的方法用来语义分割任务中

  • 因为多尺度特征的融合和利用是非常重要的,因此下面两篇文章通过先提取多尺度的特征然后在最后进行融合处理:

    • 2014 Joint training of a convolutional network and a graphical model for human pose estimation
    • 2016 Convolutional pose machines
  • 本文没有采用任何 graphical model 或其他对 human body 建模的方法就达到了更好的精度表现

论文 pipeline

在这里插入图片描述

  • 相对于其他一些论文中形成不同尺度的 features 需要借助多个阶段或者多个独立的 pipeline 的方法,本文采用一个单独的 pipeline + residual layer(残差的结构)来融合多尺度的 feature

  • 该网络在 4 × 4 4×4 4×4 像素处达到其最低的像素分辨率,允许应用更小的 filters 来比较图像的整个空间的特征。

  • 首先采用卷积层和 max pooling 将特征图的尺寸降采样到一个非常低的分辨率,当达到最低分辨率的时候,再采取 top-down 的方式(即进行上采样 upsampling);这个过程中本文方法遵循thompson等人[15]描述的过程,对较低分辨率做最近邻向上采样(nearest neighbor upsampling),然后对两组特征进行元素相加(对应尺寸的特征图)。

  • 在网络的输出层前接入两个 1 × 1 1×1 1×1 的卷积层,来产生最后的网络特征预测。

  • 对于网络最后的输出,其实是输出 n n n 个通道的 heatmaps 每个 通道负责一个 keypoint 的回归任务,每个 heatmaps 上不同点的值表示的是对应的 keypoint 在这个 pixel 上的出现的概率,上图中并未给出 1 × 1 1×1 1×1 卷积层,只给出了大致的结构

  • 在每个 stack 和 stack 之间,引入了 intermediate supervision(中间监督),如下图所示:
    在这里插入图片描述

      • 沙漏状的部分就是一个 stack,然后通过一个 1 × 1 1×1 1×1 卷积之后产生了 n n n 个 通道的 heatmap 图(图中蓝色部分),这个 heatmap 的图根据 label 产生中间监督
      • 然后将这个 heatmap 再经过一个 1 × 1 1×1 1×1 卷积层,与 stack 的那个分支进行叠加继续进行后面的任务。stack 后面的两个 1 × 1 1×1 1×1 的卷积层就是上文中提到的那个没有出现在第一个图中的结构
  • 由于如果对 256 × 256 256×256 256×256 的原图直接进行操作对 gpu 的要求会很高,因此在 hourglass 中出现的最大分辨率的图片是 64 × 64 64×64 64×64 的分辨率

  • 整个网络线从 7 × 7 , s t r i d e = 2 7×7,stride=2 7×7,stride=2 的卷积核开始,然后跟着一个残差模块,以及一个最大池化将 256 的输入图片降低到 64 维;剩下的部分就如上图所示,是一个对称的结构(上图中没有表示 size=7 的卷积和池化操作),最终每个 hourglass 的 stack 的输出特征图依然是 256 × 256 256×256 256×256

额外尝试

  • 本文也尝试过采用新提出的“使用多个小卷积核来代替大卷积核”以及“残差网络结构代替普通的 convlution 结构”还有 inception 结构,但是这些方法除了能在初期的表现中获得提升之外,对整体网络性能以及运行时间几乎没有帮助。
  • 最终在 stack hourglass 的设计中,没有使用超过 3 × 3 3×3 3×3 尺度的卷积核,并且采用了 bottleneck 结构来约束每层上的参数数量(因为 bottleneck 中包含了两个 1 × 1 1×1 1×1 卷积核来控制通道数量)

2017 RMPE: Regional Multi-Person Pose Estimation

动机

  • top-down 的方法一般分为两个阶段:
    • 首先通过 human detector 检测出 box
    • 然后再通过 pose estimator 对 box 中的人进行 estimation 这样的方法对于 box 的精确度要求很高,如果 box 没有定位准确,那么最终结果就不好
  • 因此本文构造了一个不那么依赖第一阶段检测精度的网络
  • 本文着重对比了 Faster-RCNN 和 SPEE,他们存在定位错误、冗余计算等问题

论文思路

  • 设计了一个 对称的空间映射网络(symmetric spatial transformer network SSTN)attach 到 SPEE 的基础结构上,用来从不精准的 human bounding box 中提取出高质量(准确)的 single person region
    在这里插入图片描述

  • 另外,采用了一个并行的 SPEE 分支结构(parallel SPEE branch)用来提升结果的精度
    在这里插入图片描述

  • 为了减少冗余计算的问题,提出了一个 parametric 的 pose NMS(带参数的姿态非最大值抑制),这个算法通过提出一种新的比较 pose similarity 的方法(pose distance metric)来避免多次重复计算 pose

  • 最后提出了一种 pose-guided human proposal generator(PGPG) 姿态主导的人物推荐生成器,通过学习 human detector 对于不同姿态的输出,PGPG 可以模拟产生 human 的 bounding box,这样的方式可以用来扩充数据集
    在这里插入图片描述

  • 最后在 MPII 数据集上得到了 state of the art 结果

重要相关工作

  • Insafutdinov 等人提出了一个两阶段的 pipeline:
    • 首先使用 Faster R-CNN 作为一个 human detector
    • 使用一个一元的 DeeperCut 作为第二阶段的 pose estimator
    • 论文链接:DeeperCut: A Deeper, Stronger, and Faster Multi-Person Pose Estimation Model

网络结构 + pipeline

在这里插入图片描述

  • human detector 检测出大致的人的 bounding box 得到 human proposals,然后把这个结果送入 symmetric STN + SPPE ,之所以叫对称的 STN,是因为开始的 STN 接受 human proposal 而SDTN 负责产生 pose proposal 并把 estimated human pose 再重新映射回原图的对应位置,即完成了 estimator 的工作
  • 最后 SDTN 产生的 pose proposal 会通过 pose NMS 结构进行非最大值抑制剔除,减少运算的资源浪费
  • 不同于传统的训练方法,SSTN + SPPE 部分的训练将会通过 proposals generator(PGPG) 产生的数据来完成
  • parallel 的 SPPE 只用在训练阶段做为一个 regularizer 的角色来帮助 SPPE得到更加精确的结果;当 STN 得到的 human proposal 不是 center-located 的时候,parallel SPPE 会通过 loss 来把 STN 得到的 human proposal 约束训练得到更加 center-located 的 human proposal 区域
  • 训练阶段的 parallel SPPE 的所有 layer 的权重都是冻结的不进行训练的,只是每次 SPPE 进行误差迭代的时候都是使用2条支路(SPPE 和 Parallel SPPE)输出的总误差来训练网络
  • 对于“带参数的姿态非最大值抑制”首先选择置信度最高的 pose作为参考,靠近它的pose通过淘汰标准(criterion)来消除。对于剩下的pose,重复上述过程,直到消除冗余姿势,并且仅返回唯一的pose:
    • criterion:在 Parameter pose NMS 中的 criterion 是定义了一个 pose distance metric,也就是说通过两个 pose 的距离来衡量两个 pose 是否相似,当两个 pose 的相似度小于某个阈值 η \eta η,这个 pose 就会被删掉
    • pose distance = K s i m + H s i m K_{sim} + H_{sim} Ksim+Hsim
      • K s i m K_{sim} Ksim

        • 假设现在有两个 pose P i , P j P_i,P_j Pi,Pj 他们之间的距离用 d p o s e ( P i , P j ) d_{pose}(P_i,P_j) dpose(Pi,Pj) 来表示, P i P_i Pi 的 bounding box 用 B i B_i Bi 表示,bounding box 的 center 用 k i n k_i^n kin 表示,同时用 B ( k i n ) B(k_i^n) B(kin) 代表 B i B_i Bi 中心区域的范围(因为中心区域并不是简单的一个点,而是大约 B i B_i Bi 1 / 10 1/10 1/10 的规模)如果另外一个 pose P j P_j Pj 的 bounding box 中心 k j n k_j^n kjn B i B_i Bi 的中心范围里面,那么 P i , P j P_i,P_j Pi,Pj 之间的相似度表示就是:
          在这里插入图片描述

        • 这里的 k i n k_i^n kin 中的 n n n c i n c_i^n cin 中的 n n n 都代表了第 i i i 个 pose 中的第 n n n 个 keypoint

        • c i n c_i^n cin 代表第 i i i 个 pose 的第 n n n 个 keypoint 的 confidence score;因此对于置信分数低的 keypoint,通过 t a n h tanh tanh 激活之后就会几乎为 0,相乘之后也几乎为 0;而分数高的相乘之后会接近于 1

        • 总结来说,如果有两个预测的 pose: poseA 和 poseB,如果 poseA 是置信度最大的那一个,那么就以 poseA 为标准,然后开始算 poseB 的中心位置是否在 poseA 的中心区域中,如果在,就把 poseA 和 poseB 的每一个 keypoint 进行 K s i m K_{sim} Ksim 的运算并求和得到最终的值。通过这个 K s i m K_{sim} Ksim softly 计算了各个 Pose 之间匹配的关节的数量

      • H s i m H_{sim} Hsim

        • 剩下部分的距离就是将两个 pose 的中心点进行计算,即:
          在这里插入图片描述
      • 因此最终的 distance 为:
        在这里插入图片描述

训练 trick

  • 训练 f ( P i , P j ∣ Λ , η ) f( P_i, P_j | \Lambda,\eta) f(Pi,PjΛ,η) 一共有 4 个需要训练的参数,训练中每次训练其中固定的两个参数,然后再固定其他两个参数训练另外两个。

PGPG 模块思路

  • 由于在真实的数据中,对每个 image 中的 person,每个人只有一个 bounding box 的 groundtruth;
  • 但由于通过 human detector 产生的预测的 bounding box 和 bounding box 的 groundtruth 之间坐标的差值 δ B \delta B δB 以及这个 person 的 pose 的 groundtruth P P P,我们可以根据 ( δ B ∣ P ) (\delta B|P) (δBP) 的分布 P ( δ B ∣ P ) P(\delta B|P) P(δBP) 来产生类似 human detector 产生的数据!

PGPG 的具体实现

  • 直接学习 P ( δ B ∣ P ) P(\delta B|P) P(δBP) 这个分布是很难的,因为 human 的 pose 变化太多样了
  • 因此,转而尝试学习 P ( δ B ∣ a t o m ( P ) ) P(\delta B|atom(P)) P(δBatom(P)),其中 atom§ 代表 Pose P P P 的 atomic pose
  • 首先先把数据集中所有的 pose 进行对齐(即让他们具有相同的长度);然后使用 k-means 算法来聚类这些对齐的姿势,并且计算聚类的中心点。
  • 通过聚类步骤,假设我们一共分了 a,b,c,d… 等 30 个种类的 pose,那么对于 pose 类 a 中的所有 pose 他们都可以看做是共享一个 atomic pose,即聚类数据的中心。此时,可以通过 human detector 得到 a 类中的每一个 pose 和 他们的 groundtruth pose 的坐标偏差 δ B \delta B δB
  • 之后,让这些坐标偏差(offset) 形成一个频率分布,并且让我们的数据符合 Gaussian mixture distribution;对于每一种不同的 atomic pose 大类,我们都可以得到不同的 Gaussian mixture distribution。进行可视化之后如图所示:
    在这里插入图片描述

为什么不使用 regression 的思想来约束 SPPE 而选用 parallel SPPE?

  • 虽然 STN 可以较好地截取 human proposal 到一个较为 center-located 位置,但是也不能保证训练中对一张图片每次 STN 的结果都一致,更不用说每次都和 human proposal 的 label 一致,因此在这种情况下如果采用 regression 的方式,如果每一次位置相差较大,那么对后面产生的影响将会更大,可能会导致 SPPE 的精度降低。

CVPR2017 Multi-Context Attention for Human Pose Estimation

论文思路

  • 借鉴了 stacked hourglass 的结构来产生多尺度的特征图
  • 使用 conditionla random field(CRF) 来建模 attention map 中的相邻区域的联系
  • 既采用关注整体 body 的 attention model,也采用关注部分 body 信息的 attention model
  • 创新的提出了一种 hourglass residual units 结构(HRU)来增加整个网络的感受野

论文卖点

  • 不同于普通检测采用的矩形bounding box, attention maps 是根据图片的 features 产生的,因此通过 attention 关注的兴趣区域是和图片中人的轮廓类似的、自适应的
  • 正是因为 attention 具有这么多的好处,所以他可以帮助修复哪些缺失的 body parts,也可以分辨出有歧义的 background
  • 而且与传统的采用一个 spatial softmax normalization 的方式产生注意力不同,本文设计了一种全新的 attention 方法叫做 conditional random field(条件随机场)可以更好地建模相邻区域的关系。
  • HRU 结构能够快速地增长网络的感受野
  • 这篇文章是第一个在 pose estimation 领域引入 attention 的工作

论文 pipeline

  • 首先,为了使用 attention 来帮助引导多尺度的特征表示进行学习,本文采用了 stacked hourglass 网络结构来建立多尺度的 attention model

  • 在 stacked hourglass 的每一个 stack 中,

    • 首先产生不同尺度的 feature,然后借助这些 feature 产生不同尺度的 attention maps,我们叫做 多尺度的 attention maps
    • 然后对不同的 stack 产生 attention maps,这些 attention maps 称为多语义的 attention maps。
      在这里插入图片描述
  • 换句话来说,在一个 stack 中的不同的层之间产生的 attention map 称为多尺度 attention map,而在不同 stack 之间产生的 attention map 叫做语义attention

  • 除了上述两种 attention,本文还使用了第三种 attention 机制,称为 整体-部分 attention (holistic-part attention model)模型

  • 这三种 attention 的使用可以为不同的特征进行权重的 “重分配(reweight)”从而自动地推理“感兴趣区域”

基础网络 basic network

  • 使用 8 个 stack 的 hourglass 作为 base network,输入图片尺寸是 256 × 256 256×256 256×256,输出特征尺寸是 P × 64 × 64 P×64×64 P×64×64,其中 P P P 是 keypoints 的种类数(COCO 数据集是 17类 keypoint,MPII是16类),并且使用 MSE 作为损失函数

改进的 HRU 模块

  • 原本的 hourglass 中使用的 residual 模块在本文中被 HRU 模块替换,这是 hourglass 中的 residual 模块:
    在这里插入图片描述

  • 这是 HRU 模块:
    在这里插入图片描述

  • 原本的 hourglass 的残差模块其实就是一个 N × 1 × 1 N×1×1 N×1×1 的卷积核,即 C 1 a C1_a C1a 就是通过 C 1 C_1 C1 进行 1 × 1 1×1 1×1 卷积调整通道数之后得到的,然后跟 C 2 b C2_b C2b 上采样之后的特征图进行 a d d add add 得到最终的 C 1 b C1_b C1b 的过程

  • 而在 HRU 单元中,把得到 C 1 a C1_a C1a 的过程复杂化了,变成三条支路的综合作用了, C 1 C1 C1 特征图通过:

    • B B B 支路进行 1 × 1 1×1 1×1 --> 3 × 3 3×3 3×3 --> 1 × 1 1×1 1×1 卷积
    • C C C 支路进行 池化降采样 --> 3 × 3 3×3 3×3 --> 3 × 3 3×3 3×3 --> 1 × 1 1×1 1×1 --> 上采样
    • A A A 支路直接赋值原来的 C 1 C_1 C1 的特征图
      之后,进行 a d d add add 再得到 C 1 b C1_b C1b 特征图

attention 机制

传统的卷积 attention
  • 首先对于某个网络层输出的特征图 f f f,对他使用卷积核 W a W^a Wa 卷积之后再通过 g g g 进行激活,一般 g g g 使用的是 softmax;然后得到这个特征图 f f f 的 attention map Φ \Phi Φ
  • 然后将 attention map Φ \Phi Φ 和 原特征图 f f f 进行对应位置相乘 (pixel-wise multiplication 得到 h a t t = Φ ⋆ f h^{att}=\Phi ⋆ f hatt=Φf 这个 ⋆ ⋆ 代表的是每个通道的特征图和 attention map 相乘,假设 h a t t ( c ) h^{att}(c) hatt(c) 代表的是 feature 的第 c c c 个通道,那么 h a t t ( c ) = f ( c ) ◦ Φ h^{att}(c) = f(c)◦ \Phi hatt(c)=f(c)Φ
  • 对于一个 feature map 来说,虽然他有 n n n 个通道,但是他对应的 attention map 只有一个就是 Φ \Phi Φ,因为 n n n 个通道的 feature 叠加才组成了 feature map;当 feature map 和 attention map 相乘的时候,其实是 Φ \Phi Φ 在 feature map 的通道维度上进行了广播操作。
本文的 multi-context attention 操作概要
  • 传统的 attention 是在一个 feature map 卷积过后直接采用 softmax 来得到,这样是对整个 feature map 的 normalize,是从整体的角度进行 softmax,因此忽略了相邻空间区域的联系;针对这个问题,本文提出了 CRF attention 的方法
  • 本文的 attention 是基于多尺度的 feature map 的,因此可以让整个模型的鲁棒性更加强
  • 其次,本文的多语义 attention 是通过每一个 stack 产生的
  • 最后,一个层次化的由粗到细(从整体 body 到 parts)的 attention 机制
CRF attention
  • 假设整个神经网络有 t t t 个 time steps

  • 0 0 0 个 step 产生的特征图是 f f f,它的 attention map为 σ ( W k ∗ f ) \sigma(W^k * f) σ(Wkf)

  • 后面的每个 step 的 attention map 都是 σ ( W k ∗ Φ t − 1 ) \sigma(W^k * \Phi_{t-1}) σ(WkΦt1),即使用上一个时间步的 attention 作为原料再产生 attention 这样可以将 feature 图相邻部分的关联关系也抓取出来

  • 因此 CRF attention 的公式可以综合表示为:
    在这里插入图片描述

  • 当然在这里引入了新的结构:recursive convolution

多尺度的 attention
  • 假设我们用 Φ 64 \Phi_{64} Φ64 来表示尺寸为 64 × 64 64 × 64 64×64 的 attention map

  • 在网络 upsampling 的过程中会产生各种尺度的 attention map(都使用公式8的方式产生attention map);这些不同尺度的 attention maps 可以表示为 Φ r , r = 8 , 16 , 32 , 64 \Phi_{r},r=8,16,32,64 Φr,r=8,16,32,64

  • 那么当我们将这些 Φ r , r = 8 , 16 , 32 , 64 \Phi_{r},r=8,16,32,64 Φr,r=8,16,32,64 进行 upsampling 放大成 64 × 64 64×64 64×64 尺度的 attention maps,这个过程我们可以用 Φ r − > 64 , r = 8 , 16 , 32 , 64 \Phi_{r->64},r=8,16,32,64 Φr>64,r=8,16,32,64 表示

  • 然后将这些不同尺度 upsamling 到 64 的 attention maps 进行叠加操作,得到最终的多尺度 attention maps Φ m = Σ r = 8 , 16 , 32 , 64 Φ r − > 64 \Phi_m=\Sigma_{r=8,16,32,64}\Phi_{r->64} Φm=Σr=8,16,32,64Φr>64

  • 最后,将最后一层的特征图 f f f 和多尺度的 attention map Φ m \Phi_{m} Φm 进行对应位置相乘得到 h 1 a t t h_1^{att} h1att
    在这里插入图片描述

  • 这个 f ⋆ Φ m f⋆\Phi_m fΦm 的操作只用在最后一层的 feature 上,因为中间层的进行这种操作会导致中间层的特征图出现大量的 0,不利于梯度的反向传播。

  • 我们把 h 1 a t t h_1^{att} h1att 称作 refined feature(优化的feature),因为这是 feature 通过 attention map 重新组成的结果

  • h 1 a t t h_1^{att} h1att 继续采用 CRF 进行更加包含邻接信息的 attention 操作(公式8中的步骤):
    在这里插入图片描述

  • 可以最终得到最终的包含 “多尺度”以及 “邻接信息”的特征图 h 2 a t t h_2^{att} h2att

多语义的 attention
  • 由于上面的 CRF 和 multi-resolution attention 是在每一个 stack 内部进行应用的,所以随着 stack 的不断叠加,不同 stack 的输出结果自然对应着不同的语义,而最后一个 stack 的输出结果自然包含着最丰富的的语义信息,这个在下图中可以看得出来
    在这里插入图片描述

  • 因此没有针对每个 stack 的具体的 attention 机制,所谓多语义的attention 只是多尺度 attention 的一个最终的表现结果

层次化的整体——局部的attention
  • 在第 4 到第 8 个 stack,一直采用的都是 h 1 a t t h_1^{att} h1att 来生成 body part 之间的 attention
    在这里插入图片描述

  • p ∈ { 1 , . . , P } p\in\{1,..,P\} p{1,..,P} 代表的是 p p p 类 keypoints, W p a W_p^a Wpa 是针对 p p p p + 1 p+1 p+1 通道的卷积核

  • 比如 stack4,最后输出 h 1 a t t h_1^{att} h1att 一定是有 p + 1 p+1 p+1 个通道(p个通道为 p 类 keypoint准备,另外一个通道为背景),然后采用一个 p p p 个通道的卷积核 W p a W_p^a Wpa 得到 attention map s p s_p sp,然后再使用 CRF 的方法获得各个 part 之间的 attention 关系即 Φ p \Phi_p Φp

  • 这里之所以再次强调 p p p 是因为中间层的操作虽然和这里没有什么不同,但是在整个 stack 输出的时候,必须是调整成 p + 1 p+1 p+1 个通道的,因此这里将前面的步骤带入 p p p 再重述一遍

  • 最后将 h 1 a t t h_1^{att} h1att(多尺度特征)与针对 p p p 类 body part 的 attention map Φ p \Phi_p Φp 进行乘积得到
    在这里插入图片描述

  • 最终 p p p 个 body parts 的 heatmap 是根据 h p a t t h_p^{att} hpatt 生成的
    在这里插入图片描述

  • y p ^ \hat{y_p} yp^ 是 第 p p p 个 body part 的 heatmap; w p c l s w_p^{cls} wpcls 是分类器

  • 这种情况下,可以保证 Φ p \Phi_p Φp 是针对于 body point p p p 的 attention map

CVPR2017: Openpose——Realtime Multi-Person 2D Pose Estimation using Part Affinity Fields ∗

动机

  • 社交场景中一个图片可能包含不定数量的人,他们的大小和位置都不确定
  • 由于接触、遮挡和肢体关节,人与人之间的相互作用会产生复杂的空间干扰
  • 人越多,计算量越复杂,实时预测越困难

之前工作存在的问题

以往的 top-down 解决方案及存在的问题

  • 自顶向下(top-down)方案首先要通过边界框定位人的位置,如果这个步骤出现问题,那么是致命且无法恢复的
  • 这类 top-down 方法 人数和计算量是成比例增长的

以往bottom-up 自底向上的优势和问题

  • 计算的复杂度和人数不是成正比关系的。
  • 然而,自底向上的方法不依赖于当前 body 的其他 part,也不依赖于其他的 body 提供的全局上下文线索
  • 以往的自底向上并没有维持高效,而是在全局推理方面浪费了大量的效率
  • Pishchulin 的工作开创了 自底向上,但是时间方面存在短板
  • Insfutdinov 基于 Resnet 建立了更加强大的区域 detoctor 并且确立了 image-dependent pairwise score 来提高时间效率,但还是在分钟级别
  • 【11】中采用了单独的 逻辑回归单元来进行操作,因为 pairwise 的表示很难精确地回归

论文卖点

  • 提出了一种多人姿态估计的方法
  • 精确度达到最好
  • 首次通过 PAF(part affinity fields 区域关联场) 实现了在自底向上方法中的关联得分表示,PAF 是一组二维向量场,用来编码图像域上肢体的位置和方向
  • 证明了同时推断这些自底向上的检测和关联表示,充分地编码全局上下文,允许贪婪的解析以一小部分的计算成本获得高质量的结果。

论文 pipeline

  • 我们的方法以整个图像作为两分支CNN的输入,共同预测:
    • 身体部位检测的置信图,如(b),
    • 以及区域关联场,如图©所示。
  • 解析步骤根据上面两个部分的结果,将候选身体部位关联起来(d)。
  • 最终将它们组装成图像中所有人的全身姿势(e)

在这里插入图片描述

  • 输入图大小: w ∗ h w * h wh
  • 首先,前馈网络同时预测一组人体部位位置的二维置信图 S S S(图2b)。
  • 以及部分亲和度的一组二维向量场 L L L,用于编码部分之间的关联程度(图2c)
  • 每个 body part 都会有一个置信图,一共有 J J J 个置信图 S = ( S 1 , . . . S J ) ,   S j ∈ R w ∗ h ,   j ∈ { 1... J } S = (S_1,...S_J),~ S_j \in R^{w*h}, ~j\in \{1...J\} S=(S1,...SJ), SjRwh, j{1...J}
  • 每个 limb(胳膊、脚踝、等)都会有一个矢量场,一共有 C C C 个矢量场 L = ( L 1 , . . . L C ) L = (L_1,...L_C) L=(L1,...LC), L c ∈ R w ∗ h ∗ 2 , c ∈ { 1 , . . C } L_c \in R^{w*h*2} ,c\in \{1,..C\} LcRwh2,c{1,..C} 因为每个 C C C 都对应着两个 keypoints 所以每个 L C L_C LC 是两个通道
  • 其中每一个矢量 L c L_c Lc 都会编码一个二维的矢量信息
  • 最终,上述的置信图和矢量会进行 greedy inference

同时 detection 和 association

在这里插入图片描述

  • 无论是 branch1 还是 branch2,都会经过多个 stage 进行重复迭代的操作

branch1 负责产生 confidence map

  • 在第一个 stage 之前,先用预训练的 VGG 进行特征提取,产生一个特征图 F F F
  • 然后对 F F F 再进行卷积,即: ρ 1 ( F ) \rho^1(F) ρ1(F) 得到 S 1 S^1 S1

branch2 负责预测 association field

  • 在 branch1 进行 S 1 = ρ 1 ( F ) S^1 = \rho^1(F) S1=ρ1(F) 操作的时候,branch2 进行 L 1 = ϕ 1 ( F ) L^1 = \phi^1(F) L1=ϕ1(F) 得到 PAF,其中 ϕ 1 ( ⋅ ) \phi^1(·) ϕ1() 操作也是卷积操作

合并 branch1 & branch2 进行进一步特征提取

在这里插入图片描述

  • 在 stage1 之后,每个 stage 进行操作的“原料” 都是上一个 stage 的输出 + 原始特征图 F F F
  • 其中 F F F 就是第一个 stage1 之前预训练网络产生的特征图; S t − 1 S^{t-1} St1 L t − 1 L^{t-1} Lt1 分别是上个 stage 的 branch1 和 branch2 产生的信息。 ( F , S t − 1 , L t − 1 ) (F,S^{t-1},L^{t-1}) (F,St1,Lt1) 进行特征融合后再分别经过 ρ t , ϕ t \rho^t,\phi^t ρt,ϕt 卷积操作后得到 S t , L t S^t,L^t St,Lt

中间监督 intermediate supervision

  • 为了引导每个 stage 的 branch1 和 branch2 分别产生更好的 confidence map 和 PAF,采用了阶段性的中间监督来引导训练,loss 采用的是 L 2 l o s s L_2 loss L2loss

在这里插入图片描述

  • S j ∗ S^*_j Sj 是body parts 的 confidence map 的 groundtruth
  • L c ∗ L_c^* Lc 是 PAF 的 groundtruth 标签
  • W W W 是一个 binary mask ,当这个样本的 annotation 在 p p p 位置不存在的时候, W ( p ) = 0 W(p)=0 W(p)=0
  • 中间层的监督操作可以防止梯度消失的问题;
  • 因此经过 T T T 个 stage 之后的损失函数之和可以用如下公式来计算:
    在这里插入图片描述

Confidence map 是怎么生成的

可以参考我的另一篇文章:Body estimation 学习之:(1)生成关节 heatmap 全网最详细操作教程

  • 每个 confidence map 代表的都是某个 body part 在每个 pixel 上出现的概率。

  • 如果是单人的 body estimation 的情况,假设在这个 image 中出现了 n n n 个keypoints,那么理想情况下,经过网络卷积之后,输出的 n n n 个特征通道中,每个通道应该有 1 1 1 个区域是置信度很高的;但是如果是一个图片中有多个人出现,那么 n n n 个通道的输出特征图中可能每个通道就会出现多个置信度很高的区域。

  • 假设当前的 image 中一共有多个 person,我们为每个 person 产生置信图 S j , k ∗ S_{j,k}^* Sj,k,令 x j , k ∈ R x_{j,k}\in R xj,kR 代表 person k k k 的 body part j j j 的 groundtruth 位置,置信图 S j , k ∗ S_{j,k}^* Sj,k p p p 点的值可以表示为:
    在这里插入图片描述

  • σ \sigma σ 控制着置信图中的峰值的发散范围

  • 通过对这个 S j , k ∗ S_{j,k}^* Sj,k 取最大值的操作,我们可以得到 groundtruth 的 confidence map
    在这里插入图片描述

  • 通过下面的代码生成一个人的 confidence map,每一行的图代表一个通道的(即一类 body part)confidence map

  • 其实本文中一直提到的 confidence map 就是其他文章中的 heatmap

import math
import numpy as np
import cv2
import matplotlib.pyplot as plt
from matplotlib import cm as c

# 18 个身体 keypoints 
points = [[367,  81,  2],
          [378, 118,   2],
          [358, 129,   2],
          [341, 159,   2],
          [309, 178,   2],
          [399, 108,   2],
          [433, 142,   2],
          [449, 165,   2],
          [393, 214,   2],
          [367, 273,   2],
          [396, 341,   2],
          [424, 203,   2],
          [429, 294,  2],
          [466, 362,   2],
          [360,  75,   2],
          [374,  73,   2],
          [356,  81,   2],
          [386,  78,   2]]
# 标注对应的图片
bgr_image = cv2.imread("./datasets/val2017/000000000785.jpg")
rgb_image = cv2.cvtColor(bgr_image, cv2.COLOR_BGR2RGB)

def put_heatmap(shape, center, sigma):
    # centerx,centery 分别是标注点的横纵坐标
    center_x, center_y, _ = center
    # 产生一个 w*h 的全0 mask
    heatmap = np.zeros((shape[0], shape[1]))
    height, width, _ = shape
    th = 4.6052
    delta = math.sqrt(th * 2)   # 3.0348640826238 
    # 沿着 x 轴高斯分布的最左边的边界值
    x0 = int(max(0, center_x - delta * sigma))
    # 沿着 y 轴高斯分布的最上面的边界值
    y0 = int(max(0, center_y - delta * sigma))
    # 沿着 x 轴高斯分布的最右边的边界值
    x1 = int(min(width, center_x + delta * sigma))
    # 沿着 y 轴高斯分布的最下面的边界值
    y1 = int(min(height, center_y + delta * sigma))

    # 对于 x,y圈定的区域中,生成高斯分布的 heatmap 圆点
    for y in range(y0, y1):
        for x in range(x0, x1):
            # 通过这个公式可以知道,越接近 centerx 和 centery 的点 d 的值会越小
            # 处在 centerx,centery 上,d = 0
            d = (x - center_x) ** 2 + (y - center_y) ** 2  # 高斯!
            exp = d / 2.0 / sigma / sigma  # 高斯!
            if exp > th:
                continue
            heatmap[y][x] = max(heatmap[y][x], math.exp(-exp))
            heatmap[y][x] = min(heatmap[y][x], 1.0)
    return heatmap
    
for point in points:
    guss_heatmap = put_heatmap(rgb_image.shape, point, sigma = 7)
    fg, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(40, 8))

    ax1.imshow(rgb_image)
    ax2.imshow(guss_heatmap, cmap=c.jet)
    guss_heatmap = np.array(guss_heatmap)
    
    guss_heatmap[guss_heatmap < 0] = 0
    guss_heatmap = cv2.normalize(guss_heatmap, guss_heatmap, 0, 255, cv2.NORM_MINMAX)
    guss_heatmap = np.uint8(guss_heatmap)
    jetmap = cv2.applyColorMap(255-guss_heatmap, cv2.COLORMAP_JET)
    alpha = 0.5
    out = cv2.addWeighted(rgb_image, alpha, jetmap, 1 - alpha, 0, jetmap)
    ax3.imshow(out, cmap=c.jet)

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

PAF 来组合各个 body parts

在这里插入图片描述

  • PAF 同时保留了位置和朝向的信息

  • 每个 body part 的 PAF 场都是一个二维的 vector,对于一个 limb(肢节)的区域内的每个 pixel 都会通过一个 2D 的 vector 来编码它所处位置的方向信息

  • 对于给定 groundtruth 的两个 keypoint 位置 X j 1 , k , X j 2 , k X_{j_1,k},X_{j_2,k} Xj1,k,Xj2,k 他们之间一定可以形成一个矢量 ,这个矢量的单位矢量可以表示为
    在这里插入图片描述

  • 在 limb c c c 的范围内的任何一个位置 p p p 它的矢量场的作用可以表示为
    在这里插入图片描述

  • 而对于那些不属于 limb c c c 范围内的 pixel,我们对他们的 PAF 不进行计算,因此对于 image 上所有的 pixel 的 PAF 的计算应满足下列公式:
    在这里插入图片描述

  • 接下来解释什么叫 p p p on limb c , k c,k c,k,也就是说如何定义一个 pixel 是否属于这个 limb c c c 呢,要满足下面的公式:
    在这里插入图片描述

  • σ l \sigma_l σl 是这个 body part 的宽度,例如图中的胳膊的宽度,这是一个以 pixel 为单位的值

  • l c , k = ∣ ∣ x j 2 , k − x j 1 , k ∣ ∣ 2 l_{c,k}= ||x_{j_2,k}-x_{j_1,k}||_2 lc,k=xj2,kxj1,k2

  • v ⊥ v_\bot v 表示垂直于 v v v 的单位向量

  • 这个公式的意思就是在 ( x j 2 , k , x j 1 , k ) (x_{j_2,k},x_{j_1,k}) (xj2,k,xj1,k) 的方向上,这个 p p p 不能超过 ∣ ∣ x j 2 , k − x j 1 , k ∣ ∣ 2 ||x_{j_2,k}-x_{j_1,k}||_2 xj2,kxj1,k2 这个长度范围,而在垂直于这个的方向上不能超出这个 body part 的宽度。

  • 最后,对于 limb c c c 的某个 pixel p p p 位置的 PAF 矢量场是由叠加在 p p p 位置的所有场的强度和方向决定的
    在这里插入图片描述

  • 图中存在 2 2 2 个人的话,当存在较为拥挤的情况,即两个人的胳膊很靠近甚至重叠,那么对于胳膊内的所有像素点的位置,其实需要将这两个人在这一点产生的 L c , 1 ∗ ( p ) + L c , 2 ∗ ( p ) L^*_{c,1}(p) + L^*_{c,2}(p) Lc,1(p)+Lc,2(p) 叠加后再取均值得到的。

  • 然后对于 PAF 的置信度,我们的衡量标准是沿着 PAF 场的线积分。例如上面的胳膊的情境中,我们从 X j 1 , k X_{j_1,k} Xj1,k 积分到 X j 2 , k X_{j_2,k} Xj2,k,最终得到的积分的值就是这两个 keypoints 之间的 PAF 的可信度,公式表示为
    在这里插入图片描述

  • 在实际操作中对这个 PAF 的值的结果进行了近似的代替,即:
    在这里插入图片描述

  • 在多人识别的场景中,因为对于两类 keypoints 之间的场会存在多个,例如下图:
    在这里插入图片描述

  • 都是左手手肘 -> 手腕的 PAF,在这个图中,有左手肘有两个点,左手腕也有两个点,左手肘和左手腕的连接方式不只一种,这种情况下, A A A 的左手肘到 B B B 的左手腕会有一个 PAF 场的积分值,而 A A A 的左手肘到 A A A 的左手腕也会有一个积分的 PAF 值,那么如何在这些连接方式中做出选择呢?

选择最佳的 PAF 连接方式
  • 对于 body part 的候选区域可以表示为: D J = { d j m : j ∈ { 1... J } , m ∈ { 1... N j } D_J=\{d_j^m:j\in\{1...J\},m\in\{1...N_j\} DJ={djm:j{1...J},m{1...Nj}

  • j j j 表示 keypoint 的类别是第 j j j 类,比如胳膊肘 keypoint

  • N j N_j Nj 表示在整张图中的类别 j j j 的候选点的个数,例如图中的 绿点表示胳膊肘,这类点有两个

  • 所以 d j m d_j^m djm 表示的就是在 第 j j j 类 keypoint 中的第 m m m 个候选 body part 的位置;这个 keypoint 最终是要和其他类别的的、属于同一个人的其他 keypoint 类候选点进行连接最终组成不同的 limb

  • 我们用 z j 1 , j 2 m n ∈ { 0 , 1 } z_{j_1,j_2}^{mn}\in \{0,1\} zj1,j2mn{0,1} 来表示 d j 1 , d j 2 d_{j_1},d_{j_2} dj1,dj2 这两个候选的 part 是否连接成一个部分;我们现在的目的是寻找最优的方式来组成 Z = { z j 1 , j 2 m n : f o r j 1 , j 2 ∈ { 1... J } , m ∈ { 1... N j 1 } , n ∈ { 1... N j 2 } } Z=\{z_{j_1,j_2}^{mn}:for j_1,j_2 \in \{1...J\},m\in \{1...N_{j_1}\},n\in\{1...N_{j_2}\}\} Z={zj1,j2mn:forj1,j2{1...J},m{1...Nj1},n{1...Nj2}}

  • 对于上图中,我们可以看到,在不同 joint 类间组合的方式有很多种,这组成了一个 图结构,图的顶点是 D j 1 , D j 2 D_{j_1},D_{j_2} Dj1,Dj2 来表示,比如 D j 1 D_{j_1} Dj1 就表示所有的 蓝色候选点,而 D j 2 D_{j_2} Dj2 表示所有红色的候选点,以此类推;这时候 graph 的边的权重就是通过公式(10)算出来的积分的值

  • 在这种情况下,我们想要得到的是:挑选出的 edges 具有最大的 weight。即:
    在这里插入图片描述

  • E m n E_{mn} Emn 代表的是沿着body part j 1 j_1 j1 j 2 j_2 j2 之间 PAF 场强方向的积分值

  • z j 1 , j 2 m n z_{j_1,j_2}^{mn} zj1,j2mn 的值要么为 0,要么为 1,因为有些节点类之间的关系本来就无需计算,因为 label 中会给某些节点类之间无关系,比如:头部的节点类 j h j_h jh 与胳膊肘的节点类 j e j_e je 之间的 z j h , j k m n z^{mn}_{j_h,j_k} zjh,jkmn 就为 0

  • 通过上述的计算我们最终可以得到最大的 E c E_c Ec 对应的 j 1 , j 2 j_1,j_2 j1,j2 分别对应的 m , n m,n m,n 组成当前的 limb

  • 为了减少计算的复杂度,作者不使用所有可能的 匹配点对 来进行计算,而是首先通过最少的 edges 建立一个 spanning tree 的结构,在上图中的 (c)中可以看到,并不是把所有的 蓝色点和绿色点、红色点进行全覆盖的匹配。

  • 其次,本文将匹配不同 part 的问题,分解成了一个 二部图最优匹配的问题,最后采用匈牙利算法解决,从而获得全局最优的 body part 组合

  • 通过上述两种方式简化,最终把(12)可以简化为:
    在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

暖仔会飞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值