作者:阿布🐶 未经本人允许禁止转载
所谓深度学习(Deep Learning)中的 “深度(Deep)” 即意为层数。神经网络的每一层都会对图片特征进行提取,而 “艺术风格” 则是各层提取结果的叠加 在人类的视觉系统中,从眼睛看到一件实体,到在脑中形成图像的概念,中间经历了无数层神经元的传递。底层的神经元获取到的信息是具体的,越到高层越抽象。
在人类的视觉系统中,从眼睛看到一件实体,到在脑中形成图像的概念,中间经历了无数层神经元的传递。底层的神经元获取到的信息是具体的,越到高层越抽象 使用计算机模拟这个网络,将每一层的结构分析出来,能看到在采样过程中,底层网络对于图像的细节表达得特别清楚,越到高层像素保留得越少,轮廓信息越多
使用深度学习作画最早是三个德国研究员想把计算机调教成梵高,他们研发了一种算法,模拟人类视觉的处理方式。具体是通过训练多层卷积神经网络(CNN),让计算机识别,并学会梵高的 “风格”、然后将任何一张普通的照片变成梵高的《星空》
大致实现思路如下:
- 吸收用户拍摄的照片
- 让计算机学会星空图的风格
- 计算机输出自己做的“新画”
他们开创了Deep Art公司,他们的用户可以花上 19 欧买一张适合明信片用的作品,或者多掏 100 欧,买一张大尺寸油画级别的艺术画
Prisma 比 Deep Art 先进的地方在于,它大大缩短了图像处理的时间,每张照片在 Prisma 系统内的处理时间控制在秒级别。 prisma诞生于俄罗斯,是一个仅有4个年轻人历时一个半月开发出的图片处理应用,将照片赋予毕加索式的艺术风格,是它们的广告语,它的核心技术思想就是卷积神经网络可以被看做是一个机器艺术家。
本章内容主要讲述我封装的两套实现prisma效果的代码使用。你不需要有任何机器学习,图像处理理论基础,读完这篇文章后,下载github上的代码,安装好环境(caffe的安装环境比较复杂)仿照这里的使用方式你就可以自己制作私人的艺术风格照片了
首先导入库
from __future__ import division
import matplotlib.pylab as plt
%matplotlib inline
import os
from PrismaCaffe import CaffePrismaClass
from PrismaTensor import TensorPrismaClass
import PrismaTensor
import PrismaHelper
import glob
import numpy as np
import PIL.Image
import ZCommonUtil
import itertools
复制代码
1 基于caffe框架实现prisma
1.1 首先我们直观感受一下什么叫做机器艺术家
如下代码显示出所有演示实例图片(主角还是我家阿布🐶)
sample_list = glob.glob("../sample/*.jpg")
fig, axs = plt.subplots(nrows=2, ncols=4, figsize=(15, 6));
axs_list = list(itertools.chain.from_iterable(axs))
for ind, ax in zip(range(2 * 4), axs_list):
iter_fn = sample_list[ind]
iter_img = plt.imread(iter_fn)
ax.set_title(os.path.basename(iter_fn))
ax.imshow(iter_img);
ax.set_axis_off()
复制代码
使用caffe封装的大名鼎鼎的google deepdream来看看效果
下面的代码初始化一个CaffePrisma工作实例,显示阿布美照,打印出模型网络的前几个浅层指令
cp = CaffePrismaClass(dog_mode=False)
nbks = filter(lambda nbk: nbk[-8:-1] <> '_split_', cp.net.blobs.keys()[1:-2])[:10]
abu4_file = '../sample/abu4.jpg'
PrismaHelper.show_array_ipython(np.float32(cp.resize_img(PIL.Image.open(abu4_file))))
nbks
复制代码
['conv1/7x7_s2',
'pool1/3x3_s2',
'pool1/norm1',
'conv2/3x3_reduce',
'conv2/3x3',
'conv2/norm2',
'pool2/3x3_s2',
'inception_3a/1x1',
'inception_3a/3x3_reduce',
'inception_3a/3x3']
复制代码
下面我们用这几个浅层神经元对原图风格化的效果展示
for nbk in nbks[2:-2]:
d_img = cp.fit_img(abu4_file, resize=True, nbk=nbk, iter_n=10)
PrismaHelper.show_array_ipython(np.float32(d_img))
复制代码
本章的内容的代码及示例并不能做出如下所示的效果的风格图像,如下所示的风格图像将在下一章详细讲解原理及代码,本章是基础
fig, axs = plt.subplots(nrows=1, ncols=3, figsize=(20, 10));
up_list = ['../show/up2.jpg', '../show/up3.jpg', '../show/up4.jpg']
for ind, ax in zip(range(1 * 3), axs):
iter_fn = up_list[ind]
iter_img = plt.imread(iter_fn)
# ax.set_title(os.path.basename(iter_fn))
ax.imshow(iter_img);
ax.set_axis_off()
复制代码
有没有感觉到特征的识别由浅入深的一步一步增强,也就是从edge,到shape,再到复杂的shape循序渐进的过程,试着感觉一下有没有慢慢一点点睁开眼睛的感觉,这里主要是把每层的特质放大进行夸张凸显
看一下CaffePrismaClass初始化代码
class CaffePrismaClass(BasePrismaClass):
def __init__(self, dog_mode=False):
self.net_fn = '../mode/deploy.prototxt'
if not dog_mode:
self.param_fn = '../mode/bvlc_googlenet.caffemodel'
mu = np.float32([104.0, 117.0, 123.0])
else:
self.param_fn = '../mode/dog_judge_train_iter_5000.caffemodel'
model_mean_file = '../mode/mean.binaryproto'
mean_blob = caffe.proto.caffe_pb2.BlobProto()
mean_blob.ParseFromString(open(model_mean_file, 'rb').read())
mean_npy = caffe.io.blobproto_to_array(mean_blob)
mu = np.float32(mean_npy.mean(2).mean(2)[0])
model = caffe.io.caffe_pb2.NetParameter()
text_format.Merge(open(self.net_fn).read(), model)
model.force_backward = True
open('tmp.prototxt', 'w').write(str(model))
self.net = caffe.Classifier('tmp.prototxt', self.param_fn,
mean=mu,
channel_swap=(
2, 1, 0))
复制代码
注意到上面的 bvlc_googlenet.caffemodel是google已经训练好的模型,可以通过我的网盘链接下载,提取码为eup6
至于下面代码中的../mode/dog_judge_train_iter_5000.caffemodel这个是我在爬取百度图片各种狗狗的图片,使用caffe训练模型分类中自己训练好的模型,你可以不必管
备注:上面的代码中mu = np.float32([104.0, 117.0, 123.0])常数的选择是从模型训练时的网络配置中决定的,它们只是为了提高训练识别速度,如下配置
transform_param {
mirror: true
crop_size: 224
mean_value: 104
mean_value: 117
mean_value: 123
}
复制代码
下面我们使用abu1来逐步介绍封装类的具体使用方式
abu1_file = '../sample/abu1.jpg'
PrismaHelper.show_array_ipython(np.float32(cp.resize_img(PIL.Image.open(abu1_file))))
复制代码
1.2 直接使用某个神经元层的效果
d_img = cp.fit_img(abu1_file, resize=True, nbk='conv1/7x7_s2', iter_n=10)
PrismaHelper.show_array_ipython(np.float32(d_img))
复制代码
1.3 配合PIL预处理方式处理图像
你如果用过prisma,你一定会知道prisma有很多效果比如黑白,水墨,怎么以实现吗,我们如下代码使用PIL库预处理一下图片,然后再做处理,如下的基类封装了接口和预处理操作
class BasePrismaClass(six.with_metaclass(ABCMeta, object)):
@abstractmethod
def fit_guide_img(self, img_path, gd_path, resize=False, size=480, enhance=None, iter_n=10, **kwargs):
pass
@abstractmethod
def fit_img(self, img_path, resize=False, size=480, enhance=None, iter_n=10, **kwargs):
pass
@abstractmethod
def gd_features_make(self, *args, **kwargs):
pass
@abstractmethod
def do_prisma(self, *args, **kwargs):
pass
def resize_img(self, r_img, base_width=480, keep_size=True):
if keep_size:
w_percent = (base_width / float(r_img.size[0]))
h_size = int((float(r_img.size[1]) * float(w_percent)))
else:
h_size = base_width
r_img = r_img.resize((base_width, h_size), PIL.Image.ANTIALIAS)
return r_img
def handle_enhance(self, r_img, enhance, sharpness=8.8, brightness=1.8, contrast=2.6, color=7.6, contour=2.6):
if enhance == 'Sharpness':
enhancer = ImageEnhance.Sharpness(r_img)
s_img = enhancer.enhance(sharpness)
img = s_img
elif enhance == 'Brightness':
enhancer = ImageEnhance.Brightness(r_img)
b_img = enhancer.enhance(brightness)
img = b_img
elif enhance == 'Contrast':
enhancer = ImageEnhance.Contrast(r_img)
t_img = enhancer.enhance(contrast)
img = t_img
elif enhance == 'Color':
enhancer = ImageEnhance.Color(r_img)
c_img = enhancer.enhance(color)
img = c_img
elif enhance == 'CONTOUR':
enhancer = ImageEnhance.Contrast(r_img)
t_img = enhancer.enhance(contour)
fc_img = t_img.filter(ImageFilter.CONTOUR)
img = fc_img
elif enhance == 'EDGES':
ffe_img = r_img.filter(ImageFilter.FIND_EDGES)
img = ffe_img
elif enhance == 'EMBOSS':
feb_img = r_img.filter(ImageFilter.EMBOSS)
img = feb_img
elif enhance == 'EEM':
feem_img = r_img.filter(ImageFilter.EDGE_ENHANCE_MORE)
img = feem_img
elif enhance == 'EE':
fee_img = r_img.filter(ImageFilter.EDGE_ENHANCE)
img = fee_img
else:
img = r_img
return img
复制代码
下面使用conv2/3x3和预处理效果综合显示效果看看(由于篇幅只运行两个效果,其它的读者可自行打开注释的代码查看效果)
d_img = cp.fit_img(abu1_file, resize=True, nbk='conv2/3x3', iter_n=10)
PrismaHelper.show_array_ipython(np.float32(d_img))
d_img = cp.fit_img(abu1_file, resize=True, nbk='conv2/3x3', enhance='Sharpness', iter_n=10)
PrismaHelper.show_array_ipython(np.float32(d_img))
d_img = cp.fit_img(abu1_file, resize=True, nbk='conv2/3x3', enhance='Contrast', iter_n=10)
PrismaHelper.show_array_ipython(np.float32(d_img))
# d_img = cp.fit_img(abu1_file, resize=True, nbk='conv2/3x3', enhance='Brightness', iter_n=10)
# PrismaHelper.show_array_ipython(np.float32(d_img))
# d_img = cp.fit_img(abu1_file, resize=True, nbk='conv2/3x3', enhance='CONTOUR', iter_n=10)
# PrismaHelper.show_array_ipython(np.float32(d_img))
# d_img = cp.fit_img(abu1_file, resize=True, nbk='conv2/3x3', enhance='Color', iter_n=10)
# PrismaHelper.show_array_ipython(np.float32(d_img))
# d_img = cp.fit_img(abu1_file, resize=True, nbk='conv2/3x3', enhance='EEM', iter_n=10)
# PrismaHelper.show_array_ipython(np.float32(d_img))
# d_img = cp.fit_img(abu1_file, resize=True, nbk='conv2/3x3', enhance='EE', iter_n=10)
# PrismaHelper.show_array_ipython(np.float32(d_img))
复制代码
以上的这些预处理操作会有很多种组合及变种,比如我在代码中针对CONTOUR的处理是先做了个Contrast然后再CONTOUR这样效果针对CONTOUR效果会更好
elif enhance == 'CONTOUR':
enhancer = ImageEnhance.Contrast(r_img)
t_img = enhancer.enhance(contour)
fc_img = t_img.filter(ImageFilter.CONTOUR)
img = fc_img
复制代码
如果你想做批处理风格画操作,使用fit_batch_img,其实这里没有完善,后期会修改为类似tensor prisma的批量处理实现方式,使用卷积层识别指令和图像预处理指令做product求笛卡尔积,遍历所有风格画组合
def fit_batch_img(self, img_path, resize=False, size=480, enhance=None):
"""
批量处理,但不支持并行,后修改为类似tensor prisma中的并行模式
:param img_path:
:param resize:
:param size:
:param enhance:
:return:
"""
r_img = PIL.Image.open(img_path)
if resize:
r_img = self.resize_img(r_img, size)
org_img = self.handle_enhance(r_img, enhance)
e_str = '' if enhance is None else '_' + enhance.lower()
save_path = os.path.dirname(img_path) + '/batch_caffe/' + e_str
ZCommonUtil.ensure_dir(save_path)
org_img_path = save_path + 'org.jpeg'
with open(org_img_path, 'w') as f:
org_img.save(f, 'jpeg')
org_img = np.float32(org_img)
start = 1
end = self.net.blobs.keys().index('inception_4c/pool')
nbks = self.net.blobs.keys()[start:end]
"""
不能使用多进程方式在这里并行执行,因为caffe.classifier.Classifier不支持序列化
Pickling of "caffe.classifier.Classifier" instances is not enabled
so mul process no pass
"""
for nbk in nbks:
if nbk[-8:-1] == '_split_':
continue
fn = save_path + nbk.replace('/', '_') + '.jpg'
deep_img = self.do_prisma(org_img, iter_n=10, end=nbk)
PrismaHelper.save_array_img(deep_img, fn)
return save_path
复制代码
封装的代码是deepdream的代码它由imagenet大量的图片数据来训练神经网络,并且使用google_lenet的深度模型网络大大提高了识别度,使这个网络可以判断出图片中的事物,类似于之前我的文章训练狗狗图片,对狗狗进行分类识别,风格画的实现原理是不止识别,它还把图片的特质从它的模型中选取图像重新在原图进行渲染,下一章将有重点介绍这部分的实现代码原理,这里暂且带过。具体请查看git上文件PrismaCaffe.py,核心代码如下
def _objective_l2(self, dst):
dst.diff[:] = dst.data
def _objective_guide_features(self, dst, guide_features):
x = dst.data[0].copy()
y = guide_features
ch = x.shape[0]
x = x.reshape(ch, -1)
y = y.reshape(ch, -1)
a = x.T.dot(y)
dst.diff[0].reshape(ch, -1)[:] = y[:, a.argmax(1)]
def do_prisma_step(self, step_size=1.5, end='inception_4c/output',
jitter=32, objective=None):
if objective is None:
raise ValueError('make_step objective is None!!!')
src = self.net.blobs['data']
dst = self.net.blobs[end]
ox, oy = np.random.randint(-jitter, jitter + 1, 2)
src.data[0] = np.roll(np.roll(src.data[0], ox, -1), oy, -2)
self.net.forward(end=end)
objective(dst)
self.net.backward(start=end)
g = src.diff[0]
src.data[:] += step_size / np.abs(g).mean() * g
src.data[0] = np.roll(np.roll(src.data[0], -ox, -1), -oy, -2)
def do_prisma(self, base_img, iter_n=10, octave_n=4, octave_scale=1.4,
end='inception_4c/output', **step_params):
octaves = [PrismaHelper.preprocess_with_roll(base_img, self.mean_pixel)]
for i in xrange(octave_n - 1):
octaves.append(nd.zoom(octaves[-1], (1, 1.0 / octave_scale, 1.0 / octave_scale), order=1))
src = self.net.blobs['data']
detail = np.zeros_like(octaves[-1])
for octave, octave_base in enumerate(octaves[::-1]):
h, w = octave_base.shape[-2:]
if octave > 0:
h1, w1 = detail.shape[-2:]
detail = nd.zoom(detail, (1, 1.0 * h / h1, 1.0 * w / w1), order=1)
src.reshape(1, 3, h, w)
src.data[0] = octave_base + detail
for i in xrange(iter_n):
self.do_prisma_step(end=end, **step_params)
detail = src.data[0] - octave_base
return PrismaHelper.deprocess_with_stack(src.data[0], self.mean_pixel)
def gd_features_make(self, guide, end):
h, w = guide.shape[:2]
src, dst = self.net.blobs['data'], self.net.blobs[end]
src.reshape(1, 3, h, w)
src.data[0] = PrismaHelper.preprocess_with_roll(guide, self.mean_pixel)
self.net.forward(end=end)
guide_features = dst.data[0].copy()
return guide_features
def fit_batch_img(self, img_path, resize=False, size=480, enhance=None):
"""
批量处理,但不支持并行,后修改为类似tensor primsma中的并行模式
:param img_path:
:param resize:
:param size:
:param enhance:
:return:
"""
r_img = PIL.Image.open(img_path)
if resize:
r_img = self.resize_img(r_img, size)
org_img = self.handle_enhance(r_img, enhance)
e_str = '' if enhance is None else '_' + enhance.lower()
save_path = os.path.dirname(img_path) + '/batch_caffe/' + e_str
ZCommonUtil.ensure_dir(save_path)
org_img_path = save_path + 'org.jpeg'
with open(org_img_path, 'w') as f:
org_img.save(f, 'jpeg')
org_img = np.float32(org_img)
start = 1
end = self.net.blobs.keys().index('inception_4c/pool')
nbks = self.net.blobs.keys()[start:end]
"""
不能使用多进程方式在这里并行执行,因为caffe.classifier.Classifier不支持序列化
Pickling of "caffe.classifier.Classifier" instances is not enabled
so mul process no pass
"""
for nbk in nbks:
if nbk[-8:-1] == '_split_':
continue
fn = save_path + nbk.replace('/', '_') + '.jpg'
deep_img = self.do_prisma(org_img, iter_n=10, end=nbk)
PrismaHelper.save_array_img(deep_img, fn)
return save_path
复制代码
使用预处理再加上一些其它参数微调配合浅层特征就可以做出一些比较好看的效果,如使用演示视频中的GUI可对效果进行比较好的控制,如下效果,下一章详细介绍使用方式:
def show_lydw(fd_fn):
sample_list = glob.glob(fd_fn)
sample_list = sample_list[::-1]
fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(20, 10));
for ind, ax in zip(range(1 * 2), axs):
iter_fn = sample_list[ind]
iter_img = plt.imread(iter_fn)
ax.set_title(os.path.basename(iter_fn))
ax.imshow(iter_img);
ax.set_axis_off()
复制代码
show_lydw('../show/bj*.jpg')
复制代码
show_lydw('../show/nr*.jpg')
复制代码
1.4 使用引导图进行风格引导
最后本小节我们看看prisma中会有很多风格引导,我们怎样使用CaffePrismaClass实现呢
首先将风格引导图都转换为224大小为了符合模型中对图片的要求(关于模型结构可以从代码中mode/deploy.prototxt查看详情)
img_gd_list = glob.glob("../prisma_gd/*.jpg")
for img_gd in img_gd_list:
width = 224
hsize = 224
org_img_gd = PIL.Image.open(img_gd)
r_img_gd = org_img_gd.resize((width, hsize), PIL.Image.ANTIALIAS)
filen_ame = '../prisma_gd_224/' + os.path.basename(img_gd)
ZCommonUtil.ensure_dir(filen_ame)
with open(filen_ame, 'w') as f:
r_img_gd.save(f, 'jpeg')
复制代码
选出一张看看
img_gd_list = glob.glob("../prisma_gd_224/*.jpg")
gd_path = '../prisma_gd_224/tooopen_sy_127260228921.jpg'
guide = np.float32(PIL.Image.open(gd_path))
PrismaHelper.show_array_ipython(guide)
复制代码
如下代码展示使用风格引导的风格画,和不使用风格引导做的风格画的区别
d_img = cp.fit_img(abu1_file, resize=True, nbk='conv2/3x3_reduce', iter_n=10)
PrismaHelper.show_array_ipython(np.float32(d_img))
d_img = cp.fit_guide_img(abu1_file, gd_path, resize=True, nbk='conv2/3x3_reduce', iter_n=10)
PrismaHelper.show_array_ipython(np.float32(d_img))
复制代码
观察结果可以发现,引导图的特征并没有很多的嵌入原图中,这是由于deepdream实现的的机制是等权重的方式抽取特征导致(当然你也可以修改权重,但问题又会转移到如何分配才能达到视觉上的效果好)。
总结一下CaffePrismaClass的优点就是不需要太多次的迭代训练就可以创造出一副艺术画,缺点就是针对风格引导的渲染绘制显然欠缺。
2 基于tensorflow框架实现prisma
首先看看我们的风格引导图下都有什么
img_gd_list = glob.glob("../prisma_gd/*.jpg")
fig, axs = plt.subplots(nrows=5, ncols=8, figsize=(30, 15));
axs_list = list(itertools.chain.from_iterable(axs))
for ind, ax in zip(range(5 * 8), axs_list):
iter_fn = img_gd_list[ind]
iter_img = plt.imread(iter_fn)
ax.set_title(os.path.basename(iter_fn).split('.')[0])
ax.imshow(iter_img);
ax.set_axis_off()
复制代码
2.1 实例化一个封装好的tensorflow风格画实例
tp = TensorPrismaClass()
tp
复制代码
mean_pixel: [ 123.68 116.779 103.939]
复制代码
注意代码中的 K_VGG_MAT_PATH = '../mode/vgg_imagenet.mat'是vgg模型,可以通过我的网盘链接下载,提取码为gunt
TensorPrismaClass实现原理是低层次的卷积核学习特征纹理 颜色,边界等粗线条,高层次卷积核学到的是底层特征叠加所产生的形状内容特征,最终风格画的效果是由各个层特征分配权重组合而成,不断迭代计算loss function,来寻找图像的特征分配权重,详情代码请查阅github上的PrismaTensor.py,核心代码如下:
def _conv2d(self, img, w, b):
return tf.nn.bias_add(tf.nn.conv2d(img, tf.constant(w), strides=[1, 1, 1, 1], padding='SAME'), b)
def _max_pool(self, img, k):
return tf.nn.max_pool(img, ksize=[1, k, k, 1], strides=[1, k, k, 1], padding='SAME')
def __init__(self):
self.net_fn = K_VGG_MAT_PATH
if not ZCommonUtil.file_exist(self.net_fn):
raise RuntimeError('self.net_fn not exist!!!')
self.net_layers = K_NET_LAYER
self.net_data = scipy.io.loadmat(self.net_fn)
self.mean = self.net_data['normalization'][0][0][0]
self.mean_pixel = np.mean(self.mean, axis=(0, 1))
self.weights = self.net_data['layers'][0]
def _build_vgg_net(self, shape, image_tf=None):
if image_tf is None:
image_tf = tf.placeholder('float', shape=shape)
net = dict()
current = image_tf
for ind, name in enumerate(self.net_layers):
kind = name[:4]
if kind == 'conv':
kernels, bias = self.weights[ind][0][0][0][0]
kernels = np.transpose(kernels, (1, 0, 2, 3))
bias = bias.reshape(-1)
current = self._conv2d(current, kernels, bias)
elif kind == 'relu':
current = tf.nn.relu(current)
elif kind == 'pool':
current = self._max_pool(current, 2)
net[name] = current
return net, image_tf
def _features_make(self, img, image_tf, net, features, guide):
preprocess = np.array([img - self.mean_pixel])
if guide:
for gl in K_GUIDE_LAYERS:
fs = net[gl].eval(feed_dict={image_tf: preprocess})
fs = np.reshape(fs, (-1, fs.shape[3]))
features[gl] = np.matmul(fs.T, fs) / fs.size
else:
features[K_ORG_LAYER] = net[K_ORG_LAYER].eval(feed_dict={image_tf: preprocess})
def _tensor_size(self, tensor):
return reduce(mul, (d.value for d in tensor.get_shape()), 1)
def gd_features_make(self, org_img, guide_img):
# noinspection PyUnusedLocal
with tf.Graph().as_default(), tf.Session() as sess:
org_shape = (1,) + org_img.shape
org_net, org_img_tf = self._build_vgg_net(org_shape)
org_features = dict()
self._features_make(org_img, org_img_tf, org_net, org_features, False)
guide_shapes = (1,) + guide_img.shape
guide_net, guide_img_tf = self._build_vgg_net(guide_shapes)
guide_features = dict()
self._features_make(guide_img, guide_img_tf, guide_net, guide_features, True)
return org_features, guide_features
def do_prisma(self, org_img, guide_img, ckp_fn, iter_n):
org_shape = (1,) + org_img.shape
org_features, guide_features = self.gd_features_make(org_img, guide_img)
with tf.Graph().as_default():
# out_v = tf.zeros(org_shape, dtype=tf.float32, name=None)
out_v = tf.random_normal(org_shape) * 0.256
out_img = tf.Variable(out_v)
out_net, _ = self._build_vgg_net(org_shape, out_img)
org_loss = K_ORG_WEIGHT * (2 * tf.nn.l2_loss(
out_net[K_ORG_LAYER] - org_features[K_ORG_LAYER]) /
org_features[K_ORG_LAYER].size)
style_loss = 0
for guide_layer in K_GUIDE_LAYERS:
layer = out_net[guide_layer]
_, height, width, number = map(lambda x: x.value, layer.get_shape())
size = height * width * number
feats = tf.reshape(layer, (-1, number))
gram = tf.matmul(tf.transpose(feats), feats) / size
style_gram = guide_features[guide_layer]
style_loss += K_GUIDE_WEIGHT * 2 * tf.nn.l2_loss(gram - style_gram) / style_gram.size
tv_y_size = self._tensor_size(out_img[:, 1:, :, :])
tv_x_size = self._tensor_size(out_img[:, :, 1:, :])
tv_loss = K_TV_WEIGHT * 2 * (
(tf.nn.l2_loss(out_img[:, 1:, :, :] - out_img[:, :org_shape[1] - 1, :, :]) /
tv_y_size) +
(tf.nn.l2_loss(out_img[:, :, 1:, :] - out_img[:, :, :org_shape[2] - 1, :]) /
tv_x_size))
loss = org_loss + style_loss + tv_loss
train_step = tf.train.AdamOptimizer(K_LEARNING_RATE).minimize(loss)
# noinspection PyUnresolvedReferences
def print_progress(ind, last=False):
if last or (ind > 0 and ind % K_PRINT_ITER == 0):
ZLog.info('Iteration %d/%d\n' % (ind + 1, iter_n))
ZLog.debug(' content loss: %g\n' % org_loss.eval())
ZLog.debug(' style loss: %g\n' % style_loss.eval())
ZLog.debug(' tv loss: %g\n' % tv_loss.eval())
ZLog.debug(' total loss: %g\n' % loss.eval())
best_loss = float('inf')
best = None
with tf.Session() as sess:
sess.run(tf.initialize_all_variables())
for i in range(iter_n):
last_step = (i == iter_n - 1)
if not g_doing_parallel:
print_progress(i, last=last_step)
train_step.run()
if (i > 0 and i % K_CKP_ITER == 0) or last_step:
# noinspection PyUnresolvedReferences
this_loss = loss.eval()
if this_loss < best_loss:
best_loss = this_loss
best = out_img.eval()
if not last_step:
ckp_fn_iter = K_CKP_FN_FMT % (ckp_fn, i)
PrismaHelper.save_array_img(best.reshape(org_shape[1:]) + self.mean_pixel, ckp_fn_iter)
return best.reshape(org_shape[1:]) + self.mean_pixel
复制代码
使用K5做为引导风格查看效果
gd_path = '../prisma_gd/k5.jpg'
guide = np.float32(tp.resize_img(PIL.Image.open(gd_path)))
PrismaHelper.show_array_ipython(guide)
复制代码
PrismaHelper.show_array_ipython(tp.fit_guide_img(abu1_file, gd_path, resize=True, iter_n=1800))
复制代码
如上所示经过几个小时1800次迭代完整了这幅画,我们下面看看每100次迭代的对比图,首先如下代码所示,对每100次迭代的保存的图形进行排序
k5_list = glob.glob("../sample/batch_tensor/k5*.jpeg")
k5_ind = map(lambda fn: int(fn.rsplit('.')[2].rsplit('_')[-1]), k5_list)
k5_sorted = sorted(zip(k5_ind, k5_list))
k5_sorted
复制代码
[(100, '../sample/batch_tensor/k51051051372_100.jpeg'),
(200, '../sample/batch_tensor/k51051051372_200.jpeg'),
(300, '../sample/batch_tensor/k51051051372_300.jpeg'),
(400, '../sample/batch_tensor/k51051051372_400.jpeg'),
(500, '../sample/batch_tensor/k51051051372_500.jpeg'),
(600, '../sample/batch_tensor/k51051051372_600.jpeg'),
(700, '../sample/batch_tensor/k51051051372_700.jpeg'),
(800, '../sample/batch_tensor/k51051051372_800.jpeg'),
(900, '../sample/batch_tensor/k51051051372_900.jpeg'),
(1000, '../sample/batch_tensor/k51051051372_1000.jpeg'),
(1100, '../sample/batch_tensor/k51051051372_1100.jpeg'),
(1200, '../sample/batch_tensor/k51051051372_1200.jpeg'),
(1300, '../sample/batch_tensor/k51051051372_1300.jpeg'),
(1400, '../sample/batch_tensor/k51051051372_1400.jpeg'),
(1500, '../sample/batch_tensor/k51051051372_1500.jpeg'),
(1600, '../sample/batch_tensor/k51051051372_1600.jpeg'),
(1700, '../sample/batch_tensor/k51051051372_1700.jpeg'),
(1800, '../sample/batch_tensor/k51051051372_1800.jpeg')]
复制代码
展示从第100次迭代结果到第1800次迭代结果的图像风格化的过程,特征的识别渲染由浅入深的一步一步增强,从edge,到shape。
fig, axs = plt.subplots(nrows=3, ncols=6, figsize=(18, 9));
axs_list = list(itertools.chain.from_iterable(axs))
for ind, ax in zip(range(3 * 6), axs_list):
iter_cnt, iter_fn = k5_sorted[ind]
iter_img = plt.imread(iter_fn)
ax.imshow(iter_img);
ax.set_title("k5 iter: {}".format(iter_cnt))
ax.set_axis_off()
复制代码
TensorPrismaClass可以胜任风格画渲染的使命,但问题就是速度太慢了,而且你可以查看代码类函数_features_make它对特征的筛选是引导图只使用浅层特征K_GUIDE_LAYERS = ('relu1_1', 'relu2_1', 'relu3_1', 'relu4_1', 'relu5_1'),原始图像使用K_ORG_LAYER = 'relu4_2'这样很明显无法作出一幅主题非常突出鲜明的图像,所以感觉这种方式比较定向适合特定类型的图像,普遍适应存在很大的问题
2.2 预处理图像和事后处理图像
下面换一个库日天试试(话说今天勇士赢了,好高兴😀)
kl_file = '../sample/kl.jpg'
kl_img = np.float32(tp.resize_img(PIL.Image.open(kl_file)))
PrismaHelper.show_array_ipython(kl_img)
cx6_file = '../prisma_gd/cx6.jpg'
cx6_img = np.float32(tp.resize_img(PIL.Image.open(cx6_file)))
PrismaHelper.show_array_ipython(cx6_img)
复制代码
tn_img = tp.fit_guide_img(kl_file, cx6_file, resize=True, iter_n=3500)
PrismaHelper.show_array_ipython(tn_img)
复制代码
如上代码所示提高迭代次数到3500次,减小K_RNLEAING_RATE值,不断调整K_TV_WEIGHT,K_ORG_WEIGHT,K_GUIDE_WEIGHT训练了好长时间才得到了上面的风格照片,但是下一章介绍的prisma方式,只需要秒级就能做出这样的效果,甚至更好。
如果迭代次数不足的话,毕竟这种方式太耗时了,我们有什么办法呢,最简单的方式整体提高rgb值,如下
# 整体提高rgb值
tnn_br = tn_img * 1.3
PrismaHelper.show_array_ipython(tnn_br)
复制代码
更通用有效的方式,使用base中封装好的pil对图像的预置处理函数,进行事后图像处理,如下所示
ft = PIL.Image.fromarray(np.uint8(tn_img))
# 使用base中封装好的pil对图像的预置处理函数,进行事后图像处理
ft = tp.handle_enhance(ft, 'Contrast')
ft
复制代码
# 也可以在变换的基础上再次使用CaffePrismaClass
img_np = np.float32(ft)
d_img = cp.fit_img('', resize=True, nbk='conv2/3x3', iter_n=10, img_np=img_np)
PrismaHelper.show_array_ipython(np.float32(d_img))
复制代码
当然上面的方式,更优的写完是写一个pipeline clss在流水线中定义你的操作组合方式,一步完成所需所有代码的组合,并且缓存流水线中每一步操作的图像结果,等流水线中全部的操作完成后,再去缓存文件夹中去寻找你最满意的图像,github上的代码暂时没有实现,等日后完善
2.3 一个有意思的实验
如果我用prisma做出一个图像,然后我用它作为特征图像去引导新的图像生成会有什么效果呢
guide = np.float32(tp.resize_img(PIL.Image.open('../prisma_gd/106480401.jpg')))
PrismaHelper.show_array_ipython(guide)
复制代码
如下所示,有些特征还是挖掘到了,哈哈
PrismaHelper.show_array_ipython(tp.fit_guide_img(s_file, gd_path, resize=True, size=640, iter_n=1500))
复制代码
2.4 批量转换风格画接口的使用
批量风格画图片可以使用PrismaTensor.fit_parallel_img,如下所示
def do_fit_parallel_img(path_product, resize, size, enhance, iter_n):
global g_doing_parallel
"""
要在每个进程设置模块全局变量
"""
g_doing_parallel = True
img_path = path_product[0]
gd_path = path_product[1]
prisma_img = TensorPrismaClass().fit_guide_img(img_path, gd_path, resize=resize, size=size, enhance=enhance,
iter_n=iter_n)
g_doing_parallel = False
return path_product, prisma_img
def fit_parallel_img(img_path, gd_path, resize=False, size=480, enhance=None, iter_n=800, n_jobs=-1):
if not isinstance(img_path, list) or not isinstance(gd_path, list):
raise TypeError('img_path or gd_path must list for mul process handle!')
parallel = Parallel(
n_jobs=n_jobs, verbose=0, pre_dispatch='2*n_jobs')
out = parallel(delayed(do_fit_parallel_img)(path_product, resize, size, enhance, iter_n) for path_product in
product(img_path, gd_path))
return out
复制代码
这里使用了sklearn.externals.joblib中的Parallel做并行处理,但是实际上由于tensorflow的底层的并行效率极高,所以实际并行提速是很有限的, itertools.product计算输入图像路径和特征引导图像路径的笛卡尔积
这种方式的实现的prisma的优点就是在迭代足够多的次数引导风格可以极大的渲染作用于原始输入图像上,缺点就是速度非常慢,基本单位是以小时计算的。
针对这种实现方法还有类似的开源项目可以参考Neural-Style-Transfer它使用keras框架,BFGS计算梯度loss function最小值,这样限制了输入图像必须是必须是正方形,这里我没有再次封装,因为它的耗时单位也是无法忍受的在使用cpu的情况下,也许gpu会好点,其实也就没有实际的意义,真正的prisma肯定不是使用这些方法去实现的,下一章节开始我讲使用自己的方式实现快速prisma,在渲染效果和速度上都优于以上解决方案
另外针对caffe及tensorflow的一些使用问题可以查看我的其它两篇文章:
打开股票量化的黑箱(自己动手写一个印钞机) 第三章
或者关注 股票量化专题
爬取百度图片各种狗狗的图片,使用caffe训练模型分类
或者关注 机器学习专题