paper:https://arxiv.org/pdf/1608.00367
code: https://github.com/yjn870/FSRCNN-pytorch/tree/master
目录
1. 动机
2. 方法
3. 代码对比
4. 实验结果
1. 动机
作者此前提出的SRCNN证明了CNN在图像超分领域的有效性。然而,SRCNN计算效率较低,不能达到实时性能。因此,该作者重新设计了SRCNN的网络,以提升推理速度。
改进点包括三个方面:
- 1)在网络末端引入了一个反卷积层,用于将原始分辨率的feature map映射到高分辨率图像;
- 2) 引入了沙漏型body网络,输入特征的通道维度先缩小再扩大,以降低计算量;
- 3) 采用更小的卷积核,但层次更深了;
所提出的网络能够提速40倍,同时SR质量也更好。
2. 方法
原始SRCNN有一个预处理步骤,即先将原图上采样,然后再送入网络,这无疑增加了计算量。在FSRCNN中,去掉了这个步骤,直接从原始分辨率开始,最终得到高分辨率结果。此外,如何缩小计算量也是FSRCNN重点考虑的问题,原始SRCNN虽然层数较少,但卷积核较大(9*9),因此FSRCNN探索了更小的卷积核,同时增加网络深度,整体的计算量能够降低不少。
根据上述观察,我们研究了一种更简洁、更高效的网络结构,用于快速准确的图像SR。为了解决第一个问题,我们采用反卷积层来代替双三次插值。为了进一步减轻计算负担,我们在网络的末端放置了反卷积层1,那么计算复杂度仅与原始LR图像的空间大小成正比。值得注意的是,反褶积层不等于传统插值核的简单替代,如FCN[13],或像[14]这样的“unpooling+convolution”。相反,它由各种自动学习的上采样核(见图3)组成,它们共同工作生成最终的HR输出,并用均匀插值核替换这些反卷积滤波器将导致PSNR急剧下降(例如,对于×3,Set5数据集[15]上至少0.9 dB)。
对于第二个问题,我们分别在映射层的开头和结尾添加一个收缩层和扩展层,以限制低维特征空间中的映射。此外,我们将单个宽映射层分解为几个具有固定滤波器大小为 3 × 3 的层。
FSRCNN整体网络结构如下:
FSRCNN可以分解为特征提取、收缩、映射、扩展和反卷积五个部分。前四个部分是卷积层,最后一个是反卷积层。为了更好地理解,我们将卷积层表示为 Conv(fi, ni, ci),将反卷积层表示为 DeConv(fi, ni, ci),其中变量 fi, ni, ci 分别表示滤波器大小、滤波器数量和通道数。
- Feature extraction: 这部分类似于 SRCNN 的第一部分,但在输入图像上不同。FSRCNN 在原始 LR 图像上执行特征提取,无需插值;而且,卷积核大小从9变成了5。
- Shrinking:使用1*1卷积将通道数降低;
- Non-linear mapping:使用3*3卷积进行特征映射;
- Expanding: 将通道维度再恢复回去;
- Deconvolution:反卷积层,这一层用于对先前的特征进行上采样和聚合,得到高分辨率结果。
3. 代码对比
首先看看SRCNN的代码:
from torch import nn
class SRCNN(nn.Module):
def __init__(self, num_channels=1):
super(SRCNN, self).__init__()
self.conv1 = nn.Conv2d(num_channels, 64, kernel_size=9, padding=9 // 2)
self.conv2 = nn.Conv2d(64, 32, kernel_size=5, padding=5 // 2)
self.conv3 = nn.Conv2d(32, num_channels, kernel_size=5, padding=5 // 2)
self.relu = nn.ReLU(inplace=True)
def forward(self, x):
x = self.relu(self.conv1(x))
x = self.relu(self.conv2(x))
x = self.conv3(x)
return x
再来看FSRCNN的代码:
import math
from torch import nn
class FSRCNN(nn.Module):
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)])
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()
def _initialize_weights(self):
for m in self.first_part:
if isinstance(m, nn.Conv2d):
nn.init.normal_(m.weight.data, mean=0.0, std=math.sqrt(2/(m.out_channels*m.weight.data[0][0].numel())))
nn.init.zeros_(m.bias.data)
for m in self.mid_part:
if isinstance(m, nn.Conv2d):
nn.init.normal_(m.weight.data, mean=0.0, std=math.sqrt(2/(m.out_channels*m.weight.data[0][0].numel())))
nn.init.zeros_(m.bias.data)
nn.init.normal_(self.last_part.weight.data, mean=0.0, std=0.001)
nn.init.zeros_(self.last_part.bias.data)
def forward(self, x):
x = self.first_part(x)
x = self.mid_part(x)
x = self.last_part(x)
return x
可以看出,FSRCNN网络结构更复杂了,但通过去掉预处理过程中的上采样、缩小卷积核、收缩映射过程中的通道数,最终可以得到更深却更轻量的网络,从而达到速度更快、效果更好的结果。
下表展示了从SRCNN到FSRCNN的变化过程: