文章链接:(ECCV 2018)https://arxiv.org/abs/1807.02758 代码链接:https://github.com/yulunzhang/RCAN
卷积神经网络(CNN)的深度是图像超分辨率(SR)的关键。现有SR网络面临以下问题:
随着网络的加深,这些网络也会越来越难训练。LR和低级特征中包含SR了丰富的低频信息,SR网络在不同通道中却采用了相同的方式对其进行处理,影响了CNN的表现。为了解决这些问题,我们提出了深度残差通道注意网络(RCAN)。在RCAN中,我们提出了一种由几个具有长跳跃连接的残差组构成的residual in residual(RIR)结构来构建深度网络。每个残差组中包含一些具有短跳跃连接的残差块。整个RIR结构通过运用多个跳跃连接,让低频信息绕过网络,使主干网络只学习到高频信息。
此外,文章还提出了一种通道注意机制(CA),通过考虑通道间的相互依赖性来自适应地调整不同通道的权重。
大量的实验表明RCAN在准确性和视觉效果上超越了现有的SOTA(2018)。
现在基于深度学习的SR面临的问题:
从EDSR和MDSR可以看出,网络的深度对图像SR起到很关键的作用,但是仅仅堆叠残差块构建出来的网络难以进一步提高性能了。现有的CNN-based网络对不同的通道特征都是一样看待的,而SR任务应该尽可能多的恢复一些高频信息(图像的高频信息就是灰度变化快的地方,比如边缘、角点等)。由于LR图像主要都是低频信息,最好将其直接输给最终的HR,没有必要浪费计算量。然而现有的EDSR等网络直接从LR中提取特征并对每个通道都做一样的处理,把计算量浪费在了低频信息上,阻碍了网络的性能提升。本文的主要贡献:
设计了一个残差通道注意力网络(RCAN),使网络可以变得更深并提高性能。提出了residual in residual(RIR)结构,即由多个残差组和长跳跃连接构建粗粒度的残差学习,在残差组内部再堆叠多个简化的残差块并采用短跳跃连接(大的残差内部再套娃小残差)。这种结构可以使低频信息绕过网络,从而提高信息处理的效率。提出了通道注意力(CA)机制,通过特征通道之间的相互依赖性来重新调整特征权重。网络结构如下所示:
网络由4个模块构成:
浅层特征提取——由1个卷积层head构成:conv(args.n_colors, n_feats, kernel_size);
深层特征提取——即文章提出的RIR结构;
上采样——上采样用的是PixelShuffle,并且跟EDSR是一样的策略:
2倍和3倍时先用卷积增加通道数到n_feat*scale^2,然后PixelShuffle;4倍时就相当于把2倍的上采样循环2次;重建——用1个卷积层把通道数恢复到输入图片的通道数:conv(n_feats, args.n_colors, kernel_size)。
(上采样和重建的代码通常放在一起,即tail里面)。
RCAN的部分主要代码如下:
class RCAN(nn.Module): def __init__(self, args, conv=common.default_conv): super(RCAN, self).__init__() n_resgroups = args.n_resgroups n_resblocks = args.n_resblocks n_feats = args.n_feats kernel_size = 3 reduction = args.reduction scale = args.scale[0] act = nn.ReLU(True) # RGB mean for DIV2K rgb_mean = (0.4488, 0.4371, 0.4040) rgb_std = (1.0, 1.0, 1.0) self.sub_mean = common.MeanShift(args.rgb_range, rgb_mean, rgb_std) # define head module modules_head = [conv(args.n_colors, n_feats, kernel_size)] # define body module modules_body = [ ResidualGroup( conv, n_feats, kernel_size, reduction, act=act, res_scale=args.res_scale, n_resblocks=n_resblocks) \ for _ in range(n_resgroups)] # default=10 modules_body.append(conv(n_feats, n_feats, kernel_size)) # define tail module modules_tail = [ common.Upsampler(conv, scale, n_feats, act=False), conv(n_feats, args.n_colors, kernel_size)] self.add_mean = common.MeanShift(args.rgb_range, rgb_mean, rgb_std, 1) self.head = nn.Sequential(*modules_head) self.body = nn.Sequential(*modules_body) self.tail = nn.Sequential(*modules_tail) def forward(self, x): x = self.sub_mean(x) x = self.head(x) res = self.body(x) res += x x = self.tail(res) x = self.add_mean(x) return x上采样部分代码:
class Upsampler(nn.Sequential): def __init__(self, conv, scale, n_feat, bn=False, act=False, bias=True): m = [] if (scale & (scale - 1)) == 0: # Is scale = 2^n? for _ in range(int(math.log(scale, 2))): m.append(conv(n_feat, 4 * n_feat, 3, bias)) m.append(nn.PixelShuffle(2)) if bn: m.append(nn.BatchNorm2d(n_feat)) if act: m.append(act()) elif scale == 3: m.append(conv(n_feat, 9 * n_feat, 3, bias)) m.append(nn.PixelShuffle(3)) if bn: m.append(nn.BatchNorm2d(n_feat)) if act: m.append(act()) else: raise NotImplementedError super(Upsampler, self).__init__(*m)从上面的结构图中可以看出,residual in residual(RIR)结构的最外层由G个残差组以及一个长跳跃连接构成,从而形成了一个粗粒度的残差学习。在每一个残差组的内部,则是由B个残差通道注意力块(RCAB)以及一个小的跳跃连接构成。简单来说,这个residual in residual就是大残差内部再套娃小残差。
长跳跃连接可以使网络在更加粗粒度的层次上学习到残差信息。而短跳跃连接则是一种细粒度的identity-based的跳跃连接,使得大量网络不需要的低频信息得到过滤。
为了进一步实现自适应的辨别学习(discriminative learning),作者提出了通道注意力机制(CA)并在RCAB中进行了运用,其目的是给更有价值的通道更高的权重。
残差组Residual Group主要代码:
## Residual Group (RG) class ResidualGroup(nn.Module): def __init__(self, conv, n_feat, kernel_size, reduction, act, res_scale, n_resblocks): super(ResidualGroup, self).__init__() modules_body = [] modules_body = [ RCAB( conv, n_feat, kernel_size, reduction, bias=True, bn=False, act=nn.ReLU(True), res_scale=1) \ for _ in range(n_resblocks)] # default=20 modules_body.append(conv(n_feat, n_feat, kernel_size)) self.body = nn.Sequential(*modules_body) def forward(self, x): res = self.body(x) res += x return res为了使网络将重点放在有用信息多的特征上,作者利用特征通道之间的相互依赖性,提出了一种通道注意力(CA)机制(如下图所示)。
其中最关键的一步是:怎么给每个channel-wise feature生成不一样的attention,这里主要考虑两点:
LR中有丰富的低频和高频信息,但是其中的低频部分相对比较平坦,而高频部分通常是充满边缘、纹理等细节的区域。卷积层中的每个filter都有单独的局部感受野,因此卷积的输出无法利用局部区域之外的上下文信息。(?)通道注意力机制(CA)的算法流程:
首先对输入的特征图 X = [ x 1 , … , x c , x C ] X=[x_1,\dots,x_c,x_C] X=[x1,…,xc,xC] 做全局平均池化,给每个通道都分别计算一个数据值 z c z_c zc。继续处理这些数据值 z c z_c zc,【downscale–>Relu–>upscale–>Sigmoid】,其中下采样的reduction ratio为 r。处理得到最终的通道统计量 s s s。最后,将通道统计量 s s s 与CA的输入特征图 x x x 做element-wise的相乘。(可见,CA并不改变特征图的尺寸和通道数,只是给同一通道中的所有像素都乘上一个同一个值。如果某个通道比较重要,那么里面所有的元素的权重都被乘以一个比较高的系数,反之亦然。)
CA的代码如下:
class CALayer(nn.Module): def __init__(self, channel, reduction=16): super(CALayer, self).__init__() # global average pooling: feature --> point self.avg_pool = nn.AdaptiveAvgPool2d(1) # feature channel downscale and upscale --> channel weight self.conv_du = nn.Sequential( nn.Conv2d(channel, channel // reduction, 1, padding=0, bias=True), nn.ReLU(inplace=True), nn.Conv2d(channel // reduction, channel, 1, padding=0, bias=True), nn.Sigmoid() ) def forward(self, x): y = self.avg_pool(x) y = self.conv_du(y) return x * y从图可见,RCAB就是在EDSR提出的改进版残差块 R B RB RB(去掉了两个batch norm)的基础上,再往里面加入了通道注意力(CA)。
RCAB代码:
class RCAB(nn.Module): def __init__( self, conv, n_feat, kernel_size, reduction, bias=True, bn=False, act=nn.ReLU(True), res_scale=1): super(RCAB, self).__init__() modules_body = [] for i in range(2): modules_body.append(conv(n_feat, n_feat, kernel_size, bias=bias)) if bn: modules_body.append(nn.BatchNorm2d(n_feat)) if i == 0: modules_body.append(act) modules_body.append(CALayer(n_feat, reduction)) self.body = nn.Sequential(*modules_body) self.res_scale = res_scale def forward(self, x): res = self.body(x) res += x return res
从结果看RCAN在这几个数据集上的测试效果还算比较不错的,作者也在消融实验中验证了RIR和CA的有效性。
【1】(RDN)Residual Dense Network for Image Super-Resolution
【2】(IDN)Fast and Accurate Single Image Super-Resolution via Information Distillation Network
【3】(DRN)Closed-loop Matters: Dual Regression Networks for Single Image Super-Resolution