深度学习应用“Zero Shot”超分辨率重构图像
超分辨率(SR)是一种提高图像分辨率的技术。
这些方法是从低分辨率(LR)图像中获得高分辨率(HR)输出。执行单图像超分辨率(SISR)的目的是在增加图像尺寸的同时使其质量下降的最小。应用范围很广,如医学成像,压缩,农业分析,自动驾驶到卫星图像、侦察等等。
超分辨率领域正在经历一段复兴时期。卷积神经网络和生成性对抗网络等深度学习模型的最新进展引发了各种新方法,并为基于特征工程的经典方法无法解决的问题带来了最新的研究成果。
在本文中,我们将讨论该领域的主要方法,并将深入探讨一种名为Zero Shot Super Resolution的特殊方法。
图1. ZSSR VS EDSR性能比较
目前,在深度学习领域SR主要有两种方法。
第一种方法:使用各种卷积神经网络,最好使用跳过连接(深度神经网络),并尝试将L1或L2损失最小化,从低分辨率对重建高分辨率图像。
第二种方法:生成性对抗神经网络,也称为GAN。
可以训练GAN架构以生成类似于特定数据集的分布。GAN拥有两个主要部分:生成器和判别器。
生成器学习如何从随机噪声中创建数据集分布,判别器学习如何区分数据集中的实际样本与从生成器合成创建的样本。通过以对抗性方式将它们一起训练,每个部分都经过迭代改进,最终的结果是得到一个强大的样本生成器和一个用于真实与合成样本的强大的判别器。
例如:通过向GAN提供MNIST数据集,其生成器学习如何创建手写数字,并且其判别器学习如何区分真实和合成数字。
图2.手写数字(MNIST)的GAN示意图
我们采用这种架构并将GAN/对抗性损失降至最低。
SR的GAN模型的生成器部分通常使用L2损失(也称为MSE或均方误差)或更现代的感知损失,即均方误差(MSE),但是其来自于预先在Imagenet上训练的更深层的模型(通常是VGG-16或VGG-19)作为重构损失。预训练模型的输出为我们提供了高质量的特征,而无需任何耗时的特征工程。我们比较了网络输出的特征图和HR图像。
所有这些都归结为这样一个事实,即大多数超分辨率系统的核心都有L1或L2指标。这是有道理的,因为图像重建的标准指标是PSNR(峰值信噪比)具有内置的MSE(L2损失):
PSNR = 10 * log10((data_range ** 2)/ mse),在sci-kit图像中所定义。
Data_range是数据类型中的像素范围。通常为255或1。
MSE的值越低意味着误差越小,从MSE和PSNR之间的反比关系可以看出,MSE的值越小,PSNR值越大。从逻辑上讲,PSNR值越高越好,因为它意味着信噪比更高。
图3.创建高分辨率图像
限制
在Technion研究员Yochai Blau 最近的一篇论文中,经研究证据被证实表明这两种方法都受到内在权衡特性的约束。它的效果是显而易见的,因为现有的许多SR方法难以同时提高噪声(失真)和感知(质量)指标的性能。简单来说,超分辨率图像出现得越真实,它所具有的人造噪声或伪影越多,并且超分辨率图像中的失真和噪声越少,图像看起来就越模糊。
图4.失真和感知权衡
目前的超分辨率(SR)方法的问题在于它们中的大多数都受到监督。这意味着通过对成对数据集的优化来学习算法参数。通过将低分辨率图像重建为其高分辨率图像时的损失函数最小化。
虽然已经有人进行了一些尝试来生成具有本质上不同分辨率特征的数码相机拍摄的数据集,但大多数数据集是通过使用插值方法(通常是双三次)对图像进行下采样而生成的。这可能是非常有效且经济的,但这也意味着大多数数据集不包括室外LR图像。这具有非常深刻的含义,综合创建的数据集并不代表真正的伪影和常见的低级图像现象。这导致SR模型无法学习如何对抗源于光学或数字问题的缺陷,而主要是通过综合创建低分辨率(LR)图像而生成的伪影。最麻烦的是模型可能只学会逆转下采样机制本身。
要学习图像的超分辨率,首先要使用GAN学习如何进行图像退化来试图解决这种成对数据创建的错误方法。
图5.低质量图像放大
Zero Shot Super-Resolution(Zssr)
在Weizmann研究所的Assaf Shocher撰写的论文中,我们发现了一种新颖,简单而优雅的方法来实现单图像超分辨率。他们使用小型(~100K可训练参数)全卷积神经网络(FCN ),而不是在大型数据集上训练大型网络(EDSR ~43M参数),并对目标数据本身及其许多扩展进行训练。以生成最终更高分辨率输出。它们通过先创建原始图像的缩小副本,并对其进行裁剪、翻转和旋转,从而动态生成数据增强,并生成LR-HR对。然后,他们模拟一个特定的预定义的超分辨率比例,通过先向下采样,然后向上采样,将增强效果模糊到原来的大小(等于其父级HR的大小)。导致模糊和非模糊的一对增强,两者都从原始图像按相同的随机因素缩小比例,子级LR模糊的方式模拟预先确定的比例。
图6是我们的ZSSR版本。子级LR图像经过FCNN并最终添加到其未更改的副本(输出= LR + FCN(LR))。然后我们计算关于父级HR输出的L1损失:
然后将这些对输入到FCN网络进行训练,目的是将LR图像重建的L1损失降到最小,使其与HR匹配。使用具有ReLU激活函数,深度为8层和64个滤波器的神经网络的简单架构(对于所有层,只有最后一个是线性的)。设置从输入到输出的skip connection,因此我们只需要学习从模糊LR到HR源的残差图像。
图6. ZSSR算法的图示。
原始图像I被许多不同的比例因子下采样。每一个缩小的副本都是通过先缩小再放大来模糊的。通过比较网络输出的f(LR)和匹配的HR,我们可以训练出一个LR-HR对。
最后,我们在原始图像上进行测试(model.predict)以生成超分辨率输出。
存在真实高质量图像的情况下,可以使用PSNR和SSIM指标将其与网络输出进行比较。
关于这种架构的事实是,我们在没有验证集的情况下进行训练,并且只在原始样本上进行测试。虽然一开始这可能有点违反直觉,但它符合我们的目标,并明显减少了运行时间。
神经网络学习重新缩放(或映射)函数:
关于全卷积神经网络(FCN)的另一个重要事实是我们可以使用不同的输入大小。因此,每组样本具有不同的大小。通过仔细选择正确地超参数:内核大小为3 * 3,步幅为1和填充'same',我们得到了与输入大小完全相同的输出大小,使我们能够计算它们的相对误差。我们只通过改变双三次插值来改进这个体系结构,调整大小的因素本身就是一个预定义的参数。
对这种架构的科学解释是在图像中存在重复的内部特征。在Weizmann研究所的同一组研究中显示,尺寸为5 * 5和7 * 7的小图像块在不同的位置(在一张图像中)重复它们的原始大小和跨尺度。在同一组的另一项工作中研究显示,单个图像与大图像数据集相比具有较低的内部熵。这与内部图像块和自相似性原则是一致的。针对所讨论的特定图像的神经网络进行训练,然后在其上进行测试。
图7. CSI 提高SOTA。
尽管神经网络接受域小,但神经网络能够捕获图像中对象的非局部递归。这源于特定图像的训练过程。通过使学习独立于图像大小来进一步加速学习。这是通过合并裁剪机制实现的。只从每个图像对中获取固定大小的裁剪,不过这个超参数会对运行时间和性能产生很大影响。
特定于图像的神经网络可能听起来是一个笨拙的解决方案,但现实是,尽管监督SISR方法可以得到令人印象深刻的结果,它们的性能在现实生活中的低质量图像上大大减少。学习如何对抗真实的噪声和人为干预需要创建一个非常深的神经网络,每个神经网络都经过训练并专门针对特定的问题进行处理,并且针对该特定任务需要数天甚至数周的处理。
Zero Shot Super resolution确实优于大型监督SISR模型,在具有噪声,压缩、伪影等影响较大的低分辨率图像。诸如降尺度内核,比例因子附加,噪声水平及其在训练增强上的类型等参数可供选择,从而能够更好地适应图像的特征(添加噪声有助于改善低质量LR图像的效果)。选择这些参数需要重新训练,因此不适合大型架构。
现在,我们看一下代码,讨论它是如何工作的,并强调修改它以供将来实验的方法。我们将突出强调main.py文件中的特定代码块,以更好地解释底层的内容。我们将深入研究代码实现并解释主要函数和评估指标。
导入必要的库
import argparse import numpy as np import cv2 import os import keras from keras.callbacks import LearningRateScheduler, ModelCheckpoint from keras.models import Model from keras.layers import Conv2D, Input from skimage.measure import compare_ssim as ssim from skimage.measure import compare_psnr import glob import missinglink
这里我么还使用了包括NumPy,cv2,Keras和MissingLink , MissingLink.ai SDK,它是深度学习工作流程自动化的平台,SDK通过消除管理大量实验,大型数据集,本地和外部机器以及代码版本控制的痛苦,实现了复杂深度学习模型的快速高效开发。我们将利用它来跟踪我们实验的实时值,然后,对于更高级的用户,我们将深入研究MissingLink的数据管理功能。
主程序编译
在主程序内部,我们设置了一个参数解析器,用于从CLI配置参数,获取输出目录和目标映像的路径,然后加载它。我们还将TensorFlow定义为后端,并将图像维度排序为通道最后。不同的Keras后端库(TensorFlow,Theano)以相反的方式设置这些,即通道首先分别对应于通道。通过确保设置是这样的,我们可以避免错误来为我们的神经网络提供错误的数据形状。
# main if __name__ == '__main__': np.random.seed(0) if keras.backend == 'tensorflow': keras.backend.set_image_dim_ordering('tf') # Provide an alternative to provide MissingLinkAI credential parser = argparse.ArgumentParser() parser.add_argument('--srFactor', type=int) parser.add_argument('--epochs', type=int, default=EPOCHS) #parser.add_argument('--filepath', type=str) parser.add_argument('--subdir', type=str, default='067') parser.add_argument('--filters', type=int, default=FILTERS) parser.add_argument('--activation', default=ACTIVATION) parser.add_argument('--shuffle', default=SHUFFLE) parser.add_argument('--batch', type=int, default=BATCH_SIZE) parser.add_argument('--layers', type=int, default=LAYERS_NUM) parser.add_argument('--sortOrder', default=SORT_ORDER) parser.add_argument('--scalingSteps', type=int, default=NB_SCALING_STEPS) parser.add_argument('--groundTruth', default=GROUND_TRUTH) parser.add_argument('--baseline', default=BASELINE) parser.add_argument('--flip', default=FLIP_FLAG) parser.add_argument('--noiseFlag', default=False) parser.add_argument('--noiseSTD', type = int, default=30) parser.add_argument('--project') # Override credential values if provided as arguments args = parser.parse_args() #file_name = args.filepath or file_name subdir = args.subdir SR_FACTOR = args.srFactor or SR_FACTOR FILTERS = args.filters or FILTERS EPOCHS = args.epochs or EPOCHS ACTIVATION = args.activation or ACTIVATION SHUFFLE = args.shuffle or SHUFFLE BATCH_SIZE = args.batch or BATCH_SIZE LAYERS_NUM = args.layers or LAYERS_NUM SORT_ORDER = args.sortOrder or SORT_ORDER NB_SCALING_STEPS = args.scalingSteps or NB_SCALING_STEPS GROUND_TRUTH = args.groundTruth or GROUND_TRUTH BASELINE = args.baseline or BASELINE FLIP_FLAG = args.flip or FLIP_FLAG NOISE_FLAG = args.noiseFlag or NOISE_FLAG NOISY_PIXELS_STD = args.noiseSTD or NOISY_PIXELS_STD # We're making sure These parameters are equal, in case of an update from the parser. NB_PAIRS = EPOCHS EPOCHS_DROP = np.ceil((NB_STEPS * EPOCHS) / NB_SCALING_STEPS) psnr_score = None ssim_score = None metrics_ratio = None # Path for Data and Output directories on Docker # save to disk if os.environ.get('ML'): output_paths = '/output' else: output_paths = os.path.join(os.getcwd(), 'output') if not os.path.exists(output_paths): os.makedirs(output_paths) print (output_paths) # output_paths = select_first_dir('/output', './output') # if (output_paths == './output'): # mk_dir(dir_name='./output') file_name = select_file(ORIGIN_IMAGE, subdir) print(file_name) # Load image from data volumes image = load_img(file_name) cv2.imwrite(output_paths + '/' + 'image.png', cv2.cvtColor(image, cv2.COLOR_RGB2BGR), params=[CV_IMWRITE_PNG_COMPRESSION])
构建模型
我们使用Keras函数模型API创建模型。这使我们能够创建比常见的Sequential模型更复杂的架构。
build_model()
build_model() 函数是我们定义和编译模型的地方。
def build_model(): # model filters = FILTERS # 64 kernel_size = 3 # Highly important to keep image size the same through layer strides = 1 # Highly important to keep image size the same through layer padding = "same" # Highly important to keep image size the same through layer inp = Input(shape=(None, None, NB_CHANNELS)) # seq_model = Sequential() z = (Conv2D( filters=NB_CHANNELS, kernel_size=kernel_size, activation="relu", padding=padding, strides=strides, input_shape=(None, None, NB_CHANNELS) ))(inp) # layer 1 # Create inner Conv Layers for layer in range(LAYERS_NUM): z = (Conv2D(filters=filters, kernel_size=kernel_size, strides=strides, padding=padding, activation=ACTIVATION))( z) z = (Conv2D(filters=NB_CHANNELS, kernel_size=kernel_size, strides=strides, padding=padding, activation="linear"))( z) # 8 - last layer - no relu # Residual layer out = keras.layers.add([z, inp]) # FCN Model with residual connection zssr = Model(inputs=inp, outputs=out) # acc is not a good metric for this task* # compile model zssr.compile(loss='mae', optimizer='adam') # Model summary zssr.summary() # Plot model # from keras.utils import plot_model # plot_model(zssr, to_file = output_paths + 'zssr.png') return zssr
我们定义了模型参数,以使输入和输出大小保持相等。然后,我们创建从输入层到输出层的skip connection。这可以被认为是残差块。有些工作结合了其中的一些(更复杂的skip connection)来创建更复杂的模型。大多数参数都配置为变量,因此可以通过多种方式轻松更改模型。
PSNR和SSIM
PSNR和SSIM是常见的超分辨率和重构(压缩)指标。如果有真实的图像,我们将使用PSNR和SSIM进行评估。
psnr_calc(img1,img2,scaling_fact)
PSNR—峰值信噪比,是一种评价图像的客观标准,结果越高越好。这是在psnr_calc()函数中处理的。
SSIM—结构相似性,是一种衡量两幅图像相似度的指标,结构相似性的范围为0到1 。当两张图像一模一样时,SSIM的值等于1。
def ssim_calc(img1, img2, scaling_fact): # img1 = cv2.resize(img1,None,fx=scaling_fact,fy=scaling_fact, interpolation=cv2.INTER_CUBIC) ssim_sk = ssim(img1, img2, data_range=img1.max() - img1.min(), multichannel=True) print("SSIM:", ssim_sk) return ssim_sk def psnr_calc(img1, img2, scaling_fact): # Get psnr measure from skimage lib PIXEL_MAX = 255.0 PIXEL_MIN = 0.0 sk_psnr = compare_psnr(img1, img2, data_range=PIXEL_MAX - PIXEL_MIN) print("PSNR:", sk_psnr) return sk_psnr
结构信息是指像素间具有很强的相互依赖性,特别是当它们在空间上很接近时。这些依赖关系包含了关于可视场景中对象结构的重要信息。 亮度遮掩是一种现象,在这种情况下,图像失真往往在明亮区域中不太明显,对比度遮掩也是一种现象,在图像中存在显着活动或“纹理”的地方,失真不是很显眼。
metric_results(ground_truth_image,super_image)
在调用度量计算函数之前,metric_results函数只是简单地检查是否有用于比较的真实图像。
def metric_results(ground_truth_image, super_image): try: ground_truth_image psnr_score = psnr_calc(ground_truth_image, super_image, SR_FACTOR) ssim_score = ssim_calc(ground_truth_image, super_image, SR_FACTOR) except NameError: psnr_score = None ssim_score = None return psnr_score, ssim_score
predict_func(image)
通过使用predict_func()函数,我们在原始图像上训练的模型得到了超分辨率图像。这里的主要技巧是我们首先需要将原始图像插值到目标大小中,这意味着原始大小乘以sr因子。例如,对于一个200 * 100的图像和一个sr因子为2的图像,我们将得到尺寸为400 * 200的插值图像。我们使用model.predict()函数对这个插值图像进行处理,得到一个超分辨率结果。事实上,神经网络只是提高了插值图像的质量。
def predict_func(image): # Resize original image to super res size interpolated_image = cv2.resize(image, None, fx=SR_FACTOR, fy=SR_FACTOR, interpolation=cv2.INTER_CUBIC) # SR_FACTOR # Expand dims for NN interpolated_image = np.expand_dims(interpolated_image, axis=0) # Expand dims for NN # Check if image is a 4D tensor if len(np.shape(interpolated_image)) == 4: pass else: interpolated_image = np.expand_dims(interpolated_image, axis=0) # Get prediction from NN super_image = zssr.predict(interpolated_image) # Reduce the unwanted dimension and get image from tensor super_image = np.squeeze(super_image, axis=(0)) interpolated_image = np.squeeze(interpolated_image, axis=(0)) # Normalize data type back to uint8 super_image = cv2.convertScaleAbs(super_image) interpolated_image = cv2.convertScaleAbs(interpolated_image) # Save super res image cv2.imwrite(output_paths + '/' + str(SR_FACTOR) + '_super.png', cv2.cvtColor(super_image, cv2.COLOR_RGB2BGR), params=[CV_IMWRITE_PNG_COMPRESSION]) # Save bi-cubic enlarged image cv2.imwrite(output_paths + '/' + str(SR_FACTOR) + '_super_size_interpolated.png', cv2.cvtColor(interpolated_image, cv2.COLOR_RGB2BGR), params=[CV_IMWRITE_PNG_COMPRESSION]) return super_image, interpolated_image
Accumulated Result(累积结果)
这里我们使用的是另一种方法,使用model.predict()函数,对插值图像的所有8种可能的排列(4次旋转和2次翻转),然后获取累积结果的中值(每个像素)。为此,我们使用accumulated_result()函数。
def accumulated_result(image): # Resize original image to super res size int_image = cv2.resize(image, None, fx=SR_FACTOR, fy=SR_FACTOR, interpolation=cv2.INTER_CUBIC) # SR_FACTOR print("NN Input shape:", np.shape(int_image)) super_image_accumulated = np.zeros(np.shape(int_image)) # Get super res image from the NN's output super_image_list = [] for k in range(0, 8): print("k", k) img = np.rot90(int_image, k, axes=(0, 1)) if (k > 3): print("flip") img = np.fliplr(img) # Expand dims for NN img = np.expand_dims(img, axis=0) super_img = zssr.predict(img) super_img = np.squeeze(super_img, axis=(0)) super_img = cv2.convertScaleAbs(super_img) # images should be UN-Rotated before added together # Make sure values are not threashold # First unflip and only then un-rotate to get the wanted result if (k > 3): print("unflip") super_img = np.fliplr(super_img) super_img = np.rot90(super_img, -k, axes=(0, 1)) super_image_list.append(super_img) super_image_accumulated = super_image_accumulated + super_img super_image_accumulated_avg = np.divide(super_image_accumulated, 8) # Normalize data type back to uint8 super_image_accumulated_avg = cv2.convertScaleAbs(super_image_accumulated_avg) cv2.imwrite(output_paths + '/' + str(SR_FACTOR) + '_super_image_accumulated_avg.png', cv2.cvtColor(super_image_accumulated_avg, cv2.COLOR_RGB2BGR), params=[CV_IMWRITE_PNG_COMPRESSION]) super_image_accumulated_median = np.median(super_image_list, axis=0) #### supsup = cv2.cvtColor(supsup, cv2.COLOR_RGB2BGR) super_image_accumulated_median = cv2.convertScaleAbs(super_image_accumulated_median) cv2.imwrite(output_paths + '/' + str(SR_FACTOR) + '_super_image_accumulated_median.png', cv2.cvtColor(super_image_accumulated_median, cv2.COLOR_RGB2BGR), params=[CV_IMWRITE_PNG_COMPRESSION]) return super_image_accumulated_median, super_image_accumulated_avg
需要注意的一点是将所有超分辨率输出转换回原始状态,即旋转和翻转。我们还使用了一些额外的辅助函数。
add_noise(image)
这个辅助函数允许我们在训练前向LR图像添加高斯噪声。这有助于在低质量的图像上获得更好的结果。
def add_noise(image): row, col, ch = image.shape noise = np.random.normal(0, NOISY_PIXELS_STD, (row, col, ch)) #Check image dtype before adding. noise = noise.astype('float32') # We clip negative values and set them to zero and set values over 255 to it. noisy = np.clip((image + noise), 0, 255) return noisy
当增加大量噪声时,我们甚至可以实现对低SNR图像的去噪。
preprocess(image,scale_fact,scale_fact_int,i)
preprocess()函数是我们控制HR父级使用的不同类型的操作来创建我们的增强对的地方。它需要输入:
- image - 原始图像,
- scale_fact - 我们将图像缩小到LR的因子,
- scale_fact_inter - 我们从HR父级缩小到LR子级的比例因子,
- i - 一个迭代索引,帮助我们跟踪扩充数量。
def preprocess(image, scale_fact, scale_fact_inter, i): # scale down is sthe inverse of the intermediate scaling factor scale_down = 1 / scale_fact_inter # Create hr father by downscaling from the original image hr = cv2.resize(image, None, fx=scale_fact, fy=scale_fact, interpolation=cv2.INTER_CUBIC) # Crop the HR father to reduce computation cost and set the training independent from image size #print("hr before crop:", hr.shape[0], hr.shape[1]) if CROP_FLAG: h_crop = w_crop = np.random.choice(CROP_SIZE) #print("h_crop, w_crop:", h_crop, w_crop) if (hr.shape[0] > h_crop): x0 = np.random.randint(0, hr.shape[0] - h_crop) h = h_crop else: x0 = 0 h = hr.shape[0] if (hr.shape[1] > w_crop): x1 = np.random.randint(0, hr.shape[1] - w_crop) w = w_crop else: x1 = 0 w = hr.shape[1] hr = hr[x0 : x0 + h, x1 : x1 + w] #print("hr body:", x0, x0 + h, x1, x1 + w) #print("hr shape:", hr.shape[0], hr.shape[1]) if FLIP_FLAG: # flip """ TODO check if 4 is correct or if 8 is better. Maybe change to np functions, as in predict permutations.""" # if np.random.choice(4): # flip_type = np.random.choice([1, 0, -1]) # hr = cv2.flip(hr, flip_type) # if np.random.choice(2): # hr = cv2.transpose(hr) k = np.random.choice(8) print(k) hr = np.rot90(hr, k, axes=(0, 1)) if (k > 3): hr = np.fliplr(hr) # lr = cv2.flip( lr, flip_type ) # hr is cropped and flipped then copies as lr # Blur lr son lr = cv2.resize(hr, None, fx=scale_down, fy=scale_down, interpolation=cv2.INTER_CUBIC) # Upsample lr to the same size as hr lr = cv2.resize(lr, (hr.shape[1], hr.shape[0]), interpolation=cv2.INTER_CUBIC) # Add gaussian noise to the downsampled lr if NOISE_FLAG: lr = add_noise(lr) # Save every Nth augmentation to artifacts. if SAVE_AUG and i%50==0: dir_name = os.path.join(output_paths, 'Aug/') # Create target Directory if don't exist mk_dir(dir_name=dir_name) cv2.imwrite(output_paths + '/Aug/' + str(SR_FACTOR) + '_' + str(i) + 'lr.png', cv2.cvtColor(lr, cv2.COLOR_RGB2BGR), params=[CV_IMWRITE_PNG_COMPRESSION]) cv2.imwrite(output_paths + '/Aug/' + str(SR_FACTOR) + '_' + str(i) + 'hr.png', cv2.cvtColor(hr, cv2.COLOR_RGB2BGR), params=[CV_IMWRITE_PNG_COMPRESSION]) # Expand image dimension to 4D Tensors. lr = np.expand_dims(lr, axis=0) hr = np.expand_dims(hr, axis=0) """ For readability. This is an important step to make sure we send the LR images as inputs and the HR images as targets to the NN""" X = lr y = hr return X, y
正如您所看到的,这个函数需要做的第一件事是创建原始图像的scale_down版本,方法是通过使用一个随机因子对其进行下采样来创建原始图像。然后,我们使用旋转和翻转来创建更多的数据差异。从这个操作HR父级,我们通过复制父级创建一个LR子级。然后我们通过下采样(使用SR_FACTOR)将子级模糊,然后将其上采样(使用SR_FACTOR)回到与HR父级相同的大小。这种模糊机制通过我们选择的训练前的某个因子SR来模拟分辨率的差异。
开始训练
我们调用build_model()函数并将返回的模型转换为ZSSR变量。
lrate继承自Keras LearningRateScheduler回调函数,它获取step_decay函数并通过它更新其值。
# Build and compile model zssr = build_model() # Learning rate scheduler lrate = LearningRateScheduler(step_decay) # Callbacklist # checkpoint # filepath = "/output/weights-improvement-{epoch:02d}-{val_acc:.2f}.hdf5" # # checkpoint = ModelCheckpoint(filepath, monitor='loss', verbose=1, save_best_only=True, mode='max') callbacksList = [lrate, missinglink_callback] #, checkpoint # TRAIN history = zssr.fit_generator(image_generator(image, NB_PAIRS, BATCH_SIZE, NB_SCALING_STEPS), steps_per_epoch=NB_STEPS, epochs=EPOCHS, shuffle=SHUFFLE, callbacks=callbacksList, max_queue_size=32, verbose=1) # Saving our model and weights zssr.save(output_paths + '/zssr_model.h5') # PREDICT # Get Super Resolution image by predicting on the original input with the trained NN super_image, interpolated_image = predict_func(image) # Get super resolution images super_image_accumulated_median, super_image_accumulated_avg = accumulated_result(image)
我们通过调用zssr.fit_generator()函数来开始训练,该函数来自自定义image_generator的数据提供。我们将模型保存到Data Management中。然后我们调用predict_func()函数和accumulated_result()函数来获得超分辨率输出。
Ground Truth
在我们拥有真实高分辨率图像的情况下,我们使用我们之前定义的指标来检查我们的结果:PSNR和SSIM。
if GROUND_TRUTH: # Load Ground-Truth image if one exists try: file_path_ground_truth = select_file(GROUND_TRUTH_IMAGE, subdir) if os.path.isfile(file_path_ground_truth): ground_truth_image = load_img(file_path_ground_truth) cv2.imwrite(output_paths + '/' + '_ground_truth_image.png', cv2.cvtColor(ground_truth_image, cv2.COLOR_RGB2BGR), params=[CV_IMWRITE_PNG_COMPRESSION]) print("Interpolated:") metric_results(ground_truth_image, interpolated_image) print("Super image:") psnr_score, ssim_score = metric_results(ground_truth_image, super_image) print("Super_image_accumulated_median") metric_results(ground_truth_image, super_image_accumulated_median) print("Super_image_accumulated_avg") metric_results(ground_truth_image, super_image_accumulated_avg) else: print("No Ground Truth Image") except: pass # In case we have a baseline for comparison such as EDSR we load the baseline-image and compare it with our SR-image if BASELINE: try: file_path_EDSR = select_file(BASELINE_IMAGE, subdir) if os.path.isfile(file_path_EDSR): EDSR_image = load_img(file_path_EDSR) print("EDSR") cv2.imwrite(output_paths + '/' + '_ESDR.png', cv2.cvtColor(EDSR_image, cv2.COLOR_RGB2BGR), params=[CV_IMWRITE_PNG_COMPRESSION]) psnr_score_EDSR, _ = metric_results(ground_truth_image, EDSR_image) if (psnr_score and psnr_score_EDSR) is not None: metrics_ratio = psnr_score / psnr_score_EDSR print("Metrics_ratio: ", metrics_ratio) else: print("No Baseline Image") except: pass # Setting up the experiment_id so we can later create external metrics experiment_id = missinglink_callback.experiment_id model_weights_hash = missinglink_callback.calculate_weights_hash(zssr) metrics = {'psnr': psnr_score, 'SSIM': ssim_score, 'ZSSR_EDSR_psnr_ratio': metrics_ratio} missinglink_callback.update_metrics(metrics, experiment_id=experiment_id)
我们可以使用SAVE_AUG标志保存一些训练样本的例子:
这些是两对hr-father和lr-son的样品。
我们可以看到它们旋转了90度,并且增加了相当多的噪声在lr-son。
如前所述,这样做是为了让网络学习如何修复噪声并增加(放大)数据集。
低分辨率输入(subdir 034)
这是一个简单的插值,SR_Factor为2
这是我们的超分辨率输出:
(训练周期1000,噪声标准差设为30)
噪音和颗粒的显著减少
这是清晰的真实图像
真实图像
参数调整
让我们设置一些有用的参数。这些是我们在main()之外设置的全局参数。通过调整它们的值,我们可以显著地改变架构和算法的行为。
# scaling factor SR_FACTOR = 2 # Activation layer ACTIVATION = 'relu' # Data generator random ordered SHUFFLE = False # scaling factors array order random or sorted SORT = True # Ascending or Descending: 'A' or 'D' SORT_ORDER = 'A' # number of time steps (pairs) per epoch NB_STEPS = 1 # Batch size BATCH_SIZE = 1 # Number of channels in signal NB_CHANNELS = 3 # No. of NN filters per layer FILTERS = 64 # 64 on the paper # Number of internal convolutional layers LAYERS_NUM = 6 # No. of scaling steps. 6 is best value from paper. NB_SCALING_STEPS = 1 # No. of LR_HR pairs EPOCHS = NB_PAIRS = 1500 # Default crop size (in the paper: 128*128*3) CROP_SIZE = [96]#[32,64,96,128] # Momentum # default is 0.9 # 0.86 seems to give lowest loss *tested from 0.85-0.95 BETA1 = 0.90 # 0.86 # Adaptive learning rate INITIAL_LRATE = 0.001 DROP = 0.5 # Adaptive lrate, Number of learning rate steps (as in paper) FIVE = 5 # Decide if learning rate should drop in cyclic periods. LEARNING_RATE_CYCLES = False # # EPOCHS_DROP = np.ceil((NB_STEPS * EPOCHS ) / NB_SCALING_STEPS) # Plot super resolution image when using zssr.predict PLOT_FLAG = False # Crop image for training CROP_FLAG = True # Flip flag FLIP_FLAG = True # initial scaling bias (org to fathers) SCALING_BIAS = 1 # Scaling factors - blurring parameters BLUR_LOW = 0.4 BLUR_HIGH = 0.95 # Add noise or not to transformations NOISE_FLAG = False # Mean pixel noise added to lr sons NOISY_PIXELS_STD = 30 # Save augmentations SAVE_AUG = True # If there's a ground truth image. Add to parse. GROUND_TRUTH = True # If there's a baseline image. Add to parse. BASELINE = True # png compression ratio: best quality CV_IMWRITE_PNG_COMPRESSION = 9 ORIGIN_IMAGE = 0 GROUND_TRUTH_IMAGE = 1 BASELINE_IMAGE = 2
结论
模型的图像特异性需要对每个图像进行调整以获得最佳结果 , 这可以通过手动或通过短脚本自动完成, 所有重要的超参数,如训练周期、滤波器和噪声的调整都可以的到更优异的图像。该模型在许多样本上取得了比现有EDSR模型更高的结果。超分辨率仍然是一个开放的问题,虽然许多优秀的论文在过去几年中都是以真实的结果发布,在不添加任何认为干预的情况下创建超分辨率的图像或视频仍然是一个问题。