代码:基于pytorch的FSRCNN
FSRCNN改进了SRCNN在速度上存在的缺陷:
1.SRCNN在将低分辨率图像送进网络之前,会先使用双三次插值法进行插值上采样操作,产生与groundtruth大小一致的低分辨率图像,这样会增加了计算复杂度,因为插值后的图像相比原始的低分辨率图像更大,于是在输入网络后各个卷积层的计算代价会增大,从而限制了网络的整体速度。
2.非线性映射层的计算代价太高。
FSRCNN在SRCNN基础上做了如下改变:
1.FSRCNN直接采用低分辨的图像作为输入,不同于SRCNN需要先对低分辨率的图像进行双三次插值然后作为输入;2.FSRCNN在网络的最后采用反卷积层实现上采样;3.FSRCNN中没有非线性映射,相应地出现了收缩、映射和扩展;
4.FSRCNN选择更小尺寸的滤波器和更深的网络结构。
Feature Extraction
与SRCNN不同,该网络的输入是原始低分辨图像(未进行插值操作的低分辨图像),此外卷积核大小由99改为55。这是由于现在的输入图像尺寸变小了,还有在SRCNN中,其输入图像是经过原始低分辨率图像插值之后所得,所以不必用那么大的卷积核,在原始LR图像中用一个55的卷积核就可以获得在插值之后的LR图像中用99的卷积核几乎相同的信息(关于这一点的理解,我尚不太清楚,个人认为主要原因是输入图像尺寸变小导致卷积核尺寸变小),还有卷积核的个数是1,因为是在单通道(Y颜色通道)上进行比较。
Shrinking
这一层的主要作用是降维。由于低分辨率的特征维度(这里用d表示)太高,会极大的增加计算代价,所以采用此层,并设置卷积核尺寸为1*1。维度由d降到s(s远小于d)。
non-linear mapping
本文将在SRCNN中的mapping层分为m(表示mapping层的个数)个小mapping层,每个层的卷积核大小设置3*3,卷积核的数目设为s(降维后的特征维度)。
Expanding
该层的作用与Shrinking层相反,是为了扩张特征维度。之所以使用此层是因为直接使用低维的高分辨特征进行图像恢复的话,图像质量太差。具体的设置是:卷积核尺寸设为1*1,卷积核的数目设为d,与进行Feature extraction得到的特征维度一致。
Deconvolution
执行上采样操作,接受高分辨率特征,输出最终的超分辨结果。具体设置是,卷积核大小设为9*9,卷积核的个数为1,因为输入的是单通道图像。
网络中其他一些设置包括:将每一层的损失函数设为PReLU。不使用ReLU主要是为了避免在ReLU中由零梯度导致的“dead features”。损失函数使用均方误差。参数优化使用随机梯度下降。
图中mapping那里,不是说生成了m个,是进行了m次s个通道到s个通道的映射,比如s1到s2,s2到s3这样,到最后还是生成s个通道的特征图。具体可以看代码。
model构建代码:
def __init__(self, scale_factor, num_channels=1, d=56, s=12, m=4):
super(FSRCNN, self).__init__()
self.first_part = nn.Sequential(
nn.Conv2d(num_channels, d, kernel_size=5, padding=5//2),
nn.PReLU(d)
)
self.mid_part = [nn.Conv2d(d, s, kernel_size=1), nn.PReLU(s)]
for _ in range(m):
self.mid_part.extend([nn.Conv2d(s, s, kernel_size=3, padding=3//2), nn.PReLU(s)])#进行m次s个通道到s个通道的映射
self.mid_part.extend([nn.Conv2d(s, d, kernel_size=1), nn.PReLU(d)])
self.mid_part = nn.Sequential(*self.mid_part)#把中间层的卷积都封装起来
self.last_part = nn.ConvTranspose2d(d, num_channels, kernel_size=9, stride=scale_factor, padding=9//2,
output_padding=scale_factor-1)
self._initialize_weights()
FSRCNN除了改进了以上两个不足之外,FSRCNN本身的结构使其具有另一个优势,即它能够在不同的放大倍数下进行快速的训练和测试。这主要是因为共享反卷积层之前的卷积层,使其在不同的放大倍数之间进行切换时,只需微调(fine-tune)反卷积层即可。原理示意图如下图所示:
就是因为反卷积中,padding=(kernel-1)/2,output_padding=s-1,我之前写了一篇output_padding的文章,按照这个方式赋值代入卷积的尺寸公式可以得到out_size=stride×in_size,所以说放大倍数的切换比较方便,只用变换stride就可以变换放大倍数。