反卷积图像放大应用:双线性插值详解

1. 本文目的

目的利用双线性插值作为反卷积的权重,进行图像放大

什么是图片的放大?

其实把图像的像素增加.

比如我们现在有一张单色的 2 * 2的图片.
[ 1 2 3 4 ] (1) \begin{bmatrix} 1 &2\\ 3& 4 \end{bmatrix}\tag{1} [1324](1)

简单想的想要将其扩大两倍变成4 * 4的就可以, 比如说下面这个样子
[ ? ? ? ? ? 1 ? 2 ? ? ? ? ? 3 ? 4 ] (2) \begin{bmatrix} ?&? &?&?\\ ?&1 &?&2\\ ?&? &?&?\\ ?&3 &?&4\\ \end{bmatrix}\tag{2} ?????1?3?????2?4(2)
那么接下来的目标就是求出 ? ? ?的各个取值

2. 问题分析

为了让图片的像素变多后,让他看起来比较自然.那么 ? ? ?的取值一定是和原图的取值是有关系的. 很自然的我们可以想到, 在 3 3 3 4 4 4 之间的 ? ? ? 应该让其等于 3.5 3.5 3.5是比较合理的.

我们知道反卷积是一个上采样的方法, 也具有把像素点变多的功能. 但是如果我们使用的是系统给定的随机卷积核, 靠训练得出合适的卷积核,那么明显效率会非常低.这里我们就要构造自己的卷积核,直接一步到位.

那么知道已知的点来估计其他点的方法, 即就是 插值

在反卷积中,我们只要设置 s t r i d e = 2 , k e r n e l _ s i z e = 2 stride=2, kernel\_size=2 stride=2,kernel_size=2,就可以达到输出为 4 * 4的目的. 但是我们这里拟采用双线性插值, 一个值需要通过四个数来确定, 因此我们将卷积核大小扩大两倍. 但是为了保持输出是 4 * 4的, 因此我们需要加上 p a d d i n g = 1 padding = 1 padding=1

x = torch.tensor([[[[1,2],[3,4]]]],dtype=torch.float32)
conv_trans = (nn.ConvTranspose2d(in_channels=1, out_channels=1, kernel_size=4, stride=2, padding=1))

首先对(1)进行反卷积,我们需要对(1)进行填充, 在各个元素之间,填充 s − 1 s-1 s1个0
[ 1 0 2 0 0 0 3 0 4 ] (3) \begin{bmatrix} 1 &0&2\\ 0& 0&0\\ 3& 0&4 \end{bmatrix}\tag{3} 103000204(3)
然后通过
i + 2 p − k s + 1 = o \frac{i+2p-k}{s} + 1=o si+2pk+1=o
计算出 p p p, 这里 i = 3 , k = 4 , s = 1 , o = 4 i=3,k=4,s=1,o=4 i=3,k=4,s=1,o=4可以求得 p = 2 p=2 p=2.

所以对矩阵(3)进行padding操作之后, 我们相当于对如下矩阵进行卷积操作
[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 2 0 0 0 0 0 0 0 0 0 0 0 3 0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] (4) \begin{bmatrix} 0& 0&0&0& 0&0& 0\\ 0& 0&0&0& 0&0& 0\\ 0& 0&1 &0&2&0& 0\\ 0& 0&0&0& 0&0& 0\\ 0& 0&3& 0&4&0&0\\ 0& 0&0&0& 0&0& 0\\ 0& 0&0&0& 0&0& 0\\ \end{bmatrix}\tag{4} 0000000000000000103000000000002040000000000000000(4)

现在我们的问题就是如何给定一个4*4的卷积核,依次进行卷积操作,来得到矩阵(2)

3. 操作方法

我们采用首先采用数学的理论来进行计算, 确定矩阵(2), 然后再分析卷积核如何给定

3.1. 插值计算

最简单的拉格朗日插值: 在这个点上,其系数为1,不在这个点上的系数为0的原则.

L ( x ) L(x) L(x)为插值函数, l ( x ) l(x) l(x)为拉格朗日基本多项式(或称插值基函数)
在这里插入图片描述
其表达式为
在这里插入图片描述
那么对于矩阵(2), 我们从左上角给一个原点, 右方向为一个 x x x 轴, 下方向为 y y y 轴, 并且相邻数字之间的距离为1.
那么矩阵(2)
[ ? ? ? ? ? 1 ? 2 ? ? ? ? ? 3 ? 4 ] \begin{bmatrix} ?&? &?&?\\ ?&1 &?&2\\ ?&? &?&?\\ ?&3 &?&4\\ \end{bmatrix} ?????1?3?????2?4中:
1的坐标为(2,2)
2的坐标为(4,2)
3的坐标为(2,4)
4的坐标为(4,4)

我们可以建立 L ( x , y ) = 1 ∗ ( 4 − x ) ( 4 − y ) ( 4 − 2 ) ( 4 − 2 ) + 2 ∗ ( x − 2 ) ( 4 − y ) ( 4 − 2 ) ( 4 − 2 ) + 3 ∗ ( 4 − x ) ( y − 2 ) ( 4 − 2 ) ( 4 − 2 ) + 4 ∗ ( x − 2 ) ( y − 2 ) ( 4 − 2 ) ( 4 − 2 ) (5) \begin{aligned}L(x,y)&=1*\frac{(4-x)(4-y)}{(4-2)(4-2)}+2*\frac{(x-2)(4-y)}{(4-2)(4-2)}+3*\frac{(4-x)(y-2)}{(4-2)(4-2)}+4*\frac{(x-2)(y-2)}{(4-2)(4-2)} \end{aligned}\tag{5} L(x,y)=1(42)(42)(4x)(4y)+2(42)(42)(x2)(4y)+3(42)(42)(4x)(y2)+4(42)(42)(x2)(y2)(5)

可以验证 L ( 2 , 2 ) = 1 , L ( 4 , 2 ) = 2 , L ( 2 , 4 ) = 3 , L ( 4 , 4 ) = 4 L(2,2)=1,L(4,2)=2,L(2,4)=3,L(4,4)=4 L(2,2)=1,L(4,2)=2,L(2,4)=3,L(4,4)=4

因此我们可以很容易的计算出
L ( 3 , 3 ) = 1 ∗ 1 4 + 2 ∗ 1 4 + 3 ∗ 1 4 + 4 ∗ 1 4 = 10 4 = 2.5 \begin{aligned}L(3,3)&=1*\frac{1}{4} + 2*\frac{1}{4}+3*\frac{1}{4}+4*\frac{1}{4} \\&=\frac{10}{4}=2.5\end{aligned} L(3,3)=141+241+341+441=410=2.5

L ( 3 , 4 ) = 1 ∗ 0 4 + 2 ∗ 0 4 + 3 ∗ 2 4 + 4 ∗ 2 4 = 14 4 = 3.5 \begin{aligned}L(3,4)&=1*\frac{0}{4} + 2*\frac{0}{4}+3*\frac{2}{4}+4*\frac{2}{4} \\&=\frac{14}{4}=3.5\end{aligned} L(3,4)=140+240+342+442=414=3.5
依次我们可以吧矩阵(2)写为
[ − 0.5 0 0.5 1 0.5 1 1.5 2 1.5 2 2.5 3 2.5 3 3.5 4 ] \begin{bmatrix} -0.5&0 &0.5&1\\ 0.5&1 &1.5&2\\ 1.5&2 &2.5&3\\ 2.5&3 &3.5&4\\ \end{bmatrix} 0.50.51.52.501230.51.52.53.51234

这里需要注意, 由1,2,3,4组成的正方形包含的值是内插,其余的是外插计算出来的,所以才有了 − 0.5 -0.5 0.5这种不可取的值.暂时先放着.

3.2 卷积核计算

观察式子(5), 我们是通过改变基函数的值来计算得到不同地方的插值.

但是在反卷积的过程中, 卷积核是不变的, 要变化的是(5)中的1,2,3,4

那么是否可以保持基函数不变通过改变插值点来实现插值呢?

我们观察基函数, 他的确有一个不变的特性, 即距离.

看(5)的第一个基函数,假设要求的点为P(x,y), 那么
4 − x 2 就 是 点 P 到 x = 4 的 距 离 , 记 为 a ; x − 2 2 为 P 到 x = 2 的 距 离 , 记 为 1 − a \frac{4-x}{2}就是点P到x=4的距离,记为a;\frac{x-2}{2}为P到x=2的距离,记为1-a 24xPx=4,a;2x2Px=2,1a
4 − y 2 就 是 点 P 到 y = 4 的 距 离 , 记 为 b ; y − 2 2 为 P 到 y = 2 的 距 离 , 记 为 1 − b \frac{4-y}{2}就是点P到y=4的距离,记为b;\frac{y-2}{2}为P到y=2的距离,记为1-b 24yPy=4,b;2y2Py=2,1b

所以
L = 1 ∗ a b + 2 ∗ ( 1 − a ) b + 3 ∗ a ( 1 − b ) + 4 ∗ ( 1 − a ) ( 1 − b ) L=1*ab+2*(1-a)b+3*a(1-b)+4*(1-a)(1-b) L=1ab+2(1a)b+3a(1b)+4(1a)(1b)
说明我们现在只要固定住距离, 就可以通过改变插值点来获得插值,这就是我们卷积核所做的事情.

不失一般性,我可以把P点固定在中心, 即 ( 3 , 3 ) (3,3) (3,3), 那么 a = b = 1 / 2 a=b=1/2 a=b=1/2,

将其对应于如下的卷积过程
在这里插入图片描述
卷积核应该是 [ ? ? ? ? ? 0.25 ? 0.25 ? ? ? ? ? 0.25 ? 0.25 ] \begin{bmatrix} ?&? &?&?\\ ?&0.25 &?&0.25\\ ?&? &?&?\\ ?&0.25&?&0.25\\ \end{bmatrix} ?????0.25?0.25?????0.25?0.25
可以得出卷积值为2.5

下一步卷积在这里插入图片描述
在做这个卷积的时候,我们还是使用1,2,3,4作为插值点,但是位置不同了.

倘若我们还是用原来的四个 0.5 0.5 0.5去做计算, 那明显是不正确的.这里还是依赖于1,2,3,4作为插值点,但是求出的值不再是对应于原来的P点的值, 因此在这种情况下,我们需要对P 点进行变化

这卷积核向右边移动了一个位置, 我们吧 P P P也向右边移动一个位置到 ( 4 , 3 ) (4,3) (4,3)

这个时候 a = 0 , b = 1 / 2 a=0,b=1/2 a=0,b=1/2,那么卷积核即为

卷积核应该是 [ ? ? ? ? 0 0.25 0.5 0.25 ? ? ? ? 0 0.25 0.5 0.25 ] \begin{bmatrix} ?&? &?&?\\ 0&0.25 &0.5&0.25\\ ?&? &?&?\\ 0&0.25&0.5&0.25\\ \end{bmatrix} ?0?0?0.25?0.25?0.5?0.5?0.25?0.25
可以得出卷积值为3

同样的道理

下面的卷积核相对于第一次卷积向下移动了一次,点 P P P对应于 ( 3 , 4 ) (3,4) (3,4),此时 a = 1 / 2 , b = 0 a=1/2,b=0 a=1/2,b=0
在这里插入图片描述

卷积核应该是 [ ? 0 ? 0 0 0.25 0.5 0.25 ? 0.5 ? 0.5 0 0.25 0.5 0.25 ] \begin{bmatrix} ?&0 &?&0\\ 0&0.25 &0.5&0.25\\ ?&0.5 &?&0.5\\ 0&0.25&0.5&0.25\\ \end{bmatrix} ?0?000.250.50.25?0.5?0.500.250.50.25
可以得出卷积值为3.5

还有最后一种情况的卷积为
在这里插入图片描述
P = ( 4 , 4 ) P=(4,4) P=(4,4),则 a = b = 0 a=b=0 a=b=0
卷积核应该是 [ 0 0 0 0 0 0.25 0.5 0.25 0 0.5 1 0.5 0 0.25 0.5 0.25 ] (6) \begin{bmatrix} 0&0 &0&0\\ 0&0.25 &0.5&0.25\\ 0&0.5 &1&0.5\\ 0&0.25&0.5&0.25\\ \end{bmatrix}\tag{6} 000000.250.50.2500.510.500.250.50.25(6)
可以得出卷积值为4

我们上次所给出的例子,就可以卷积出
[ 2.5 3 3.5 4 ] \begin{bmatrix} 2.5&3\\ 3.5&4\\ \end{bmatrix} [2.53.534]

卷积核把往上移一移再做卷积运算
就可以完善
[ 1 1.5 2 2 2.5 3 3 3.5 4 ] \begin{bmatrix} 1&1.5&2\\ 2&2.5&3\\ 3&3.5&4\\ \end{bmatrix} 1231.52.53.5234
这个结果和我们通过插值计算的 内插 的部分是完全一致的!

3.3 代码

我们可以把计算卷积核的公式写成
[ a 1 b 1 a 2 b 2 ( 1 − a 1 ) b 1 ( 1 − a 2 ) b 2 a 3 b 3 a 4 b 4 ( 1 − a 3 ) b 3 ( 1 − a 4 ) b 4 a 1 ( 1 − b 1 ) a 2 ( 1 − b 2 ) ( 1 − a 1 ) ( 1 − b 1 ) ( 1 − a 2 ) ( 1 − b 2 ) a 3 ( 1 − b 3 ) a 4 ( 1 − b 4 ) ( 1 − a 3 ) ( 1 − b 3 ) ( 1 − a 4 ) ( 1 − b 4 ) ] (7) \begin{bmatrix} a_{1}b_{1}&a_{2}b_{2} &(1-a_{1})b_{1}&(1-a_{2})b_{2}\\ a_{3}b_{3}&a_{4}b_{4} &(1-a_{3})b_{3}&(1-a_{4})b_{4}\\ a_{1}(1-b_{1})&a_{2}(1-b_{2}) &(1-a_{1})(1-b_{1}) &(1-a_{2})(1-b_{2})\\ a_{3}(1-b_{3})&a_{4}(1-b_{4}) &(1-a_{3})(1-b_{3})&(1-a_{4})(1-b_{4})\\ \end{bmatrix}\tag{7} a1b1a3b3a1(1b1)a3(1b3)a2b2a4b4a2(1b2)a4(1b4)(1a1)b1(1a3)b3(1a1)(1b1)(1a3)(1b3)(1a2)b2(1a4)b4(1a2)(1b2)(1a4)(1b4)(7)
若我们使 f ( x ) = ( 4 − x ) / 2 f(x)=(4-x)/2 f(x)=(4x)/2
a 1 = b 1 = f ( 4 ) , a 2 = b 3 = f ( 3 ) , a 3 = b 2 = f ( 4 ) , a 4 = b 4 = f ( 3 ) a_{1}=b_{1}=f(4),a_{2}=b_{3}=f(3),a_{3}=b_{2}=f(4),a_{4}=b_{4}=f(3) a1=b1=f(4),a2=b3=f(3),a3=b2=f(4),a4=b4=f(3)
因为 a 1 = a 3 , a 2 = a 4 , b 1 = b 2 , b 3 = b 4 a_{1}=a_{3},a_{2}=a_{4},b_{1}=b_{2},b_{3}=b_{4} a1=a3,a2=a4,b1=b2,b3=b4,因此我们可以把(7)写成两个矩阵的乘积
( 7 ) = ( b 1 b 3 1 − b 1 1 − b 3 ) ∗ ( a 1 a 2 1 − a 1 1 − a 2 ) (7)=\begin{pmatrix} b_{1} \\ b_{3}\\ 1-b_{1}\\ 1-b_{3}\\ \end{pmatrix} *\begin{pmatrix} a_{1}&a_{2}&1-a_{1}&1-a_{2}\end{pmatrix} (7)=b1b31b11b3(a1a21a11a2)
代码思想:
先计算两个矩阵的前面两个值 b 1 , b 3 , a 1 , a 2 b_1,b_3,a_1,a_2 b1,b3,a1,a2.因为他们都是通过 f ( x ) f(x) f(x)直接计算得到

然后计算两个矩阵的后面的两个值,通过 1 − f ( x ) 1-f(x) 1f(x)得到
这里factor为放大的倍数

import numpy as np

def bilinear_kernel(factor):
    kernel_size=2 *factor
    og = np.ogrid[:factor, :factor]
    og = [kernel_size - og[i] for i in range(len(og))] //og[0]=[3 4],og[1]=[3;4]
    b13 = (kernel_size - og[0])/factor
    a12 = (kernel_size - og[1])/factor
    one_minus_b13 = 1 - (kernel_size - og[0])/factor
    one_minus_a12 = 1 - (kernel_size - og[1])/factor
    left = np.vstack((b13, one_minus_b13))
    right = np.hstack((a12,one_minus_a12))
    return left * right
    
print(bilinear_kernel(2))   ```
然后我们放大两倍可以看到结果为
```python
array([[0.  , 0.  , 0.  , 0.  ],
       [0.  , 0.25, 0.5 , 0.25],
       [0.  , 0.5 , 1.  , 0.5 ],
       [0.  , 0.25, 0.5 , 0.25]])

其结果和我们的计算结果(6)保持一致

4.实验

4.1. 基本实现

因为图像是多通道的.所以还需要调整一下我们的bilinear_kernel函数

def bilinear_kernel(in_channel, out_channel, factor):
    kernel_size=2 *factor
    og = np.ogrid[:factor, :factor]
    og = [kernel_size - og[i] for i in range(len(og))]
    b13 = (kernel_size - og[0])/factor
    a12 = (kernel_size - og[1])/factor
    one_minus_b13 = 1 - (kernel_size - og[0])/factor
    one_minus_a12 = 1 - (kernel_size - og[1])/factor
    left = np.vstack((b13, one_minus_b13))
    right = np.hstack((a12,one_minus_a12))
    ## 把结果变成多通道的
    weight = np.zeros((in_channel, out_channel, kernel_size, kernel_size), dtype='float32')
    weight[range(in_channel), range(out_channel), :, :] = left * right
    
    return weight

我准备了一张png图片进行读取

x = plt.imread("1.png")
x.shape

结果为(275, 1138, 4).说明这是一张四维的图片.下面定义反卷积,并且定义反卷积的权重

x = plt.imread("1.png")
plt.subplot(1,2,1)
plt.imshow(x)
print(x.shape)
x = torch.from_numpy(x.astype('float32')).permute(2, 0, 1).unsqueeze(0)
conv_trans = nn.ConvTranspose2d(in_channels=4, out_channels=4, kernel_size=4, stride=2, padding=1)
# nn.ConvTranspose2d()
# 将其定义为 bilinear kernel
conv_trans.weight.data = torch.from_numpy(bilinear_kernel(4, 4, 2))
conv_trans.bias.data = torch.zeros(4)
y = conv_trans(x).data.squeeze().permute(1, 2, 0).numpy()
plt.subplot(1,2,2)
plt.imshow(y)
print(y.shape)

结果我们可以看到,的确放大了两倍.看坐标轴!!!

(275, 1138, 4)
(550, 2276, 4)
在这里插入图片描述

4.2 代码优化

在上文中, 我们用的是 a a a b b b来衡量是点P到 x = 4 和 y = 4 x=4和y=4 x=4y=4的距离的百分比, f ( x ) = ( 4 − x ) / 2 f(x)=(4-x)/2 f(x)=(4x)/2
( a 1 a 2 1 − a 1 1 − a 2 ) (8) \begin{pmatrix} a_{1}&a_{2}&1-a_{1}&1-a_{2}\end{pmatrix}\tag{8} (a1a21a11a2)(8)
这里对应的是 f ( 4 ) , f ( 3 ) , 1 − f ( 4 ) , 1 − f ( 3 ) f(4),f(3),1-f(4),1-f(3) f(4),f(3),1f(4),1f(3), 就是这里无法统一用一个函数表达,所以我们上面才分开计算再合并.我们希望 1 − f ( 4 ) = f ( 2 ) , 1 − f ( 3 ) = f ( 1 ) 1-f(4)=f(2),1-f(3)=f(1) 1f(4)=f(2),1f(3)=f(1),这样我们就可以直接使用 ( f ( 4 ) , f ( 3 ) , f ( 2 ) , f ( 1 ) ) (f(4),f(3),f(2),f(1)) (f(4),f(3),f(2),f(1))一个表达式就可以表示出(8). 在代码中就比较方便计算了.
1 − f ( 4 ) = f ( 2 ) 1-f(4)=f(2) 1f(4)=f(2),设衡量距离的点是 x 0 x_0 x0,则
1 − x 0 − 4 2 = x 0 − 2 2 (9) 1-\frac{x_0-4}{2}=\frac{x_0-2}{2} \tag{9} 12x04=2x02(9)
上式就表明,只有当 f ( 4 ) f(4) f(4) f ( 2 ) f(2) f(2)表示到某衡量点的距离百分比各为50%的时候, (9)才能成立.

def bilinear_kernel1(in_channels, out_channels, factor):
    kernel_size = 2 * factor
    center = kernel_size / 2
    og = np.ogrid[:kernel_size, :kernel_size]
    filt = (1 - abs(og[0] - center) / factor) * (1 - abs(og[1] - center) / factor)
    weight = np.zeros((in_channels, out_channels, kernel_size, kernel_size), dtype='float32')
    weight[range(in_channels), range(out_channels), :, :] = filt
    
    return torch.from_numpy(weight)

为了调整出来的og计算方便,在代码中, 由于我们给定的对称性, 可以把 ( f ( 4 ) , f ( 3 ) , f ( 2 ) , f ( 1 ) ) (f(4),f(3),f(2),f(1)) (f(4),f(3),f(2),f(1))写为 ( 1 − f ( 1 ) , 1 − f ( 2 ) , 1 − f ( 3 ) , 1 − f ( 4 ) ) (1-f(1),1-f(2),1-f(3),1-f(4)) (1f(1),1f(2),1f(3),1f(4))

到此.代码优化完毕

  • 9
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值