Stacked Hourglass Networks for Human Pose Estimation (人体姿态识别之堆叠沙漏网络)

一,翻译

Abtract. 概述

这项工作引入了一种新的卷积网络架构来完成人体姿态估计任务。 在所有尺度上进行处理并整合,以最好地捕捉与身体相关的各种空间关系。 我们展示了重复使用自下而上,自顶向下的处理结合中间监督是提高网络性能的关键。 我们将这种架构称为“堆叠式沙漏”网络,这种网络基于池化和上采样的后续步骤,以产生最终的预测集。 FLIC和MPII基准测试达到了最新的结果,超越了所有最新的方法。

Keywords. 关键词 人体姿态检测

这里写图片描述

1. Introduction 介绍

在理解图像和视频中人们的关键一步是准确的姿态估计。 给定一个RGB图像,我们希望确定身体重要关键点的精确像素位置。 清楚的识别一个人的姿势和肢体对于动作识别等更高层次的任务非常有用,并且还可以作为人机交互和动画等领域的基础工具。
作为一个公认的视觉问题,姿态估计在过去的各种艰巨的挑战一直困扰着研究人员。良好的姿态估计系统必须对遮挡和严重变形具有鲁棒性,在罕见和新颖的姿态上成功,并且由于服装和照明等因素而影响外观的变化。早期的工作使用鲁棒的图像特征和复杂的结构化预测来处理这样的二元结构:前者用于产生局部解释,而后者用于推断全局一致的姿态。
然而,这种传统的流水线已经被卷积神经网络极大地重塑,卷积神经网络是许多计算机视觉任务中性能爆炸的主要驱动力。最近的姿态估计系统已经普遍采用了卷积神经网络作为其主要构建块,很大程度上取代了手工绘制的特征和图形模型;这种策略在之前的标准基准上产生了巨大的改进。
我们继续沿着这个轨迹,引入一种新的“堆积沙漏”网络设计来预测人体姿势。网络捕获和整合图像的所有尺度的信息。我们把这个设计看作沙漏,基于我们对池化后的可视化和随后的上采样来获得网络的输出。像许多产生像素输出的卷积方法一样,沙漏网络汇集到非常低的分辨率,然后上采样并结合多个分辨率的特征。另一方面,现有设计中的沙漏效果主要体现在其更为对称的拓扑结构中。
我们在一个沙漏上连续不断地将多个沙漏模块放在一起。这允许重复的自下而上、自上而下的跨尺度推理。与中间监督的使用相结合,重复的双向推理对网络性能有着至关重要的作用。在MPII上,在所有关节上的平均精度提高了2%,在膝盖和脚踝等多个关节上有45%的改善。

随着Toshev的“DeepPose”的推出,对人体姿态估计的研究开始从经典方式转向深度网络.Toshev et al。 使用它们的网络直接回归关节的x,y坐标.Tompson的工作反而通过并行地通过多个分辨率库运行图像来产生热图,以同时捕捉各种尺度的特征。 我们的网络设计在很大程度上构建了他们的工作,探索如何跨越不同尺度捕获信息并调整他们的方法,以便跨不同解决方案组合功能。
这里写图片描述
汤姆森提出的方法的一个关键特征是联合使用一个卷积神经网络和一个图形模型。他们的图形模型学习典型的关节之间的空间关系。其他人最近以类似的方式解决了这一点,关于如何接近一元得分生成和成对比较相邻关节的变化。聚类检测到典型的方向,以便当其分类预测时,附加信息可用以指示相邻关节的可能位置。在没有使用图形模型或任何明确的人体建模的情况下,我们可以获得更好的性能。有几种用于姿势估计的连续预测方法的例子。CARRIRA使用他们所说的迭代误差反馈。一组预测包含在输入中,并且每个通过网络进一步预测这些预测。他们的方法需要多阶段训练,并且在每次迭代中共享权重。伟在多阶段姿态机器的工作,但现在使用的特征提取的神经网络。鉴于我们使用中间监督,我们的工作在精神上类似于这些方法,但是我们的积木(沙漏模块)是二元的。Hu& RAMANN有一个与我们更相似的体系结构,它也可以用于多个阶段的预测,但是它们的模型在计算的自下而上和自上而下的部分以及迭代之间建立了权重。在他们的工作中建立一个级联预测。这在提高高精度范围内的定位性能的同时,提高了算法的使用率,减少了存储器的使用。一个考虑是,对于许多失败情况,局部窗口内位置的重新定位将不会有很大的改进,因为错误情况通常由闭塞或错误归因的肢体组成。对于这两种情况,在局部尺度上的任何进一步的评估都不能改善预测。姿态估计问题有变化,包括使用诸如深度或运动线索的附加特征。同时,多人同时注释更具挑战性。此外,还有类似于奥利维拉的工作,它基于完全卷积网络执行人类部分分割。我们的工作仅集中于从RGB图像中的单个人的姿势的关键点定位的任务。
汤姆森提出的方法的一个关键特征是联合使用一个ConvNets和一个图形模型。他们的图形模型学习典型的关节之间的空间关系。其他人最近以类似的方式解决了这一点,关于如何接近一元得分生成和成对比较相邻关节的变化。聚类检测到典型的方向,以便当其分类预测时,附加信息可用以指示相邻关节的可能位置。在没有使用图形模型或任何明确的人体建模的情况下,我们可以获得更好的性能。有几种用于姿势估计的连续预测方法的例子。CARRIRA使用他们所说的迭代误差反馈。一组预测包含在输入中,并且每个通过网络进一步预测这些预测。他们的方法需要多阶段训练,并且在每次迭代中共享权重。伟在多阶段姿态机器的工作,但现在使用的特征提取的神经网络。鉴于我们使用中间监督,我们的工作在精神上类似于这些方法,但是我们的积木(沙漏模块)是二元的。Hu& RAMANN有一个与我们更相似的体系结构,它也可以用于多个阶段的预测,但是它们的模型在计算的自下而上和自上而下的部分以及迭代之间建立了权重。在他们的工作中建立一个级联预测。这在提高高精度范围内的定位性能的同时,提高了算法的使用率,减少了存储器的使用。一个考虑是,对于许多失败情况,局部窗口内位置的重新定位将不会有很大的改进,因为错误情况通常由闭塞或错误归因的肢体组成。对于这两种情况,在局部尺度上的任何进一步的评估都不能改善预测。姿态估计问题有变化,包括使用诸如深度或运动线索的附加特征。同时,多人同时注释更具挑战性。此外,还有类似于奥利维拉的工作,它基于完全卷积网络执行人类部分分割。我们的工作仅集中于从RGB图像中的单个人的姿势的关键点定位的任务。
我们在堆叠之前的沙漏模块紧密连接到完全卷积网络和其他设计,可以在多个尺度上处理空间信息以进行密集预测。谢等人。给出典型架构的总结。我们的沙漏模块主要从其自底向上处理(从高分辨率到低分辨率)和从上到下处理(从低分辨率到高分辨率)之间的更加对称的容量分布中偏离这些设计。例如,完全卷积网络和整体嵌套体系结构在自底向上的处理中都很重,但在自上而下的处理过程中很轻,它只包含跨多个尺度的(加权)合并的预测。完全卷积网络也经历了多个阶段的训练。堆叠之前的沙漏模块也与conv-deconv和编码器 - 解码器架构有关。 Noh等人使用conv-deconv体系结构进行语义分割,Rematas等人用它来预测物体的反射率图。赵等人。为监督,无监督和半监督学习创造一个统一的框架,增加重建损失.Yang et al。采用编码器 - 解码器架构,无需跳过连接即可生成图像。 Rasmus等人提出了一种具有特殊“调制”跳过连接的去噪自动编码器,用于无监督/半监督特征学习。这些网络的对称拓扑结构是相似的,但操作的本质是非常不同的,因为我们不使用unpool或deconv层。相反,我们依靠简单的最近邻居上采样和跳过连接进行自顶向下处理。我们工作的另一个主要区别是,我们通过堆叠多个沙漏来执行重复的自下而上,自上而下的推理。
我们在堆叠之前的沙漏模块紧密连接到完全卷积网络和其他设计,可以在多个尺度上处理空间信息以进行密集预测。谢等人。给出典型架构的总结。我们的沙漏模块主要从其自底向上处理(从高分辨率到低分辨率)和从上到下处理(从低分辨率到高分辨率)之间的更加对称的容量分布中偏离这些设计。例如,完全卷积网络和整体嵌套体系结构在自底向上的处理中都很重,但在自上而下的处理过程中很轻,它只包含跨多个尺度的(加权)合并的预测。完全卷积网络也经历了多个阶段的训练。堆叠之前的沙漏模块也与conv-deconv和编码器 - 解码器架构有关。 Noh等人使用conv-deconv体系结构进行语义分割,Rematas等人用它来预测物体的反射率图。赵等人。为监督,无监督和半监督学习创造一个统一的框架,增加重建损失.Yang et al。采用编码器 - 解码器架构,无需跳过连接即可生成图像。 Rasmus等人提出了一种具有特殊“调制”跳过连接的去噪自动编码器,用于无监督/半监督特征学习。这些网络的对称拓扑结构是相似的,但操作的本质是非常不同的,因为我们不使用unpool或deconv层。相反,我们依靠简单的最近邻居上采样和跳过连接进行自顶向下处理。我们工作的另一个主要区别是,我们通过堆叠多个沙漏来执行重复的自下而上,自上而下的推理。
这里写图片描述

3. Network Architecture 网络结构

3.1 Hourglass Design

沙漏的设计是由于需要捕捉每个尺度的信息。虽然本地证据对于识别脸部和手部等特征至关重要,但最终的姿势估计需要对整个身体的连贯理解。人的方向,肢体的布置以及相邻关节的关系是在图像中不同尺度下最好识别的众多线索之一。沙漏是一种简单的,最小化的设计,能够捕捉所有这些特征并将它们结合在一起以输出按像素进行预测。网络必须具有一些机制来有效地处理和整合不同尺度的特征。一些方法通过使用单独的流水线来解决这个问题,这些流水线以多种分辨率独立处理图像,并在网络中稍后组合功能。相反,我们选择使用具有跳过层的单个管道来保留每个分辨率下的空间信息。网络在4x4像素处达到最低分辨率,允许应用更小的空间滤镜,以比较图像整个空间的特征。沙漏的设置如下:卷积和最大池化层用于将特征处理到极低解析度。在每个最大汇集步骤中,网络分支并在原始预汇集分辨率下应用更多卷积。在达到最低分辨率之后,网络开始自上而下的上采样和跨越尺度的特征组合。为了将信息汇集到两个相邻分辨率上,我们遵循Tompson等人描述的过程并且对下面的最近邻上采样分辨率,然后按照元素添加两组特征。沙漏的拓扑结构是对称的,因此对于出现在路上的每个层都有一个对应的层上升。在达到网络的输出分辨率之后,连续两轮1x1卷积应用于产生最终的网络预测。网络的输出是一组热图,其中对于给定的热图,网络预测在每个像素处关节存在的概率。完整的模块(不包括最终的1x1层)如图3所示。
在保持整体沙漏形状的同时,特定的层次实现仍然具有一定的灵活性。不同的选择可以对网络的最终性能和训练过程中等影响。我们在网络层设计中尝试了很多种。最近的工作表明了采用1x1卷积的减少步骤的价值,以及使用连续的较小滤波器捕捉更大的空间环境的好处。例如,可以用两个单独的3x3过滤器代替5x5过滤器。我们测试了我们的整体网络设计,根据这些见解交换了不同的层模块。我们在从具有大型滤波器的标准卷积层切换到没有减少步骤的情况下,经历了网络性能的提高,以及He等人提出的残留学习模块等更新的方法。和基于“初始”的设计。在对这些设计类型进行初步性能改进之后,对层进行各种额外的探索和修改几乎没有进一步提升性能或培训时间。

3.2 Layer Implementation 层实现

在保持整体沙漏形状的同时,特定的层次实现仍然具有一定的灵活性。不同的选择可以对网络的最终性能和训练过程等产生影响。我们在网络中探索层设计的几种选择。最近的工作表明了采用1x1卷积的减少步骤的价值,以及使用连续的较小滤波器捕捉更大的空间环境的好处。例如,可以用两个单独的3x3过滤器代替5x5过滤器。我们测试了我们的整体网络设计,根据这些见解交换了不同的层模块。我们在将标准卷积层从大型滤波器切换到没有减少步骤后,经历了网络性能的提高,并且采用了新的方法,例如He等人和“基于初始”的设计提出的剩余学习模块。在这些类型的初始性能改进之后的设计,对层的各种额外探索和修改几乎没有进一步提升性能或培训时间。我们的最终设计大量使用了剩余模块。从不使用大于3x3的过滤器,瓶颈会限制每层参数的总数,从而减少总内存使用量。我们的网络中使用的模块如图4所示。为了将其放入完整的网络设计环境中,图3中的每个框表示一个残留模块。在256x256的全输入分辨率下操作需要大量的GPU内存,所以沙漏的最高分辨率(以及最终的输出分辨率)为64x64。这并不影响网络产生精确联合预测的能力。整个网络始于具有步幅2的7x7卷积层,随后是残余模块和一轮最大池化,以将分辨率从256降低到64.两个随后的残留模块位于图3所示的沙漏之前。在整个沙漏所有剩余模块输出256个功能。
这里写图片描述

3.3 Stacked Hourglass with Intermediate Supervision(具有中继监督的沙漏模型)

我们把我们的网络架构进一步堆叠多个端到端沙漏,馈送前一级的沙漏输出作为下一个输入。这为网络提供了一种重复自下而上、自上而下推理的机制,允许对整个图像的初始估计和特征进行重新评估。这种方法的关键是在预测的中间过程可以定义损失函数。预测是通过每个沙漏之后产生的,其中网络有机会在本地和全局上下文中处理特征。随后的沙漏模块允许这些高级别特征再次被处理,以进一步评估和重新评估高阶空间关系。这类似于具有多个迭代阶段和中间监督的强性能的其他姿态估计方法。
考虑仅使用单个沙漏模块的中间监督应用的限制。什么是在过程中产生一套初步的预测最合适的地方?大多数高阶特征只存在于较低的分辨率,除了在上采样发生时。如果在网络进行上采样之后提供监督,那么在更大的全局上下文中,这些特征不能相互重新评估。如果我们希望网络能够进行最好的重新预测,这些预测不能在局部范围内专门评估。与其他联合预测的关系以及对整个图像的一般上下文和理解是至关重要的。在池化之前,在卷积过程中应用监督是可能的,但是在这一点上,给定像素的特征是处理相对局部接收域的结果,因此不知道临界全局线索。
重复的自下而上、自顶向下的堆叠的沙漏模型,缓解了这些担忧。在每个沙漏模块内集成局部和全局线索,并要求网络产生早期预测,要求它在整个网络中仅对图像进行高级理解。自下而上、自上而下处理的后续阶段允许对这些特征进行更深层次的重新考虑。
这种在尺度之间来回变换的方法尤其重要,因为保持特征的空间位置对于进行局部化定位是必不可少的。关节的精确位置是由网络作出的其他决定不可缺少的线索。在结构化的问题,如姿态估计,输出是一个相互作用的许多不同的特点,应该汇集在一起形成一个连贯的理解的场景。矛盾的证据和解剖的不可能性是一个很大的让步,在某个地方犯了一个错误,通过来回的网络可以保持精确的局部信息,同时考虑和重新考虑特征的整体一致性。
通过将它们映射到更大数量的信道,并用另一个1x1卷积将其映射到特征空间中,将它们从沙漏中添加到中间特征,并从先前沙漏阶段输出的特征(如图4所示)。所得到的输出直接用作后续沙漏模块的输入,该模块产生另一组预测。在网络设计中,使用了八个沙漏。重要的是要注意,权重不共享沙漏模块,损失应用到所有的沙漏的预测使用相同的地面真理。

4. Training Details 训练细节

MPII Human Pose Dataset 数据处理:
- 采用MPII提供的scale和center标注信息,以目标人为中心,裁剪图片
- Resized to 256x256
- Rotation (+/- 30 degrees)
- Scaling (.75-1.25)
未进行平移处理,因为图像中目标人的位置是很重要的信息.

Training:
- Torch7
- rmsprop with a learning rate of 2.5e-4
- drop the learning rate once by a factor of 5 after validation accuracy plateaus
- 3 days on a 12GB NVIDIA TitanX GPU
- Batch normalization
- Mean Squared Error (MSE) loss 计算估计的heatmap和参考heatmap的误差
- single forward pass of the network takes 75 ms
Testing:
原始图片和其翻转图片,输入到网络,取输出heatmaps的平均值
最终输出的各heatmap的最大值位置作为关节点位置
这里写图片描述
MPII Human Pose 测试集的人体姿态估计结果

二,理解

#coding = utf-8
import tensorflow as tf
import tensorflow.contrib.layers
import numpy as np

def conv2d(inputs, filters, kernel_size=1, strides=1, pad='SAME', name='None'):
    with tf.name_scope(name):
        kernel = tf.Variable(tf.contrib.layers.xavier_initializer(uniform=False)(
            [kernel_size,kernel_size,inputs.get_shape().as_list()[3], filters]), name='weights')
        conv = tf.nn.conv2d(inputs, kernel, [1,strides,strides,1], padding='SAME', data_format='NHWC')
        return conv

def conv_bn_relu(inputs, filters, kernel_size=1, strides=1, pad='SAME', name='conv_bn_relu'):
    kernel = tf.Variable(tf.contrib.layers.xavier_initializer(uniform=False)(
        [kernel_size, kernel_size, inputs.get_shape().as_list()[3], filters]), name='weights')
    conv = tf.nn.conv2d(inputs, kernel, [1,strides,strides,1], padding='SAME', data_format='NHWC')
    norm = tf.contrib.layers.batch_norm(conv, 0.9, epsilon=1e-5, activation_fn=tf.nn.relu)
    return norm

def conv_block(inputs, numOut, name='conv_block'):
    with tf.name_scope('norm_1'):
        norm_1 = tf.contrib.layers.batch_norm(inputs, 0.9, epsilon=1e-5, activation_fn=tf.nn.relu)
        conv_1 = conv2d(norm_1, int(numOut / 2), kernel_size=1, strides=1, pad='SAME', name='conv')
    with tf.name_scope('norm_2'):
        norm_2 = tf.contrib.layers.batch_norm(conv_1, 0.9, epsilon=1e-5, activation_fn=tf.nn.relu)
        conv_2 = conv2d(norm_2, int(numOut / 2), kernel_size=1, strides=1, pad='SAME', name='conv')
    with tf.name_scope('norm_3'):
        norm_3 = tf.contrib.layers.batch_norm(conv_2, 0.9, epsilon=1e-5, activation_fn=tf.nn.relu)
        conv_3 = conv2d(norm_3, int(numOut), kernel_size=1, strides=1, pad='SAME', name='conv')
    return conv_3

#Residual Model

def skip_layer(inputs, numOut):
    with tf.name_scope('skip_layer'):
        if(inputs.get_shape().as_list()[3]==numOut):
            return inputs
        else:
            conv = conv2d(inputs, numOut, kernel_size=1, strides=1, pad='SAME', name='conv')
            return conv

def residual(inputs, numOut, name='residual_block'):
    convb = conv_block(inputs, numOut)
    skip = skip_layer(inputs, numOut)
    return tf.add_n([convb, skip], name='res_block')

def hourglass(inputs, n, numOut, name='hourglass'):
    with tf.name_scope(name):
        up_1 = residual(inputs, numOut, name='up_1')
        low_ = tf.nn.max_pool(inputs, ksize=[1,2,2,1], strides=[1,2,2,1], padding='SAME')
        low_1 = residual(low_, numOut, name='low_1')
        if(n>0):
            low_2 = hourglass(low_1, n-1, numOut, name='low_2')
        else:
            low_2 = residual(low_1, numOut, name='low_2')
        low_3 = residual(low_2, numOut, name='low_3')
        low_4 = tf.image.resize_nearest_neighbor(low_3, tf.shape(up_1)[1:3], name='upsamping')
        return tf.add_n([low_4, up_1], name='out_hg')

#mnist 28*28
def create_variable(name, shape, initializer,
    dtype=tf.float32, trainable=True):
    return tf.get_variable(name, shape=shape, dtype=dtype,
            initializer=initializer, trainable=trainable)

def weight_variable(shape):
    initial = tf.truncated_normal(shape, stddev=0.1)
    return tf.Variable(initial)

def bias_variable(shape):
    initial = tf.constant(0.1, shape=shape)
    return tf.Variable(initial)

def fc(inputs, n_out, use_bias=True):
    inputs_shape = inputs.get_shape().as_list()
    n_in = inputs_shape[-1]
    with tf.name_scope('fc'):
        weight = create_variable("weight", shape=[n_in, n_out],
                    initializer=tf.random_normal_initializer(stddev=0.01))
        if use_bias:
            bias = create_variable("bias", shape=[n_out,],
                                   initializer=tf.zeros_initializer())
            return tf.nn.xw_plus_b(inputs, weight, bias)
        return tf.matmul(inputs, weight)


class Hourglass(object):
    def __init__(self, inputs, labels, num_classes=10, is_training=True, scope="HourglassNet"):
        self.inputs = inputs
        self.num_classes = 10
        self.is_training = is_training
        with tf.variable_scope(scope):
            net = conv_bn_relu(inputs, filters=64, kernel_size=6, strides=2)
            net = residual(net, numOut=128, name='r1')
            net = tf.contrib.layers.max_pool2d(net, [2,2], [2,2], padding='SAME')
            net = hourglass(net, n=2, numOut=64)
            net = tf.reshape(net, [-1,7*7*64])
            W_fc1 = weight_variable([7 * 7 * 64, 1024])
            b_fc1 = bias_variable([1024])
            net = tf.nn.relu(tf.matmul(net,W_fc1)+b_fc1)
            self.logits = fc(net, self.num_classes)
            self.predictions = tf.nn.softmax(self.logits)
            self.loss = -tf.reduce_mean(labels * tf.log(self.predictions))

if __name__ == '__main__':
    from tensorflow.examples.tutorials.mnist import input_data
    mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
    x = tf.placeholder("float", [None, 784])
    y_ = tf.placeholder("float", [None, 10])
    x_image = tf.reshape(x, [-1,28,28,1])
    x_lable = tf.reshape(y_, [-1,10])
    HourglassNet = Hourglass(x_image, x_lable)
    train_step = tf.train.AdamOptimizer(1e-4).minimize(HourglassNet.loss)
    keep_prob = tf.placeholder("float")
    correct_prediction = tf.equal(tf.arg_max(HourglassNet.predictions, 1), tf.arg_max(y_, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
    init = tf.global_variables_initializer()

    with tf.Session() as sess:
        sess.run(init)
        for i in range(10000):
            batch = mnist.train.next_batch(50)
            if i % 10 == 0:
                train_accuracy = accuracy.eval(feed_dict={x: batch[0], y_: batch[1], keep_prob: 1.0})
                print("step %d, training accurary %g" % (i, train_accuracy))
            train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})


猜你喜欢

转载自blog.csdn.net/just_sort/article/details/80055403
今日推荐