# 图像风格迁移
## 简介
- 利用卷积神经网络实现图像风格的迁移。
## 画风迁移
- 简单来说就是将另一张图像的绘画风格在不改变原图图像内容的情况下加入到原图像中,从而“创造”名家风格的绘画作品。
- 这其中牵扯到很多难题,但是很多问题已经被一一攻克,一般而言,这类问题将涉及到图像风格的捕捉、图像风格的迁移、图像风格的组合等主要流程。
- 整体看来,卷积核特征的捕捉及应用、图像相干性的优化、画风的组合将是难题。
## 图像风格捕捉
- 原理
- 通过将格拉姆矩阵(gram matrix)应用于卷积神经网络各层能够捕获该层的样式,所以,如果从填充了随机噪声的图像开始,对其进行优化使得网络各层的格拉姆矩阵与目标图像的格拉姆矩阵相匹配,那么不难理解,生成的图像将会模仿目标图像的风格。
- 可以定义一个style损失函数,计算两组激活输出值各自减去格拉姆矩阵之后计算平方误差。在原始图像和目标图像(莫奈的《睡莲》)上进行训练,将两幅图片输入VGG16的卷积神经网络,对每个卷积层计算上述style损失并多层累加,对损失使用lbfgs进行优化(它需要梯度值和损失值进行优化)。
- 其实,很多观点认为,匹配图像风格的最好方式是直接匹配所有层的激活值,事实上,格拉姆矩阵效果会更好一些,这点并不容易理解。其背后的原理是,通过计算给定层的每个激活值与其他激活值的乘积,我们获得了神经元之间的相关性。这些相关性可以理解为图像风格的编码,因为他们衡量了激活值的分布情况,而不是激活值本身。
- 这也带来了几个问题。一个就是零值问题,在任一被乘数为零的情况下,一个向量和自己的转置的点积都会是零,模型无法在零值处识别相关性。由于零值频繁出现,可以在执行点积前为特征值添加一个小的差量delta,delta去-1即可。还有一个问题就是,我们计算了所有激活值的格拉姆矩阵,难道不是应该针对像素通道计算吗?事实上,我们为每个像素的通道都计算了格拉姆矩阵,然后观察它们在整个图像上的相关性,这样做提供了一个捷径:可以计算通道均值并将其用作格拉姆矩阵,这会帮助获得一幅平均风格的图像,更有普适性。
- 同时,添加了总变分损失(total variation loss),要求网络时刻检查相邻像素的差异,否则,图像会趋于像素化且更加不平缓。某种程度上,这种方法与用于持续检查层权重与层输出的正则化过程非常类似,整体效果相当于在输出像素上添加了一个略微模糊的滤镜。(本质上,这是一种模糊化的方法)该部分的结果是将最后一个组成成分添加到了损失函数中,使得图像整体更像内容图像而不是风格图像。这里所做的,就是有效优化生成图像,使得上层的激活值对应图像内容,下层的激活值对应图像风格,也就是网络底层对应图像风格,网络高层对应图像内容,通过这种方式实现图像风格转换。
- 代码
- ```python
def gram_matrix(x):
if K.image_data_format() != 'channels_first':
x = K.permute_dimensions(x, (2, 0, 1))
features = K.batch_flatten(x)
return K.dot(features - 1, K.transpose(features - 1)) - 1
def style_loss(layer_1, layer_2):
gr1 = gram_matrix(layer_1)
gr2 = gram_matrix(layer_2)
return K.sum(K.square(gr1 - gr2)) / (np.prod(layer_2.shape).value ** 2)
w, h = 740, 468
style_image = K.variable(preprocess_image(style1_image_path, target_size=(h, w)))
result_image = K.placeholder(style_image.shape)
input_tensor = K.concatenate([style_image, result_image], axis=0)
print(input_tensor.shape)
model = vgg16.VGG16(input_tensor=input_tensor, weights='imagenet', include_top=False)
# caculate loss
feature_outputs = [layer.output for layer in model.layers if '_conv' in layer.name]
loss_style = K.variable(0.)
for idx, layer_features in enumerate(feature_outputs):
loss_style += style_loss(layer_features[0, :, :, :], layer_features[1, :, :, :])
class Evaluator(object):
def __init__(self, loss_total, result_image, **other):
grads = K.gradients(loss_total, result_image)
outputs = [loss_total] + list(other.values()) + grads
self.iterate = K.function([result_image], outputs)
self.other = list(other.keys())
self.other_values = {}
self.shape = result_image.shape
self.loss_value = None
self.grads_values = None
def loss(self, x):
outs = self.iterate([x.reshape(self.shape)])
self.loss_value = outs[0]
self.grad_values = outs[-1].flatten().astype('float64')
self.other_values = dict(zip(self.other, outs[1:-1]))
return self.loss_value
def grads(self, x):
return np.copy(self.grad_values)
style_evaluator = Evaluator(loss_style, result_image)
def run(evaluator, image, num_iter=50):
for i in range(num_iter):
clear_output()
image, min_val, info = fmin_l_bfgs_b(evaluator.loss, image.flatten(), fprime=evaluator.grads, maxfun=20)
showarray(deprocess_image(image.copy(), h, w))
print("Current loss value:", min_val)
print(' '.join(k + ':' + str(evaluator.other_values[k]) for k in evaluator.other))
return image
x = np.random.uniform(0, 255, result_image.shape) - 128.
res = run(style_evaluator, x, num_iter=50)
def total_variation_loss(x, exp=1.25):
_, d1, d2, d3 = x.shape
if K.image_data_format() == 'channels_first':
a = K.square(x[:, :, :d2 - 1, :d3 - 1] - x[:, :, 1:, :d3 - 1])
b = K.square(x[:, :, :d2 - 1, :d3 - 1] - x[:, :, :d2 - 1, 1:])
else:
a = K.square(x[:, :d1 - 1, :d2 - 1, :] - x[:, 1:, :d2 - 1, :])
b = K.square(x[:, :d1 - 1, :d2 - 1, :] - x[:, :d1 - 1, 1:, :])
return K.sum(K.pow(a + b, exp))
loss_variation = total_variation_loss(result_image) / 5000
loss_with_variation = loss_variation + loss_style
evaluator_with_variation = Evaluator(loss_with_variation, result_image)
x = np.random.uniform(0, 255, result_image.shape) - 128.
res = run(evaluator_with_variation, x, num_iter=100)
```
- 结果
- ![](https://img-blog.csdnimg.cn/20190730195156361.png)
- ![](https://img-blog.csdnimg.cn/20190730212119588.png)
- 显然,使用总变分损失后,原来的噪声图像产生的结果更像一个内容图像了。
## 图像风格迁移
- 原理
- 要想将捕获到的风格从一个图像应用到另一个图像上,需要使用一个损失函数来平衡一个图像的内容和另一个图像的风格。
- 在现有图像上运行上面的代码是不难的,但是结果并不那么令人满意,它看起来似乎将风格应用到了现有图像上,但是程序的不断执行,原始图像不断分解,坚持运行,最后生成一幅与原图独立的新图像,显然,这不是想要。可以通过在损失函数中添加第三个组成成分来解决这个问题,该成分会考虑生成图像和参考图像的差异,这就形成了内容损失(content loss),其作用于网络最后一层。最后一层包含与网络所看到的的映像最近似的内容,因此这也是真正希望保持一致的内容。
- 通过把各层在网络中的位置纳入考量来微调风格损失(style loss),希望较低层承载更多权重,因为较低层能够捕获更多的图像纹理/风格,也希望较高�
没有合适的资源?快使用搜索试试~ 我知道了~
温馨提示
关于机器学习的项目,新手可作为入门项目学习,欢迎下载使用。关于机器学习的项目,新手可作为入门项目学习,欢迎下载使用。关于机器学习的项目,新手可作为入门项目学习,欢迎下载使用。关于机器学习的项目,新手可作为入门项目学习,欢迎下载使用。关于机器学习的项目,新手可作为入门项目学习,欢迎下载使用。关于机器学习的项目,新手可作为入门项目学习,欢迎下载使用。关于机器学习的项目,新手可作为入门项目学习,欢迎下载使用。关于机器学习的项目,新手可作为入门项目学习,欢迎下载使用。关于机器学习的项目,新手可作为入门项目学习,欢迎下载使用。关于机器学习的项目,新手可作为入门项目学习,欢迎下载使用。关于机器学习的项目,新手可作为入门项目学习,欢迎下载使用。关于机器学习的项目,新手可作为入门项目学习,欢迎下载使用。关于机器学习的项目,新手可作为入门项目学习,欢迎下载使用。
资源推荐
资源详情
资源评论
收起资源包目录
深度学习实战项目:利用卷积神经网络实现图像风格的迁移(含源码、数据集、说明文档).zip (8个子文件)
cont
NeuralStyle.ipynb 691KB
data
animated.gif 7.8MB
water-lilies-1919-2.jpg 605KB
VanGogh-starry_night_ballance1.jpg 1.84MB
road-to-versailles-at-louveciennes.jpg 372KB
Okerk2.jpg 202KB
VanGogh_Farmhouse.jpeg 531KB
README.md 14KB
共 8 条
- 1
资源评论
c++服务器开发
- 粉丝: 3042
- 资源: 4463
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功