风格迁移

风格迁移算法经历多次定义和更新,现在应用在许多智能手机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. Expo大作战(九)--expo的工作原理,以及版本发行说明

    简要:本系列文章讲会对expo进行全面的介绍,本人从2017年6月份接触expo以来,对expo的研究断断续续,一路走来将近10个月,废话不多说,接下来你看到内容,将全部来与官网 我猜去全部机翻+个人 ...

  2. LeetCode题解之Find All Duplicates in an Array

    1.题目描述 2.问题分析 将数组中的元素 A[i] 放到 A[ A[i] - 1] 的位置.然后遍历一边数组,如果不满足 A[i] == i+1,则将A[i]添加到 结果中. 3.代码 vector ...

  3. LeetCode 题解之Number Complement

    1.题目描述 2.题目分析 使用 C++的 bitset 库进行操作: 3.代码 int findComplement(int num) { bitset<> b(num); string ...

  4. Quick and Easy Installation of Oracle Database 12c on Oracle Linux in Oracle VM VirtualBox

    发贴人 Sergio-Oracle 于2018-4-18 23:10:15在Oracle Linux Introduction How Does This Work? Requirements Bef ...

  5. 大数据开发实战:HDFS和MapReduce优缺点分析

    一. HDFS和MapReduce优缺点 1.HDFS的优势 HDFS的英文全称是 Hadoop Distributed File System,即Hadoop分布式文件系统,它是Hadoop的核心子 ...

  6. It was not possible to find any compatible framework version

    It was not possible to find any compatible framework version The specified framework 'Microsoft.NETC ...

  7. .后面是方法不加引号 ,后面是"名"要加引号

    .后面是方法不加引号 ,后面是"名"要加引号  

  8. cpu的核心数及线程关系

    CPU个数.核心数.逻辑CPU个数:一个物理CPU可以有多个核心,一个CPU核就是一个物理线程,由英特尔开发超线程技术可以把一个物理线程模拟出两个线程来使用,使得单个核心用起来像两个核一样,以充分发挥 ...

  9. Python接口自动化--URL参数的编码和解码 6

    # _*_ coding:utf-8 _*_ #python2 import urllib #有时,需要从上一个请求的url获取参数,传到下一个请求中,中文会显示为编码的形式,这时候就需要进行解码 u ...

  10. Netty入门(八)构建Netty HTTP/HTTPS应用

    HTTP/HTTPS 是最常见的一种协议,这节主要是看一下 Netty 提供的 ChannelHaandler. 一.HTTP Decoder,Encoder 和 Codec HTTP 是请求-响应模 ...