文档章节

Prisma修图软件的图片风格转换算法

断桥残雪断桥残雪
 断桥残雪断桥残雪
发布于 2016/12/07 17:14
字数 2821
阅读 484
收藏 1

在计算机视觉领域,卷积神经网络的表现十分亮眼。今天要讲的图片风格转换算法,也就是前阵子突然火起来的图片处理软件Prisma的主要算法(具体模型可能有点差异),并且谷歌的Deep Dream项目也是类似算法,都是基于深度卷积神经网络模型。关于卷积神经网络的具体介绍,请阅读以下历史文章:

我所理解的深度学习(一)——BP图模型算法

我所理解的深度学习(二)——卷积神经网络基础

我所理解的深度学习(三)——卷积神经网络应用①

我所理解的深度学习(四)——卷积神经网络应用②

(来自个人微信公众号:deeplearningdigest)

1.图片风格转换模型

那么什么是图片的风格转换呢?风格转换是指已知有两张图片,其中图片A作为风格,另一个图片B作为内容,现在来再生一张图片C,图片C的风格是基于图片A的,而C的内容却是类似于图片B。

例如现在将下面的广州塔图片作为内容图片:

 

将下面梵高的《星空》作为风格图片:

 

利用风格转换算法我们就可以让深度卷积神经网络重新生成一张下面的图片:

 

那么图片转换算法具体是怎么样的呢?最直观的解释就是:我们希望构建一个网络,这个网络既能学习到内容图片的内容,又能学习到风格图片的风格,并且能在二者之间做出折中,最后生成一张全新的照片;同时,我们希望这个网络是可微的,也就是说我们希望可以使用梯度下降算法去优化网络,因此我们就需要去创建一个目标损失函数。

这里我们需要构建一个深度卷积神经网络,这个网络需要分别学习到内容和风格,并且我们需要观察其内容还原以及风格还原的效果。如下图所示,我们将内容图片以及风格图片分别输入到深度卷积神经网络(这里使用的是原始的VGG网络)中,同时利用每一层学习到的特征图来分别再现内容和风格。在下面一行图片中可以看到,对于内容再现而言,随着网络层次的加深,内容再现将会越来越偏离原始内容图片;而在上面一行图片中可以看到,对于风格再现而言,随着网络层次的加深,风格再现将会越来越接近原始风格图片。(其中再现的五层卷积神经网络分别是conv1_1,conv2_1,conv3_1,conv4_1,conv5_1)

 

下面一幅图中包含几个著名的艺术照片,小图是艺术图片,然后用于图A得到的几幅再生图片:

 

在图片合成过程中,内容与风格的学习肯定是一个折中的过程,而不能完全分离,虽然训练内容照片的损失函数与训练风格图片的损失函数是分开的,但是我们可以强加一个权重因子来表明侧重于风格还是侧重于内容。当侧重于内容时,图片看起来就会更像原始的内容图片,而不太像原始的风格图片;当侧重于风格时,图片看起来就会更像原始的风格图片,而不太像原始的内容图片。下面一幅图恰好说明了二者之间不同侧重的结果。其中,最上面一行数字表示内容因子:风格因子,数值越大,再生图片越侧重于内容。

接下来我们具体介绍一下如何使用深度卷积神经网络构建一个画风转换模型。

 

我们的网络是建立在VGG网络基础上的(如果不了解什么是VGG网络的话,可以看文章开头介绍的几篇历史文章),我们使用了19层的VGG网络特征作为网络的初始预训练权重,其中我们使用了16层卷积层和5层池化层,这里池化方法用的是平均池化,因为我们发现在图片合成中平均池化比最大池化性能表现更佳。

 

首先我们看如何定义基于内容的损失函数L_con。

 

当图片输入到深度卷积神经网络中时,经过多层非线性的卷积与池化操作,我们得到了特征信息的高度表达。然后,我们通过输入一张白噪声图片与一张原始的内容图片进行对比,对比两个不同的输入情况下网络的某一层还原后的内容差异。假设我们对比的是第l层卷积层,并且该层特征图的大小为M,特征图的个数为N,p为原始图片,x为又白噪声再生的图片,假定P_l为原始图片在l层的分布表示,假定F_l为白噪声图在l层的分布表示(因为白噪声相当于没有输入,因此这里相当于是网络再生的),那么我们可以定义内容的损失函数为:

 

然后看如何定义基于风格的损失函数L_sty。

 

这里我们需要定义一个Gram矩阵,Gram矩阵的作用是表示不同特征图之间的关系。Gram矩阵的计算公式如下,即每一层的特征矩阵的元素平方和。

为了得到符合原始风格图片的风格,我们还是采取类似于内容再生中的方法,即使用一幅白噪声图片与原始的风格图片去比较每一个卷积层的特征分布差异。假定a为原始风格图片,x为再生图片,A_l以及G_l分别为它们在第l层的分布表示,于是第l层的风格损失为:

由于风格层通常需要设置为多层去观察,并且每一层都有一定的权重,因此总风格损失为:

其中wl为每一层的权重因子。

 

 

最后需要结合风格损失和内容损失得到总损失函数。

 

输入一幅白噪声图片,我们希望它所得到的再现图片与内容图片的再现的距离达到最小,同时也要与风格图片的再现的距离达到最小。假定p为内容图片,a为风格图片,x为再生图片,我们要优化的总目标函数为:

其中alpha与beta均为内容及风格的权重因子。我们让卷积层conv4_2作为内容的特征层,而让卷积层conv1_1,conv2_1,conv3_1,conv4_1,conv5_1作为风格的特征层,并且分别给予它们一定的权重因子。

 

2.使用TensorFlow实现模型

 

首先导入基本的库:

import tensorflow as tf

import numpy as np

import scipy.io

import scipy.misc

import os

 

设置一些基本的全局变量:

IMAGE_W = 650 

IMAGE_H = 566

#内容图片

CONTENT_IMG =  './images/15.jpg'

#风格图片

STYLE_IMG = './images/StarryNight.jpg'

#输出目录

OUTOUT_DIR = './results5'

OUTPUT_IMG = 'results.png'

#预训练的VGG19层深度卷积神经网络模型

VGG_MODEL = 'imagenet-vgg-verydeep-19.mat'

#噪声比例为0.7

INI_NOISE_RATIO = 0.7

#风格强度为500

STYLE_STRENGTH = 500

ITERATION = 5000

#内容层

CONTENT_LAYERS =[('conv4_2',1.)]

#风格层

STYLE_LAYERS=[('conv1_1',1.),('conv2_1',1.5),('conv3_1',2.),('conv4_1',2.5),('conv5_1',3.)]

MEAN_VALUES = np.array([123, 117, 104]).reshape((1,1,1,3))

 

构建卷积层及池化层,注意这里使用的是平均池化:

def build_net(ntype, nin, nwb=None):

  if ntype == 'conv':

    return tf.nn.relu(tf.nn.conv2d(nin, nwb[0], strides=[1, 1, 1, 1], padding='SAME')+ nwb[1])

  elif ntype == 'pool':

    return tf.nn.avg_pool(nin, ksize=[1, 2, 2, 1],

                  strides=[1, 2, 2, 1], padding='SAME')

 

构建获取VGG网络参数的函数:

def get_weight_bias(vgg_layers, i,):

  weights = vgg_layers[i][0][0][0][0][0]

  weights = tf.constant(weights)

  bias = vgg_layers[i][0][0][0][0][1]

  bias = tf.constant(np.reshape(bias, (bias.size)))

  return weights, bias

 

构建19层深度神经网络模型,并且使用VGG网络参数作为初始参数:

def build_vgg19(path):

  net = {}

  vgg_rawnet = scipy.io.loadmat(path)

  vgg_layers = vgg_rawnet['layers'][0]

  net['input'] = tf.Variable(np.zeros((1, IMAGE_H, IMAGE_W, 3)).astype('float32'))

  net['conv1_1'] = build_net('conv',net['input'],get_weight_bias(vgg_layers,0))

  net['conv1_2'] = build_net('conv',net['conv1_1'],get_weight_bias(vgg_layers,2))

  net['pool1']   = build_net('pool',net['conv1_2'])

  net['conv2_1'] = build_net('conv',net['pool1'],get_weight_bias(vgg_layers,5))

  net['conv2_2'] = build_net('conv',net['conv2_1'],get_weight_bias(vgg_layers,7))

  net['pool2']   = build_net('pool',net['conv2_2'])

  net['conv3_1'] = build_net('conv',net['pool2'],get_weight_bias(vgg_layers,10))

  net['conv3_2'] = build_net('conv',net['conv3_1'],get_weight_bias(vgg_layers,12))

  net['conv3_3'] = build_net('conv',net['conv3_2'],get_weight_bias(vgg_layers,14))

  net['conv3_4'] = build_net('conv',net['conv3_3'],get_weight_bias(vgg_layers,16))

  net['pool3']   = build_net('pool',net['conv3_4'])

  net['conv4_1'] = build_net('conv',net['pool3'],get_weight_bias(vgg_layers,19))

  net['conv4_2'] = build_net('conv',net['conv4_1'],get_weight_bias(vgg_layers,21))

  net['conv4_3'] = build_net('conv',net['conv4_2'],get_weight_bias(vgg_layers,23))

  net['conv4_4'] = build_net('conv',net['conv4_3'],get_weight_bias(vgg_layers,25))

  net['pool4']   = build_net('pool',net['conv4_4'])

  net['conv5_1'] = build_net('conv',net['pool4'],get_weight_bias(vgg_layers,28))

  net['conv5_2'] = build_net('conv',net['conv5_1'],get_weight_bias(vgg_layers,30))

  net['conv5_3'] = build_net('conv',net['conv5_2'],get_weight_bias(vgg_layers,32))

  net['conv5_4'] = build_net('conv',net['conv5_3'],get_weight_bias(vgg_layers,34))

  net['pool5']   = build_net('pool',net['conv5_4'])

  return net

 

构建内容损失函数:

def build_content_loss(p, x):

  M = p.shape[1]*p.shape[2]

  N = p.shape[3]

  loss = (1./(2* N**0.5 * M**0.5 )) * tf.reduce_sum(tf.pow((x - p),2))  

  return loss

 

构建Gram矩阵以及风格损失函数:

def gram_matrix(x, area, depth):

  x1 = tf.reshape(x,(area,depth))

  g = tf.matmul(tf.transpose(x1), x1)

  return g

 

def build_style_loss(a, x):

  #M为面积。N为深度。

  M = a.shape[1]*a.shape[2]

  N = a.shape[3]

  A = gram_matrix_val(a, M, N )

  G = gram_matrix(x, M, N )

  loss = (1./(4 * N**2 * M**2)) * tf.reduce_sum(tf.pow((G - A),2))

  return loss

 

构建图片的读写函数,这里图片的读取过程减去了图片均值:

def read_image(path):

  image = scipy.misc.imread(path)

  image = image[np.newaxis,:IMAGE_H,:IMAGE_W,:] 

  image = image - MEAN_VALUES

  return image

 

def write_image(path, image):

  image = image + MEAN_VALUES

  image = image[0]

  image = np.clip(image, 0, 255).astype('uint8')

  scipy.misc.imsave(path, image)

 

训练主函数入口,与其他神经网络训练过程不同的是,这里的输入数据只有三个:内容图片、风格图片、白噪声图片,而内容图片与风格图片是作为比照的,白噪声图片输入用来再生图片,通过使得总误差函数达到极小值,采取梯度下降算法,最终训练2000个Epoch。得到文末的几幅图片结果。

def main():

  net = build_vgg19(VGG_MODEL)

  sess = tf.Session()

  sess.run(tf.initialize_all_variables())

  #产生一张白噪声图片

  noise_img = np.random.uniform(-20, 20, (1, IMAGE_H, IMAGE_W, 3)).astype('float32')

  content_img = read_image(CONTENT_IMG)

  style_img = read_image(STYLE_IMG)

 

  #把输入赋给content_img

  sess.run([net['input'].assign(content_img)])

  

  cost_content = sum(map(lambda l,: l[1]*build_content_loss(sess.run(net[l[0]]) ,  net[l[0]])

    , CONTENT_LAYERS))

 

  sess.run([net['input'].assign(style_img)])

  cost_style = sum(map(lambda l: l[1]*build_style_loss(sess.run(net[l[0]]) ,  net[l[0]])

    , STYLE_LAYERS))

 

  cost_total = cost_content + STYLE_STRENGTH * cost_style

  optimizer = tf.train.AdamOptimizer(2.0)

 

  train = optimizer.minimize(cost_total)

  sess.run(tf.initialize_all_variables())

  sess.run(net['input'].assign( INI_NOISE_RATIO* noise_img + (1.-INI_NOISE_RATIO) * content_img))

 

  if not os.path.exists(OUTOUT_DIR):

      os.mkdir(OUTOUT_DIR)

 

  for i in range(ITERATION):

    sess.run(train)

    if i%2 ==0:

      result_img = sess.run(net['input'])

      print sess.run(cost_total)

      write_image(os.path.join(OUTOUT_DIR,'%s.png'%(str(i).zfill(4))),result_img)

  

  write_image(os.path.join(OUTOUT_DIR,OUTPUT_IMG),result_img)

 

 

3.实验结果

 

风格图片:梵高——《星空》

 

 

内容图片1:

 

 

再生图片1(大小有所裁剪):

 

 

内容图片2:

 

 

再生图片2:

 

 

© 著作权归作者所有

断桥残雪断桥残雪
粉丝 53
博文 139
码字总数 94909
作品 0
广州
程序员
私信 提问
修图手残星人有福了:Adobe 让你几秒钟就能自动修出大师级照片

摘要 修图越来越简单了,这件事会让你开心吗? 每次拍完照你要花多久修图才发到朋友圈? 不论你的答案是几分钟,都意味着对修图这件事的重视,即便那些说自己喜欢用原图的人(比如我自己),...

谁是大英雄
2017/12/13
0
0
线上分享|Prisma算法理论讲解分析与tensorflow复现

极视角线上分享第一期(微信群) 时间:本周五晚(10月14日)8:00-9:30 分享人:万元芳(创业团队AG Group创始人) 主题:Prisma核心算法理论讲解分析与tensorflow复现 导语 “全球有一半的人...

dany_dai
2016/10/14
682
0
深度学习微信精选文章

公众号——深度学习每日摘要 所有文章(持续更新中): 聊聊语音识别的发展历程 说说重要的贝叶斯公式吧 我对入门深度学习的切身体会 聊聊隐马尔科夫模型(HMM) 关于防止过拟合的一些想法 ...

断桥残雪断桥残雪
2016/12/02
590
2
梵高眼中的世界(一)实时图像风格转换简介

本文目录: Introduction Related work Methods Gram 矩阵 Batch Normalization Introduction 不久前,一个名叫Prisma的APP在微博和朋友圈火了起来。Prisma是个能够将图像风格转换为艺术风格...

Efackw13
2017/05/28
0
0
景不美,P 图软件补,自动修图系统背后的技术原理是什么?

研究人员们将推出一个能够按照专业摄影师的风格进行自动修图的新系统,分分钟拍大片。 如果你是个不会拍照的男孩子,恰好你的女友又是个喜欢拍照的姑娘,那么你之后的拍照水平可能会被疯狂吐...

图普科技
2017/08/09
0
0

没有更多内容

加载失败,请刷新页面

加载更多

实战项目-学成在线(一)

之前看的黑马程序员实战项目之一,打算以博客的形式写出来,也让自己重新温习一下。 1、项目背景 略(就是当前这东西很火,我们重点在开发,这些就略过) 2、功能模块 门户,学习中心,教学管...

lianbang_W
22分钟前
2
0
基于Vue的数字输入框组件开发

本文转载于:专业的前端网站➫基于Vue的数字输入框组件开发 1、概述 Vue组件开发的API:props、events和slots 2、组件代码 github地址:https://github.com/MengFangui/VueInputNumber 效果:...

前端老手
30分钟前
2
0
百度地图根据经纬度获取运动轨迹

<!DOCTYPE html><html><head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="initial-scale=1.0, user-scalable=n......

泉天下
32分钟前
4
0
学习记录(day04-axios增删改查、v-for循环、页面加载成功处理函数)

[TOC] 1.1 基本语法:插值表达式 <template> <div> {{username}} <br/> {{1+2+3}} <br/> {{'你的名字是:' + username}} <br/> {{'abc'.split('')}} </div><......

庭前云落
今天
3
0
CentOS Linux 7上将ISO映像文件写成可启动U盘

如今,电脑基本上都支持U盘启动,所以,可以将ISO文件写到U盘上,用来启动并安装操作系统。 我想将一个CentOS Linux 7的ISO映像文件写到U盘上,在CentOS Linux 7操作系统上,执行如下命令: ...

大别阿郎
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部