当前位置: 华文星空 > 知识

超分辨率网络SRCNN论文复现(Pytorch)

2022-09-10知识

前言:

网上关于使用pytorch复现SRCNN的文章和代码已经多如牛毛,为什么我还要写这篇文章呢?

这是因为在我一开始学习超分辨率网络时,发现网上的代码并没有严格按照论文中的表述进行复现,对数据的处理和评价指标PSNR的计算也没有与论文达到一致。

这些问题导致网络的输出结果与论文无法在相同标准下比较,带来了很大的麻烦。例如,因为python和matlab的插值算法等与论文不同,导致PSNR的数值与论文的数值不在一个baseline。这些问题导致复现的结果无法直接与论文结果比较,从而无法确认我们是否真的正确复现了论文,这对学习超分辨率网络是有害的。如果一开始的路就歪了,那之后想要纠正就会付出更大代价。

以上种种,促成了这篇文章(可能之后会有一个系列)的诞生。本文 不会详细介绍网络的原理 ,文章的目的主要是记录SRCNN的复现过程,同时将复现时一些容易出现错误的细节问题进行总结,做到 尽量正确还原论文结果 。如有错误,欢迎指正!

论文:SRCNN官方网站(含论文和caffe代码)

我的代码:Pytorch复现SRCNN

转载请附加原文链接。


一、网络的结构

SRCNN的网络结构很简单,只有3层卷积网络。前两层使用ReLU激活函数。

一些表示的说明:

  • lr :输入的低分辨率图片.
  • sr :超分辨率后的图片.
  • hr :原始的高分辨率图片.
  • scale :图片放大的倍率.
  • f :卷积层的卷积核大小(即宽和高).
  • c :输入的通道数(对应卷积核的高).
  • n :卷积层输出的特征图的数量(即卷积核的数量).
  • Conv(c,n,f) :表示卷积层的输入通道数是 c ,输出通道数 n ,卷积核大小 f\times f .
  • 因此论文中的SRCNN网络可表示为: Conv(1,64,9)-ReLU-Conv(64,32,1)-ReLU-Conv(32,1,5)

    Pytorch中的实现方法:

    class SRCNN ( nn . Module ): def __init__ ( self , padding = False , num_channels = 1 ): super ( SRCNN , self ) . __init__ () self . conv1 = nn . Sequential ( nn . Conv2d ( num_channels , 64 , kernel_size = 9 , padding = 4 * int ( padding ), padding_mode = 'replicate' ), nn . ReLU ( inplace = True )) self . conv2 = nn . Sequential ( nn . Conv2d ( 64 , 32 , kernel_size = 1 , padding = 0 ), # n1 * 1 * 1 * n2 nn . ReLU ( inplace = True )) self . conv3 = nn . Conv2d ( 32 , num_channels , kernel_size = 5 , padding = 2 * int ( padding ), padding_mode = 'replicate' ) def forward ( self , x ): x = self . conv1 ( x ) x = self . conv2 ( x ) x = self . conv3 ( x ) return x def init_weights ( self ): for L in self . conv1 : if isinstance ( L , nn . Conv2d ): L . weight . data . normal_ ( mean = 0.0 , std = 0.001 ) L . bias . data . zero_ () for L in self . conv2 : if isinstance ( L , nn . Conv2d ): L . weight . data . normal_ ( mean = 0.0 , std = 0.001 ) L . bias . data . zero_ () self . conv3 . weight . data . normal_ ( mean = 0.0 , std = 0.001 ) self . conv3 . bias . data . zero_ ()

    细节一: 论文中,网络在训练时卷积层不进行padding,在测试时进行padding,padding的像素数量为每边填充 \lfloor f/2 \rfloor ,padding方式为 ‘replicate’ ,如果采用零填充会导致输出的图片四周有边框(boarder effect)。

    细节二: 所有卷积层的权重weight使用均值为零,标准差为0.001的正态分布初始化,偏置bias为零。


    二、数据集的准备

    训练集: SRCNN的训练集使用91-images。

    1、数据增广(augment):为了提高模型的泛化能力,对数据集进行了增广。方法是:将原始图片先进行 0°、90°、180°、270° 旋转,然后缩放 0.6、0.7、0.8、0.9、1.0 ,得到20倍于91-images的数据集,共1820张HR图片。

    2、准备LR-HR数据对:

  • 将HR图片裁剪为scale的整数倍。
  • 将HR图片从RGB空间转换到YCbCr空间,之后的训练仅使用YCbCr的Y通道。
  • 使用‘bicubic’插值方法将HR图片缩小scale倍,再使用相同方法放大scale倍得到与HR尺寸相同,但经过两次‘bicubic’插值的LR图片。
  • scale=3 的情况下,使用 stride=14,size=33 ,对LR和HR图片进行裁剪,得到对应位置的33*33尺寸的sub-images,LR和HR对应位置的sub-images形成一对数据对。
  • 由于网络在训练时没有进行padding,所以经过卷积后图片尺寸会变小,计算公式:out=(in-filter size +2*padding)/stride+1 。输入33*33,经过卷积核大小分别为9、1、5,stride=1的卷积后,得到的输出大小为21*21。因此取HR的sub-images中心21*21像素作为label,这样就得到了训练时的数据::size_{input}=33 size_{label}=21 。
  • 在经过上述处理后最后得到276864对数据对,即每个epoch处理276864对。

    细节一: 由于python的PIL库中的‘bicubic’插值与matlab的‘bicubic’不同,因此需要在python中实现matlab的‘bicubic’插值。本人在网上找到了一个python实现matlab中imresize函数的库,经过验证与matlab得到的结果一致。

    细节二: 同样的,matlab中rgb2ycbcr的函数也略有不同,主要是转换矩阵的值和取整上的差别。以下为python实现的相同效果的转换函数:

    # https://en.wikipedia.org/wiki/YCbCr m = np . array ([[ 65.481 , 128.553 , 24.966 ], [ - 37.797 , - 74.203 , 112.0 ], [ 112 , - 93.786 , - 18.214 ]]) def rgb2ycbcr ( rgb ): shape = rgb . shape if len ( shape ) == 3 : rgb = rgb . reshape (( shape [ 0 ] * shape [ 1 ], 3 )) ycbcr = np . dot ( rgb , m . transpose () / 255. ) ycbcr [:, 0 ] += 16. ycbcr [:, 1 :] += 128. ycbcr = np . round ( ycbcr ) return ycbcr . reshape ( shape ) def ycbcr2rgb ( ycbcr ): shape = ycbcr . shape if len ( shape ) == 3 : ycbcr = ycbcr . reshape (( shape [ 0 ] * shape [ 1 ], 3 )) rgb = copy . deepcopy ( ycbcr ) rgb [:, 0 ] -= 16. rgb [:, 1 :] -= 128. rgb = np . dot ( rgb , np . linalg . inv ( m . transpose ()) * 255. ) rgb = np . round ( rgb ) return rgb . clip ( 0 , 255 ) . reshape ( shape )

    细节三: 转换为YCbCr时需要注意,如果图片本身就是灰度图,只有一个通道,就不需要再转化,直接将该通道作为Y通道输入即可。

    验证集: 验证集使用Set5。

    与训练集的处理相同,但是可以不用裁剪sub-images,直接将完整的图片作为输入,这样验证的结果更接近实际的测试结果。

    测试集: 测试集使用Set5、Set14。测试集的处理将在第四节介绍。


    三、模型训练设置

    训练时一些详细的参数论文中并没有提及,因此为了能够加速训练结果收敛,一些设置是我自己的经验,文中使用括号标注。

    训练的设置如下:

  • 优化器(Optimizer) :SGD,即随机梯度下降法,(momentum=0.9)。
  • 损失函数(Criterion) :MSE,即对应像素的均方误差。
  • Batch Size :论文未提及(128)。
  • 学习率(Learning Rate) :前两层卷积层为 10^{-4} ,最后一层为10^{-5} 。(由于文中的batch size没有说明,因此学习率根据实际采用的batch size设置,本人使用的学习率为10^{-2} 、10^{-3} 。)

  • 四、测试PSNR

    PSNR的计算公式如下:

    PSNR=10*log_{10}(\frac{MAX^2}{MSE})=20*log_{10}(\frac{MAX}{\sqrt{MSE}})

    其中的 MAX 为像素的取值范围的最大值,如果图像的像素为 [0,1] ,则MAX=1 ;如果为 [0,255] ,则MAX=255 。

    在测试时计算PSNR,按照「江湖规矩」,需要将原始的高清图片 hr 的四个边缘各裁剪scale个像素。

    由于在测试时会进行padding,因此输出的 sr 与 hr 同尺寸,所以为了计算PSNR,也要对 sr 进行同样的处理。

    由于输入网络的是Y通道,因此若要得到完整的图片,需要将输出的Y通道与输入前的CbCr组合成完整的YCbCr图像,然后转换为RGB。若为灰度图则无需处理。


    五、最终结果

    以下结果均为上述设置下,训练 6000 epoch 得到的结果。

    在Set5、Set14上测试PSNR:

    图片结果: