风格迁移

风格迁移算法经历多次定义和更新,现在应用在许多智能手机APP上。

风格迁移在保留目标图片内容的基础上,将图片风格引用在目标图片上。



风格本质上是指在各种空间尺度上图像中的纹理,颜色和视觉图案;内容是图像的高级宏观结构。

实现风格迁移背后的关键概念与所有深度学习算法的核心相同:定义了一个损失函数来指定想要实现的目标,并最大限度地减少这种损失。

知道自己想要实现的目标:在采用参考图像的样式的同时保留原始图像的内容。如果我们能够在数学上定义内容和样式,那么最小化的适当损失函数将是以下内容:

loss = distance(style(reference_image) - style(generated_image)) +

distance(content(original_image) - content(generated_image))

distance是一个如L2范数的函数,content计算图片内容表示的函数;style计算图片风格表示的函数。最小化损失函数导致style(generated_image)和引用图片style(reference_image)尽可能接近,而content(generated_image))与内容图片content(original_image)尽可能接近,最终达到风格迁移的目标。

内容损失函数

我们已经知道网络模型前几层的激活函数值表示图片的局部信息,高层网络激活值包括全局性、抽象性的特征信息。换言之,卷积网的不同层的激活值提供了在不同空间尺度上图像内容的分解。因此,期望通过convnet中上层的表示捕获更全局和抽象的图像内容。

内容损失函数的另一种选择是在目标图像上计算的预训练的网络中的上层的激活与生成的图像上计算的相同层的激活之间的L2范数。这保证从上层看生成的图像看起来与原始目标图像类似。假设卷积网的上层看到的是输入图像的内容,那么这就是保存图像内容的一种方式。

风格损失函数

内容损失函数仅使用单个上层,但是Gatys定义的风格损失函数使用多个convnet层:尝试捕获由convnet提取的所有空间比例的样式参考图像的外观,而不仅仅是单个比例。对于风格的损失,Gatys使用图层激活的Gram矩阵:给定图层的要素图的内积。该内积可以理解为表示层的特征之间的相关性的图。这些特征相关性捕获特定空间尺度的模式的统计数据,其在经验上对应于在该尺度下找到的纹理的外观。

因此,风格损失旨在在风格参考图像和生成的图像之间保持不同层的激活内的类似内部相关性。反过来,这保证了在不同空间尺度上找到的纹理在样式参考图像和生成的图像中看起来相似。

可以使用预训练好的网络模型定义损失函数:

  • 通过在目标内容图像和生成的图像之间保持类似的高级图层激活来保留内容。卷积网应该“看到”目标图像和生成的图像包含相同的内容;
  • 通过在低级图层和高级图层的激活中保持类似的相关性来保留样式。特征相关性捕获纹理:生成的图像和样式参考图像应在不同的空间尺度共享相同的纹理。

Keras实现

使用VGG19网络模型实现风格迁移。流程:

  1. 设置一个网络,同时为风格参考图像,目标图像和生成图像计算VGG19图层激活函数值;
  2. 使用在这三个图像上计算的图层激活值来定义前面描述的损失函数,可以将其最小化以实现风格迁移;
  3. 设置梯度下降过程以最小化此损失函数。

定义风格图片、目标图片路径地址;为了确保两张处理图片尺寸相同(尺寸不同会增加处理难度),对两张图片进行resize操作,大小为400px。

定义初始变量

from keras.preprocessing.image import load_img,img_to_array

target_image_path = 'img/protrait.jpg'
style_reference_image_path = 'img/transfer_style_reference.jpg' width, height = load_img(target_image_path).size
img_height = 400
img_width = int(width*img_height/height)

定义辅助函数方便加载、预处理、后期处理VGG19卷积网络接收和产生的图片。

辅助函数

import numpy as np
from keras.applications import vgg19 def preprocess_image(image_path):
img = load_img(image_path,target_size=(img_height,img_width))
img = img_to_array(img)
img = np.expand_dims(img,axis=0)
img = vgg19.preprocess_input(img)
return img def deprocess_image(x):
x[:,:,0] += 103.939#zero-centering 0中心化:减去均值
x[:,:,1] += 116.779
x[:,:,2] += 123.68
x = x[:,:,::-1]#BGR---> RGB
x = np.clip(x,0,255).astype('uint8')
return x

设置vgg19网络。以风格图片、目标图片、生成图片的placeholder三张图片的batch作为输入。

加载预训练VGG19模型,应用

from keras import backend as K

target_image = K.constant(preprocess_image(target_image_path))
style_reference_image=K.constant(preprocess_image(style_reference_image_path))
combination_image = K.placeholder((1,img_height,img_width,3))#包含生成图片
input_tensor = K.concatenate([target_image,style_reference_image,
combination_image],axis=0)#形成输入张量
model = vgg19.VGG19(input_tensor=input_tensor,weights='imagenet',include_top=False) print("Model loaded.")

定义内容损失,确保目标图片和生成图片在VGG19卷积网络的上层网络中相似。

内容损失

def content_loss(base,combination):
return K.sum(K.square(combination-base))

定义风格损失。使用辅助函数计算Gram矩阵:在原始特征矩阵中找到的相关图。

风格损失

def gram_matrix(x):
features = K.batch_flatten(K.permute_dimensions(x,(2,0,1)))
gram = K.dot(features,K.transpose(features))
return gram def style_loss(style,combination):
S = gram_matrix(style)
C = gram_matrix(combination)
channels = 3
size = img_height*img_width
return K.sum(K.square(S-C)) / (4.*(channels ** 2) * (size ** 2))

除了这两种损失函数外,加第三种:总变异损失,其对所生成的组合图像的像素进行操作。它鼓励生成的图像中的空间连续性,从而避免过度像素化的结果。可以将其解释为正则化损失。

变异损失

def total_variation_loss(x):
a = K.square(
x[:,:img_height-1,:img_width-1,:]-
x[:,1:,:img_width-1,:]
)
b = K.square(
x[:,img_height-1,:img_width-1,:] -
x[:,:img_height-1,1:,:]
)
return K.sum(K.pow(a+b, 1.25))

最小化损失函数是三种损失函数的加权平均。为了计算内容损失,只需要使用一个上层网络--block5_conv2网络层;计算风格损失,需要使用多个网络层:从底层网络到高层网络。最后加上变异损失。

依赖于使用风格图片和内容图片,可能需要微调content_weight系数(内容损失对全部损失的贡献程度)。大content_weight意味着目标内容在生成图片中更容易识别。

定义最终损失函数

output_dict = dict([(layer.name,layer.output) for layer in model.layers])
content_layer = 'block5_conv2'
style_layers = ['block1_conv1','block2_conv1',
'block3_conv1','block4_conv1','block5_conv1'] total_variation_weight = 1e-4
style_weight = 1.
content_weight = 0.025 loss = K.variable(0.)#最终损失值
layer_features = outputs_dict[content_layer]
target_image_features = layer_features[0, :, :, :]
combination_features = layer_features[2, :, :, :]
loss+=content_weight*content_loss(target_image_features,combination_features)#加内容损失 for layer_name in style_layers:#加风格损失
layer_features = outputs_dict[layer_name]
style_reference_features = layer_features[1, :, :, :]
combination_features = layer_features[2, :, :, :]
sl = style_loss(style_reference_features, combination_features)
loss += (style_weight / len(style_layers)) * sl #加变异损失,得到最终损失函数值
loss += total_variation_weight * total_variation_loss(combination_image)

最后,使用梯度下降算法。Gatys论文中使用L-BFGS算法。L-BFGS算法包含在SciPy包中,在SciPy实现中有两个限制:

  • 要求的损失函数值、梯度函数值作为两个独立的函数传递;
  • 必须flat展开向量,而图片数组是3D。

单独计算损失函数的值和梯度的值是低效的,因为这样做会导致两者之间的大量冗余计算;这个过程几乎是共同计算过程的两倍。要绕过这个,将设置一个名为Evaluator的Python类,它同时计算损失值和梯度值,在第一次调用时返回损失值,并缓存下一次调用的梯度。

梯度更新

grads = K.gradients(loss, combination_image)[0]
fetch_loss_and_grads = K.function([combination_image], [loss, grads]) class Evaluator(object):
def __init__(self):
self.loss_value = None
self.grads_values = None def loss(self, x):
assert self.loss_value is None
x = x.reshape((1, img_height, img_width, 3))
outs = fetch_loss_and_grads([x])
loss_value = outs[0]
grad_values = outs[1].flatten().astype('float64')
self.loss_value = loss_value
self.grad_values = grad_values
return self.loss_value def grads(self, x):
assert self.loss_value is not None
grad_values = np.copy(self.grad_values)
self.loss_value = None
self.grad_values = None
return grad_values evaluator = Evaluator()

最终,使用SciPy的L-BFGS算法运行梯度下降,每次迭代过程中保存当前生成的图片。

风格迁移循环

from scipy.optimize import fmin_l_bfgs_b
from scipy.misc import imsave
import time result_prefix = 'my_result'
iterations = 20 x = preprocess_image(target_image_path)#目标图片路径
x = x.flatten()#展开,应用l-bfgs for i in range(iterations):
print('Start of iteration', i)
start_time = time.time()
#在生成图片上运行L-BFGS优化;注意传递计算损失和梯度值必须为两个不同函数作为参数
x, min_val, info = fmin_l_bfgs_b(evaluator.loss, x,
fprime=evaluator.grads,maxfun=20)
print('Current loss value:', min_val)
img = x.copy().reshape((img_height, img_width, 3))
img = deprocess_image(img)
fname = result_prefix + '_at_iteration_%d.png' % i
imsave(fname, img)
print('Image saved as', fname)
end_time = time.time()
print('Iteration %d completed in %ds' % (i, end_time - start_time))

请记住,这种技术所实现的仅仅是图像重新构造或纹理转移的一种形式。最适用于具有强烈纹理和高度自相似性的样式参考图像,并且内容目标不需要高级别的细节以便可识别。它通常无法实现相当抽象的功能,例如将一幅肖像的风格转移到另一幅肖像。该算法更接近经典信号处理而不是AI。

另外,请注意运行此风格迁移算法很慢。但是,由设置操作的转换非常简单,只要有适当的训练数据,它就可以通过一个小型,快速的前馈卷积网络学习。因此,可以通过首先花费大量计算周期来生成固定样式参考图像的输入输出训练示例,使用概述的方法,然后训练一个简单的convnet来学习这种特定于样式的转换,从而实现快速样式转换。一旦完成,对给定图像进行风格化是即时的:它只是这个小小的一个前向传递。

小结

  • 风格迁移包括创建新图像,该图像保留目标图像的内容,同时还捕获参考图像的样式;
  • 内容可以通过卷积网络的高层网络捕获;
  • 风格通过卷积网络的不同网络层激活函数的内部相关性计算;
  • 因此,深度学习允许将风格迁移表达为使用由预训练的convnet对定义的损失进行优化的过程。

Keras实现风格迁移的更多相关文章

  1. keras图像风格迁移

    风格迁移: 在内容上尽量与基准图像保持一致,在风格上尽量与风格图像保持一致. 1. 使用预训练的VGG19网络提取特征 2. 损失函数之一是"内容损失"(content loss) ...

  2. 图像风格迁移(Pytorch)

    图像风格迁移 最后要生成的图片是怎样的是难以想象的,所以朴素的监督学习方法可能不会生效, Content Loss 根据输入图片和输出图片的像素差别可以比较损失 \(l_{content} = \fr ...

  3. Gram格拉姆矩阵在风格迁移中的应用

    Gram定义 n维欧式空间中任意k个向量之间两两的内积所组成的矩阵,称为这k个向量的格拉姆矩阵(Gram matrix) 根据定义可以看到,每个Gram矩阵背后都有一组向量,Gram矩阵就是由这一组向 ...

  4. 『cs231n』通过代码理解风格迁移

    『cs231n』卷积神经网络的可视化应用 文件目录 vgg16.py import os import numpy as np import tensorflow as tf from downloa ...

  5. fast neural style transfer图像风格迁移基于tensorflow实现

    引自:深度学习实践:使用Tensorflow实现快速风格迁移 一.风格迁移简介 风格迁移(Style Transfer)是深度学习众多应用中非常有趣的一种,如图,我们可以使用这种方法把一张图片的风格“ ...

  6. Distill详述「可微图像参数化」:神经网络可视化和风格迁移利器!

    近日,期刊平台 Distill 发布了谷歌研究人员的一篇文章,介绍一个适用于神经网络可视化和风格迁移的强大工具:可微图像参数化.这篇文章从多个方面介绍了该工具. 图像分类神经网络拥有卓越的图像生成能力 ...

  7. ng-深度学习-课程笔记-14: 人脸识别和风格迁移(Week4)

    1 什么是人脸识别( what is face recognition ) 在相关文献中经常会提到人脸验证(verification)和人脸识别(recognition). verification就 ...

  8. [DeeplearningAI笔记]卷积神经网络4.6-4.10神经网络风格迁移

    4.4特殊应用:人脸识别和神经网络风格转换 觉得有用的话,欢迎一起讨论相互学习~Follow Me 4.6什么是神经网络风格转换neural style transfer 将原图片作为内容图片Cont ...

  9. cs231n---卷积网络可视化,deepdream和风格迁移

    本课介绍了近年来人们对理解卷积网络这个“黑盒子”所做的一些可视化工作,以及deepdream和风格迁移. 1 卷积网络可视化 1.1 可视化第一层的滤波器 我们把卷积网络的第一层滤波器权重进行可视化( ...

随机推荐

  1. Charles抓取https请求

    最近公司将Windows产品的http请求,替换成https请求了,当https请求超过5次失败,就自动切换回http请求.测试时使用Charles抓包测试. 一.http抓包 http抓包比较简单, ...

  2. 【转】python version 2.7 required,which was not found in the registry

    源地址:http://www.cnblogs.com/thinksasa/archive/2013/08/26/3283695.html 方法:新建一个register.py 文件,把一下代码贴进去, ...

  3. EntityFramework Code-First 简易教程(一)

    前言:学习了EF框架这么久,还没有好好总结一番,正好遇到一国外的网站,发现不错,随即翻译过来,一是让自己复习一遍,二是供广大初学者学习,翻译过程中加入了一些自己的理解,如有错误,还请指出,多谢多谢.好 ...

  4. Huawei 常用基本配置命令一

    华为交换机的三种视图: 用户视图, 系统视图, 接口视图 用户视图: 刚开始登入交换机时的视图,一般看到的是尖括号<> . save // 配置完交换机后保存当前配置的命令 system- ...

  5. setuid、setgid、sticky的权限简单用法

    如何设置setuid.setgid.sticky的权限: setuid :置于 u 的 x 位,原位置有执行权限,就置为 s,没有了为 S . chmod 4xxx file chmod u+s xx ...

  6. MySQL基础之 索引

    MySQL索引讲解 索引的好处: MySQL索引的建立对于MySQL的高效运行是很重要的,索引可以大大提高MySQL的检索速度. 打个比方,如果合理的设计且使用索引的MySQL是一辆兰博基尼的话,那么 ...

  7. PyQt5--EventSender

    # -*- coding:utf-8 -*- ''' Created on Sep 14, 2018 @author: SaShuangYiBing Comment: This example is ...

  8. Fedora29 安装 spring tool suite 4.2

    下载安装包 下载地址:https://spring.io/tools 文件:STS-4.2.0.RELEASE.tar.gz 解压部署软件包 解压文件至 /opt/STS-4.2.0.RELEASE/ ...

  9. Python datetime.md

    datetime datetime模块包含了一些用于时间解析.格式化.计算的函数. Times 时间值由time类来表示, Times有小时, 分, 秒和微秒属性. 以及包含时区信息. 初始化time ...

  10. 一些node模块的学习思考

    12月14日清单 1 readline模块 var readline = require("readline"); // input 是必须的,output是可选的 rl = re ...