使用VGG19迁移学习实现图像风格迁移


2020.3.15 更新:

使用Python 3.7 + TensorFlow 2.0的实现:

有趣的深度学习——使用TensorFlow 2.0实现图片神经风格迁移 (https://blog.csdn.net/aaronjny/article/details/104879258)


转载请注明出处:https://blog.csdn.net/aaronjny/article/details/79681080


一直想要做个图像风格迁移来玩玩的,感觉还是蛮有意思的。

所谓图像风格迁移,即给定内容图片A,风格图片B,能够生成一张具有A图片内容和B图片风格的图片C。

比如说,我们可以使用梵高先生的名画《星夜》 作为风格图片,来与其他图片生成具有《星夜》风格新图片。emmm,夭寿啦,机器帮你画世界名画啦。。。

举两个生成的例子:

均使用《星夜》作为风格图片(可以替换,我以《星夜》为例):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PBbLpmXy-1584256961127)(https://raw.githubusercontent.com/AaronJny/nerual_style_change/master/sample/input_style_1.jpg)]

示例1:

网络上找到的一张风景图片。

内容图片:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hBlBxLwz-1584256961128)(https://raw.githubusercontent.com/AaronJny/nerual_style_change/master/sample/input_content_1.jpg)]

生成图片:

生成图片的尺寸比较小,没办法,我的显卡太差了,尺寸大一点的话显卡内存不足。

示例2:

嗷嗷嗷,狼人嚎叫~

内容图片:

生成图片:

效果还凑合吧,可以接受。

下面记录实现过程。


一.获取预训练的vgg19模型

VGG19是Google DeepMind发表在ICLR 2015上的论文《VERY DEEP CONVOLUTIONAL NETWORK SFOR LARGE-SCALE IMAGE RECOGNITION》中提出的一种DCNN结构。

众所周知,CNN在图片处理上表现良好,VGG19提出后,也被用在图像处理上。我这里要用到的VGG19模型就是在imagenet数据集上预训练的模型。

一般认为,深度卷积神经网络的训练是对数据集特征的一步步抽取的过程,从简单的特征,到复杂的特征。

训练好的模型学习到的是对图像特征的抽取方法,所以在imagenet数据集上训练好的模型理论上来说,也可以直接用于抽取其他图像的特征,这也是迁移学习的基础。自然,这样的效果往往没有在新数据上重新训练的效果好,但能够节省大量的训练时间,在特定情况下非常有用。

预训练好的VGG19模型可以从这里下载,模型大小500M+。

二.模型编写

这里的模型基本上就是VGG19模型,只是稍微做了一些修改。

我们要从预训练的模型中,获取卷积层部分的参数,用于构建我们自己的模型。VGG19中的全连接层舍弃掉,这一部分对提取图像特征基本无用。

要注意的是,我这里提取出来的VGG参数全部是作为constant(即常量)使用的,也就是说,这些参数是不会再被训练的,在反向传播的过程中也不会改变。

另外,输入层要设置为Variable,我们要训练的就是这个。最开始输入一张噪音图片,然后不断地根据内容loss和风格loss对其进行调整,直到一定次数后,该图片兼具了风格图片的风格以及内容图片的内容。当训练结束时,输入层的参数就是我们生成的图片。

附一张VGG结构图:

这个代码里主要是定义VGG,至于LOSS在训练过程中进行说明。

models.py

# -*- coding: utf-8 -*-
# @Time    : 18-3-23 下午12:20
# @Author  : AaronJny
# @Email   : Aaron__7@163.com
import tensorflow as tf
import numpy as np
import settings
import scipy.io
import scipy.miscclass Model(object):def __init__(self, content_path, style_path):self.content = self.loadimg(content_path)  # 加载内容图片self.style = self.loadimg(style_path)  # 加载风格图片self.random_img = self.get_random_img()  # 生成噪音内容图片self.net = self.vggnet()  # 建立vgg网络def vggnet(self):# 读取预训练的vgg模型vgg = scipy.io.loadmat(settings.VGG_MODEL_PATH)vgg_layers = vgg['layers'][0]net = {}# 使用预训练的模型参数构建vgg网络的卷积层和池化层# 全连接层不需要# 注意,除了input之外,这里参数都为constant,即常量# 和平时不同,我们并不训练vgg的参数,它们保持不变# 需要进行训练的是input,它即是我们最终生成的图像net['input'] = tf.Variable(np.zeros([1, settings.IMAGE_HEIGHT, settings.IMAGE_WIDTH, 3]), dtype=tf.float32)# 参数对应的层数可以参考vgg模型图net['conv1_1'] = self.conv_relu(net['input'], self.get_wb(vgg_layers, 0))net['conv1_2'] = self.conv_relu(net['conv1_1'], self.get_wb(vgg_layers, 2))net['pool1'] = self.pool(net['conv1_2'])net['conv2_1'] = self.conv_relu(net['pool1'], self.get_wb(vgg_layers, 5))net['conv2_2'] = self.conv_relu(net['conv2_1'], self.get_wb(vgg_layers, 7))net['pool2'] = self.pool(net['conv2_2'])net['conv3_1'] = self.conv_relu(net['pool2'], self.get_wb(vgg_layers, 10))net['conv3_2'] = self.conv_relu(net['conv3_1'], self.get_wb(vgg_layers, 12))net['conv3_3'] = self.conv_relu(net['conv3_2'], self.get_wb(vgg_layers, 14))net['conv3_4'] = self.conv_relu(net['conv3_3'], self.get_wb(vgg_layers, 16))net['pool3'] = self.pool(net['conv3_4'])net['conv4_1'] = self.conv_relu(net['pool3'], self.get_wb(vgg_layers, 19))net['conv4_2'] = self.conv_relu(net['conv4_1'], self.get_wb(vgg_layers, 21))net['conv4_3'] = self.conv_relu(net['conv4_2'], self.get_wb(vgg_layers, 23))net['conv4_4'] = self.conv_relu(net['conv4_3'], self.get_wb(vgg_layers, 25))net['pool4'] = self.pool(net['conv4_4'])net['conv5_1'] = self.conv_relu(net['pool4'], self.get_wb(vgg_layers, 28))net['conv5_2'] = self.conv_relu(net['conv5_1'], self.get_wb(vgg_layers, 30))net['conv5_3'] = self.conv_relu(net['conv5_2'], self.get_wb(vgg_layers, 32))net['conv5_4'] = self.conv_relu(net['conv5_3'], self.get_wb(vgg_layers, 34))net['pool5'] = self.pool(net['conv5_4'])return netdef conv_relu(self, input, wb):"""进行先卷积、后relu的运算:param input: 输入层:param wb: wb[0],wb[1] == w,b:return: relu后的结果"""conv = tf.nn.conv2d(input, wb[0], strides=[1, 1, 1, 1], padding='SAME')relu = tf.nn.relu(conv + wb[1])return reludef pool(self, input):"""进行max_pool操作:param input: 输入层:return: 池化后的结果"""return tf.nn.max_pool(input, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')def get_wb(self, layers, i):"""从预训练好的vgg模型中读取参数:param layers: 训练好的vgg模型:param i: vgg指定层数:return: 该层的w,b"""w = tf.constant(layers[i][0][0][0][0][0])bias = layers[i][0][0][0][0][1]b = tf.constant(np.reshape(bias, (bias.size)))return w, bdef get_random_img(self):"""根据噪音和内容图片,生成一张随机图片:return:"""noise_image = np.random.uniform(-20, 20, [1, settings.IMAGE_HEIGHT, settings.IMAGE_WIDTH, 3])random_img = noise_image * settings.NOISE + self.content * (1 - settings.NOISE)return random_imgdef loadimg(self, path):"""加载一张图片,将其转化为符合要求的格式:param path::return:"""# 读取图片image = scipy.misc.imread(path)# 重新设定图片大小image = scipy.misc.imresize(image, [settings.IMAGE_HEIGHT, settings.IMAGE_WIDTH])# 改变数组形状,其实就是把它变成一个batch_size=1的batchimage = np.reshape(image, (1, settings.IMAGE_HEIGHT, settings.IMAGE_WIDTH, 3))# 减去均值,使其数据分布接近0image = image - settings.IMAGE_MEAN_VALUEreturn imageif __name__ == '__main__':Model(settings.CONTENT_IMAGE, settings.STYLE_IMAGE)

三.模型训练

这里简述一下训练的思路:

1.首先,我们使用VGG中的一些层的输出来表示图片的内容特征和风格特征。比如,我使用[‘conv4_2’,‘conv5_2’]表示内容特征,使用[‘conv1_1’,‘conv2_1’,‘conv3_1’,‘conv4_1’]表示风格特征。

# 定义计算内容损失的vgg层名称及对应权重的列表
CONTENT_LOSS_LAYERS = [('conv4_2', 0.5),('conv5_2',0.5)]
# 定义计算风格损失的vgg层名称及对应权重的列表
STYLE_LOSS_LAYERS = [('conv1_1', 0.2), ('conv2_1', 0.2), ('conv3_1', 0.2), ('conv4_1', 0.2), ('conv5_1', 0.2)]

2.将内容图片输入网络,计算内容图片在网络指定层(比如[‘conv4_2’,‘conv5_2’])上的输出值。

3.计算内容损失。我们可以这样定义内容损失:内容图片在指定层上提取出的特征矩阵,与噪声图片在对应层上的特征矩阵的差值的L2范数。即求两两之间的像素差值的平方。

对应每一层的内容损失函数:

(话说为了写这个公式,我还跑去现学了latex语法= =)

Li=12∗M∗N∑ij(Xij−Pij)2L_{i}=\frac{1}{2*M*N}\sum_{ij}^{}(X_{ij}-P_{ij})^2 Li​=2∗M∗N1​ij∑​(Xij​−Pij​)2

其中,X是噪声图片的特征矩阵,P是内容图片的特征矩阵。M是P的长*宽,N是信道数。

最终的内容损失为,每一层的内容损失加权和,再对层数取平均。

4.将风格图片输入网络,计算风格图片在网络指定层(比如[‘conv1_1’,‘conv2_1’,‘conv3_1’,‘conv4_1’])上的输出值。

5.计算风格损失。我们使用风格图像在指定层上的特征矩阵的GRAM矩阵来衡量其风格,风格损失可以定义为风格图像和噪音图像特征矩阵的格莱姆矩阵的差值的L2范数。

对于每一层的风格损失函数:

Li=14∗M2∗N2∑ij(Gij−Aij)2L_{i}=\frac{1}{4*M^2*N^2}\sum_{ij}(G_{ij}-A_{ij})^2 Li​=4∗M2∗N21​ij∑​(Gij​−Aij​)2

其中M是特征矩阵的长*宽,N是特征矩阵的信道数。G为噪音图像特征的Gram矩阵,A为风格图片特征的GRAM矩阵。

最终的风格损失为,每一层的风格损失加权和,再对层数取平均。

6.最终用于训练的损失函数为内容损失和风格损失的加权和。

Ltotal=αLcontent+βLstyleL_{total}=\alpha L_{content}+\beta L_{style} Ltotal​=αLcontent​+βLstyle​

7.当训练开始时,我们根据内容图片和噪声,生成一张噪声图片。并将噪声图片喂给网络,计算loss,再根据loss调整噪声图片。将调整后的图片喂给网络,重新计算loss,再调整,再计算…直到达到指定迭代次数,此时,噪声图片已兼具内容图片的内容和风格图片的风格,进行保存即可。

具体代码如下:

train.py

# -*- coding: utf-8 -*-
# @Time    : 18-3-23 下午12:22
# @Author  : AaronJny
# @Email   : Aaron__7@163.com
import tensorflow as tf
import settings
import models
import numpy as np
import scipy.miscdef loss(sess, model):"""定义模型的损失函数:param sess: tf session:param model: 神经网络模型:return: 内容损失和风格损失的加权和损失"""# 先计算内容损失函数# 获取定义内容损失的vgg层名称列表及权重content_layers = settings.CONTENT_LOSS_LAYERS# 将内容图片作为输入,方便后面提取内容图片在各层中的特征矩阵sess.run(tf.assign(model.net['input'], model.content))# 内容损失累加量content_loss = 0.0# 逐个取出衡量内容损失的vgg层名称及对应权重for layer_name, weight in content_layers:# 提取内容图片在layer_name层中的特征矩阵p = sess.run(model.net[layer_name])# 提取噪音图片在layer_name层中的特征矩阵x = model.net[layer_name]# 长x宽M = p.shape[1] * p.shape[2]# 信道数N = p.shape[3]# 根据公式计算损失,并进行累加content_loss += (1.0 / (2 * M * N)) * tf.reduce_sum(tf.pow(p - x, 2)) * weight# 将损失对层数取平均content_loss /= len(content_layers)# 再计算风格损失函数style_layers = settings.STYLE_LOSS_LAYERS# 将风格图片作为输入,方便后面提取风格图片在各层中的特征矩阵sess.run(tf.assign(model.net['input'], model.style))# 风格损失累加量style_loss = 0.0# 逐个取出衡量风格损失的vgg层名称及对应权重for layer_name, weight in style_layers:# 提取风格图片在layer_name层中的特征矩阵a = sess.run(model.net[layer_name])# 提取噪音图片在layer_name层中的特征矩阵x = model.net[layer_name]# 长x宽M = a.shape[1] * a.shape[2]# 信道数N = a.shape[3]# 求风格图片特征的gram矩阵A = gram(a, M, N)# 求噪音图片特征的gram矩阵G = gram(x, M, N)# 根据公式计算损失,并进行累加style_loss += (1.0 / (4 * M * M * N * N)) * tf.reduce_sum(tf.pow(G - A, 2)) * weight# 将损失对层数取平均style_loss /= len(style_layers)# 将内容损失和风格损失加权求和,构成总损失函数loss = settings.ALPHA * content_loss + settings.BETA * style_lossreturn lossdef gram(x, size, deep):"""创建给定矩阵的格莱姆矩阵,用来衡量风格:param x:给定矩阵:param size:矩阵的行数与列数的乘积:param deep:矩阵信道数:return:格莱姆矩阵"""# 改变shape为(size,deep)x = tf.reshape(x, (size, deep))# 求xTxg = tf.matmul(tf.transpose(x), x)return gdef train():# 创建一个模型model = models.Model(settings.CONTENT_IMAGE, settings.STYLE_IMAGE)# 创建sessionwith tf.Session() as sess:# 全局初始化sess.run(tf.global_variables_initializer())# 定义损失函数cost = loss(sess, model)# 创建优化器optimizer = tf.train.AdamOptimizer(1.0).minimize(cost)# 再初始化一次(主要针对于第一次初始化后又定义的运算,不然可能会报错)sess.run(tf.global_variables_initializer())# 使用噪声图片进行训练sess.run(tf.assign(model.net['input'], model.random_img))# 迭代指定次数for step in range(settings.TRAIN_STEPS):# 进行一次反向传播sess.run(optimizer)# 每隔一定次数,输出一下进度,并保存当前训练结果if step % 50 == 0:print 'step {} is down.'.format(step)# 取出input的内容,这是生成的图片img = sess.run(model.net['input'])# 训练过程是减去均值的,这里要加上img += settings.IMAGE_MEAN_VALUE# 这里是一个batch_size=1的batch,所以img[0]才是图片内容img = img[0]# 将像素值限定在0-255,并转为整型img = np.clip(img, 0, 255).astype(np.uint8)# 保存图片scipy.misc.imsave('{}-{}.jpg'.format(settings.OUTPUT_IMAGE,step), img)# 保存最终训练结果img = sess.run(model.net['input'])img += settings.IMAGE_MEAN_VALUEimg = img[0]img = np.clip(img, 0, 255).astype(np.uint8)scipy.misc.imsave('{}.jpg'.format(settings.OUTPUT_IMAGE), img)if __name__ == '__main__':train()

四、一些配置信息

settings.py

# -*- coding: utf-8 -*-
# @Time    : 18-3-23 下午12:22
# @Author  : AaronJny
# @Email   : Aaron__7@163.com# 内容图片路径
CONTENT_IMAGE = 'images/content.jpg'
# 风格图片路径
STYLE_IMAGE = 'images/style.jpg'
# 输出图片路径
OUTPUT_IMAGE = 'output/output'
# 预训练的vgg模型路径
VGG_MODEL_PATH = 'imagenet-vgg-verydeep-19.mat'
# 图片宽度
IMAGE_WIDTH = 450
# 图片高度
IMAGE_HEIGHT = 300
# 定义计算内容损失的vgg层名称及对应权重的列表
CONTENT_LOSS_LAYERS = [('conv4_2', 0.5),('conv5_2',0.5)]
# 定义计算风格损失的vgg层名称及对应权重的列表
STYLE_LOSS_LAYERS = [('conv1_1', 0.2), ('conv2_1', 0.2), ('conv3_1', 0.2), ('conv4_1', 0.2), ('conv5_1', 0.2)]
# 噪音比率
NOISE = 0.5
# 图片RGB均值
IMAGE_MEAN_VALUE = [128.0, 128.0, 128.0]
# 内容损失权重
ALPHA = 1
# 风格损失权重
BETA = 500
# 训练次数
TRAIN_STEPS = 3000

更多

以上即是全部内容,感觉还是挺有意思的。

附上项目GitHub地址:https://github.com/AaronJny/nerual_style_change

学识浅薄,文中如有纰漏错误之处,还请诸位大佬斧正,万分感谢!

参考

[1]A Neural Algorithm of Artistic Style

[2]深度学习实战(一)快速理解实现风格迁移

TensorFlow练手项目三:使用VGG19迁移学习实现图像风格迁移相关推荐

  1. TensorFlow练手项目二:基于循环神经网络(RNN)的古诗生成器

    基于循环神经网络(RNN)的古诗生成器 2019.01.02更新: 代码比较老了,当时的开发环境为Python 2.7 + TensorFlow 1.4,现在可能无法直接运行了.如果有兴趣,可以移步我 ...

  2. 深度学习实战-图像风格迁移

    图像风格迁移 文章目录 图像风格迁移 简介 画风迁移 图像风格捕捉 图像风格迁移 图像风格内插 补充说明 简介 利用卷积神经网络实现图像风格的迁移. 画风迁移 简单来说就是将另一张图像的绘画风格在不改 ...

  3. 基于深度学习的图像风格迁移算法的基本介绍。

    基于神经网络的图像风格迁移算法 这个算法还是蛮有趣的,之前就有宣传说让电脑来学习梵高作画,虽然有些夸张,但是实际效果出来还是挺不错的. 接下来,我们要按照以下三个部分来进行介绍,提出,方法以及结论. ...

  4. 论文总结:基于深度学习的图像风格迁移研究

    基于深度学习的图像风格迁移研究 前言 图像风格迁移方法 基于图像迭代的图像风格迁移方法 基于模型迭代的图像风格迁移方法 卷积神经网络 生成对抗网络 CycleGAN 前言 什么是深度学习? 深度学习是 ...

  5. 读“基于深度学习的图像风格迁移研究综述”有感

    前言 关于传统非参数的图像风格迁移方法和现如今基于深度学习的图像风格迁移方法. 基于深度学习的图像风格迁移方法:基于图像迭代和模型迭代的两种方法的优缺点. 基于深度学习的图像风格迁移方法的存在问题及其 ...

  6. 动手学深度学习之图像风格迁移

    参考伯禹学习平台<动手学深度学习>课程内容内容撰写的学习笔记 原文链接:https://www.boyuai.com/elites/course/cZu18YmweLv10OeV/less ...

  7. java图像风格迁移_Python+OpenCV 图像风格迁移(模仿名画)

    现在很多人都喜欢拍照(自拍).有限的滤镜和装饰玩多了也会腻,所以就有 APP 提供了模仿名画风格的功能,比如 prisma.versa 等,可以把你的照片变成 梵高.毕加索.蒙克 等大师的风格. 这种 ...

  8. cnn风格迁移_快速图像风格迁移思想在无线通信中的另类应用:算法拟合

    在本文中,并不是介绍最新的一些论文,而是回顾自己在很早(半年前?)读过的几篇文章.[1]Learning to optimize: Training deep neural networks for ...

  9. 毕业设计 - 题目:基于深度学习的图像风格迁移 - [ 卷积神经网络 机器视觉 ]

    文章目录 0 简介 1 VGG网络 2 风格迁移 3 内容损失 4 风格损失 5 主代码实现 6 迁移模型实现 7 效果展示 8 最后 0 简介 今天学长向大家介绍一个机器视觉项目 基于深度学习卷积神 ...

最新文章

  1. 我有一个很好的思维习惯-反思
  2. “汇新杯”新兴科技+互联网创新大赛青年创客专项赛决赛
  3. UVa11100 The Trip,2007(贪心)
  4. 【深度学习】一分钟速学 | NMS, IOU 与 SoftMax
  5. 程序运行实例数量的控制
  6. Win10 OpenCV3.3.0+VS2013配置大坑,OpenCV解决方案编译报错“找不到python36_d.lib”错误...
  7. 2018最新java基础学习路线
  8. Cisco Packet Tracer Student思科命令
  9. 华为太极magisk安装教程_教程:如何升级太极内部的应用
  10. unity3d-学习笔记8-卡牌游戏制作(实现动态读取卡片信息并且在游戏界面展现)
  11. 华为od77 python最大消费金额
  12. 原创短视频被搬运,平台也会侵权吗
  13. UOS 22.0家庭版使用体验
  14. 《XML实用大全》一
  15. MySQL order by的不同排序规则
  16. java 快速创建map_快速创建 Map
  17. 全国二级c语言库理论,全国计算机等级考试二级C语言理论基础习.doc
  18. Java实现按键精灵(鼠标篇)
  19. Hibernate Validation各注解的用法
  20. Tessent专栏第五篇:TessentMemoryBIST用户手册第二章下

热门文章

  1. 小程序真正实现多个文件上传,不通过循环调用uni.uploadFile
  2. python调用cplex求解装箱问题_装箱问题的CPLEX求解
  3. 计算机设备维修更换记录,单位电脑设备维护(维修)
  4. IT的幽默 典型的人傻钱多,地主家的傻儿子系列! 人不学习枉少年
  5. 数据结构学习笔记之快速排序(非递归)
  6. 夫妻, 是先离开的人幸福,还是后离开的人幸福?
  7. ant-vue实现上传excel文件并解析内容
  8. 深入理解WebView
  9. -bash: java: command not found (Linux)
  10. mybatis异常Mapper method attempted to return null from a method with a primitive re