完成情况

到1月10日,完成了如下的内容

  1. 熟悉了深度学习的基本概念,卷积神经网络中卷积等操作内容

  2. 阅读并充分理解论文<<14ECCV_Learning a Deep Convolutional Network for Image Super-Resolution>>中的Introduction,Related Work,Convolutional Neural Networks for Super-Resolution的内容

  3. 参照github中srcnn的代码,初步实现自己的代码,一下为参照github的项目

    https://github.com/Edwardlzy/SRCNN
    https://github.com/tegg89/SRCNN-Tensorflow

    并产生一系列的结果

    论文前三章的解释

    引言

    1,介绍了图像超分辨率的概念,传统的技术和分类
    图像超分辨率重建是一个需要先知条件的,也就是通过已知的条件来进行重建。传统的就是基于样例的方法,基于样例的又分为两个部分,一个是内部样例,就是内部样例的相似度,另一个是基于外部样例的,具体的就是低分辨率子块和高分辨率子块有一个映射对应。
    2,基于外部样例的,稀疏编码
    稀疏编码是基于外部样例的图片超分辨率技术之一。大概的步骤如下:
    (1),图像的剪裁和预处理(减去均值归一化)
    (2),图像子块通过低分辨率字段进行稀疏编码
    (3),通过高分辨率字典进行重构高分辨率子块
    (4),聚合
    其中应用到了两个字典,低分辨率字典和高分辨率字典。这个过程中,比较重要的是字典的优化和映射函数的优化
    3,卷积神经网络和稀疏编码的关系

  4. 1,
    稀疏编码的四个步骤等同于一个深卷积神经网络。基于这一事实,我们考虑用一个卷积神经网络直接学习低分辨率和高分辨率图像之间端到端的映射

  5. 2,
    使用卷积神经网络就没有显示的使用字典和流形的概念。使用卷积神经网络除了图像的预处理剩下的都可以通过学习得到。
    4,SRCNN概念的引入和性能展示

  6. 1,
    我们提出的模型命名为超分辨率卷积神经网络 (Super-Resolution Convolutional Neural Network,SRCNN)

  7. 2,
    (1),简约
    (2),性能也十分卓越
    (3),在CPU上可以快速重建,速度快,无需考虑优化问题
    (4),大数据量会是性能更优秀,但是对于传统方法确实挑战
    5,图片
    图片说明
    所提出的超分辨率卷积神机网络经过很少的训练次数就可优于双三次插值方法,经过适量的训练可胜过基于稀疏编码的方法。其表现经过的训练迭代可以进一步提升。
    6,引言的总结

  8. 1,网络除了很少的预/后处理,可以直接学习低分辨率和高分辨率图像的端到端映射。

  9. 2,基于深度学习的超分辨率方法与传统的基于稀疏编码的超分辨率方法之间的联系

  10. 3,深度学习是可以应用到分辨率重建上的

    相关研究

    传统的方法有哪些,最后一项,基于图像子块的方法,基于图像子块的方法,也就是基于实例的方法,分为两种情况,基于内部实例的,根据自相似特性重建;然后就是基于外部的,通过学习得到低分辨率子块和高分辨率子块之间的映射,大概的过程如下

    输入子块,通过字典找到最近的低分辨率子块,然后通过映射找到对应的高分辨率子块

    卷积神经网络的超分辨率

    模型阐述

    场景的展示

    有一个低分辨率的图片,通过双三次插值的方式放大,放大之后的图片被称作是低分辨率图片,Y,通过一个映射得到F(Y),我们要让这个F(Y)尽可能的原本的高分辨率的图片相同,我们的目的就是得到这个映射函数F

    获取映射的步骤

    1,图像子块的提取和表示
    将图像划分为多个子块(重叠),并将每个子块表示为高维的向量。每一个高维向量都对应着一组数量等于该向量维度的特征映射。
    2,非线性映射
    将这一组高维向量非线性到另一组高维向量上面去。这一组高维向量就是图像高分辨率子块的表征。这一组高维向量对应着一组数量等于该向量维度的特征映射。
    3,重建
    将2中的高分辨率子块的表征聚合成为高分辨率子块。

    图片展示

    图片说明
    第一层,将低分辨率子块映射成一个个高维向量
    第二层,将低分辨率的向量映射到高维的向量上,得到的向量是对应的高分辨率子块的表征
    第三层,产生最后的高分辨率子块

    图像子块的提取和表示

    1,密集的提取子块
    2,用一组预先训练好的基来表示它们,它们就变成了高维向量

    每一个滤波器就是一个基

    3,
    图片说明
    W1,B1就是滤波器和偏置,就是卷积操作,W1是cf1f1的n1个滤波器,c就是通道数。
    W1对图像,也就是Y就是卷积操作,是n1个c
    f1*f1的滤波器对W1进行卷积,最终的结果就是n1个特征映射

    分线性映射

    第一层为每个图像子块提取一个n1维特征。在第二次操作中,我们将每个 n1维向量映射到一个n2维向量中。
    图片说明
    在这里,W2包含大小为n1/f2/f2的n2个滤波器,B2是n2维的。每个输出的n2维矢量在概念上是一个重建后的的高分辨率图像子块的特征表达。

    重建

    图片说明

    与稀疏编码的关系

    训练

    均方误差作为损失函数
    评判标准为PSNR
    随机梯度下降法和标准的反向传播进行最小化

    图片的预处理方法

    为合成低分辨率样本,我们将子图像用高斯核模糊,然后按照尺度因子将其下采样,再将其用双三次插值按相同因子放大。
    训练时避免边缘效应,卷基层没有进行填充

    代码解释

    以下代码是参考github上的实现的

    https://github.com/Edwardlzy/SRCNN
    https://github.com/tegg89/SRCNN-Tensorflow

    图片相关的处理

    对于图片的处理,我们都写在了util.py中,以下是编写代码的思路

  11. 我们要将图片转化为h5的格式,然后供模型训练和测试

  12. 在进行训练或者测试之前,我们要将图片大小转化为3的倍数,这里我们用到的是3

  13. 对于训练的数据,首先是传入图片,然后我们以步长14来进行划分,得到许多33*33的小图片,然后以这些小图片进行训练

    读入图片

  14. 首先是从文件夹中读入这些图片的路径,

  15. 然后转化为数组的形式,

  16. 同时将RGB的空间转化到YCbCr

    def prepare_data(sess, dataset):
      if FLAGS.is_train:
          filenames = os.listdir(dataset)
          data_dir = os.path.join(os.getcwd(), dataset)
          data = glob.glob(os.path.join(data_dir, "*.bmp"))
      else:
          # 确定测试数据集合的文件夹为Set5
          data_dir = os.path.join((os.path.join(os.getcwd(), dataset)), "Set5")
          data = glob.glob(os.path.join(data_dir, "*.bmp"))
      return data

    针对训练集和测试集的不同,我们在不同的文件夹下读取,这个函数运行的结果,就是一系列的图片的路径字符串,得到了这些字符串,我们将就按照这些路径,将图片读入程序,并转变颜色空间

    def imread(path, is_grayscale=True):
      # 读指定路径的图像
      if is_grayscale:
          return scipy.misc.imread(path, flatten=True, mode='YCbCr').astype(np.float)
      else:
          return scipy.misc.imread(path, mode='YCbCr').astype(np.float)

    图片的预处理

    裁剪图片,归一化图片,针对于每一个子图,生成对于的高分辨率图片,和低分辨率图片

    def modcrop(image, scale=3):
      # 把图像的长和宽都变成scale的倍数
      if len(image.shape) == 3:
          h, w, _ = image.shape
          h = h - np.mod(h, scale)
          w = w - np.mod(w, scale)
          image = image[0:h, 0:w, :]
      else:
          h, w = image.shape
          h = h - np.mod(h, scale)
          w = w - np.mod(w, scale)
          image = image[0:h, 0:w]
      return image

    modcrop将图片的长和宽都变成了3的倍数

    def preprocess(path, scale=3):
      # 对路径下的image裁剪成scale整数倍,再对image缩小1/scale倍后,放大scale倍以得到低分辨率图input_,调整尺寸后的image为高分辨率图label_
      # image = imread(path, is_grayscale=True)
      # label_ = modcrop(image, scale)
      scale -= 1
      image = scipy.misc.imread(path, mode='YCbCr').astype(np.float)
      image = modcrop(image, scale)
      label_ = image[:, :, 0]
      # 标准化
      image = image / 255.
      label_ = label_ / 255.
      input_ = scipy.ndimage.interpolation.zoom(label_, (1. / (scale)), mode='wrap', prefilter=False)  # order=4  mode='wrap',
      input_ = scipy.ndimage.interpolation.zoom(input_, ((scale) / 1.), mode='wrap', prefilter=False)
      label_small = modcrop_small(label_)  # 把原图裁剪成和输出一样的大小
      input_small = modcrop_small(input_)  # 把原图裁剪成和输出一样的大小
      imsave(input_small, "/home/chengcongyue/PycharmProjects/SRCNN5/bicubic.bmp")  # 保存插值图像
      imsave(label_small, "/home/chengcongyue/PycharmProjects/SRCNN5/sample/origin.bmp")  # 保存原始图像
      imsave(input_, "/home/chengcongyue/PycharmProjects/SRCNN5/sample/input_.bmp")  # 保存input_图像
      imsave(label_, "/home/chengcongyue/PycharmProjects/SRCNN5/sample/label_.bmp")  # 保存label_图像
      return input_, label_

    这个方法分步介绍
    传入图片的路径

    scale -= 1
    image = scipy.misc.imread(path, mode='YCbCr').astype(np.float)
    # 执行完这一步之后,传入的图片就是矩阵了
    image = modcrop(image, scale)# 裁剪
    label_ = image[:, :, 0]# label_就是我们的清晰图片,因为一会我们要将image模糊
       image = image / 255.
      label_ = label_ / 255.

    图像的归一化,标准化

    input_ = scipy.ndimage.interpolation.zoom(label_, (1. / (scale)), mode='wrap', prefilter=False)  # order=4  mode='wrap',
    input_ = scipy.ndimage.interpolation.zoom(input_, ((scale) / 1.), mode='wrap', prefilter=False)

    双三次插值

    label_small = modcrop_small(label_)  # 把原图裁剪成和输出一样的大小
    input_small = modcrop_small(input_)  # 把原图裁剪成和输出一样的大小

    这里是为了一会进行性能的测试,psnr

      imsave(input_small, "/home/chengcongyue/PycharmProjects/SRCNN5/sample/bicubic.bmp")  # 保存插值图像
      imsave(label_small, "/home/chengcongyue/PycharmProjects/SRCNN5/sample/origin.bmp")  # 保存原始图像
      imsave(input_, "/home/chengcongyue/PycharmProjects/SRCNN5/sample/input_.bmp")  # 保存input_图像
      imsave(label_, "/home/chengcongyue/PycharmProjects/SRCNN5/sample/label_.bmp")  # 保存label_图像

    最后保存这些图片,一会bicubic.bmp,origin.bmp都是要进行比较的,所以要保证图片大小是一致的

    切割图片

    对于训练和测试的两种情况不同

    def input_setup(sess, config):
      # global nx#后加
      # global ny#后加
      # 读图像集,制作子图并保存为h5文件格式
      # 读取数据路径
      if config.is_train:
          data = prepare_data(sess, dataset="Train")
          print(len(data))  #
      else:
          data = prepare_data(sess, dataset="Test")
          print(len(data))  #

    首先就是根据配置,选择读取的文件夹,data就是字符串数组,数组元素对应的就是图片的路径字符串

      sub_input_sequence = []
      sub_label_sequence = []

    存储子图片

      padding = abs(config.image_size - config.label_size) // 2  # 6
      # padding=0;#修改padding值,测试效果

    input和label的裁剪方式是不同的,input每一次循环是3333的方式截取了,label是每一次循环按照2121的方式截取的

      # 训练
      if config.is_train:
          for i in range(len(data)):  # 一幅图作为一个data
              input_, label_ = preprocess(data[i], config.scale)  # 得到data[]的LR和HR图input_和label_
              if len(input_.shape) == 3:
                  h, w, _ = input_.shape
              else:
                  h, w = input_.shape
              # 把input_和label_分割成若干自图sub_input和sub_label
              for x in range(0, h - config.image_size + 1, config.stride):
                  for y in range(0, w - config.image_size + 1, config.stride):
                      sub_input = input_[x:x + config.image_size, y:y + config.image_size]  # [33 x 33]
                      sub_label = label_[x + padding:x + padding + config.label_size,
                                  y + padding:y + padding + config.label_size]  # [21 x 21]
                      sub_input = sub_input.reshape(
                          [config.image_size, config.image_size, 1])  # 按image size大小重排 因此 imgae_size应为33 而label_size应为21
                      sub_label = sub_label.reshape([config.label_size, config.label_size, 1])
                      sub_input_sequence.append(sub_input)  # 在sub_input_sequence末尾加sub_input中元素 但考虑为空
                      sub_label_sequence.append(sb_label)

    对于每一个图片路径就是一个循环,根据这个图片路径,生成一个预处理过的图片,得到它的input_,lable_,也就是模糊的和清晰的,然后按照步长14来生成子图,核心语句

    sub_input = input_[x:x + config.image_size, y:y + config.image_size]  # [33 x 33]
                      sub_label = label_[x + padding:x + padding + config.label_size,
                                  y + padding:y + padding + config.label_size]  # [21 x 21]
    else:
          # 测试
          input_, label_ = preprocess(data[3], config.scale)  # 测试图片
          if len(input_.shape) == 3:
              h, w, _ = input_.shape
          else:
              h, w = input_.shape
          nx = 0  # 后注释
          ny = 0  # 后注释
          # 自图需要进行合并操作
          for x in range(0, h - config.image_size + 1, config.stride):  # x从0到h-33+1 步长stride(21)
              nx += 1
              ny = 0
              for y in range(0, w - config.image_size + 1, config.stride):  # y从0到w-33+1 步长stride(21)
                  ny += 1
                  # 分块sub_input=input_[x:x+33,y:y+33]  sub_label=label_[x+6,x+6+21, y+6,y+6+21]
                  sub_input = input_[x:x + config.image_size, y:y + config.image_size]  # [33 x 33]
                  sub_label = label_[x + padding:x + padding + config.label_size,
                              y + padding:y + padding + config.label_size]  # [21 x 21]
                  sub_input = sub_input.reshape([config.image_size, config.image_size, 1])
                  sub_label = sub_label.reshape([config.label_size, config.label_size, 1])
                  sub_input_sequence.append(sub_input)
                  sub_label_sequence.append(sub_label)

    对于测试来说,我们要将测试的图片按照步长21进行划分,仍然是3333和2121的子块,不过现在我们需要记录nx和ny,相当于是子块的数量,坐标

           # 上面的部分和训练是一样的
      arrdata = np.asarray(sub_input_sequence)  # [?, 33, 33, 1]
      arrlabel = np.asarray(sub_label_sequence)  # [?, 21, 21, 1]
      make_data(sess, arrdata, arrlabel)  # 存成h5格式
      if not config.is_train:  # 后注释
          return nx, ny  # 后注释

    图片处理的总结

    图片分为三个步骤,依次为

    读入图片,预处理图片,分割图片

    建立模型

    model.py

    构造方法

    我们创建了一个SRCNN的类,它的构造方法如下

      def __init__(self,
                   sess,
                   image_size=33,
                   label_size=21,
                   batch_size=64,
                   c_dim=1,
                   checkpoint_dir=None,
                   sample_dir=None):
          self.sess = sess#操作的tensorflow
          self.is_grayscale = (c_dim == 1)# 默认是但颜色通道
          self.image_size = image_size# 33
          self.label_size = label_size# 21
          self.batch_size = batch_size# 默认是128
          self.c_dim = c_dim#颜色通道,1
          self.checkpoint_dir = checkpoint_dir
          self.sample_dir = sample_dir
          self.build_model()

    build_model()方法

    # 搭建网络
      def build_model(self):
          self.images = tf.placeholder(tf.float32, [None, self.image_size, self.image_size, self.c_dim], name='images')
          self.labels = tf.placeholder(tf.float32, [None, self.label_size, self.label_size, self.c_dim], name='labels')
          # 第一层CNN:对输入图片的特征提取。(9 x 9 x 64卷积核)
          # 第二层CNN:对第一层提取的特征的非线性映射(1 x 1 x 32卷积核)
          # 第三层CNN:对映射后的特征进行重建,生成高分辨率图像(5 x 5 x 1卷积核)
          # 权重
          self.weights = {
              # 论文中为提高训练速度的设置 n1=32 n2=16
              'w1': tf.Variable(tf.random_normal([9, 9, 1, 64], stddev=1e-3), name='w1'),
              'w2': tf.Variable(tf.random_normal([1, 1, 64, 32], stddev=1e-3), name='w2'),
              'w3': tf.Variable(tf.random_normal([5, 5, 32, 1], stddev=1e-3), name='w3')
          }
          # 偏置
          self.biases = {
              'b1': tf.Variable(tf.zeros([64]), name='b1'),
              'b2': tf.Variable(tf.zeros([32]), name='b2'),
              'b3': tf.Variable(tf.zeros([1]), name='b3')
          }
          self.pred = self.model()
          # 以MSE作为损耗函数
          self.loss = tf.reduce_mean(tf.square(self.labels - self.pred))
          self.saver = tf.train.Saver()

    网络模型

      def model(self):
          conv1 = tf.nn.relu(
              tf.nn.conv2d(self.images, self.weights['w1'], strides=[1, 1, 1, 1], padding='VALID') + self.biases['b1'])
          conv2 = tf.nn.relu(
              tf.nn.conv2d(conv1, self.weights['w2'], strides=[1, 1, 1, 1], padding='VALID') + self.biases['b2'])
          conv3 = tf.nn.conv2d(conv2, self.weights['w3'], strides=[1, 1, 1, 1], padding='VALID') + self.biases['b3']
          return conv3

    保存checkpoint

      def save(self, checkpoint_dir, step):
          model_name = "SRCNN.model"
          model_dir = "%s_%s" % ("srcnn", self.label_size)
          checkpoint_dir = os.path.join(checkpoint_dir, model_dir)  # 再一次确定路径为 checkpoint->srcnn_21下
          if not os.path.exists(checkpoint_dir):
              os.makedirs(checkpoint_dir)
          self.saver.save(self.sess,
                          os.path.join(checkpoint_dir, model_name),  # 文件名为SRCNN.model-迭代次数
                          global_step=step)
    
      def load(self, checkpoint_dir):
          print(" [*] Reading checkpoints...")
          model_dir = "%s_%s" % ("srcnn", self.label_size)
          checkpoint_dir = os.path.join(checkpoint_dir, model_dir)  # 路径为checkpoint->srcnn_labelsize(21)
          # 加载路径下的模型(.meta文件保存当前图的结构; .index文件保存当前参数名; .data文件保存当前参数值)
          ckpt = tf.train.get_checkpoint_state(checkpoint_dir)
          if ckpt and ckpt.model_checkpoint_path:
              ckpt_name = os.path.basename(ckpt.model_checkpoint_path)
              self.saver.restore(self.sess, os.path.join(checkpoint_dir,
                                                         ckpt_name))  # saver.restore()函数给出model.-n路径后会自动寻找参数名-值文件进行加载
              return True
          else:
              return False

    训练和测试

    # 主函数调用(训练或测试)
      def train(self, config):
          if config.is_train:  # 判断是否为训练(main传入)
              input_setup(self.sess, config)
          else:
              nx, ny = input_setup(self.sess, config)

    完成上面的操作,训练或者是测试的图片就已经形成h5文件了

          # 训练为checkpoint下train.h5
          # 测试为checkpoint下test.h5
          if config.is_train:
              data_dir = os.path.join('./{}'.format(config.checkpoint_dir), "train.h5")
          else:
              data_dir = os.path.join('./{}'.format(config.checkpoint_dir), "test.h5")

    获取到h5文件的路径

          train_data, train_label = read_data(data_dir)  # 读取.h5文件(由测试和训练决定)

    根据路径就可以得到h5文件中的train_data,train_lable,也就是模糊的,和清晰的

          global_step = tf.Variable(0)  # 定义global_step 它会自动+1
          # 通过exponential_decay函数生成学习率
          learning_rate_exp = tf.train.exponential_decay(config.learning_rate, global_step, 1480, 0.98,
                                                         staircase=True)  # 每1个Epoch 学习率*0.98
          # 标准反向传播的随机梯度下降
          self.train_op = tf.train.GradientDescentOptimizer(learning_rate_exp).minimize(self.loss,
                                                                                        global_step=global_step)
          tf.global_variables_initializer().run()  
          counter = 0
          start_time = time.time()
          if self.load(self.checkpoint_dir):
              print(" [*] Load SUCCESS")
          else:
              print(" [!] Load failed...")

    这里可以读取系数文件,可以在系数文件上继续进行训练

          # 训练
          if config.is_train:
              print("Training...")
              for ep in range(config.epoch):  # 迭代次数的循环
                  # 以batch为单元
                  batch_idxs = len(train_data) // config.batch_size
                  for idx in range(0, batch_idxs):
                      batch_images = train_data[idx * config.batch_size: (idx + 1) * config.batch_size]
                      batch_labels = train_label[idx * config.batch_size: (idx + 1) * config.batch_size]
                      counter += 1
                      _, err = self.sess.run([self.train_op, self.loss],
                                             feed_dict={self.images: batch_images, self.labels: batch_labels})
                      if counter % 10 == 0:  # 10的倍数step显示
                          print("Epoch: [%2d], step: [%2d], time: [%4.4f], loss: [%.8f]" \
                                % ((ep + 1), counter, time.time() - start_time, err))
                      if counter % 500 == 0:  # 500的倍数step存储
                          self.save(config.checkpoint_dir, counter)

    对于每一次的迭代,还要使用batch细化,每一次的batch就会取一个图片,对模型进行训练,每500次就存储一下文件

          # 测试
          else:
              print("Testing...")
              result = self.pred.eval({self.images: train_data, self.labels: train_label})  # 从test.h中来
              result = merge(result, [nx, ny])
              result = result.squeeze()  # 除去size为1的维度
              # result= exposure.adjust_gamma(result, 1.07)#调暗一些
              image_path = os.path.join(os.getcwd(), config.sample_dir)
              image_path = os.path.join(image_path, "MySRCNN.bmp")
              imsave(result, image_path)

    测试的话,将需要测试的内容,这个时候已经是h5文件了,传入到模型中,也就是self.pred.eval。。。,得到结果,拼接结果,最后存储图片

    运行结果

    当前迭代次数为2550000次,先使用论文中的图片进行验证,验证的标准为PSNR,tensorflow也内置了它的实现方式
    图片说明
    运行的过程
    对一下的图片进行测试

    论文提供的图片测试

    图片说明
    分别对婴儿,鸟,蝴蝶,头,女人进行测试,需要说明的是,我们是在YCbCr中的Y进行测试的,最后展示的都是灰色的图片

    图片说明
    从左到右分别是模糊的(双三次插值),超分辨率重建的,原图,效果还是十分明显的,为了进行性能指标的测试,我们将原图裁剪的和SRCNN的图片一样的大小,它的PSNR如下

    import cv2 as cv
    import tensorflow as tf
    def psnr(tf_img1, tf_img2):
      return tf.image.psnr(tf_img1, tf_img2, max_val=255)
    img_input2 = cv.imread("./sample/MySRCNN.bmp")
    img_input = cv.imread("./sample/origin.bmp")
    with tf.Session() as sess:
     sess.run(tf.global_variables_initializer())
     x=sess.run(psnr(img_input,img_input2))
     #y=sess.run(psnr(img_input,img_input3))
     print(x)
     #print(y)

    以上是测试的代码,运行的结果
    图片说明
    效果已经十分明显了,达到了33,然后按照相同的方法测试剩下的图片

    婴儿

    图片说明
    psnr为
    图片说明

    蝴蝶

    图片说明
    psnr
    图片说明

    女人

    图片说明
    psnr
    图片说明

    图片说明
    psnr
    图片说明

    总结

    对于5张论文的图的测试,已经初步的体现了SRCNN的重建效果,对于婴儿,头,鸟都有着不错的复原效果(psnr都达到了30以上),对于女人和婴儿,从视觉上看效果还是比较明显的,但是psnr没有超过30,模型需要继续迭代

    自己的图片测试

    图片说明
    我们比较前两张图片
    图片说明
    复原效果十分明显,psnr如下
    图片说明
    也达到了34,复原效果比较明显