QCon 演讲火热征集中,快来分享技术实践与洞见! 了解详情
写点什么

TensorFlow 工程实战(五):构建 DeblurGAN 模型,将模糊相片变清晰

  • 2019-08-15
  • 本文字数:10926 字

    阅读完需:约 36 分钟

TensorFlow工程实战(五):构建DeblurGAN模型,将模糊相片变清晰

在拍照时,常常因为手抖或补光不足,导致拍出的照片很模糊。本文将介绍如何利用 DeblurGAN 模型将模糊的照片变清晰。

本文摘选自电子工业出版社出版、李金洪编著的《深度学习之TensorFlow工程化项目实战》一书的实例 54:TensorFlow 构建 DeblurGAN 模型,将模糊相片变清晰。


DeblurGAN 模型是一个对抗神经网络模型,由生成器模型和判别器模型组成。


  • 生成器模型,根据输入的模糊图片模拟生成清晰的图片。

  • 判别器模型,用在训练过程中,帮助生成器模型达到更好的效果。具体可以参考论文

实例描述

有一套街景拍摄的照片数据集,其中包含清晰照片和模糊照片。要求:


(1)用该数据集训练 DeblurGAN 模型,使模型具有将模糊图片转成清晰图片的能力。


(2)DeblurGAN 模型能将数据集之外的模糊照片变清晰。


本实例的代码用 tf.keras 接口编写。具体过程如下。

一、获取样本

本实例使用 GOPRO_Large 数据集作为训练样本。GOPRO_Large 数据集里包含高帧相机拍摄的街景图片(其中的照片有的清晰,有的模糊)和人工合成的模糊照片。样本中每张照片的尺寸为 720 pixel×1280 pixel。

1. 下载 GOPRO_Large 数据集

可以通过以下链接获取原始的 GOPRO_Large 数据集:


https://drive.google.com/file/d/1H0PIXvJH4c40pk7ou6nAwoxuR4Qh_Sa2/view

2. 部署 GOPRO_Large 数据集

在 GOPRO_Large 数据集中有若干套实景拍摄的照片。每套照片中包含有 3 个文件夹:


  • 在 blur 文件夹中,放置了模糊的照片。

  • 在 sharp 文件夹中,放置了清晰的照片。

  • 在 blur_gamma 文件夹中,放置了人工合成的模糊照片。


从 GOPRO_Large 数据集的 blur 与 sharp 文件夹里,各取出 200 张模糊与清晰的图片,放到本地代码的同级目录 image 文件夹下用作训练。其中,模糊的图片放在 image/train/A 文件夹下,清晰的图片在 image/train/B 文件夹下。

二、准备 SwitchableNorm 算法模块

SwitchableNorm 算法与其他的归一化算法一样,可以被当作函数来使用。由于在当前的 API 库里没有该代码的实现,所以需要自己编写一套这样的算法。


SwitchableNorm 算法的实现不是本节重点,其原理已经在见《深度学习之 TensorFlow 工程化项目实战》一书的 10.1.6 小节介绍。这里直接使用《深度学习之 TensorFlow 工程化项目实战》一书配套资源代码“switchnorm. py”即可。


直接将该代码放到本地代码文件夹下,然后将其引入。


提示:

在 SwitchableNorm 算法的实现过程中,定义了额外的变量参数。所以在运行时,需要通过会话中的 tf.global_variables_initializer 函数对其进行初始化,否则会报“SwitchableNorm 类中的某些张量没有初始化”之类的错误。

三、代码实现:构建 DeblurGAN 中的生成器模型

DeblurGAN 中的生成器模型是使用残差结构来实现的。其模型的层次结构顺序如下:


(1)通过 1 层卷积核为 7×7、步长为 1 的卷积变换。保持输入数据的尺寸不变。


(2)将第(1)步的结果进行两次卷积核为 3×3、步长为 2 的卷积操作,实现两次下采样效果。


(3)经过 5 层残差块。其中,残差块是中间带有 Dropout 层的两次卷积操作。


(4)仿照(1)和(2)步的逆操作,进行两次上采样,再来一个卷积操作。


(5)将(1)的输入与(4)的输出加在一起,完成一次残差操作。


该结构使用“先下采样,后上采样”的卷积处理方式,这种方式可以表现出样本分布中更好的潜在特征。具体代码如下:


代码 1 deblurmodel


from tensorflow.keras import layers as KLfrom tensorflow.keras import models as KMfrom switchnorm import SwitchNormalization   #载入SwitchableNorm算法ngf = 64            #定义生成器模型原始卷积核个数ndf = 64            #定义判别器模型原始卷积核个数input_nc = 3          #定义输入通道output_nc = 3        #定义输出通道n_blocks_gen = 9        #定义残差层数量
#定义残差块函数def res_block(input, filters, kernel_size=(3, 3), strides=(1, 1), use_dropout=False): x = KL.Conv2D(filters=filters, #使用步长为1的卷积操作,保持输入数据的尺寸不变 kernel_size=kernel_size, strides=strides, padding='same')(input)
x = KL.SwitchNormalization()(x) x = KL.Activation('relu')(x)
if use_dropout: #使用dropout方法 x = KL.Dropout(0.5)(x) x = KL.Conv2D(filters=filters, #再做一次步长为1的卷积操作 kernel_size=kernel_size, strides=strides,padding='same')(x) x = KL.SwitchNormalization()(x) #将卷积后的结果与原始输入相加 merged = KL.Add()([input, x]) #残差层 return merged
def generator_model(image_shape ,istrain = True): #构建生成器模型 #构建输入层(与动态图不兼容) inputs = KL.Input(shape=(image_shape[0],image_shape[1], input_nc)) #使用步长为1的卷积操作,保持输入数据的尺寸不变 x = KL.Conv2D(filters=ngf, kernel_size=(7, 7), padding='same')(inputs) x = KL.SwitchNormalization()(x) x = KL.Activation('relu')(x)
n_downsampling = 2 for i in range(n_downsampling): #两次下采样 mult = 2**i x = KL.Conv2D(filters=ngf*mult*2, kernel_size=(3, 3), strides=2, padding='same')(x) x = KL.SwitchNormalization()(x) x = KL.Activation('relu')(x)
mult = 2**n_downsampling for i in range(n_blocks_gen): #定义多个残差层 x = res_block(x, ngf*mult, use_dropout= istrain)
for i in range(n_downsampling): #两次上采样 mult = 2**(n_downsampling - i) #x = KL.Conv2DTranspose(filters=int(ngf * mult / 2), kernel_size=(3, 3), strides=2, padding='same')(x) x = KL.UpSampling2D()(x) x = KL.Conv2D(filters=int(ngf * mult / 2), kernel_size=(3, 3), padding='same')(x) x = KL.SwitchNormalization()(x) x = KL.Activation('relu')(x)
#步长为1的卷积操作 x = KL.Conv2D(filters=output_nc, kernel_size=(7, 7), padding='same')(x) x = KL.Activation('tanh')(x)
outputs = KL.Add()([x, inputs]) #与最外层的输入完成一次大残差 #防止特征值域过大,进行除2操作(取平均数残差) outputs = KL.Lambda(lambda z: z/2)(outputs) #构建模型 model = KM.Model(inputs=inputs, outputs=outputs, name='Generator') return model
复制代码


代码第 11 行,通过定义函数 res_block 搭建残差块的结构。


代码第 32 行,通过定义函数 generator_model 构建生成器模型。由于生成器模型输入的是模糊图片,输出的是清晰图片,所以函数 generator_model 的输入与输出具有相同的尺寸。


代码第 65 行,在使用残差操作时,将输入的数据与生成的数据一起取平均值。这样做是为了防止生成器模型的返回值的值域过大。在计算损失时,一旦生成的数据与真实图片的像素数据值域不同,则会影响收敛效果。

四、代码实现:构建 DeblurGAN 中的判别器模型

判别器模型的结构相对比较简单。


(1)通过 4 次下采样卷积(见代码第 74~82 行),将输入数据的尺寸变小。


(2)经过两次尺寸不变的 1×1 卷积(见代码第 85~92 行),将通道压缩。


(3)经过两层全连接网络(见代码第 95~97 行),生成判别结果(0 还是 1)。


具体代码如下。


代码 1 deblurmodel(续)


def discriminator_model(image_shape):#构建判别器模型
n_layers, use_sigmoid = 3, False inputs = KL.Input(shape=(image_shape[0],image_shape[1],output_nc)) #下采样卷积 x = KL.Conv2D(filters=ndf, kernel_size=(4, 4), strides=2, padding='same')(inputs) x = KL.LeakyReLU(0.2)(x)
nf_mult, nf_mult_prev = 1, 1 for n in range(n_layers):#继续3次下采样卷积 nf_mult_prev, nf_mult = nf_mult, min(2**n, 8) x = KL.Conv2D(filters=ndf*nf_mult, kernel_size=(4, 4), strides=2, padding='same')(x) x = KL.BatchNormalization()(x) x = KL.LeakyReLU(0.2)(x)
#步长为1的卷积操作,尺寸不变 nf_mult_prev, nf_mult = nf_mult, min(2**n_layers, 8) x = KL.Conv2D(filters=ndf*nf_mult, kernel_size=(4, 4), strides=1, padding='same')(x) x = KL.BatchNormalization()(x) x = KL.LeakyReLU(0.2)(x) #步长为1的卷积操作,尺寸不变。将通道压缩为1 x = KL.Conv2D(filters=1, kernel_size=(4, 4), strides=1, padding='same')(x) if use_sigmoid: x = KL.Activation('sigmoid')(x)
x = KL.Flatten()(x) #两层全连接,输出判别结果 x = KL.Dense(1024, activation='tanh')(x) x = KL.Dense(1, activation='sigmoid')(x)
model = KM.Model(inputs=inputs, outputs=x, name='Discriminator') return model
复制代码


代码 13 行(书中第 81 行),调用了批量归一化函数,使用了参数 trainable 的默认值 True。


代码 31 行(书中第 99 行),用 tf.keras 接口的 Model 类构造判别器模型 model。在使用 model 时,可以设置 trainable 参数来控制模型的内部结构。

五、代码实现:搭建 DeblurGAN 的完整结构

将判别器模型与生成器模型结合起来,构成 DeblurGAN 模型的完整结构。具体代码如下:


代码 1 deblurmodel(续)


def g_containing_d_multiple_outputs(generator, discriminator,image_shape):    inputs = KL.Input(shape=(image_shape[0],image_shape[1],input_nc)  )    generated_image = generator(inputs)      #调用生成器模型    outputs = discriminator(generated_image)   #调用判别器模型    #构建模型    model = KM.Model(inputs=inputs, outputs=[generated_image, outputs])    return model
复制代码


函数 g_containing_d_multiple_outputs 用于训练生成器模型。在使用时,需要将判别器模型的权重固定,让生成器模型不断地调整权重。

六、代码实现:引入库文件,定义模型参数

编写代码实现如下步骤:


(1)载入模型文件——代码文件“10-1 deblurmodel”。


(2)定义训练参数。


(3)定义函数 save_all_weights,将模型的权重保存起来。


具体代码如下:


代码 2 训练 deblur


import osimport datetimeimport numpy as npimport tqdmimport tensorflow as tfimport globfrom tensorflow.python.keras.applications.vgg16 import VGG16from functools import partialfrom tensorflow.keras import models as KMfrom tensorflow.keras import backend as K     #载入Keras的后端实现deblurmodel = __import__("10-1  deblurmodel")   #载入模型文件generator_model = deblurmodel.generator_modeldiscriminator_model = deblurmodel.discriminator_modelg_containing_d_multiple_outputs = deblurmodel.g_containing_d_multiple_outputs
RESHAPE = (360,640) #定义处理图片的大小epoch_num = 500 #定义迭代训练次数
batch_size =4 #定义批次大小critic_updates = 5 #定义每训练一次生成器模型需要训练判别器模型的次数#保存模型BASE_DIR = 'weights/'def save_all_weights(d, g, epoch_number, current_loss): now = datetime.datetime.now() save_dir = os.path.join(BASE_DIR, '{}{}'.format(now.month, now.day)) os.makedirs(save_dir, exist_ok=True) #创建目录 g.save_weights(os.path.join(save_dir, 'generator_{}_{}.h5'.format(epoch_number, current_loss)), True) d.save_weights(os.path.join(save_dir, 'discriminator_{}.h5'.format(epoch_number)), True)
复制代码


代码第 16 行将输入图片的尺寸设为(360,640),使其与样本中图片的高、宽比例相对应(样本中图片的尺寸比例为 720∶1280)。


提示:

在 TensorFlow 中,默认的图片尺寸顺序是“高”在前,“宽”在后。

七、代码实现:定义数据集,构建正反向模型

本小节代码的步骤如下:


(1)用 tf.data.Dataset 接口完成样本图片的载入(见代码第 1~26 行,书中第 29~54 行)。


(2)将生成器模型和判别器模型搭建起来。


(3)构建 Adam 优化器,用于生成器模型和判别器模型的训练过程。


(4)以 WGAN 的方式定义损失函数 wasserstein_loss,用于计算生成器模型和判别器模型的损失值。其中,生成器模型的损失值是由 WGAN 损失与特征空间损失两部分组成。


(5)将损失函数 wasserstein_loss 与优化器一起编译到可训练的判别器模型中(见代码第 42 行,书中第 70 行)。


具体代码如下:


代码 2 训练 deblur(续)


path = r'./image/train'A_paths, =os.path.join(path, 'A', "*.png")      #定义样本路径B_paths = os.path.join(path, 'B', "*.png")#获取该路径下的png文件A_fnames, B_fnames = glob.glob(A_paths),glob.glob(B_paths)#生成Dataset对象dataset = tf.data.Dataset.from_tensor_slices((A_fnames, B_fnames))
def _processimg(imgname): #定义函数调整图片大小 image_string = tf.read_file(imgname) #读取整个文件 image_decoded = tf.image.decode_image(image_string) image_decoded.set_shape([None, None, None])#形状变化,否则下面会转化失败 #变化尺寸 img =tf.image.resize( image_decoded,RESHAPE) image_decoded = (img - 127.5) / 127.5 return image_decoded def _parseone(A_fname, B_fname): #解析一个图片文件 #读取并预处理图片 image_A,image_B = _processimg(A_fname),_processimg(B_fname) return image_A,image_B
dataset = dataset.shuffle(buffer_size=len(B_fnames))dataset = dataset.map(_parseone) #转化为有图片内容的数据集dataset = dataset.batch(batch_size) #将数据集按照batch_size划分dataset = dataset.prefetch(1)
#定义模型g = generator_model(RESHAPE) #生成器模型d = discriminator_model(RESHAPE) #判别器模型d_on_g = g_containing_d_multiple_outputs(g, d,RESHAPE) #联合模型
#定义优化器d_opt = tf.keras.optimizers.Adam(lr=1E-4, beta_1=0.9, beta_2=0.999, epsilon=1e-08)d_on_g_opt = tf.keras.optimizers.Adam(lr=1E-4, beta_1=0.9, beta_2=0.999, epsilon=1e-08)
#WGAN的损失def wasserstein_loss(y_true, y_pred): return tf.reduce_mean(y_true*y_pred)
d.trainable = Trued.compile(optimizer=d_opt, loss=wasserstein_loss) #编译模型d.trainable = False
复制代码


代码第 42 行(书中第 70 行),用判别器模型对象的 compile 方法对模型进行编译。之后,将该模型的权重设置成不可训练。这是因为,在训练生成器模型时,需要将判别器模型的权重固定。只有这样,在训练生成器模型过程中才不会影响到判别器模型。

八、代码实现:计算特征空间损失,并将其编译到生成器模型的训练模型中

生成器模型的损失值是由 WGAN 损失与特征空间损失两部分组成。本小节将实现特征空间损失,并将其编译到可训练的生成器模型中去。

1. 计算特征空间损失的方法

计算特征空间损失的方法如下:


(1)用 VGG 模型对目标图片与输出图片做特征提取,得到两个特征数据。


(2)对这两个特征数据做平方差计算。

2. 特征空间损失的具体实现

在计算特征空间损失时,需要将 VGG 模型嵌入到当前网络中。这里使用已经下载好的预训练模型文件“vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5”。读者可以自行下载,也可以在《深度学习之 TensorFlow 工程化项目实战》一书的配套资源中找到。


将预训练模型文件放在当前代码的同级目录下,并利用 tf.keras 接口将其加载。

3. 编译生成器模型的训练模型

将 WGAN 损失函数与特征空间损失函数放到数组 loss 中,调用生成器模型的 compile 方法将损失值数组 loss 编译进去,实现生成器模型的训练模型。


具体代码如下:


代码 2 训练 deblur(续)


#计算特征空间损失def perceptual_loss(y_true, y_pred,image_shape):    vgg = VGG16(include_top=False,weights="vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5",                input_shape=(image_shape[0],image_shape[1],3) )        loss_model = KM.Model(inputs=vgg.input, outputs=vgg.get_layer('block3_conv3').output)    loss_model.trainable = False    return tf.reduce_mean(tf.square(loss_model(y_true) - loss_model(y_pred)))
myperceptual_loss = partial(perceptual_loss, image_shape=RESHAPE)myperceptual_loss._name_= 'myperceptual_loss'#构建损失loss = [myperceptual_loss, wasserstein_loss]loss_weights = [100, 1] #将损失调为统一数量级d_on_g.compile(optimizer=d_on_g_opt, loss=loss, loss_weights=loss_weights)d.trainable = True
output_true_batch, output_false_batch = np.ones((batch_size, 1)), -np.ones((batch_size, 1))
#生成数据集迭代器iterator = dataset.make_initializable_iterator()datatensor = iterator.get_next()
复制代码


代码第 14 行(书中第 85 行),在计算生成器模型损失时,将损失值函数 myperceptual_loss 与损失值函数 wasserstein_loss 一起放到列表里。


代码第 15 行(书中第 86 行),定义了损失值的权重比例[100,1]。这表示最终的损失值是:函数 myperceptual_loss 的结果乘上 100,将该积与函数 wasserstein_loss 的结果相加所得到和。


提示:

权重比例是根据每个函数返回的损失值得来的。

将 myperceptual_loss 的结果乘上 100,是为了让最终的损失值与函数 wasserstein_loss 的结果在同一个数量级上。


损失值函数 myperceptual_loss、wasserstein_loss 分别与模型 d_on_g 对象的输出值 generated_image、outputs 相对应。

九、代码实现:按指定次数训练模型

按照指定次数迭代调用训练函数 pre_train_epoch,然后在函数 pre_train_epoch 内遍历整个 Dataset 数据集,并进行训练。步骤如下:


(1)取一批次数据。


(2)训练 5 次判别器模型。


(3)将判别器模型权重固定,训练一次生成器模型。


(4)将判别器模型设为可训练,并循环第(1)步,直到整个数据集遍历结束。


具体代码如下:


代码 2 训练 deblur(续)


#定义配置文件config = tf.ConfigProto()config.gpu_options.allow_growth = Trueconfig.gpu_options.per_process_gpu_memory_fraction = 0.5sess = tf.Session(config=config)        #建立会话(session)  def pre_train_epoch(sess, iterator,datatensor):  #迭代整个数据集进行训练    d_losses = []    d_on_g_losses = []    sess.run( iterator.initializer )
while True: try: #获取一批次的数据 (image_blur_batch,image_full_batch) = sess.run(datatensor) except tf.errors.OutOfRangeError: break #如果数据取完则退出循环 generated_images = g.predict(x=image_blur_batch, batch_size=batch_size) #将模糊图片输入生成器模型
for _ in range(critic_updates): #训练5次判别器模型 d_loss_real = d.train_on_batch(image_full_batch, output_true_batch) #训练,并计算还原样本的loss值
d_loss_fake = d.train_on_batch(generated_images, output_false_batch) #训练,并计算模拟样本的loss值 d_loss = 0.5 * np.add(d_loss_fake, d_loss_real)#二者相加,再除以2 d_losses.append(d_loss)
d.trainable = False #固定判别器模型参数 d_on_g_loss = d_on_g.train_on_batch(image_blur_batch, [image_full_batch, output_true_batch]) #训练并计算生成器模型loss值 d_on_g_losses.append(d_on_g_loss)
d.trainable = True #恢复判别器模型参数可训练的属性 if len(d_on_g_losses)%10== 0: print(len(d_on_g_losses),np.mean(d_losses), np.mean(d_on_g_losses)) return np.mean(d_losses), np.mean(d_on_g_losses)#初始化SwitchableNorm变量K.get_session().run(tf.global_variables_initializer())for epoch in tqdm.tqdm(range(epoch_num)): #按照指定次数迭代训练 #迭代训练一次数据集 dloss,gloss = pre_train_epoch(sess, iterator,datatensor) with open('log.txt', 'a+') as f: f.write('{} - {} - {}\n'.format(epoch, dloss, gloss)) save_all_weights(d, g, epoch, int(gloss)) #保存模型sess.close() #关闭会话
复制代码


代码第 36 行(书中第 130 行),进行全局变量的初始化。初始化之后,SwitchableNorm 算法就可以正常使用了。


提示:

即便是 tf.keras 接口,其底层也是通过静态图上的会话(session)来运行代码的。

在代码第 36 行(书中第 130 行)中演示了一个用 tf.keras 接口实现全局变量初始化的技巧:

(1)用 tf.keras 接口的后端类 backend 中的 get_session 函数,获取 tf.keras 接口当前正在使用的会话(session)。

(2)拿到 session 之后,运行 tf.global_variables_initializer 方法进行全局变量的初始化。

(3)代码运行后,输出如下结果:

1%|          | 6/50 [15:06<20:43:45, 151.06s/it]10 >-0.4999978220462799 678.8936
20 -0.4999967348575592 680.67926
……
1%|         | 7/50 [17:29<20:32:16, 149.97s/it]10 >-0.49999643564224244 737.67645
20 -0.49999758243560793 700.6202
30 -0.4999980672200521 672.0518
40 -0.49999826729297636 666.23425
50 -0.4999982775449753 665.67645
……


同时可以看到,在本地目录下生成了一个 weights 文件夹,里面放置的便是模型文件。

十、代码实现:用模型将模糊相片变清晰

在权重 weights 文件夹里找到以“generator”开头并且是最新生成(按照文件的生成时间排序)的文件。将其复制到本地路径下(作者本地的文件名称为“generator_499_0.h5”)。这个模型就是 DeblurGAN 中的生成器模型部分。


在测试集中随机复制几个图片放到本地 test 目录下。与 train 目录结构一样:A 放置模糊的图片,B 放置清晰的图片。


下面编写代码来比较模型还原的效果。具体如下:


代码 3 使用 deblur 模型


import numpy as npfrom PIL import Imageimport globimport osimport tensorflow as tf              #载入模块deblurmodel = __import__("10-1  deblurmodel")generator_model = deblurmodel.generator_model
def deprocess_image(img): #定义图片的后处理函数 img = img * 127.5 + 127.5 return img.astype('uint8')
batch_size = 4RESHAPE = (360,640) #定义要处理图片的大小
path = r'./image/test'A_paths, B_paths = os.path.join(path, 'A', "*.png"), os.path.join(path, 'B', "*.png")#获取该路径下的png文件A_fnames, B_fnames = glob.glob(A_paths),glob.glob(B_paths)#生成Dataset对象dataset = tf.data.Dataset.from_tensor_slices((A_fnames, B_fnames))
def _processimg(imgname): #定义函数调整图片大小 image_string = tf.read_file(imgname) #读取整个文件 image_decoded = tf.image.decode_image(image_string) image_decoded.set_shape([None, None, None]) #形状变化,否则下面会转化失败 #变化尺寸 img =tf.image.resize( image_decoded,RESHAPE)#[RESHAPE[0],RESHAPE[1],3]) image_decoded = (img - 127.5) / 127.5 return image_decoded def _parseone(A_fname, B_fname): #解析一个图片文件 #读取并预处理图片 image_A,image_B = _processimg(A_fname),_processimg(B_fname) return image_A,image_B
dataset = dataset.map(_parseone) #转化为有图片内容的数据集dataset = dataset.batch(batch_size) #将数据集按照batch_size划分dataset = dataset.prefetch(1)
#生成数据集迭代器iterator = dataset.make_initializable_iterator()datatensor = iterator.get_next()g = generator_model(RESHAPE,False) #构建生成器模型g.load_weights("generator_499_0.h5") #载入模型文件
#定义配置文件config = tf.ConfigProto()config.gpu_options.allow_growth = Trueconfig.gpu_options.per_process_gpu_memory_fraction = 0.5sess = tf.Session(config=config) #建立sessionsess.run( iterator.initializer )ii= 0while True: try: #获取一批次的数据 (x_test,y_test) = sess.run(datatensor) except tf.errors.OutOfRangeError: break #如果数据取完则退出循环 generated_images = g.predict(x=x_test, batch_size=batch_size) generated = np.array([deprocess_image(img) for img in generated_images]) x_test = deprocess_image(x_test) y_test = deprocess_image(y_test) print(generated_images.shape[0]) for i in range(generated_images.shape[0]): #按照批次读取结果 y = y_test[i, :, :, :] x = x_test[i, :, :, :] img = generated[i, :, :, :] output = np.concatenate((y, x, img), axis=1) im = Image.fromarray(output.astype(np.uint8)) im = im.resize( (640*3, int( 640*720/1280) ) ) print('results{}{}.png'.format(ii,i)) im.save('results{}{}.png'.format(ii,i)) #将结果保存起来 ii+=1
复制代码


代码第 44 行,在定义生成器模型时,需要将其第 2 个参数 istrain 设为 False。这么做的目的是不使用 Dropout 层。


代码执行后,系统会自动在本地文件夹的 image/test 目录下加载图片,并其放到模型里进行清晰化处理。最终生成的图片如图 1 所示。



图 1 DeblurGAN 的处理结果


图 1 中有 3 个子图。左、中、右依次为原始、模糊、生成后的图片。比较图 1 中的原始图片(最左侧的图片)与生成后的图片(最右侧的图片)可以发现,最右侧模型生成的图片比中间的模糊图片更为清晰。


本文摘选自电子工业出版社出版、李金洪编著的《深度学习之TensorFlow工程化项目实战》一书,更多实战内容点此查看。



本文经授权发布,转载请联系电子工业出版社。


系列文章:


TensorFlow 工程实战(一):用 TF-Hub 库微调模型评估人物年龄


TensorFlow 工程实战(二):用 tf.layers API 在动态图上识别手写数字


TensorFlow 工程实战(三):结合知识图谱实现电影推荐系统


TensorFlow 工程实战(四):使用带注意力机制的模型分析评论者是否满意


TensorFlow 工程实战(五):构建 DeblurGAN 模型,将模糊相片变清晰(本文)


TensorFlow 工程实战(六):在 iPhone 手机上识别男女并进行活体检测


2019-08-15 12:1015787

评论

发布
暂无评论
发现更多内容

EasyCV带你复现更好更快的自监督算法-FastConvMAE

阿里云大数据AI技术

深度学习 算法 计算机视觉

开发者问第四期|统一扫码服务、机器学习服务等问题解答

HarmonyOS SDK

TiDB+TiSpark部署--安装,扩缩容及升级操作

TiDB 社区干货传送门

安装 & 部署

分享一个研发工作优先级的计算公式 | Liga译文

LigaAI

Scrum 产品经理 敏捷开发 产品优先级 企业号九月金秋榜

打破联接壁垒,华为云IoT到底强在哪?

华为云开发者联盟

云计算 后端 物联网 华为云 企业号九月金秋榜

元宇宙场景技术实践|虚拟直播间搭建教程

ZEGO即构

音视频开发 元宇宙 虚拟直播

一个代码仓库(免费)与技术点 的故事

八点半的Bruce.D

GitHub Linux 网络服务 GitHub仓库

HarmonyOS助力构建“食用菌智慧农场”

HarmonyOS开发者

HarmonyOS

2.69分钟完成BERT训练!新发CANN 5.0加持

华为云开发者联盟

人工智能 企业号九月金秋榜

云图说丨DDoS防护解决方案:DDoS大流量攻击防得住

华为云开发者联盟

云计算 后端 华为云 企业号九月金秋榜

从近期欧美法规看软件供应链安全趋势

墨菲安全

软件供应链安全 开源安全与治理

软件测试 | 测试开发 | 谁懂这篇文,玩游戏还会卡顿?

测吧(北京)科技有限公司

测试

蓝海变红海,NFT 的未来在哪里

TinTinLand

区块链 创业 web3 NFT生态链游

软件测试 | 测试开发 | 解决 App 自动化测试的常见痛点(弹框及首页启动加载完成判断处理)

测吧(北京)科技有限公司

测试

感觉最近vue相关面试题回答的不好,那就总结一下吧

bb_xiaxia1998

Vue 前端

软件测试 | 测试开发 | app自动化测试之Appium 源码修改定制分析

测吧(北京)科技有限公司

测试

中国DevOps平台市场,华为云再次位居领导者位置

华为云开发者联盟

云计算 华为云 企业号九月金秋榜

基于云原生技术打造全球融合通信网关

阿里云CloudImagine

云原生 网络 通信 通信云

2022最新腾讯面经分享:Java 面试刷题 PDF(17 大专题 )

Java-fenn

Java 编程 程序员 面试 java面试

智能电饭煲

OpenHarmony开发者

OpenHarmony

老生常谈!数据库如何存储时间?你真的知道吗?

小小怪下士

Java 数据库 编程 程序员

2022年面试复盘大全500道:Redis+ZK+Nginx+数据库+分布式+微服务

小小怪下士

数据库 redis 分布式 微服务 java面试

模块一

早安

极客时间架构训练营

高精度的“文件转换excel”背后藏着这些解题思路!

合合技术团队

人工智能 表格识别

软件测试 | 测试开发 | 背熟这些 Docker 命令,面试再也不怕啦~

测吧(北京)科技有限公司

测试

9月《中国数据库行业分析报告》重磅发布!关键词:软硬兼施,创新融合

墨天轮

数据库 oracle cpu 硬件 国产数据库

软件测试 | 测试开发 | app自动化测试之Andriod微信小程序的自动化测试

测吧(北京)科技有限公司

测试

腾讯云,DevOps 领导者!

CODING DevOps

腾讯云 DevOps IDC CODING

ESP32-C3入门教程 基础篇(八、NVS — 非易失性存储库的使用)

矜辰所致

ESP32-C3 9月月更 NVS

爆肝整理5000字!HTAP的关键技术有哪些?| StoneDB学术分享会#3

StoneDB

数据库 HTAP StoneDB 企业号九月金秋榜 9月月更

作为一个菜鸟前端开发,面了20+公司之后整理的面试题

beifeng1996

前端 React

TensorFlow工程实战(五):构建DeblurGAN模型,将模糊相片变清晰_AI&大模型_李金洪_InfoQ精选文章