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
s−1个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+2p−k+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∗(4−2)(4−2)(4−x)(4−y)+2∗(4−2)(4−2)(x−2)(4−y)+3∗(4−2)(4−2)(4−x)(y−2)+4∗(4−2)(4−2)(x−2)(y−2)(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)=1∗41+2∗41+3∗41+4∗41=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)=1∗40+2∗40+3∗42+4∗42=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
24−x就是点P到x=4的距离,记为a;2x−2为P到x=2的距离,记为1−a
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
24−y就是点P到y=4的距离,记为b;2y−2为P到y=2的距离,记为1−b
所以
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=1∗ab+2∗(1−a)b+3∗a(1−b)+4∗(1−a)(1−b)
说明我们现在只要固定住距离, 就可以通过改变插值点来获得插值,这就是我们卷积核所做的事情.
不失一般性,我可以把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(1−b1)a3(1−b3)a2b2a4b4a2(1−b2)a4(1−b4)(1−a1)b1(1−a3)b3(1−a1)(1−b1)(1−a3)(1−b3)(1−a2)b2(1−a4)b4(1−a2)(1−b2)(1−a4)(1−b4)⎦⎥⎥⎤(7)
若我们使
f
(
x
)
=
(
4
−
x
)
/
2
f(x)=(4-x)/2
f(x)=(4−x)/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)=⎝⎜⎜⎛b1b31−b11−b3⎠⎟⎟⎞∗(a1a21−a11−a2)
代码思想:
先计算两个矩阵的前面两个值
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)
1−f(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=4和y=4的距离的百分比,
f
(
x
)
=
(
4
−
x
)
/
2
f(x)=(4-x)/2
f(x)=(4−x)/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}
(a1a21−a11−a2)(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),1−f(4),1−f(3), 就是这里无法统一用一个函数表达,所以我们上面才分开计算再合并.我们希望
1
−
f
(
4
)
=
f
(
2
)
,
1
−
f
(
3
)
=
f
(
1
)
1-f(4)=f(2),1-f(3)=f(1)
1−f(4)=f(2),1−f(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)
1−f(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}
1−2x0−4=2x0−2(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)) (1−f(1),1−f(2),1−f(3),1−f(4))
到此.代码优化完毕