上采样方法综述:线性插值,转置卷积,上池化

1. 上采样的定义

上采样的技术是图像进行超分辨率的必要步骤,最近看到了CVPR2019有一些关于上采样的文章,所以想着把上采样的方法做一个简单的总结。

看了一些文章后,发现上采样大致被总结成了三个类别:

1、基于线性插值的上采样
2、基于深度学习的上采样(转置卷积)
3、Unpooling的方法

其实第三种只是做各种简单的补零或者扩充操作,下文将不对其进行涉及。

2. 线性插值

参考:https://zhuanlan.zhihu.com/p/110754637

线性插值用的比较多的主要有三种:最近邻插值算法、双线性插值、双三次插值(BiCubic),当然还有各种其改进型。在如今SR中这些方法仍然广泛应用。这些方法各有优劣和劣势,主要在于处理效果和计算量的差别。

  • 计算效果:最近邻插值算法 < 双线性插值 < 双三次插值
  • 计算速度:最近邻插值算法 > 双线性插值 > 双三次插值

最近邻法(Nearest Interpolation):计算速度最快,但是效果最差。
双线性插值(Bilinear Interpolation):双线性插值是用原图像中4(22)个点计算新图像中1个点,效果略逊于双三次插值,速度比双三次插值快,属于一种平衡美,在很多框架中属于默认算法。
双三次插值(Bicubic interpolation):双三次插值是用原图像中16(4
4)个点计算新图像中1个点,效果比较好,但是计算代价过大。

1、最近邻算法

最近邻插值算法是最简单的一种插值算法,当图片放大时,缺少的像素通过直接使用与之最近原有颜色生成(直接找到原图像中对应的点,将数值赋值给新图像矩阵中的点),也就是说照搬旁边的像素这样做结果产生了明显可见的锯齿。在待求像素的四邻像素中,将距离待求像素最近的邻灰度赋给待求像素。

找到原图像中的最近的点的公式:

在这里插入图片描述

总结

上图效果是最近邻法的计算过程示意图,由上图可见,最近邻法不需要计算只需要寻找原图中对应的点,所以最近邻法速度最快,但是会破坏原图像中像素的渐变关系,原图像中的像素点的值是渐变的,但是在新图像中局部破坏了这种渐变关系。

1.9 单线性插值

在这里插入图片描述

通式:

在这里插入图片描述

在这里插入图片描述

2、双线性插值算法

已知Q12,Q22,Q11,Q21,但是要插值的点为P点,这就要用双线性插值了(双线性插值就是做两次线性变换)

首先在 x 轴方向上,对R1和R2两个点进行插值
然后根据R1和R2对P点进行插值,这就是所谓的双线性插值。

  • 双线性插值是分别在两个方向计算了共3次单线性插值,如图所示,先在x方向求2次单线性插值,获得R1(x, y1)、R2(x, y2)两个临时点,再在y方向计算1次单线性插值得出P(x, y)(实际上调换2次轴的方向先y后x也是一样的结果)。

在这里插入图片描述

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

可以把上式汇成所要计算的f(x,y)

在这里插入图片描述

通式:
在这里插入图片描述
f ( Q x x ) f(Q_{xx}) f(Qxx)权重的表达式:每个点的权重都和待求点和对角点的距离有关

2.2双线性插值优化的方向

  • 1、几何中心点重合。见我的另一篇文章《【上采样问题】双线性插值,几何中心点重合,align_corners》
  • 2、align_corners角点是否对齐。见我的另一篇文章《【上采样问题】双线性插值,几何中心点重合,align_corners》
  • 3、将浮点运算转换成整数运算(见我的另一篇文章见《【上采样问题】将浮点运算转换成整数运算》)

3、双三次插值算法(bicubic)

双三次插值在SR中引用的比较多,其计算也比较复杂速度较慢。它实际上也是一种插值的方式,但是并不是通过线性插值,而是通过邻近的4x4的像素做加权。

首先第一步是先构建作者所说的BiCubic函数

在这里插入图片描述

其中aij = W(x)* W(y),即我们要分别求取x,y方向的W,然后相乘作为权重。之后的计算就是对 4x4 值做加权,即可求出(u,v)的像素值。

至于x的取值访问如何理解,我简单把我的理解绘制成这张图,当要求的像素点为(u,v),则内圈的四个点权值计算选用|x|<=1的计算公式,外圈的选择另一个公式。

在这里插入图片描述

3. 深度学习

1、转置卷积

基于深度学习的上采样实际上就是通过训练转置卷积核对图片的尺寸进行扩充,当然为了取得好的效果,肯定不单单是一个转置卷积核就可以办到的,很多的学者对上采样的网络做了很多工作。

目前使用得最多的deconvolution有2种:

方法1:full卷积, 完整的卷积可以使得原来的定义域变大
方法2:记录pooling index,然后扩大空间,再用卷积填充

1.1 stride=1的非填充的反卷积

附录1补充了全填充的full卷积的过程

  • 非填充卷积的转置等效卷积的full全填充,
  • 完全填充卷积的转置等效是非填充卷积

在这里插入图片描述

如果full卷积输入时2×2 经过kernel时3×3,步长为1,然后,padding=2的时候输出时4×4
如果反卷积输入时2×2 经过kernel时3×3,步长为1,然后,padding=0的时候输出时4×4

在我之前的文章《【信号处理第十二章】转置卷积》中提到:

  • 反卷积的时候,是有补0的,即使人家管这叫no padding( p=0),这是因为卷积的时候从蓝色 4×4 缩小为绿色 2×2,所以对应的 p=0 反卷积应该从蓝色 2×2 扩展成绿色 4×4。
  • 而且转置并不是指这个 3×3 的核 w w w 变为 w T w^T wT
  • 但如果将卷积计算写成矩阵乘法, Y ⃗ = C X ⃗ Y⃗ =CX⃗ Y=CX(其中 Y ⃗ Y⃗ Y 表示将 Y ⃗ Y⃗ Y 拉成一维向量, X ⃗ X⃗ X 同理),那么反卷积确实可以表示为 C T Y ⃗ C^TY⃗ CTY,而这样的矩阵乘法,恰恰等于 w w w 左右翻转再上下翻转后与补0的 Y Y Y 卷积的情况。

如果:定义“纯净”是指没有进行padding的操作。则Valid卷积就是“纯净”的卷积,full卷积是“纯净”的反卷积。字句话参考自:https://blog.csdn.net/qq_41368247/article/details/86626446

代码示例:

  • input是2×2
  • 转置卷积是3×3,步长1,padding是0,不填充
  • 卷积是3×3,步长是1,padding是2,全填充
  • 输出结果均是4×4
import torch.nn as nn
import torch

X = torch.ones((1,1,2,2))#生成一个四维的数,其中第一维可以理解为batchsize,batchsize为1
print(X.shape)
print(X)
dconv = nn.ConvTranspose2d(1,  1,  kernel_size=3, stride=1, padding=0,output_padding=0, bias= False)
print(dconv(X))


X = torch.ones((1,1,2,2))#生成一个四维的数,其中第一维可以理解为batchsize,batchsize为1
print(X.shape)
print(X)
conv =   nn.Conv2d(1, 1, kernel_size=3, stride=1, padding=2,bias=False)
print(conv(X))

torch.Size([1, 1, 2, 2])
tensor([[[[1., 1.],
          [1., 1.]]]])
tensor([[[[ 0.3177,  0.1498, -0.0741,  0.0938],
          [ 0.1339,  0.0702,  0.0650,  0.1287],
          [-0.1084, -0.1578, -0.2352, -0.1859],
          [ 0.0754, -0.0782, -0.3744, -0.2208]]]],
       grad_fn=<SlowConvTranspose2DBackward>)
torch.Size([1, 1, 2, 2])
tensor([[[[1., 1.],
          [1., 1.]]]])
tensor([[[[ 0.2798,  0.1409,  0.0367,  0.1756],
          [ 0.0033,  0.1347,  0.3264,  0.1950],
          [-0.4309, -0.1247,  0.0274, -0.2788],
          [-0.1544, -0.1186, -0.2623, -0.2982]]]],
       grad_fn=<ThnnConv2DBackward>)

1.2 stride>1, 扩大空间,然后反卷积

对于stride>1的反卷积过程:

步长大于1的反卷积在计算时,在其输入特征单元之间插入 ( s t r i d e r − 1 ) (strider-1) (strider1)个0,插入0后把其看出是新的特征输入。

举个例子:

  • 输入:2x2,
  • 卷积核:4x4,
  • 滑动步长:3,
  • 输出:7x7

在这里插入图片描述

在这里插入图片描述

1.输入图片每个像素进行一次反卷积(in=2, k=4, s=3, p=0),根据反卷积大小计算可以知道每个像素的卷积后大小为 1+4-1=4, 即4x4大小的特征图,输入有4个像素所以4个4x4的特征图
2.将4个特征图进行步长为3的fusion(即相加); 例如红色的特征图仍然是在原来输入位置(左上角),绿色还是在原来的位置(右上角),步长为3是指每隔3个像素进行fusion,重叠部分进行相加,即输出的第1行第4列是由红色特阵图的第一行第四列与绿色特征图的第一行第一列相加得到,其他如此类推。

可以看出full反卷积的大小是由卷积核大小滑动步长决定, in是输入大小, k是卷积核大小, s是滑动步长, out是输出大小
得到 out = (in - 1) * s + k
上图过程就是, (2 - 1) * 3 + 4 = 7

等价于full的卷积模式(in=4, k=4, s=1, p=3)输出7×7,见1.3扩大空间再用卷积填充的解释

代码验证

import torch.nn as nn
import torch
'''
    测试卷积操作  ConvTranspose2d
'''
X = torch.ones((1,1,2,2))#生成一个四维的数,其中第一维可以理解为batchsize,batchsize为1
print(X.shape)
print(X)
dconv = nn.ConvTranspose2d(1,  1,  kernel_size=4, stride=3, padding=0,output_padding=0, bias= False)
print(dconv(X))

结果:

tensor([[[[ 0.0872, -0.2410,  0.1025, -0.1363, -0.2410,  0.1025, -0.2235],
          [ 0.1666,  0.0411, -0.1581,  0.3365,  0.0411, -0.1581,  0.1699],
          [-0.0411,  0.2479,  0.0950,  0.1764,  0.2479,  0.0950,  0.2175],
          [ 0.0910, -0.0481,  0.0820, -0.1576, -0.0481,  0.0820, -0.2486],
          [ 0.1666,  0.0411, -0.1581,  0.3365,  0.0411, -0.1581,  0.1699],
          [-0.0411,  0.2479,  0.0950,  0.1764,  0.2479,  0.0950,  0.2175],
          [ 0.0039,  0.1929, -0.0205, -0.0213,  0.1929, -0.0205, -0.0252]]]],
       grad_fn=<SlowConvTranspose2DBackward>)
import torch.nn as nn
import torch

X = torch.zeros((1,1,4,4))#生成一个四维的数,其中第一维可以理解为batchsize,batchsize为1
X[0][0][0][0]=1.
X[0][0][0][3]=1.
X[0][0][3][0]=1.
X[0][0][3][3]=1.
print(X.shape)
print(X)
conv =   nn.Conv2d(1, 1, kernel_size=4, stride=1, padding=3,bias=False)
print(conv(X))
tensor([[[[ 0.0651,  0.1391, -0.2263,  0.3148,  0.1391, -0.2263,  0.2497],
          [ 0.2020,  0.1740,  0.2184,  0.4468,  0.1740,  0.2184,  0.2448],
          [-0.1709, -0.1127,  0.2469, -0.4130, -0.1127,  0.2469, -0.2421],
          [ 0.2511,  0.1885, -0.4343,  0.6134,  0.1885, -0.4343,  0.3623],
          [ 0.2020,  0.1740,  0.2184,  0.4468,  0.1740,  0.2184,  0.2448],
          [-0.1709, -0.1127,  0.2469, -0.4130, -0.1127,  0.2469, -0.2421],
          [ 0.1860,  0.0494, -0.2081,  0.2986,  0.0494, -0.2081,  0.1126]]]],

1.3扩大空间再用卷积填充

来一个例子:

  • 假设原图是3X3,首先使用上采样让图像变成7X7
  • 填充原始图像空白的像素点。使用一个3X3的卷积核对图像进行滑动步长为1的valid卷积,得到一个5X5的图像.
  • 结论: 1. 使用上采样扩大图片,2. 使用反卷积填充图像内容,使得图像内容变得丰富,这也是CNN输出end to end结果的一种方法。

在这里插入图片描述

除了以上这种单纯通过补零和unpooling来做输入的扩大的方法之外,近几年来,随着语义分割和超分辨率的发展,也出现了很多其他深度学习的方法来做上采样,取得了很好的效果。

2、PixelShuffle(亚像素卷积,CVPR2016)

PixelShuffle是在CVPR2016的Real-Time Single Image and Video Super-Resolution Using an Effificient Sub-Pixel Convolutional Neural Network一文中提出。

ESPCN的主要概念是关注于亚像素卷积层,通过三次卷积之后输出与原图一样尺寸的 r 2 r^2 r2通道的输出图,再通过如下图所示的reshape方法将 H × W × r 2 H×W×r^2 H×W×r2 的特征图转成 r H × r W rH×rW rH×rW 的输出图。而扩大的倍数刚刚好等同于通道数。这样的做法可以让网络去学习到一种插值方法,并存在于前面三层卷积层的参数中。

在这里插入图片描述这个方法的提出对超分辨率有两个与之前不同的贡献:

  • 一个是不需要再通过一开始进行线性插值来扩大输入的尺寸了,从而可以用更小的卷积核就可以获得很好的效果,
  • 另一个是作者认为bicubic是一种卷积的特殊情况(即计算权重相乘求和),用卷积学习可以学会比手工设计更好的拟合方式。

3、DUpsampling(亚像素卷积,CVPR2019)

这个上采样方法是在CVPR2019中的Decoders Matter for Semantic Segmentation: Data-Dependent Decoding Enables Flexible Feature Aggregation∗中提出的,从下面的网络结构来看和PixelShuffle有点类似,它是通过卷积学习亚像素,并最后重组来获得更大的图像。

在这里插入图片描述
DUpsampling在对特征图的操作上有所不同,是先通过将单个像素所对应的C个通道reshape成一个 1 × C 1×C 1×C的向量,与 C × N C×N C×N的矩阵相乘得到 1 × N 1×N 1×N的向量,再reshape成为 2 × 2 × N / 4 2×2×N/4 2×2×N/4(2应该指的是放大倍数,即 r × r × N / r 2 r×r×N/r^2 r×r×N/r2)的扩大后的亚像素块,组合成放大后的特征图。

以上这两种算法都是基于数据去训练的,可以获得比线性插值更好的效果,但是与线性插值相比存在的问题是:

  • 1、对于不同的放大倍数的图像需要训练不同的网络(因为通道数的改变);
  • 2、不容易进行连续的放大,比如1.1倍,1.2倍这样。说不容易而不是不能是因为可以适当放大输入图像或者对权重和步长进行调整后放缩,但是计算很复杂,效果没有整数倍好,线性插值却很容易办到。

4、Meta-Upscale(任意尺度缩放,CVPR2019)

在这里插入图片描述

先上一张图,这张图我觉得比较好的说明了原来的方法要如何做一次非整数尺度的放缩,这样方便进一步了解Meta-SR的思路。

Meta-SR中的Meta-Upscale是在CVPR2019上的Meta-SR: A Magnifification-Arbitrary Network for Super-Resolution一文中提出来的,作者提出了一种可以任意尺度缩放的方法,可以实现较好效果的非整数的放缩。

在这里插入图片描述

FLR 表示由特征学习模块提取的特征,并假定缩放因子是 r。对于 SR 图像上的每个像素(i, j),文中认为它由 ILR 图像上像素(i′,j′)的特征与一组相应卷积滤波器的权重所共同决定。从这一角度看,放大模块可视为从 FLR 到 ISR 的映射函数。

在这里插入图片描述

我对此的理解是,kxk应该是所设定的与ISR所相关的一个像素搜索范围。及这个范围内的所有像素都是和最后输出的像素是有关系的,需要对其计算相应的权重并加权计算。

HWx(InCxoutC)我的理解是图像最后输出的长x宽x输入的通道数(文中为64)x输出的通道数(文中为3)做为权值生成的输出的通道数。

对于一个像素的计算我们需要对64个通道上feature map上的对应9个值加权求和,就可以计算出输出的一个通道值,重复3次就是一个值的3个通道数的值,重复HW次就可以计算出整个输出。

那么知道最后如何计算输出之后,我们关心的是文中提到的是怎么来做对不同的放缩大小的权值计算的以及如何在任意尺度下完成输出像素和LR特征图上的对应。

作者把 Meta-Upscale 模块由三个重要的函数,即 Location Projection、Weight Prediction、Feature Mapping(这个就是上文讲的如何乘以权值得到最后的输出)。

先说下文中的Location Projection,如下图:

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

通过向下取整使得ISR中的每一个值都可以在ILR上找到一个对应的值。如放大1.5倍,那么ILR上的0对应ISR中的0和1(0/1.5<1,1/1.5<1)。

而权重预测Weight Prediction在网络中是通过构建了两层全链接层和relu来实现的,输出在上文已经讲过了就不再重复,而文中用于预测的输入是这样获得的,如下式:

在这里插入图片描述

总结一下,Meta-SR中的思路有点像RPN,做了一个外部的推荐网络,来推荐放缩的权值及像素的对应关系,在非整数倍放大上起的不错的效果。

5、CAPAFE(内容关注与核重组,思路新颖,ICCV2019)

CAPAFE是出自ICCV2019中的CARAFE: Content-Aware ReAssembly of FEatures 一文中的,作者提出了构建一种内容感知并重组特征的上采样方法,看到这篇文章的时候感觉到它的结构思路与之前有很大的不同

在这里插入图片描述

CAPAFE的整个网络结构由两部分组成,一部分是核预测模块(Kernel Prediction Module),用于生成用于重组计算的核上的权重。另一部分是内容感知重组模块(Content-aware Reassembly Module),用于将计算到的权重将通道reshape成一个kxk的矩阵作为核与原本输入的特征图上的对应点及以其为中心点的kxk区域做卷积计算,获得输出。

首先我们先关注下核预测模块是如何预测出权重值的,这一部分文中分为三部分:通道压缩器(Channel Compressor)、内容编码(Content Encoder)、核归一化(Kernel Normaliaer)。输入的Feature map通过1x1的卷积核,完成对通道数的压缩,文中提及这一步的原因是为了减少计算量,而且文中说通过实验将通道数压缩在64不会影响效果,文中给的Cm为64。之后通过多层Kenconder x Kenconder的卷积核完成对特征图的计算,输出HxWxσ2xKup2的特征图,这里Kenconder的大小与感受野有关,越大生成的权重与周围的内容的关联性越高。最后将特征图reshape成σHxσWxKup2,之后单独对每一个像素的所有通道用softmax做归一化,为了是最后生成的重组核内权重值和为1。这样权重的预测就完成了。

之后将预测好的权重对每一个像素都拉成kupxkup的卷积核即图中对应的Wl,通过对(i/σ,j/σ)向下映射的方法(floor function)找到每个像素在原来的feature map上对应的点,以其为中心点构建出kupxkup的区域做卷积计算,就可以得到一个像素的输出,重复操作可以获得整个输出。

在这里插入图片描述

CAPAFE的整个网络内的参数量特别少,只有Content Encoder中的NxKenconder x Kenconderxσ2xKup2的卷积核的参数,同时它的上采样思路也比较不同,当然它无法如Meta-SR那样做连续缩放。

4. 上池化—Unpooling

做各种简单的补零或者扩充操作

参考

https://blog.csdn.net/qq_34919792/article/details/102697817

附录1:full模式的卷积

在这里插入图片描述

full模式:

  • 图中蓝色为原图像,
  • 白色为对应卷积所增加的padding,通常全部为0,
  • 绿色是卷积后图片。

卷积的滑动是从卷积核右下角与图片左上角重叠开始进行卷积,滑动步长为1,卷积核的中心元素对应卷积后图像的像素点。

可以看到卷积后的图像是4X4,比原图2X2大了,我们还记1维卷积大小是n1+n2-1,这里原图是2X2,卷积核3X3,卷积后结果是4X4,与一维完全对应起来了。其实full才是完整的卷积计算,same和valid 其他比它小的卷积结果都是省去了部分像素的卷积。

这里,我们可以总结出full,same,valid三种卷积后图像大小的计算公式:

1.full: 滑动步长为1,图片大小为N1xN1,卷积核大小为N2xN2,卷积后图像大小:N1+N2-1 x N1+N2-1

2.same: 滑动步长为1,图片大小为N1xN1,卷积核大小为N2xN2,卷积后图像大小:N1xN1

3.valid:滑动步长为S,图片大小为N1xN1,卷积核大小为N2xN2,卷积后图像大小:(N1-N2)/S+1 x (N1-N2)/S+1

代码测试:

import torch.nn as nn
import torch
'''
    测试卷积操作  conv2d
'''
X = torch.ones((1,1,2,2))#生成一个四维的数,其中第一维可以理解为batchsize,batchsize为1
print(X.shape)
print(X)
conv =   nn.Conv2d(1, 1, kernel_size=3, stride=1, padding=2,bias=False)
#print(list(conv.parameters()))
print(conv(X))

结果:

torch.Size([1, 1, 2, 2])
tensor([[[[1., 1.],
          [1., 1.]]]])
tensor([[[[ 0.0881, -0.0073,  0.1544,  0.2498],
          [-0.1050, -0.4433, -0.2422,  0.0961],
          [-0.1854, -0.4761, -0.1779,  0.1128],
          [ 0.0077, -0.0401,  0.2187,  0.2665]]]],
       grad_fn=<ThnnConv2DBackward>)
  • 11
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Hali_Botebie

文中错误请不吝指正!!!!!!

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

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

打赏作者

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

抵扣说明:

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

余额充值