SPP,PPM、ASPP和FPN结构理解和总结 1 综述 2 总结

转载:https://blog.****.net/muyijames/article/details/106996209

转载:https://www.jianshu.com/p/f743bd9041b3

转载:https://blog.****.net/m0_37798080/article/details/103163397

SPP论文链接:
Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition

pspNet论文链接:
Pyramid Scene Parsing Network

ASPP论文链接(此处已deeplab v3为例):
deeplab v3(2017年):Rethinking Atrous Convolution for Semantic Image Segmentation
deeplab v3+(2018年):Encoder-Decoder with Atrous Separable
Convolution for Semantic Image Segmentation

FPN论文连接:
Feature Pyramid Networks for Object Detection

1.1 SPP结构(Spatial Pyramid Pooling)

在何恺明2015年《Spatial Pyramid Pooling in Deep ConvolutionalNetworks for Visual Recognition》被提出,改论文主要改进两点:

解决CNN需要固定输入图像的尺寸,导致不必要的精度损失的问题

因为带有全连接层的网络结构都需要固定输入图像的尺度,当然后期也有直接用conv层代替FC层的,比如SSD网络直接用conv层来计算边界框坐标和置信度的。

解决R-CNN对候选区域进行重复卷积计算,导致计算冗余的问题

因为R-CNN网络中基于segment seletive输出的2000个候选框都要重新计算feature map较为耗时,因此提出了候选区域到全图的特征(feature map)之间的对应映射,这样图像只需计算一次前向传播即可。
在之后的 fast R-CNN 和 faster R-CNN 都采用这种映射关系,为ROI pooling层。
但在mask R-CNN中,用ROI Align替代了ROI pooling层,其认为两次量化的候选框与最开始的回归候选框有一定偏差,影响检测和分割准确度,ROI Align中不进行float量化,通过双线性内插计算四个坐标点,然后进行max pooling。

1.2 PPM结构(Pyramid Pooling Module)

应用在语义分割中,PSPNet网络结构如下:

SPP,PPM、ASPP和FPN结构理解和总结
1 综述
2 总结
(1)Input Image:即自然场景下拍摄的包含不同目标的原始图;
(2)Feature Map:即通过前面CNN获得的特征图,,这个CNN是预训练的ResNet;
(3)Pyramid Pooling Module:上图中方框POOL表示采用1x1、2x2、3x3和6x6四种不同尺寸的pooling操作得到多个尺寸的特征图,并对这些尺寸的特征图再次进行“1x1的Conv”来减少通道数。然后采用双线性插值进行UPSAMPLE,即通过上采样来获得金字塔模块前相同尺寸的特征图,并在通道上进行拼接;
(4)Final Prediction:即最终预测结果;

PPM模块代码如下,结合代码更直观:

#下面是单个PPM模块
def interp_block(prev_layer, level, feature_map_shape, input_shape):
    if input_shape == (473, 473):
        kernel_strides_map = {1: 60,
                              2: 30,
                              3: 20,
                              6: 10}
    elif input_shape == (713, 713):
        kernel_strides_map = {1: 90,
                              2: 45,
                              3: 30,
                              6: 15}
    else:
        print("Pooling parameters for input shape ",
              input_shape, " are not defined.")
        exit(1)

    names = [
        "conv5_3_pool" + str(level) + "_conv",
        "conv5_3_pool" + str(level) + "_conv_bn"
    ]
    kernel = (kernel_strides_map[level], kernel_strides_map[level])
    strides = (kernel_strides_map[level], kernel_strides_map[level])
    prev_layer = AveragePooling2D(kernel, strides=strides)(prev_layer)  #或者maxPooling;
    prev_layer = Conv2D(512, (1, 1), strides=(1, 1), name=names[0],
                        use_bias=False)(prev_layer)
    prev_layer = BN(name=names[1])(prev_layer)
    prev_layer = Activation('relu')(prev_layer)
    # prev_layer = Lambda(Interp, arguments={
    #                    'shape': feature_map_shape})(prev_layer)
    prev_layer = Interp(feature_map_shape)(prev_layer) #此处进行上采样,resize到原来feature map的size;
    return prev_layer

#下面是1,2,3,6的PPM模块
def build_pyramid_pooling_module(res, input_shape):
    """Build the Pyramid Pooling Module."""
    # ---PSPNet concat layers with Interpolation
    feature_map_size = tuple(int(ceil(input_dim / 8.0))
                             for input_dim in input_shape)  #原图经过resnet后变为1/8;
    print("PSP module will interpolate to a final feature map size of %s" %
          (feature_map_size, ))

    interp_block1 = interp_block(res, 1, feature_map_size, input_shape)
    interp_block2 = interp_block(res, 2, feature_map_size, input_shape)
    interp_block3 = interp_block(res, 3, feature_map_size, input_shape)
    interp_block6 = interp_block(res, 6, feature_map_size, input_shape)

    # concat all these layers. resulted
    # shape=(1,feature_map_size_x,feature_map_size_y,4096)
    res = Concatenate()([res,
                         interp_block6,
                         interp_block3,
                         interp_block2,
                         interp_block1])
    return res

#pspnet网络结构
def build_pspnet(nb_classes, resnet_layers, input_shape, activation='softmax'):
    """Build PSPNet."""
    print("Building a PSPNet based on ResNet %i expecting inputs of shape %s predicting %i classes" % (
        resnet_layers, input_shape, nb_classes))

    inp = Input((input_shape[0], input_shape[1], 3))
    res = ResNet(inp, layers=resnet_layers)
    psp = build_pyramid_pooling_module(res, input_shape)
#后面是经过3×3,1×1的卷积后一次上采样到原图
    x = Conv2D(512, (3, 3), strides=(1, 1), padding="same", name="conv5_4",
               use_bias=False)(psp)
    x = BN(name="conv5_4_bn")(x)
    x = Activation('relu')(x)
    x = Dropout(0.1)(x)

    x = Conv2D(nb_classes, (1, 1), strides=(1, 1), name="conv6")(x)
    # x = Lambda(Interp, arguments={'shape': (
    #    input_shape[0], input_shape[1])})(x)
    x = Interp([input_shape[0], input_shape[1]])(x)
    x = Activation('softmax')(x)

    model = Model(inputs=inp, outputs=x)

    # Solver
    sgd = SGD(lr=learning_rate, momentum=0.9, nesterov=True)
    model.compile(optimizer=sgd,
                  loss='categorical_crossentropy',
                  metrics=['accuracy'])
    return model

1.3 ASPP结构(Atrous Spatial Pyramid Pooling)

可以认为是SPP在语义分割中的应用,结合了空洞卷积可在不丢失分辨率(不进行下采样)的情况下扩大卷积核的感受野。

回顾空洞卷积

Deep CNN 中普通卷积对于其他任务还有一些致命性的缺陷。较为著名的是 up-sampling 和 pooling layer 的设计。
主要问题有:

  • Up-sampling / pooling layer (e.g. bilinear interpolation) is deterministic. (参数不可学习)
  • 内部数据结构丢失;空间层级化信息丢失。
  • 小物体信息无法重建 (假设有四个pooling layer 则 任何小于 2^4 = 16 pixel 的物体信息将理论上无法重建。)
    在这样问题的存在下,语义分割问题一直处在瓶颈期无法再明显提高精度, 而 dilated convolution 的设计就良好的避免了这些问题。

在图像分割领域,图像输入到CNN(典型的网络比如FCN[3])中,FCN先像传统的CNN那样对图像做卷积再pooling,降低图像尺寸的同时增大感受野,但是由于图像分割预测是pixel-wise的输出,所以要将pooling后较小的图像尺寸upsampling到原始的图像尺寸进行预测(upsampling一般采用deconv反卷积操作,deconv可参见知乎答案如何理解深度学习中的deconvolution networks?),之前的pooling操作使得每个pixel预测都能看到较大感受野信息。因此图像分割FCN中有两个关键,一个是pooling减小图像尺寸增大感受野,另一个是upsampling扩大图像尺寸。在先减小再增大尺寸的过程中,肯定有一些信息损失掉了,那么能不能设计一种新的操作,不通过pooling也能有较大的感受野看到更多的信息呢?答案就是dilated conv。

此处已deeplab v3网络为例,deeplab v3论文中的ASPP结构如下如所示

SPP,PPM、ASPP和FPN结构理解和总结
1 综述
2 总结

  • 其中的1*1卷积,论文中的解释是当 rate = feature map size 时,dilation conv 就变成了 1 ×1 conv,所以这个 1 × 1 conv相当于rate很大的空洞卷积。还加入了全局池化,再上采样到原来的 feature map size,思想来源于PSPnet。为什么用 rate = [6, 12, 18] ?是论文实验得到的,因为这个搭配比例的 mIOU 最高。
  • 在 backbone 的每一个block里面又参考了HDC的思想,设置了 [1,2,1] 的rate,所以每个conv的rate = Rate * rate。论文给出的 Multi-grid 部分结果如下所示。
    SPP,PPM、ASPP和FPN结构理解和总结
1 综述
2 总结

这里提一下deeplab v3+ 结构,是对V3的一个加强,主要有两个改进点

  • 对ASPP中的 upsample 进行了改进

下图来自 deeplab v3+ 论文中,(a) 是deeplab v3的结构,( c) 是deeplab v3+的结构(Encode-Decode),v3+ 中将上采样变成了2次 4× 的 upsample,相比于v3中直接进行 8× 的 upsample,具有更丰富的语义信息,所以对物体边缘分割效果较好。

SPP,PPM、ASPP和FPN结构理解和总结
1 综述
2 总结

  • deeplab v3+中另一个改进点,将 modify xception 作为 backbone;(这个改进点与ASPP无关)

如此下图所示,(1)加深了网络结构层数,且利用了 Atrous Separable Convolution 来减少权重参数;(2)利用 stride = 2 的 depthwise conv 来替代max pooling 进行下采样;(3)在每一个 depthwise conv 后都加了 BN 和 relu 层。作者在论文中做了各种尝试组合,有兴趣的可查看论文

SPP,PPM、ASPP和FPN结构理解和总结
1 综述
2 总结

Decoded module

  xception的输出2048维特征接到ASPP上得到256维multi-scale context feature map(一般s=16),再4×上采样,和backbone上的同分辨率的low-level feature map concat(一般是entry flow的第一个shortcut block的输出,刚好s=4)。这个时候要让low-level feature map在concat后的总特征图中占比小,因为它的语义信息太少了,所以接了1*1的低维conv,这个地方可不是为了降低计算量,关于这个conv的channel选取,论文给出了对比试验Table 1。concat后再接3*3 conv block,它的channel和block个数,论文中也进行了实验验证Table 2.最后再进行4×上采样,达到原图的分辨率。此外,作者还实验了将aspp和decode中的卷积替换为depthwise conv,mIOU没有明显降低,flops大大降低了。

 SPP,PPM、ASPP和FPN结构理解和总结
1 综述
2 总结

空洞卷积潜在问题 1:The Gridding Effect

假设我们仅仅多次叠加 dilation rate 2 的 3 x 3 kernel 的话,则会出现这个问题:

SPP,PPM、ASPP和FPN结构理解和总结
1 综述
2 总结

(a)图对应3x3的1-dilated conv,和普通的卷积操作一样,(b)图对应3x3的2-dilated conv,实际的卷积kernel size还是3x3,但是空洞为1,也就是对于一个7x7的图像patch,只有9个红色的点和3x3的kernel发生卷积操作,其余的点略过。也可以理解为kernel的size为7x7,但是只有图中的9个点的权重不为0,其余都为0。 可以看到虽然kernel size只有3x3,但是这个卷积的感受野已经增大到了7x7(如果考虑到这个2-dilated conv的前一层是一个1-dilated conv的话,那么每个红点就是1-dilated的卷积输出,所以感受野为3x3,所以1-dilated和2-dilated合起来就能达到7x7的conv),(c)图是4-dilated conv操作,同理跟在两个1-dilated和2-dilated conv的后面,能达到15x15的感受野。对比传统的conv操作,3层3x3的卷积加起来,stride为1的话,只能达到(kernel-1)*layer+1=7的感受野,也就是和层数layer成线性关系,而dilated conv的感受野是指数级的增长。

dilated的好处是不做pooling损失信息的情况下,加大了感受野,让每个卷积输出都包含较大范围的信息。

我们发现 kernel 并不连续,也就是并不是所有的 pixel 都用来计算了,因此这里将信息看做 checker-board 的方式会损失信息的连续性。这对 pixel-level dense prediction 的任务来说是致命的。

潜在问题 2:Long-ranged information might be not relevant.

我们从 dilated convolution 的设计背景来看就能推测出这样的设计是用来获取 long-ranged information。然而光采用大 dilation rate 的信息或许只对一些大物体分割有效果,而对小物体来说可能则有弊无利了。如何同时处理不同大小的物体的关系,则是设计好 dilated convolution 网络的关键。

HDC(Hybrid Dilated Convolution)

针对以上几个问题,图森组的文章对其提出了较好的解决的方法。他们设计了一个称之为 HDC 的设计结构。
它有几个特性,可以从一定程度上解决上述问题。这里咱不讨论。我们可以从一张图来对比一下正常空洞卷积与HDC的效果:

SPP,PPM、ASPP和FPN结构理解和总结
1 综述
2 总结

 可以看到经过卷积之后,HDC能够获得更多的图像信息,不会出现像正常空洞卷积一样的小方块;

1.4 FPN结构(Feature Pyramid Networks for Object Detection)

FPN通常用在 object detection 网络中,通常低层的特征语义信息比较少,但是目标位置准确;高层的特征语义信息比较丰富,但是目标位置比较粗略。FPN 即是对两者进行了融合,同时利用低层特征高分辨率和高层特征的高语义信息,通过融合这些不同层的特征达到预测的效果。并且预测是在每个融合后的特征层上单独进行的,这和常规的特征融合方式不同。

FPN结构如下图:
SPP,PPM、ASPP和FPN结构理解和总结
1 综述
2 总结
自底向上的路径:
具体而言,当 backbone 是 ResNet 时,我们使用每个阶段的最后一个residual block输出的特征激活输出。 对于conv2,conv3,conv4 和 conv5 输出,我们将这些最后residual block的输出表示为 {C2,C3,C4,C5},并且它们相对于输入图像具有 {4, 8, 16, 32} 的步长。

自顶向下的路径:
通过对在空间上更抽象但语义更强高层特征图进行上采样来幻化高分辨率的特征。在C5上附加一个1×1卷积层来生成低分辨率图P5,随后通过侧向连接从底向上的路径,使得高层特征得到增强。将低分辨率的特征图做2倍上采样,最终的特征映射集称为{P2,P3,P4,P5},分别对应于{C2,C3,C4,C5},具有相同的尺寸。通过按元素相加,将上采样映射与相应的自底而上映射合并。
最后,在每个合并的图上附加一个3×3卷积来生成最终的特征映射,这是为了减少上采样的混叠效应。

作者在将其应用到 RPN 和 fast / faster R-CNN 中,在论中有详细的实验数据,大家有兴趣可自行查阅。

在YOLO v3中也采用了类似 FPN 的结构,但里面用了concat 进行特征融合

2 总结

空间金字塔思想在图像处理中具有很重要的作用,
传统图像处理中:

  • 在SIFT 中利用高斯差分金字塔 (DOG) 保持尺度不变性;
  • 在配准 / 匹配算法中利用空间金字塔进行粗匹配和细匹配达到效率优化;

CNN 网络中:

    • 在含有FC层网络中利用 SPP 改进输入需要固定尺度的问题;
    • 在语义分割中利用 ASPP 在不丢失信息时组合不同感受野的语义信息,提高分割精度;
    • 在 object detection 网络中利用 FPN 改善小目标难检测的问题;