浅谈deeplabV1-V3中细节思想

一:FCN介绍

  FCN对图像实际进行了像素级别的分类,将每个像素都看作一个训练样本,不仅要预测其类别,还要计算其 softmax 分类的损失。这一进展解决了语义级别的图像分割问题

  • FCN相当于图像分割领域的milestone,提出全卷积层替代了CNN最后的FC层,可以接受任意尺寸的输入图像大小。
  • 利用反卷积层对最后一个卷积层的特征图(heatmap)进行上采样,使它恢复到与原图相同的尺寸,这样就保留了原始图像的空间信息,然后可以在上采样得到的特征图上逐像素进行分类,从而能够对原图的每一个像素都进行预测,最后再逐个像素计算 softmax 分类的损失;
    在这里插入图片描述

  采用skip layer的方法,在浅层处减小upsampling的步长,得到的fine layer 和 高层得到的coarse layer做融合,然后再upsampling得到输出。这种做法兼顾local和global信息,即文中说的combining what and where,取得了不错的效果提升。FCN-32s为59.4,FCN-16s提升到了62.4,FCN-8s提升到62.7。可以看出效果还是很明显的。


FCN 的优势在于

可以接受任意大小的输入图像(没有全连接层)
更加高效,避免了使用邻域带来的重复计算和空间浪费的问题。

不足也很突出

结果不够精细 ,进行8倍上采样虽然比32倍的效果好了很多,但是上采样的结果还是比较模糊,对图像中的细节不敏感。
是对各个像素进行分类,没有充分考虑像素与像素之间的关系,缺乏空间一致性。

二:针对FCN的Deeplab-VGG优雅改进

  Deeplab 是谷歌在FCN的基础上搞出来的。FCN为了得到一个更加dense的score map,将一张500x500的输入图像,直接在第一个卷积层上conv1_1加了一个100的padding,最终在fc7层勉强得到一个16x16的score map。
  Deeplab这里使用了一个非常优雅的做法:将VGG网络的pool4和pool5层的stride由原来的2改为了1,再加上 1 padding。就是这样一个改动,使得vgg网络总的stride由原来的32变成8,进而使得在输入图像为514x514时,fc7能得到67x67的score map, 要比FCN确实要dense很多很多。这样的话尺寸就缩小为原本的8倍,但是这样的话之后节点的感受野就会发生变化。

三:Hole算法

  于是乎,作者想出了一招,来解决两个看似有点矛盾的问题: 既想利用已经训练好的模型进行fine-tuning,又想改变网络结构得到更加dense的score map. 这个解决办法就是采用Hole算法。如下图(a) (b)所示,在以往的卷积或者pooling中,一个filter中相邻的权重作用在feature map上的位置都是物理上连续的。如下图©所示,为了保证感受野不发生变化,某一层的stride由2变为1以后,后面的层需要采用hole算法,具体来讲就是将连续的连接关系是根据hole size大小变成skip连接的(图©为了显示方便直接画在本层上了)。不要被©中的padding为2吓着了,其实2个padding不会同时和一个filter相连。

   pool4的stride由2变为1,则紧接着的conv5_1, conv5_2和conv5_3中hole size为2。接着pool5由2变为1, 则后面的fc6中hole size为4。

贴出实现代码:

主要是im2col(前传)和col2im(反传)中做了改动 (增加了hole_w, hole_h),这里只贴cpu的用于理解:

//forward
template <typename Dtype>
void im2col_cpu(const Dtype* data_im, 
    const int num, const int channels, const int height, const int width,
    const int kernel_h, const int kernel_w, const int pad_h, const int pad_w,
    const int stride_h, const int stride_w, const int hole_h, const int hole_w,
    Dtype* data_col) {
  // effective kernel if we expand the holes (trous)
  const int kernel_h_eff = kernel_h + (kernel_h - 1) * (hole_h - 1);
  const int kernel_w_eff = kernel_w + (kernel_w - 1) * (hole_w - 1);
  int height_col = (height + 2 * pad_h - kernel_h_eff) / stride_h + 1;
  int width_col = (width + 2 * pad_w - kernel_w_eff) / stride_w + 1;
  int channels_col = channels * kernel_h * kernel_w;
  for (int n = 0; n < num; ++n) {
    for (int c = 0; c < channels_col; ++c) {
      int w_offset = (c % kernel_w)  * hole_w;
      int h_offset = ((c / kernel_w) % kernel_h) * hole_h;
      int c_im = c / kernel_w / kernel_h;
      for (int h = 0; h < height_col; ++h) {
        const int h_im = h * stride_h + h_offset - pad_h;
        for (int w = 0; w < width_col; ++w) {
          const int w_im = w * stride_w + w_offset - pad_w;
          data_col[((n * channels_col + c) * height_col + h) * width_col + w] =
            (h_im >= 0 && h_im < height && w_im >= 0 && w_im < width) ?
            data_im[((n * channels + c_im) * height + h_im) * width + w_im] : 
            0.; // zero-pad
        } //width_col
      } //height_col
    } //channels_col
  } //num
}
 
//backward
template <typename Dtype>
void col2im_cpu(const Dtype* data_col,
    const int num, const int channels, const int height, const int width,
    const int kernel_h, const int kernel_w, const int pad_h, const int pad_w,
    const int stride_h, const int stride_w, const int hole_h, const int hole_w,
    Dtype* data_im) {
  caffe_set(num * channels * height * width, Dtype(0), data_im);
  const int kernel_h_eff = kernel_h + (kernel_h - 1) * (hole_h - 1);
  const int kernel_w_eff = kernel_w + (kernel_w - 1) * (hole_w - 1);
  int height_col = (height + 2 * pad_h - kernel_h_eff) / stride_h + 1;
  int width_col = (width + 2 * pad_w - kernel_w_eff) / stride_w + 1;
  int channels_col = channels * kernel_h * kernel_w;
  for (int n = 0; n < num; ++n) {
    for (int c = 0; c < channels_col; ++c) {
      int w_offset = (c % kernel_w)  * hole_w;
      int h_offset = ((c / kernel_w) % kernel_h) * hole_h;
      int c_im = c / kernel_w / kernel_h;
      for (int h = 0; h < height_col; ++h) {
    const int h_im = h * stride_h + h_offset - pad_h;
        for (int w = 0; w < width_col; ++w) {
          const int w_im = w * stride_w + w_offset - pad_w;
          if (h_im >= 0 && h_im < height && w_im >= 0 && w_im < width) {
            data_im[((n * channels + c_im) * height + h_im) * width + w_im] += 
              data_col[((n * channels_col + c) * height_col + h) * width_col + w];
          }
        }
      }
    }
  }
}

四:多孔金字塔池化(ASPP)的提出

  在实验中发现 DCNNs 做语义分割时精准度不够的问题,根本原因是 DCNNs 的高级特征的平移不变性,即高层次特征映射,根源于重复的池化和下采样。

  针对信号下采样或池化降低分辨率,DeepLab 是采用的 atrous(带孔)算法扩展感受野,利用空洞卷积代替传统的池化方法,获取更多的上下文信息以解决物体在多尺度图像中状态不同的问题。同时,作者受SPP启发,最终采用多孔金字塔池化方法。
在这里插入图片描述
  以vgg16网络为例,ASPP的处理方式如下图。对pool5输出的featuremap(处理前的featuremap大小为2828512)进行四种rate不同的卷积处理,再将四种处理后的结果concate(方式与spp相同)。
在这里插入图片描述

五: Fully-Connected CRFs

  弱分类器产生的分割图往往很粗糙,通常使用CRF来平滑去噪。而深度学习网络产生的分割图很平滑并且连续,在这种情况下需要使用short-range CRFs来恢复局部结构的细节而不是进一步平滑它。但采用该方法仍无法恢复结构中细小的部分,作者又采用了全连接条件随机场来恢复细节,效果很好。

CRFs的具体讲解请看:https://blog.csdn.net/qq_31347869/article/details/91128646
在这里插入图片描述
然后这张图展示了CRF处理前后的效果对比,可以看出用了CRF以后,细节确实改善了很多:
在这里插入图片描述

六:Deeplab v3+完全体

在这里插入图片描述
针对于v3,新引入了decoder和Xception来提高网络表现能力并降低计算复杂度。

1)Decoder

  在Deeplabv3中,特征图被直接双线性插值上采样16倍变为与输入图像相同大小的图像,这种方法无法获得分割目标的细节。因此本文提出了一种简单有效的decoder如下图。encoder features来自于Deeplabv3(output_stride=16)。encoder features首先双线性插值上采样4倍,然后与网络中产生的空间分辨率相同的低层特征concate。在concate之前,先让低层特征通过一个11的卷积核以将channel减少到256,。concate之后,通过几个33卷积来重新定义特征,紧接着双线性插值上采样4倍。

2)Xception

  作者修改了Xception模型以适应图像分割任务。作者修改内容如下:层数更多;在ASPP和decoder模块将所有的最大池化操作换成depthwise separable convolutions with striding(深度可分割卷积),每3个depthwise separable convolutions 后加一个batch normalizaiton和Relu,主要用来提高网络时效性。

在这里插入图片描述


  至此我将图像分割领域中的FCN以及DeepLab系列的framework中细节部分都讲解了一番,希望对大家有所帮助,有不懂的地方或者建议,欢迎大家在下方留言评论。

我是努力在CV泥潭中摸爬滚打的江南咸鱼,我们一起努力,不留遗憾!

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值