Press "Enter" to skip to content

如何用GAN和Keras实现图像去模糊

本站内容均来自兴趣收集,如不慎侵害的您的相关权益,请留言告知,我们将尽快删除.谢谢.


将模糊图像变清晰向来是图像处理领域的一项有趣工作,计算机视觉专家Raphaël Meudec近期发布了一篇教程,讲解如何用生成式对抗神经网络()和机器学习框架Keras实现图像去模糊处理。
Keras代码见文章末尾。
GAN快速回顾
2014年,Ian Goodfellow提出了对人工智能发展具有重要意义的生成式对抗网络(GAN),此后围绕GAN诞生了一大批研究。在GAN中,有两个相互对抗的神经网络:一个生成器,一个鉴别器。生成器会通过创建以假乱真的虚假输入来误导鉴别器;另一方面,鉴别器会判断输入是真是假。

GAN训练过程

模型的训练部分主要有3个步骤:

  • 用生成器 创建基于噪声的虚假输入
  • 用真实和虚假这两种输入 训练鉴别器
  • 训练整个模型 :将鉴别器和生成器连接在一起构成模型

注意,在第三步中鉴别器的权重是保持不变的。
将两个神经网络连接在一起的原因是生成器的输出不可能有反馈。我们唯一的衡量指标是鉴别器是否接受生成的样本。
上面是GAN的简单介绍,如果想详细了解GAN的工作原理,可以参看我们分享的这篇文章:
景略集智:你会有猫的:AI帮你生成各种各样的喵。
数据
Ian Goodfellow首次应用GAN是用它来生成MNIST数据。在本教程,我们使用GAN用于去除图像模糊。所以,生成器的输入不是噪声,而是模糊的图像。
我们训练模型用的数据集为GOPRO数据集。你可以下载该数据集的轻量版或完整版。数据集包含了人工制作的来自多个街景的模糊图像,解压文件后在“scenes”的子文件夹中。
我们首先将图像分为文件夹A(模糊图像)和文件夹B(清晰图像)。这种A&B架构借鉴了加州大学伯克利学院研究团队在2017年提出的图像到图像翻译架构pix2pix。我创建了一个 自定义脚本来执行此项任务,可以参考使用!
模型
训练过程和我们前面说的一样。首先,我们看看神经网络架构。
生成器
生成器的目的是复制清晰图像。该神经网络基于ResNet网络的代码快,它会不断追踪应用到原始模糊图像的演变情况。ResNet代码块中还用了UNet的基本版本,不过我没有用到它。这两种网络都能很好的执行图像去模糊。

去模糊GAN模型的架构

核心部分是9个ResNet Block,用于对原始图像进行上采样。我们看看如何用Keras实现:

from .layers import Input, Conv2D, Activation, BatchNormalization
from keras.layers.merge import Add
from keras.layers.core import Dropout
def res_block(input, filters, kernel_size=(3,3), strides=(1,1), use_dropout=False):
    """
    Instanciate a Keras Resnet Block using sequential API.
    :param input: Input tensor
    :param filters: Number of filters to use
    :param kernel_size: Shape of the kernel for the convolution
    :param strides: Shape of the strides for the convolution
    :param use_dropout: Boolean value to determine the use of dropout
    :return: Keras Model
    """
    x = ReflectionPadding2D((1,1))(input)
    x = Conv2D(filters=filters,
               kernel_size=kernel_size,
               strides=strides,)(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)
    if use_dropout:
        x = Dropout(0.5)(x)
    x = ReflectionPadding2D((1,1))(x)
    x = Conv2D(filters=filters,
                kernel_size=kernel_size,
                strides=strides,)(x)
    x = BatchNormalization()(x)
    # 两个卷积层后将输入和输出直接连接
    merged = Add()([input, x])
    return merged

该ResNet层基本上是一个卷积层,添加输入和输出来组成最终输出。

from keras.layers import Input, Activation, Add
from keras.layers.advanced_activations import LeakyReLU
from keras.layers.convolutional import Conv2D, Conv2DTranspose
from keras.layers.core import Lambda
from keras.layers.normalization import BatchNormalization
from keras.models import Model
from layer_utils import ReflectionPadding2D, res_block
ngf = 64
input_nc = 3
output_nc = 3
input_shape_generator = (256, 256, input_nc)
n_blocks_gen = 9
def generator_model():
    """Build generator architecture."""
    # 当前版本 : ResNet Block
    inputs = Input(shape=image_shape)
    x = ReflectionPadding2D((3, 3))(inputs)
    x = Conv2D(filters=ngf, kernel_size=(7,7), padding='valid')(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)
    # 增加过滤器数量
    n_downsampling = 2
    for i in range(n_downsampling):
        mult = 2**i
        x = Conv2D(filters=ngf*mult*2, kernel_size=(3,3), strides=2, padding='same')(x)
        x = BatchNormalization()(x)
        x = Activation('relu')(x)
    # 应用9 个ResNet Block
    mult = 2**n_downsampling
    for i in range(n_blocks_gen):
        x = res_block(x, ngf*mult, use_dropout=True)
    # 将过滤器数量减少到3个(RGB)
    for i in range(n_downsampling):
        mult = 2**(n_downsampling - i)
        x = Conv2DTranspose(filters=int(ngf * mult / 2), kernel_size=(3,3), strides=2, padding='same')(x)
        x = BatchNormalization()(x)
        x = Activation('relu')(x)
    x = ReflectionPadding2D((3,3))(x)
    x = Conv2D(filters=output_nc, kernel_size=(7,7), padding='valid')(x)
    x = Activation('tanh')(x)
    # 增加从输入到输出的直接连接,重定位为[-1, 1]
    outputs = Add()([x, inputs])
    outputs = Lambda(lambda z: z/2)(outputs)
    model = Model(inputs=inputs, outputs=outputs, name='Generator')
    return model

按照我们的计划,这9个ResNetBlock被用于输入的上采样版本。我们 为输入和输出创建了一个连接 ,并除以2以保持输出归一化。
这就是生成器部分,我们接着看鉴别器的架构。
鉴别器
它的目标就是确定输入图像是否是人工创建的。所以,鉴别器的架构是卷积的,输出一个单一值。

from keras.layers import Input
from keras.layers.advanced_activations import LeakyReLU
from keras.layers.convolutional import Conv2D
from keras.layers.core import Dense, Flatten
from keras.layers.normalization import BatchNormalization
from keras.models import Model
ndf = 64
output_nc = 3
input_shape_discriminator = (256, 256, output_nc)
def discriminator_model():
    """Build discriminator architecture."""
    n_layers, use_sigmoid = 3, False
    inputs = Input(shape=input_shape_discriminator)
    x = Conv2D(filters=ndf, kernel_size=(4,4), strides=2, padding='same')(inputs)
    x = LeakyReLU(0.2)(x)
    nf_mult, nf_mult_prev = 1, 1
    for n in range(n_layers):
        nf_mult_prev, nf_mult = nf_mult, min(2**n, 8)
        x = Conv2D(filters=ndf*nf_mult, kernel_size=(4,4), strides=2, padding='same')(x)
        x = BatchNormalization()(x)
        x = LeakyReLU(0.2)(x)
    nf_mult_prev, nf_mult = nf_mult, min(2**n_layers, 8)
    x = Conv2D(filters=ndf*nf_mult, kernel_size=(4,4), strides=1, padding='same')(x)
    x = BatchNormalization()(x)
    x = LeakyReLU(0.2)(x)
    x = Conv2D(filters=1, kernel_size=(4,4), strides=1, padding='same')(x)
    if use_sigmoid:
        x = Activation('sigmoid')(x)
    x = Flatten()(x)
    x = Dense(1024, activation='tanh')(x)
    x = Dense(1, activation='sigmoid')(x)
    model = Model(inputs=inputs, outputs=x, name='Discriminator')
return model

最后一步是创建完整的模型。我们这个GAN的特殊之处是输入都是真实图像而非噪声。所以,生成器的输出会给我们一个直接的反馈。

from keras.layers import Input
from keras.models import Model
def generator_containing_discriminator_multiple_outputs(generator, discriminator):
    inputs = Input(shape=image_shape)
    generated_images = generator(inputs)
    outputs = discriminator(generated_images)
    model = Model(inputs=inputs, outputs=[generated_images, outputs])
return model

下面看看我们如何用两种损失充分利用这种特殊之处。
模型训练
损失
我们从两个层面提取损失值:生成器末尾和整个模型的末尾。
第一个是感知损失(perceptual loss ),在生成器的输出上直接计算得来,它能保证GAN模型面向去模糊任务。它会比较VGG卷积的第一批卷积的输出结果。

import keras.backend as K
from keras.applications.vgg16 import VGG16
from keras.models import Model
image_shape = (256, 256, 3)
def perceptual_loss(y_true, y_pred):
    vgg = VGG16(include_top=False, weights='imagenet', input_shape=image_shape)
    loss_model = Model(inputs=vgg.input, outputs=vgg.get_layer('block3_conv3').output)
    loss_model.trainable = False
return K.mean(K.square(loss_model(y_true) - loss_model(y_pred)))

第二个损失是Wasserstein损失(Wasserstein loss),在整个模型的输出上执行得来。它取自两张图像之间差距的平均值,以优化GAN网络的收敛而著称。

import keras.backend as K
def wasserstein_loss(y_true, y_pred):
return K.mean(y_true*y_pred)

训练路线
第一步是加载数据,将所有模型初始化。我们用我们的自定义函数来加载数据集,并为模型添加Adam优化器。我们设置Keras的可训练选项来防止训练鉴别器。

# 加载数据集
data = load_images('./images/train', n_images)
y_train, x_train = data['B'], data['A']
# 初始化模型
g = generator_model()
d = discriminator_model()
d_on_g = generator_containing_discriminator_multiple_outputs(g, d)
# 初始化优化器
g_opt = Adam(lr=1E-4, beta_1=0.9, beta_2=0.999, epsilon=1e-08)
d_opt = Adam(lr=1E-4, beta_1=0.9, beta_2=0.999, epsilon=1e-08)
d_on_g_opt = Adam(lr=1E-4, beta_1=0.9, beta_2=0.999, epsilon=1e-08)
# 编译模型
d.trainable = True
d.compile(optimizer=d_opt, loss=wasserstein_loss)
d.trainable = False
loss = [perceptual_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

然后,我们启动训练周期,将数据集划分批次。

for epoch in range(epoch_num):
  print('epoch: {}/{}'.format(epoch, epoch_num))
  print('batches: {}'.format(x_train.shape[0] / batch_size))
  # 将图像随机划分批次
  permutated_indexes = np.random.permutation(x_train.shape[0])
  for index in range(int(x_train.shape[0] / batch_size)):
      batch_indexes = permutated_indexes[index*batch_size:(index+1)*batch_size]
      image_blur_batch = x_train[batch_indexes]
      image_full_batch = y_train[batch_indexes]

最后,我们根据两种损失依次训练鉴别器和生成器。我们用生成器来生成虚假输入,训练鉴别器从真实输入中分辨出虚假输入,然后训练整个模型。

for epoch in range(epoch_num):
  for index in range(batches):
    # [批次准备]
    # 生成虚假输入
    generated_images = g.predict(x=image_blur_batch, batch_size=batch_size)
    # 用虚假和真实输入训练鉴别器多次
    for _ in range(critic_updates):
        d_loss_real = d.train_on_batch(image_full_batch, output_true_batch)
        d_loss_fake = d.train_on_batch(generated_images, output_false_batch)
        d_loss = 0.5 * np.add(d_loss_fake, d_loss_real)
    d.trainable = False
    # 只用鉴别器的分辨结果和生成的图像训练生成器
    d_on_g_loss = d_on_g.train_on_batch(image_blur_batch, [image_full_batch, output_true_batch])
    d.trainable = True

完整的循环过程可以参考GitHub上的代码仓库:
https://www. github.com/raphaelmeude c/deblur-gan
训练所用物料
我使用了带有深度学习AMI(3.0版本)的AWS实例(p2.xlarge)以及轻量版GOPRO数据集来训练模型,训练时间大约为5个小时(50个周期)。
图像去模糊结果

从左至右:原始图像;模糊图像;GAN去模糊后的图像

上面的输出图像就是我们这款去除图像模糊的GAN的结果。即便是非常模糊的图像,神经网络也能处理为更为令人信服的图像。比如,图中车灯更清楚了,树枝也更加醒目。

左为GOPRO测试图像,右为GAN输出图像

一个局不足之处就是图像表面会出现一层浅浅的纹理图案,可能是由于使用了VGG作为损失造成的。

左为GOPRO测试图像,右为GAN输出图像

以上就是我们使用GAN和Keras实现图像去模糊的整个过程,如果你对PyTorch比较熟悉,可以参看这个用PyTorch实现的版本:
https:// github.com/RaphaelMeude c/deblur-gan
对计算机视觉感兴趣的朋友,可以多尝试类似的项目实践。
附本项目Keras代码地址:
https:// github.com/RaphaelMeude c/deblur-gan
本项目研究论文地址:
https:// arxiv.org/pdf/1711.0706 4.pdf

Be First to Comment

发表评论

电子邮件地址不会被公开。 必填项已用*标注