100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > 基于神经网络的图像风格迁移解析与实现

基于神经网络的图像风格迁移解析与实现

时间:2018-09-19 01:12:39

相关推荐

基于神经网络的图像风格迁移解析与实现

概述

最近对图像风格迁移这种技术突然非常感兴趣,大概是之前读到了一篇关于对抗生成网络生成逼真人脸的论文文献,于是对这种技术很是好奇,大致了解了一下这个领域。我大致将其分为三个研究领域:

基于传统的机器学习的图像风格迁移。这个时代的图像风格迁移基于一个共同的思路:分析某一种风格的图像建立属于它的数学模型或者统计模型,再对待改变的图像改变其统计分布,使其更好的契合建立的模型。(这种听起来就好原始啊,感觉一定很难,暂时没找到基于这种方法进行的图像风格迁移,留待以后进行补充吧。)基于卷积神经网络的图像风格迁移。这一概念最早于提出在《A neural algorithm of artistic style》这篇文章中。它的具体实现思路是:输入原始内容图像、原始风格图像,利用网络分别学习属于他们的特征,在还原原始内容图像的时候,使用经过卷积层学习到的原始风格图像的特征,这样就可以在还原一张图像的时候使其具有另外一张图像的风格了。(这篇博客里主要记录基于这种方法的原理理解和实现流程。)基于对抗生成网络的图像风格迁移。这个应该是时下比较流行的一种方法,这种方法不同于卷积神经网络的图像风格迁移的一点在于:它不在是输入一张图像学习其风格,而是可以输入一整类的图像,从这些图像中学习它们的共同特征,并基于这些特征生成尽可能与这些图像相似的图像。这个应该在下一篇文章里研究吧。

基于神经网络的图像风格迁移

基于这里其实可以将这个过程细化为3个小的步骤;

1. 利用内容损失还原图像内容

2. 利用风格损失还原图像风格

3. 结合内容损失和风格损失,完成图像风格迁移

利用内容损失还原图像内容

基于这篇文章《A neural algorithm of artistic style》中的实现方法可以在《21个项目玩转深度学习》这本书中找到。

这里实现利用的是常用的VGGNet,先选取一幅原始图像,经过VGGNet计算后得到各个卷积层的特征。接下来根据这些卷积层的特征,还原对应的这种特征的图像。

数学符号不会敲啊。。。(我直接记录书上的图片吧)

利用内容损失还原图像内容

上面讲到的是基于图像内容损失来还原原始图像内容的,那么如何表示一张图像的风格呢?这里使用卷积层特征的Gram矩阵。

利用风格损失,就可以还原出图像的风格了。

结合内容损失和风格损失,完成图像风格迁移

书上的图挺模糊的,后面我再贴自己做的图吧。

快速图像风格迁移原理

TensorFliow实现快速图像风格迁移

这部分数据共包括两个文件夹 chapter_7 和 chapter_7_data 两个数据集。

这里我使用的是 python3.6版本和TensorFlow1.13.1版本,并且要提前安装pyyaml库。

pip install pyyaml

使用预训练模型

这部分代码中提供了7个预训练的模型:wave.ckpt-done、cubist.ckpt-done、denoised_starry.skpt-done、mosaic.ckpt-done、scream.ckpt-done、feathers. ckpt-done。回到源码目录chapter_7/,在其中新建一个model文件夹,然后把需要使用的模型文件复制到这个文件夹models/wave.ckpt-done。接下来运行下面的命令可以生成一张风格化图像了:

注:如果是python3.6版本,务必请参考这篇博文 /qq_43232373/article/details/94393957 model.py 文件进行修改,否则会报错。(如果是python2.7版本请直接忽略)

python eval.py --model_file models/wave.ckpt-done --image_file img/beauty.jpg

–model_file 后面指定了与训练的模型的文件位置。如果没有把预训练模型保存为models/wave.ckpt-done,也可以自行替换为相应的文件位置。–image_file表示需要进行风格化的图像,在这里指定的是img目录下名为

test.jpg 的示例图像),也可以使用自己的图像进行尝试,同样只需要指定合适的文件位置即可。

自己做了两张,贴一下:

这就是使用预训练好的模型进行实验,还蛮好看。

训练自己的模型(适合自己想生成任意风格的)

如何训练自己的图像生成模型呢?这里以wave为例,介绍训练模型的全过程。

在训练之前,需要完成两项前期准备工作。首先在chapter_7/中新建一个文件夹pretrained,并将chapter_7_data 文件夹下的vgg16.ckpt复制到pretrained文件夹中。最后文件的路径是pretrained/vgg16.ckpt。另外需要下载COCO数据集(有点大,12个G)。将该数据集解压后会得到一个train文件夹,其中应该含有大量jpg格式的图片。Windows用户请将该文件夹移动到chapter_7/中。Linux用户可以不用移动,只要在chapter_7/中使用下面的命令,简历到train文件夹的符号连接可以了:

ln -s <到train文件夹的路径> train

接下来就可以训练模型了。以wave为例,对应命令为:

python train.py -c conf/wave.yml

该命令放入含义是利用已经写好的conf/wave.yml文件来训练模型。wave.yml为配置文件,内容为:

## Basic configuration 基础配置style_image: img/wave.jpg # targeted style image 指定原始的风格图像# 这个模型的名字,一般和图像名称保持一致。这个名字决定了checkpoint和events文件的保存文件夹naming: "wave" # the name of this model. Determine the path to save checkpoint and events file.# checkpoint和events文件保存的根目录。最后所有的checkpoint和events文件会被保存在<model_path>/<naming>下model_path: models # root path to save checkpoint and events file. The final path would be <model_path>/<naming>## Weight of the loss 各损失权重content_weight: 1.0 # weight for content features loss内容损失的权重style_weight: 220.0 # weight for style features loss风格损失权重# 损失的权重。这是原论文中提到的一个损失。在这个项目中发现设定它的权重为0也不会影响收敛,所以没有提及tv_weight: 0.0 # weight for total variation loss## The size, the iter number to runimage_size: 256 #训练原始图像大小batch_size: 4 #一次batch的样本数epoch: 2 # 跑的epoch的运行次数## Loss Network 损失网络loss_model: "vgg_16" #使用vgg_16模型content_layers: # use these layers for content loss使用conv3_3定义内容损失- "vgg_16/conv3/conv3_3"style_layers: # use these layers for style loss使用这些卷积层定义风格损失- "vgg_16/conv1/conv1_2"- "vgg_16/conv2/conv2_2"- "vgg_16/conv3/conv3_3"- "vgg_16/conv4/conv4_3"checkpoint_exclude_scopes: "vgg_16/fc" # we only use the convolution layers, so ignore fc layers.我们只用到卷积层,所以不需要fc层loss_model_file: "pretrained/vgg_16.ckpt" # the path to the checkpoint # 预训练模型vgg_16.ckpt对应的位置

如果希望训练新的“风格”,可以选取一张风格图片,并编写新的yml配置文件。其中,需要把style_image修改为新图片所在位置,并修改对应的naming。这样就可以进行训练了。最后,可以使用训练完成的checkpoint生成图片。杂训练新的“风格”时,有可能会需要调整个个损失之间的权重。调整的方法在下一节中进行叙述。

在TensorBoard中监控训练情况

在训练过程中,可以打开TensorBoard监控训练情况。仍以wave模型为例:

tensorboard --logdir models/wave/

访问http://localhost:6006即可打开Tensorboard的主页面。训练时最先关心的应该是损失损失下降的情况。损失主要由风格损失、内容损失两项构成。展开loss选项卡可以看到损失的变化情况,如图7-7所示:

center_loss和style_loss分别对应了内容损失和风格损失,中间的regularizer_loss可以暂时不用理会。最理想的情况是content_loss和style_loss随着训练地不断下降。在训练的初期可能会出现只有style_loss下降而content_loss上升的情况,不过这是暂时的,最后两个损失都会出现较为稳定的下降。

当训练新的“风格”时,再时可能还会需要调整配置文件中的content_ weight 和style_ weight 。当content_weight 过大时,观察到的generated图像会非常接近原始的origin图像。而style_ weight 过大时,会导致图像过于接近原始的风格图像,此时的generated 图像如图7-9 所示,几乎看不到origin图像的内容。在训练时,需要合理调整style_weight和content_weight的比重。

项目实现细节

最后讨论项目的细节。该项目使用了两个网络,即损失网络与生成网络。损失网络为VGG16模型,用的是TensorFlow Slim中已经写好的代码,图像生成网络可以自己进行定义。

1. 损失网络、图像生成网络的定义与引用

损失网络使用TenorFlow Slim 的VGG16模型, 它的实际定义位置是在nets/vgg.py文件中,不过没再必要知道它的详细源码,只需要了解是如何在

训练过程中引用它的。而图像生成网络在 models.py 中定义,它的关键代码如下:

# 定义图像生成网络def net(image, training):# 一开始在图片的上下左右加上一些额外的“边框”,目的是消除边缘效应# Less border effects when padding a little before passing through ..image = tf.pad(image, [[0, 0], [10, 10], [10, 10], [0, 0]], mode='REFLECT')# 三层卷积层with tf.variable_scope('conv1'):conv1 = relu(instance_norm(conv2d(image, 3, 32, 9, 1)))with tf.variable_scope('conv2'):conv2 = relu(instance_norm(conv2d(conv1, 32, 64, 3, 2)))with tf.variable_scope('conv3'):conv3 = relu(instance_norm(conv2d(conv2, 64, 128, 3, 2)))# 仿照ResNet定义一些跳过连接with tf.variable_scope('res1'):res1 = residual(conv3, 128, 3, 1)with tf.variable_scope('res2'):res2 = residual(res1, 128, 3, 1)with tf.variable_scope('res3'):res3 = residual(res2, 128, 3, 1)with tf.variable_scope('res4'):res4 = residual(res3, 128, 3, 1)with tf.variable_scope('res5'):res5 = residual(res4, 128, 3, 1)# 定义卷积之后定义反卷积# 反卷积不采用通常的转置卷积的方式,而是采用先放大,在做卷积的方式# print(res5.get_shape())with tf.variable_scope('deconv1'):# deconv1 = relu(instance_norm(conv2d_transpose(res5, 128, 64, 3, 2)))deconv1 = relu(instance_norm(resize_conv2d(res5, 128, 64, 3, 2, training)))with tf.variable_scope('deconv2'):# deconv2 = relu(instance_norm(conv2d_transpose(deconv1, 64, 32, 3, 2)))deconv2 = relu(instance_norm(resize_conv2d(deconv1, 64, 32, 3, 2, training)))with tf.variable_scope('deconv3'):# deconv_test = relu(instance_norm(conv2d(deconv2, 32, 32, 2, 1)))deconv3 = tf.nn.tanh(instance_norm(conv2d(deconv2, 32, 3, 9, 1)))# decanv3是经过tanh函数得到的输出值,所以它的值域范围是-1~1# 知道RGB图像的像素范围是0~255,所以这里对deconv3进行这样的缩放y = (deconv3 + 1) * 127.5# Remove border effect reducing padding.# 最后取出一开始为了防止边缘效应而加入的“边框”height = tf.shape(y)[1]width = tf.shape(y)[2]y = tf.slice(y, [0, 10, 10, 0], tf.stack([-1, height - 20, width - 20, -1]))return y

图像生成网络的原理主要是先对图像卷积计算,然后再进行“反卷积”计算。相当于对图像进行编码,然后再还原为图像。在“反卷积”的过程中,一般使用转置卷积,但在这里可能会导致一些堆叠噪声。此处,使用resize_conv2d来代替转置卷积,它的原理是先对图片放大,然后再进行卷积计算。此外,还有一些提高图像质量的小技巧。比如使用所谓的instance

normalization 代替常用的batch normalization 。关于instance normalization 、转置卷积的详细原理,可以参阅相关资料进行了解,这里不再详细展

开了。

定义好图像生成网络和损失网络后,可以在训练时引用。相应的代码在 train.py文件中:

# network_fn是损失网络的函数。因为不需要对损失函数训练,所以is_training = Falsenetwork_fn = nets_factory.get_network_fn(FLAGS.loss_model,num_classes=1,is_training=False)# 损失网络中要用的图像的预处理函数image_preprocessing_fn, image_unprocessing_fn = preprocessing_factory.get_preprocessing(FLAGS.loss_model,is_training=False)# 读入训练图像processed_images = reader.image(FLAGS.batch_size, FLAGS.image_size, FLAGS.image_size,'train/', image_preprocessing_fn, epochs=FLAGS.epoch)# 此处引用图像生成网络。是图像生成网络,generated是生成的图像 # 设置training = True,因为要训练该网络generated = (processed_images, training=True)# 将生成的图像generated同样使用image_preprocessing_fn进行处理# 因为generated同样需要送到损失网络中计算lossprocessed_generated = [image_preprocessing_fn(image, FLAGS.image_size, FLAGS.image_size)for image in tf.unstack(generated, axis=0, num=FLAGS.batch_size)]processed_generated = tf.stack(processed_generated)# 将原始图像、生成图像送到损失网络中# 这里将它们合并后再送到网络中计算,因为同一的计算可以加快速度# 将原始图像、生成图像送到损失网络并计算后,将使用结果endpoints_dict 计算损失_, endpoints_dict = network_fn(tf.concat([processed_generated, processed_images], 0), spatial_squeeze=False)

2. 内容损失和风格损失的定义

损失的定义基本由文件loss .py中的函数完成。先来介绍如何定义内容损失:

# endpoints_dict是上一节提到的损失网络各层的计算结果# content_layers是定义使用哪些层的差距计算损失,默认配置是conv3_3def content_loss(endpoints_dict, content_layers):content_loss = 0for layer in content_layers:# 上一节中把生成的图像、原始图像同时传入损失网络中计算# 所以这里先把他们区分开# 读者可以参照函数tf.concat与tf.split的文档理解此处的内容generated_images, content_images = tf.split(endpoints_dict[layer], 2, 0)size = tf.size(generated_images)# 所谓的内容损失,是生成图片generated_images与原始图片激活content_images的L*L距离content_loss += tf.nn.l2_loss(generated_images - content_images) * 2 / tf.to_float(size) # remain the same as in the paperreturn content_loss

在看如何定义风格损失:

# 定义风格损失# style_layers为定义使用哪些层计算风格损失。默认为conv1_2、conv2_2、conv3_3、conv4_3# style_features_t是利用原始的风格图片计算的层的激活# 如在wave模型中是img/wave.jpg计算的激活def style_loss(endpoints_dict, style_features_t, style_layers):style_loss = 0# summary是为TensorBoard服务的style_loss_summary = {}for style_gram, layer in zip(style_features_t, style_layers):# 计算风格损失,只需要计算生成图片generated_imgs与目标风格# style_features_t的差距。因此不需要取出content_imagesgenerated_images, _ = tf.split(endpoints_dict[layer], 2, 0)size = tf.size(generated_images)# 调用gram函数计算Gram矩阵。风格损失定义为生成图片与目标风格Gram矩阵的L*L的Losslayer_style_loss = tf.nn.l2_loss(gram(generated_images) - style_gram) * 2 / tf.to_float(size)style_loss_summary[layer] = layer_style_lossstyle_loss += layer_style_lossreturn style_loss, style_loss_summary

在train.py中,直接利用上面的函数可以得到总的损失:

"""Build Losses"""# 定义内容损失content_loss = losses.content_loss(endpoints_dict, FLAGS.content_layers)# 定义风格损失style_loss, style_loss_summary = losses.style_loss(endpoints_dict, style_features_t, FLAGS.style_layers)# 定义tv损失,该损失在实际训练中并没有被应道,因为在训练时都采用tv_weight=0tv_loss = losses.total_variation_loss(generated) # use the unprocessed image# 总损失是这些损失的加权和,最后利用总损失优化图像生成网络即可loss = FLAGS.style_weight * style_loss + FLAGS.content_weight * content_loss + FLAGS.tv_weight * tv_loss

3. 确定训练、保存的变量

在本项目中,只需要训练图像生成网络中的变量,而不需要训练损失网络中的变量。在把模型保存成checkpoint时,也只需要保存图像生成网络中的变量。TenorFlow会默认训练、保存所有变量,因此必须把需要训练和需要保存的变量找出来,这也是本项目中的一个注意点。对应的代码同样在

train.py中:

# 找出需要训练的变量variable_to_train = []# 使用tf.trainable_variables()找出所有可训练的变量for variable in tf.trainable_variables():# 如果不在损失网络中,把它们加入列表variable_to_trainif not(variable.name.startswith(FLAGS.loss_model)):variable_to_train.append(variable)# 定义训练步骤时指定var_list=variable_to_train。这样不会训练损失网络train_op = tf.train.AdamOptimizer(1e-3).minimize(loss, global_step=global_step, var_list=variable_to_train)# 找出所有需要保存的变量variables_to_restore = []# 用tf.global_variable()找出所有变量for v in tf.global_variables():# 不在损失网络中则加入列表variables_to_restoreif not(v.name.startswith(FLAGS.loss_model)):variables_to_restore.append(v)# 定义saver时指定只会保存variables_to_restoresaver = tf.train.Saver(variables_to_restore, write_version=tf.train.SaverDef.V1)

OK,这就是全部的流程了,前面挺简单的,后面如果需要生成自己风格的图像建议认真尝试一下,代码数据有需要的话留言就好。

——————————————————————

附注: 不少同学想参考代码数据,为防止忘记就放在这里了,有需要自取哈。

风格迁移模型及代码数据

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。