AwesomeAI之图像超分(1)——RDN

(5) 2024-07-24 10:12

Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说
AwesomeAI之图像超分(1)——RDN,希望能够帮助你!!!。

原论文:Residual Dense Network for Image Super-Resolution

数据集 DIV2K

DIV2K中共有1000张2K分辨率图像。其中,训练用图像800张,验证用图像100张,测试用图像100张。

如何从HR(High Resolution,高分辨率,Super Resolution,SR,超分辨率)得到LR(Low Resolution,低分辨率)图像?训练输入LR的图片使用该2k图片通过下面3种处理得到:

  • BI方式:主要通过Bicubic下采样得到,缩小比例为x2,x3,x4;
  • BD方式:先对原始图片做(7*7卷积,1.6方差)高斯滤波,再对滤波后图片做下采样;
  • DN方式:先做Bicubic下采样,再加30%的高斯噪声。

网络结构

AwesomeAI之图像超分(1)——RDN_https://bianchenghao6.com/blog__第1张

图1:RDN

输入的是低分辨率图像——LR,输出的是高分辨率图像——HR。
上面的网络称为RDN(Residual Dense Network),其包括4个模块:

  • shallow feature extraction net (SFENet)
    网络的前2个卷积层
  • redidual dense blocks (RDBs)
    RDB = Residual Block + Dense Block,是两者的整合。单个RDB的内部结构如下图:
    AwesomeAI之图像超分(1)——RDN_https://bianchenghao6.com/blog__第2张
图2:RDB

RDB的CM机制(contiguous memory mechanism)其实就是图2中的一个个Conv+ReLU。比如对于第 d d d个RDB,其有 C C C个Conv+ReLU,那么:
对于第1个Conv+ReLU的CM机制是指:
F d , 1 = R e L U ( W d , 1 T ∗ F d − 1 ) F_{d,1}=ReLU(W_{d,1}^T*F_{d-1}) Fd,1=ReLU(Wd,1TFd1)
对于第2个Conv+ReLU的CM机制是指:
F d , 2 = R e L U ( W d , 2 T ∗ [ F d − 1 , F d , 1 ] ) F_{d,2}=ReLU(W_{d,2}^T*[F_{d-1},F_{d,1}]) Fd,2=ReLU(Wd,2T[Fd1,Fd,1])
对于第 c c c个Conv+ReLU的CM机制是指:
F d , c = R e L U ( W d , c T ∗ [ F d − 1 , F d , 1 , . . . , F d , c − 1 ] ) , 1 ≤ c ≤ C F_{d,c}=ReLU(W_{d,c}^T*[F_{d-1},F_{d,1},...,F_{d,c-1}]), 1 \leq c \leq C Fd,c=ReLU(Wd,cT[Fd1,Fd,1,...,Fd,c1]),1cC

local feature fusion (LFF)就是图2中的 F d , L F = H L F F d ( [ F d − 1 , F d , 1 , . . . , F d , c , . . . , F d , C ] ) F_{d,LF}=H_{LFF}^d([F_{d-1},F_{d,1,...,F_{d,c,...,F_{d,C}}}]) Fd,LF=HLFFd([Fd1,Fd,1,...,Fd,c,...,Fd,C]),也就是Concat+1*1Conv

local residual learning (GRL)就是图2中的 F d = F d − 1 + F d , L F F_d=F_{d-1}+F_{d,LF} Fd=Fd1+Fd,LF
3者的区别如下图:
AwesomeAI之图像超分(1)——RDN_https://bianchenghao6.com/blog__第3张

图3:RB、DB及RDB对比
  • dense feature fusion (DFF)
    global feature fusion (GFF)
    就是图1中的Concat+1*1Conv部分,即 F G F = H G F F ( [ F 1 , . . . , F D ] ) F_{GF}=H_{GFF}([F_1,...,F_D]) FGF=HGFF([F1,...,FD])
    global residual learning (GRL)
    就是图1中的 F D F = F − 1 + F G F F_{DF}=F_{-1}+F_{GF} FDF=F1+FGF
    所以DFF = GFF+GRL
  • up-sampling net (UPNet)
    表示RDN网络最后的上采样+卷积操作。实现了输入图片的放大操作。

RDN和DenseNet block的区别:
(1)RDN中的RDB模块去掉了DenseNet每个block中的batchnorm
(2)RDN中的RDB模块去掉了DenseNet每个block中的pooling
(3)DenseNet中每一个dense block的输出都是concat起来的。而RDB将d-1层的特征也和1到d层的特征做了局部特征融合(local feature fusion (LFF)),更好的保证了信息流的贯通。
(4)在整个RDN网络上,每一个RDB模块的输出都会最终被concat起来利用。而DenseNet 整个网络中只使用每一个DenseBlock最后的输出。

==================================
https://blog.csdn.net/_/article/details/

RDN和DenseNet block的区别:
(1)RDN中的RDB模块去掉了DenseNet每个block中的batchnorm
(2)RDN中的RDB模块去掉了DenseNet每个block中的pooling
(3)DenseNet中每一个dense block的输出都是concat起来的。而RDB将d-1层的特征也和1到d层的特征做了局部特征融合(local feature fusion (LFF)),更好的保证了信息流的贯通。
(4)在整个RDN网络上,每一个RDB模块的输出都会最终被concat起来利用。而DenseNet 整个网络中只使用每一个DenseBlock最后的输出。

RDN和SRDenseNet 的区别:
(1)RDN通过3个方面改进SRDenseNet 中使用的传统DenseBlock模块。1,加入了contiguous memory (CM) mechanism 使得先前的RDB模块和当前的RDB模块都有直接接触。2,得益于local feature fusion (LFF) ,RDB模块可以容许更大的增长率。3,RDB中Local residual
learning (LRL) 的应用增加了信息和梯度的流动。
(2)RDB内部没有稠密连接。
(3)SRDenseNet 使用L2 loss,RDB使用L1 loss。

RDN和MemNet 的区别:
(1)MemNet 需要对原始图片使用Bicubic插值方式进行上采样,而RDN直接使用原始低分辨图片,优势就是可以减少计算量和提高效果。
(2)MemNet 中包含逆向和门限单元的模块就不再接受先前模块的输入,而RDB各个模块之间是有信息流交互的。
(3)MemNet 没有全部利用中间的特征信息,而RDN通过Global Residual Learning 将所有信息都利用起来。

==================================

实现细节

github实现: 论文作者实现、第三方实现。这里以第三方实现代码进行分析。

构建网络

def model(self): F_1 = tf.nn.conv2d(self.images, self.weightsS['w_S_1'], strides=[1,1,1,1], padding='SAME') + self.biasesS['b_S_1'] F0 = tf.nn.conv2d(F_1, self.weightsS['w_S_2'], strides=[1,1,1,1], padding='SAME') + self.biasesS['b_S_2'] FD = self.RDBs(F0) FGF1 = tf.nn.conv2d(FD, self.weightsD['w_D_1'], strides=[1,1,1,1], padding='SAME') + self.biasesD['b_D_1'] FGF2 = tf.nn.conv2d(FGF1, self.weightsD['w_D_2'], strides=[1,1,1,1], padding='SAME') + self.biasesD['b_D_2'] FDF = tf.add(FGF2, F_1) FU = self.UPN(FDF) # FU = self.UPN(F_1) IHR = tf.nn.conv2d(FU, self.weight_final, strides=[1,1,1,1], padding='SAME') + self.bias_final return IHR 

输入的LR图片shape=(32,32);
w_S_1=[ks, ks, self.c_dim, G0]=(3,3,3,64);
w_S_2=[ks, ks, G0, G]=(3,3,64,64)
b_S_1=[G0]=(64,)
b_S_2=[G]=(64,)

w_D_1=[1, 1, G * D, G0]=(1,1,64*16,64)=(1,1,1024,64)
w_D_2=[ks, ks, G0, G0]=(3,3,64,64)
b_D_1=[G0]=(64,)
b_D_2=[G0]=(64,)

这里重点看下RDBs的实现:

def RDBs(self, input_layer): rdb_concat = list() rdb_in = input_layer for i in range(1, self.D+1): x = rdb_in for j in range(1, self.C+1): tmp = tf.nn.conv2d(x, self.weightsR['w_R_%d_%d' %(i, j)], strides=[1,1,1,1], padding='SAME') + self.biasesR['b_R_%d_%d' % (i, j)] tmp = tf.nn.relu(tmp) x = tf.concat([x, tmp], axis=3) x = tf.nn.conv2d(x, self.weightsR['w_R_%d_%d' % (i, self.C+1)], strides=[1,1,1,1], padding='SAME') + self.biasesR['b_R_%d_%d' % (i, self.C+1)] rdb_in = tf.add(x, rdb_in) rdb_concat.append(rdb_in) return tf.concat(rdb_concat, axis=3) 

一共有self.D=16个RDB,每一个RDB有self.C=8个卷积层,所有的卷积都是padding=“SAME”,即每一层卷积的输出=输入。RBDs输出的就是图1中的Concat后的结果,其实,RDBs的实现是相当简单的。

再来看看最后的UPNet实现:

def UPN(self, input_layer): x = tf.nn.conv2d(input_layer, self.weightsU['w_U_1'], strides=[1,1,1,1], padding='SAME') + self.biasesU['b_U_1'] x = tf.nn.relu(x) x = tf.nn.conv2d(x, self.weightsU['w_U_2'], strides=[1,1,1,1], padding='SAME') + self.biasesU['b_U_2'] x = tf.nn.relu(x) x = tf.nn.conv2d(x, self.weightsU['w_U_3'], strides=[1,1,1,1], padding='SAME') + self.biasesU['b_U_3'] x = self.PS(x, self.scale) return x 

w_U_1=[5, 5, G0, 64]=[5, 5, 64, 64]
w_U_2=[3, 3, 64, 32]
w_U_3=[3, 3, 32, self.c_dim * self.scale * self.scale ]=(3, 3, 32, 333)=(3, 3, 32, 27)

b_U_1=[64]
b_U_2=[32]
b_U_3=[self.c_dim * self.scale * self.scale]=[27]

此处UPNet的实现和图1优点不同。其次,SFENet中的两个卷积层是没有经过ReLU的,这和图1是一致的。UPNet中的卷积结果要经过ReLU,这个图1是不同的。代码中的UPN只到图1的Upscale部分,图1中Upscale下面的卷积不在上面的UPN代码中,而在model代码中。所以self.PS就对应图1中的Upscale。

构建模型

def build_model(self, images_shape, labels_shape): self.images = tf.placeholder(tf.float32, images_shape, name='images') self.labels = tf.placeholder(tf.float32, labels_shape, name='labels') self.weightsS, self.biasesS = self.SFEParams() self.weightsR, self.biasesR = self.RDBParams() self.weightsD, self.biasesD = self.DFFParams() self.weightsU, self.biasesU = self.UPNParams() self.weight_final = tf.Variable(tf.random_normal([self.kernel_size, self.kernel_size, self.c_dim, self.c_dim], stddev=np.sqrt(2.0/9/3)), name='w_f') self.bias_final = tf.Variable(tf.zeros([self.c_dim], name='b_f')), self.pred = self.model() # self.loss = tf.reduce_mean(tf.square(self.labels - self.pred)) self.loss = tf.reduce_mean(tf.abs(self.labels - self.pred)) self.summary = tf.summary.scalar('loss', self.loss) self.model_name = "%s_%s_%s_%s_x%s" % ("rdn", self.D, self.C, self.G, self.scale) self.saver = tf.train.Saver(max_to_keep=10) 

这里的损失函数就是 l 1 l_1 l1损失即 L = 1 n ∑ i = 1 n ∣ y i − H i ∣ L=\frac{1}{n}\sum_{i=1}^n|y_i-H_i| L=n1i=1nyiHi

训练模型

def train(self, config): print("\nPrepare Data...\n") data = input_setup(config) if len(data) == 0: print("\nCan Not Find Training Data!\n") return data_dir = get_data_dir(config.checkpoint_dir, config.is_train, config.scale) data_num = get_data_num(data_dir) batch_num = data_num // config.batch_size images_shape = [None, self.image_size, self.image_size, self.c_dim] labels_shape = [None, self.image_size * self.scale, self.image_size * self.scale, self.c_dim] self.build_model(images_shape, labels_shape) counter = self.load(config.checkpoint_dir, restore=False) epoch_start = counter // batch_num batch_start = counter % batch_num global_step = tf.Variable(counter, trainable=False) learning_rate = tf.train.exponential_decay(config.learning_rate, global_step, config.lr_decay_steps*batch_num, config.lr_decay_rate, staircase=True) optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate) learning_step = optimizer.minimize(self.loss, global_step=global_step) tf.global_variables_initializer().run(session=self.sess) merged_summary_op = tf.summary.merge_all() summary_writer = tf.summary.FileWriter((os.path.join(config.checkpoint_dir, self.model_name, "log")), self.sess.graph) self.load(config.checkpoint_dir, restore=True) print("\nNow Start Training...\n") for ep in range(epoch_start, config.epoch): # Run by batch images for idx in range(batch_start, batch_num): batch_images, batch_labels = get_batch(data_dir, data_num, config.batch_size) counter += 1 _, err, lr = self.sess.run([learning_step, self.loss, learning_rate], feed_dict={ 
   self.images: batch_images, self.labels: batch_labels}) if counter % 10 == 0: print("Epoch: [%4d], batch: [%6d/%6d], loss: [%.8f], lr: [%.6f], step: [%d]" % ((ep+1), (idx+1), batch_num, err, lr, counter)) if counter % 10000 == 0: self.save(config.checkpoint_dir, counter) summary_str = self.sess.run(merged_summary_op, feed_dict={ 
   self.images: batch_images, self.labels: batch_labels}) summary_writer.add_summary(summary_str, counter) if counter > 0 and counter == batch_num * config.epoch: self.save(config.checkpoint_dir, counter) break summary_writer.close() 

1、数据输入
images_shape=(32,32,3)
labels_shape=(96,96,3)
首先要明白输入的样本是什么,已经label是什么,这段代码在utils.py中:

def preprocess(path, scale = 3, eng = None, mdouble = None): img = imread(path) label_ = modcrop(img, scale) if eng is None: # input_ = cv2.resize(label_, None, fx=1.0/scale, fy=1.0/scale, interpolation=cv2.INTER_CUBIC) input_ = PIL_resize(label_, 1.0/scale, PIL.Image.BICUBIC) else: input_ = np.asarray(eng.imresize(mdouble(label_.tolist()), 1.0/scale, 'bicubic')) input_ = input_[:, :, ::-1] label_ = label_[:, :, ::-1] return input_, label_ 

数据集下载:http://data.vision.ee.ethz.ch/cvl/DIV2K/DIV2K_train_HR.zip
我们下载的是800张HR图片。我们对这800张HR图片按比例1.0/scale= 1 3 \frac{1}{3} 31进行缩小(即下采样),并采用BICUBIC插值法进行差值,之后得到的就是有锯齿状的LR图片。我们的样本就是原始的HR图片。
2、导入模型
3、计算epoch和batch
4、引入指数衰减的学习率:
decayed_learning_rate = learning_rate * decay_rate ^ (global_step / decay_steps)
5、使用AdamOptimizer进行优化;

今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。

上一篇

已是最后文章

下一篇

已是最新文章

发表回复