图像风格迁移

文章目录

  • 图像风格迁移
    • 简介
    • 画风迁移
    • 图像风格捕捉
    • 图像风格迁移
    • 图像风格内插
    • 补充说明

简介

  • 利用卷积神经网络实现图像风格的迁移。

画风迁移

  • 简单来说就是将另一张图像的绘画风格在不改变原图图像内容的情况下加入到原图像中,从而“创造”名家风格的绘画作品。
  • 这其中牵扯到很多难题,但是很多问题已经被一一攻克,一般而言,这类问题将涉及到图像风格的捕捉、图像风格的迁移、图像风格的组合等主要流程。
  • 整体看来,卷积核特征的捕捉及应用、图像相干性的优化、画风的组合将是难题。

图像风格捕捉

  • 原理

    • 通过将格拉姆矩阵(gram matrix)应用于卷积神经网络各层能够捕获该层的样式,所以,如果从填充了随机噪声的图像开始,对其进行优化使得网络各层的格拉姆矩阵与目标图像的格拉姆矩阵相匹配,那么不难理解,生成的图像将会模仿目标图像的风格。
    • 可以定义一个style损失函数,计算两组激活输出值各自减去格拉姆矩阵之后计算平方误差。在原始图像和目标图像(莫奈的《睡莲》)上进行训练,将两幅图片输入VGG16的卷积神经网络,对每个卷积层计算上述style损失并多层累加,对损失使用lbfgs进行优化(它需要梯度值和损失值进行优化)。
    • 其实,很多观点认为,匹配图像风格的最好方式是直接匹配所有层的激活值,事实上,格拉姆矩阵效果会更好一些,这点并不容易理解。其背后的原理是,通过计算给定层的每个激活值与其他激活值的乘积,我们获得了神经元之间的相关性。这些相关性可以理解为图像风格的编码,因为他们衡量了激活值的分布情况,而不是激活值本身。
    • 这也带来了几个问题。一个就是零值问题,在任一被乘数为零的情况下,一个向量和自己的转置的点积都会是零,模型无法在零值处识别相关性。由于零值频繁出现,可以在执行点积前为特征值添加一个小的差量delta,delta去-1即可。还有一个问题就是,我们计算了所有激活值的格拉姆矩阵,难道不是应该针对像素通道计算吗?事实上,我们为每个像素的通道都计算了格拉姆矩阵,然后观察它们在整个图像上的相关性,这样做提供了一个捷径:可以计算通道均值并将其用作格拉姆矩阵,这会帮助获得一幅平均风格的图像,更有普适性。
    • 同时,添加了总变分损失(total variation loss),要求网络时刻检查相邻像素的差异,否则,图像会趋于像素化且更加不平缓。某种程度上,这种方法与用于持续检查层权重与层输出的正则化过程非常类似,整体效果相当于在输出像素上添加了一个略微模糊的滤镜。(本质上,这是一种模糊化的方法)该部分的结果是将最后一个组成成分添加到了损失函数中,使得图像整体更像内容图像而不是风格图像。这里所做的,就是有效优化生成图像,使得上层的激活值对应图像内容,下层的激活值对应图像风格,也就是网络底层对应图像风格,网络高层对应图像内容,通过这种方式实现图像风格转换。
  • 代码
    • 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)) - 1def 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()) + gradsself.iterate = K.function([result_image], outputs)self.other = list(other.keys())self.other_values = {}self.shape = result_image.shapeself.loss_value = Noneself.grads_values = Nonedef 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_valuedef 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 imagex = 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.shapeif 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)
      
  • 结果
    • 显然,使用总变分损失后,原来的噪声图像产生的结果更像一个内容图像了。

图像风格迁移

  • 原理

    • 要想将捕获到的风格从一个图像应用到另一个图像上,需要使用一个损失函数来平衡一个图像的内容和另一个图像的风格。
    • 在现有图像上运行上面的代码是不难的,但是结果并不那么令人满意,它看起来似乎将风格应用到了现有图像上,但是程序的不断执行,原始图像不断分解,坚持运行,最后生成一幅与原图独立的新图像,显然,这不是想要。可以通过在损失函数中添加第三个组成成分来解决这个问题,该成分会考虑生成图像和参考图像的差异,这就形成了内容损失(content loss),其作用于网络最后一层。最后一层包含与网络所看到的的映像最近似的内容,因此这也是真正希望保持一致的内容。
    • 通过把各层在网络中的位置纳入考量来微调风格损失(style loss),希望较低层承载更多权重,因为较低层能够捕获更多的图像纹理/风格,也希望较高层更多地参与到图像内容的捕获之中。这样,算法更容易平衡图像内容(使用最后一层)和图像风格(主要较低层)。最后,平衡损失函数的三个部分。
  • 代码
    • def content_loss(base, combination):return K.sum(K.square(combination - base))w, h = load_img(base_image_path).size
      base_image = K.variable(preprocess_image(base_image_path))
      style_image = K.variable(preprocess_image(style2_image_path, target_size=(h, w)))
      combination_image = K.placeholder(style_image.shape)
      input_tensor = K.concatenate([base_image, style_image, combination_image], axis=0)model = vgg16.VGG16(input_tensor=input_tensor, weights='imagenet', include_top=False)feature_outputs = [layer.output for layer in model.layers if '_conv' in layer.name]
      loss_content = content_loss(feature_outputs[-1][0, :, :, :], feature_outputs[-1][2, :, :, :])
      loss_variation = total_variation_loss(combination_image)
      loss_style = K.variable(0.)for idx, layer_features in enumerate(feature_outputs):loss_style += style_loss(layer_features[1, :, :, :], layer_features[2, :, :, :]) * (0.5 ** idx)loss_content /= 40
      loss_variation /= 10000
      loss_total = loss_content + loss_variation + loss_stylecombined_evaluator = Evaluator(loss_total, combination_image, loss_content=loss_content, loss_variation=loss_variation, loss_style=loss_style)
      run(combined_evaluator, preprocess_image(base_image_path), num_iter=100)
      
  • 结果
    • 素材

    • 通过梵高的《星空》作为风格图,将其风格迁移到阿姆斯特丹的老教堂图片上,效果如下(这是风格迁移很经典的图)。

图像风格内插

  • 原理

    • 已经捕获了两种图像风格,希望在另一幅图像上应用一种介于两者之间的风格,如何做呢?可以使用一个包含浮点数值的损失函数,浮点数值表示每种风格应用的百分比。
    • 加载两种不同风格的图像,为每种风格创建损失值,然后引入占位符summerness,调控百分比来调整各种风格的占比。
    • 通过在损失变量中再次增加成分,使得可以指定两种不同风格的权重,当然,也可以进一步增加更多的风格图像并调整权重,但是要注意下调那些“压制”效果好的风格比重,以免其影响较大。
  • 代码
    • w, h = load_img(base_image_path).size
      base_image = K.variable(preprocess_image(base_image_path))
      winter_style_image = K.variable(preprocess_image('data/road-to-versailles-at-louveciennes.jpg', target_size=(h, w)))
      summer_style_image = K.variable(preprocess_image('data/VanGogh_Farmhouse.jpeg', target_size=(h, w)))
      combination_image = K.placeholder(summer_style_image.shape)
      input_tensor = K.concatenate([base_image, summer_style_image, winter_style_image, combination_image], axis=0)model = vgg16.VGG16(input_tensor=input_tensor, weights='imagenet', include_top=False)feature_outputs = [layer.output for layer in model.layers if '_conv' in layer.name]
      loss_content = content_loss(feature_outputs[-1][0, :, :, :], feature_outputs[-1][2, :, :, :])
      loss_variation = total_variation_loss(combination_image)loss_style_summer = K.variable(0.)
      loss_style_winter = K.variable(0.)
      for idx, layer_features in enumerate(feature_outputs):loss_style_summer += style_loss(layer_features[1, :, :, :], layer_features[-1, :, :, :]) * (0.5 ** idx)loss_style_winter += style_loss(layer_features[2, :, :, :], layer_features[-1, :, :, :]) * (0.5 ** idx)loss_content /= 40
      loss_variation /= 10000summerness = K.placeholder()
      loss_total = (loss_content + loss_variation + loss_style_summer * summerness + loss_style_winter * (1 - summerness))combined_evaluator = Evaluator(loss_total, combination_image, loss_content=loss_content, loss_variation=loss_variation, loss_style_summer=loss_style_summer,loss_style_winter=loss_style_winter)
      iterate = K.function([combination_image, summerness], combined_evaluator.iterate.outputs)
      combined_evaluator.iterate = lambda inputs: iterate(inputs + [1.0])  # 1.0夏天风格
      res = run(combined_evaluator, preprocess_image(base_image_path), num_iter=50)path = 'data/summer_winter_%d/20.jpg'
      def save(res, step):img = deprocess_image(res.copy(), h, w)imsave(path % step, img)for step in range(1, 21, 1):combined_evaluator = Evaluator(loss_total, combination_image, loss_content=loss_content, loss_variation=loss_variation, loss_style_summer=loss_style_summer,loss_style_winter=loss_style_winter)iterate = K.function([combination_image, summerness], combined_evaluator.iterate.outputs)combined_evaluator.iterate = lambda inputs: iterate(inputs + [1.0 - step / 20.])  # 0.05-1.0的夏天风格的图像res = run(combined_evaluator, preprocess_image(base_image_path), num_iter=50)save(res, step)
      
  • 结果
    • 素材

    • 通过调控上述两个风格的比例,设定summer风格100%,结果如下。
    • 通过循环调控夏天风格的比例,产生多幅图像,组合形成动态图,如下。

补充说明

  • 参考书籍《深度学习实战:Deep Learning Cookbook》。
  • 具体项目代码上传至我的Github,欢迎Star或者Fork。
  • 博客同步至个人博客网站,欢迎查看。

深度学习实战-图像风格迁移相关推荐

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

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

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

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

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

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

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

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

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

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

  6. TensorFlow练手项目三:使用VGG19迁移学习实现图像风格迁移

    使用VGG19迁移学习实现图像风格迁移 2020.3.15 更新: 使用Python 3.7 + TensorFlow 2.0的实现: 有趣的深度学习--使用TensorFlow 2.0实现图片神经风 ...

  7. 基于深度学习的图像风格转换

    距离上次写博客已经好久好久好久了,真是懈怠的生活节奏,整天混吃等死玩游戏,前些日子做毕业设计时总算又学了点新东西.学了一点深度学习和卷积神经网络的知识,附带着详细学习了一下前段时间我觉得比较有意思的图 ...

  8. 基于深度学习的绘画风格迁移

    代码来自:<零起点TensorFlow快速入门>,这里只记录几个出问题的地方: 1 import arrow 出错 网上直接百度python arrow的安装,到arrow官网下载,然后c ...

  9. 图像风格迁移_图像风格迁移—谷歌大脑团队任意图像风格化迁移论文详解

    点击蓝字关注我们 AI研习图书馆,发现不一样的世界 风格迁移 图像风格化迁移是一个很有意思的研究领域,它可以将一张图的风格迁移到另外一张图像上,由此还诞生了Prisma和Ostagram这样的商业化产 ...

最新文章

  1. php 二维数组排序详解: array_multisort
  2. 43 | 套路篇:网络性能优化的几个思路(上)
  3. Android Intent机制详解
  4. 使用分析函数进行行列转换
  5. Linux目录结构介绍-http://yangrong.blog.51cto.com/6945369/1288072
  6. YSlow[转:大众点评]
  7. 剖析ActiveX控件安全问题
  8. python测试驱动开发百度云_【有书共读】《Python测试驱动开发》读书笔记01
  9. 男人这辈子挺难的 ^^^
  10. CodeSmith 注册
  11. 海康摄像SDK开发笔记(一):海康威视网络摄像头SDK介绍与模块功能
  12. java面经2020
  13. Zipf齐夫分布及Java实现
  14. 为什么在计算机里打开U盘会闪退,U盘闪退怎么办?
  15. 认识涤生大数据的几个月,彻底改变了我
  16. 第三方能查到我们的微信聊天记录?
  17. 家具建材行业B2B电商平台解决方案
  18. 章10 外国语言测试
  19. 第四版人民币冠号资料
  20. 实现 Vue 服务端渲染(Vue SSR)

热门文章

  1. c语言加法器程序代码,利用EDA设计加法器和减法器并且附有程序代码的实验报告...
  2. 三件套都有什么_床上四件套和三件套都包括哪些物品?床上四件套选购指南
  3. mysql误删了数据_MySQL误删数据
  4. css动画(transition,translate,rotate,scale)
  5. Uniapp壁纸小程序源码/双端微信抖音小程序源码
  6. DKMS解决nvidia-smi驱动丢失的问题
  7. 360查出 HEUR/Malware.QVMxx.Gen 病毒含义
  8. lpc1768的gpio库函数_LPC1768之GPIO输入和输出配置基础例程
  9. 电单车中的N车模长得啥样呢?
  10. 程雷被机器人_机器人登台表演节目?程雷惨遭机器人戏耍郭德纲一旁大笑!