超分辨率第二章-FSRCNN

超分辨率第二章-FSRCNN

FSRCNN是2016年提出的超分辨率模型,使用后端上采样(转置卷积的方法),在一定程度上解决了SRCNN的问题。

参考博客

论文地址

模型位置:F:\Github下载\FSRCNN-pytorch-master

一.模型介绍

  • FSRCNN改进了SRCNN在速度上存在的缺陷

    • SRCNN在将低分辨率图像送进网络之前,会先使用双三次插值法进行插值上采样操作,产生与groundtruth大小一致的低分辨率图像,这样会增加了计算复杂度,因为插值后的图像相比原始的低分辨率图像更大,于是在输入网络后各个卷积层的计算代价会增大,从而限制了网络的整体速度

    • SRCNN非线性映射层的计算代价太高,参数过多(高维度下的映射)。

  • FSRCNN在SRCNN基础上做了如下改变:

    • FSRCNN直接采用低分辨的图像作为输入,不同于SRCNN需要先对低分辨率的图像进行双三次插值然后作为输入,FSRCNN在网络的最后采用反卷积层实现上采样,小尺寸的图像在映射学习阶段可以有效地提升运算速度

    • FSRCNN中没有非线性映射,相应地出现了收缩层、映射(多个卷积核为3*3的层)和扩展层,在低维空间中进行映射学习可以有效地提升运算速度

    • FSRCNN选择更小尺寸的滤波器和更深的网络结构。

    • 所有卷积层(反卷积层除外)都可以由不同放大因子的网络共享,能够在保持恢复质量(即图像超分辨率重建后的质量)不降低的前提下,通过迁移(或共享)其卷积层来快速地对不同放大因子(upscaling factors)的图像进行训练和测试。

      具体来说,我们可以从以下几个方面来理解这句话:

      1. 卷积层的迁移:在深度学习中,迁移学习是一种常见的技术,它允许将一个已经训练好的模型的部分(如网络层)用于另一个相关的任务中,以减少训练时间和提高模型性能。FSRCNN利用这一思想,将网络中用于特征提取的卷积层设计为与放大因子无关。这意味着,无论是将图像放大2倍、3倍还是4倍,这些卷积层都可以保持不变,而不需要为每个放大因子重新训练。
      2. 快速训练和测试:由于卷积层可以跨不同的放大因子进行迁移,因此FSRCNN在训练和测试时可以节省大量时间。对于一个新的放大因子,我们只需要重新训练或调整网络的反卷积层,该层直接负责根据给定的放大因子来重建图像。这种方式大大减少了训练成本,并加快了测试速度。
      3. 无损失的恢复质量:尽管FSRCNN通过迁移卷积层来简化训练和测试过程,但它并没有牺牲图像超分辨率重建的质量。这得益于其网络架构的精心设计,特别是反卷积层能够根据目标放大因子有效地重建图像细节。因此,无论是在哪个放大因子下,FSRCNN都能提供高质量的图像恢复结果。
  • 对比

    • SRCNN结构(飞镖型维度变化:小-大-小,前端上采样)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      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结构(沙漏型维度变化:大-小-大,后端上采样)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      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()

    • 图片:conv(卷积核大小,输出维度,输入维度)

      • pAZz6IA.png

二.数据集

以img-91作为训练集,Set5作为测试集。

三.模型搭建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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):#执行4次,卷积核大小为3*3的非线性映射层
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) #通过nn.Sequential(*self.mid_part)转换为一个Sequential模块

#模型的第三部分:转置卷积层
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*

四.模型训练

  • 代码与上一章内容相同

    1
    python train.py --train-file=data_set/train_set/91-image_x3.h5 --eval-file=data_set/eval_set/Set5_x3.h5 --outputs-dir=outputs #运行命令

结果:pAuVdT1.png

五.模型测试

这里将低分辨率进行超分处理后的预测Y通道与双三次插值后图像的 Cb、Cr通道的结合的意义:

YCbCr色彩空间中的Y代表亮度信息,而Cb和Cr代表色度信息(蓝色和红色的色度差)。RGB色彩空间则直接由红(R)、绿(G)、蓝(B)三个颜色通道组成。

在图像处理中,由于人眼对亮度的敏感度高于对色度的敏感度,因此很多图像处理算法会选择在YCbCr色彩空间中进行处理,尤其是在超分辨率等任务中,可以独立地对亮度(Y通道)和色度(Cb、Cr通道)进行处理,以达到更好的视觉效果和计算效率。

在超分辨率任务中,由于亮度信息(Y通道)包含了图像的主要结构信息,因此通常会通过深度学习等算法对低分辨率图像的Y通道进行预测,以获得高分辨率的Y通道。而色度信息(Cb、Cr通道)则相对简单,可以通过传统的插值方法(如双三次插值)从低分辨率图像中直接获得高分辨率的版本。

将预测的Y通道与bicubic插值得到的Cb、Cr通道结合,实际上是在保持色度信息不变的同时,仅对亮度信息进行增强或修正。这样做的好处是可以在保持图像颜色自然性的同时,显著提高图像的清晰度和细节表现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
cudnn.benchmark = True
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
model = FSRCNN(scale_factor=args.scale).to(device)

state_dict = model.state_dict()
#加载模型权重文件
for n, p in torch.load(args.weights_file, map_location=lambda storage, loc: storage).items():
if n in state_dict.keys():
state_dict[n].copy_(p)
else:
raise KeyError(n)

model.eval()
# 计算调整后的图像尺寸,确保它是缩放因子的整数倍
image = pil_image.open(args.image_file).convert('RGB')
image_width = (image.width // args.scale) * args.scale
image_height = (image.height // args.scale) * args.scale

# 生成高分辨率(HR)、低分辨率(LR)和双三次插值放大后的图像(bicubic)
hr = image.resize((image_width, image_height), resample=pil_image.BICUBIC)
lr = hr.resize((hr.width // args.scale, hr.height // args.scale), resample=pil_image.BICUBIC) #下采样降低分辨率
bicubic = lr.resize((lr.width * args.scale, lr.height * args.scale), resample=pil_image.BICUBIC) #双三次插值增大分辨率
bicubic.save(args.image_file.replace('.', '_bicubic_x{}.'.format(args.scale)))

#将RGB图像转换为YCbCr色彩空间,并返回处理后的Y通道和原始的YCbCr图像,高分辨率和低分辨率图像接受Y通道,而双三次插值图像接受色度通道((Cb、Cr通道)
lr, _ = preprocess(lr, device)
hr, _ = preprocess(hr, device)
_, ycbcr = preprocess(bicubic, device)

with torch.no_grad():
preds = model(lr).clamp(0.0, 1.0) #此处只需输入原始低分辨率图像,不需要输入双三次插值后的图像,此时进行将超分处理。

#计算峰值信噪比
psnr = calc_psnr(hr, preds)
print('PSNR: {:.2f}'.format(psnr))

preds = preds.mul(255.0).cpu().numpy().squeeze(0).squeeze(0)#将模型预测结果缩放到0-255范围,并转换为NumPy数组,去除不必要的维度。

#将预测的Y通道与bicubic图像的Cb和Cr通道结合,之后转换为RGB格式,裁剪到0-255范围,转换为PIL图像,并保存。
output = np.array([preds, ycbcr[..., 1], ycbcr[..., 2]]).transpose([1, 2, 0])
output = np.clip(convert_ycbcr_to_rgb(output), 0.0, 255.0).astype(np.uint8)
output = pil_image.fromarray(output)
output.save(args.image_file.replace('.', '_fsrcnn_x{}.'.format(args.scale)))

#python test.py --weights-file=outputs/x3/best.pth --image-file=data/car.bmp
-------------本文结束-------------