梦幻风图片?我用Python分分钟做出来!!
每日一句:人生苦短,快学Python!
本期我们利用卷积神经网络制作一个处理图片的小工具,最终效果对比如下:
原图:
效果图(多张,可以左右滑动):
<<< 左右滑动见更多 >>>
具体原理简单来说就是,训练一个神经网络,每层网络逐步提取越来越高级的图像特征,通过分析一些特定层的输出发现,当它识别到了一些特定的模式,就会将这些特征显著地增强,而且层数越高,识别的模式就越复杂。
然后网络将识别到的复杂特征组合起来形成完整的解释,并将这些信息反向传播到网络,每个神经元再显示出它想增强的模式或特征。通过以上过程,我们就可以迫使神经网络在图片中产生一些原本不存在的内容,即达到了梦幻的效果~
下面是实战演练环节。
本文代码在以下环境通过测试:
python == 3.6.13
numpy == 1.16.4
scipy == 1.2.1
Pillow == 8.2.0
matplotlib == 3.3.4
tensorflow == 1.2.0
首先,导入相关的库
import numpy as np
import PIL.Image
import scipy.misc
import tensorflow as tf
忽略无关的警告
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
定义网络模型
# 导入inception模型
# tensorflow提供了以“.pb”为扩展名的文件,可以事先将模型导入到pb文件中,在需要的时候导出
model_fn = 'tensorflow_inception_graph.pb'
# 创建图和会话
graph = tf.Graph()
sess = tf.InteractiveSession(graph=graph)
with tf.gfile.FastGFile(model_fn, 'rb') as f:
graph_def = tf.GraphDef()
graph_def.ParseFromString(f.read())
# 定义输入图像的占位符
t_input = tf.placeholder(np.float32, name='input')
# 图像预处理——减均值
# 训练inception模型时做了减均值预处理,此处也需减同样的均值以保持一致
imagenet_mean = 117.0
# 图像预处理——增加维度
# 图像数据格式一般是(height,width,channels),为同时将多张图片输入网络而在前面增加一维,变为(batch,height,width,channel)
t_preprocessed = tf.expand_dims(t_input - imagenet_mean, 0)
# 导入模型并将预处理图像送入网络中
tf.import_graph_def(graph_def, {'input': t_preprocessed})
找出卷积层
layers = [op.name for op in graph.get_operations() if op.type == 'Conv2D']
# 输出卷积层层数和卷积层名称
print('Number of layers', len(layers))
print(layers)
运行之前的内容可以得到如下输出:
Number of layers 59
['import/conv2d0_pre_relu/conv', 'import/conv2d1_pre_relu/conv', 'import/conv2d2_pre_relu/conv', 'import/mixed3a_1x1_pre_relu/conv', 'import/mixed3a_3x3_bottleneck_pre_relu/conv',
.......
.......
'import/mixed5b_3x3_pre_relu/conv', 'import/mixed5b_5x5_bottleneck_pre_relu/conv', 'import/mixed5b_5x5_pre_relu/conv', 'import/mixed5b_pool_reduce_pre_relu/conv', 'import/head0_bottleneck_pre_relu/conv', 'import/head1_bottleneck_pre_relu/conv']
是不是很有规律呢?没错,忽略一些次要词语,剩下的12个词就是咱们要用的部分,分别为:
'conv2d0', 'conv2d1', 'conv2d2', 'mixed3a', 'mixed3b', 'mixed4a', 'mixed4b', 'mixed4c', 'mixed4d', 'mixed4e','mixed5a', 'mixed5b'
这12个词,便是预训练模型里的12个卷积层,不同的卷积层对图片进行处理,我们可以得到风格不同的图片。后面会用到这部分内容。
接下来是相关函数的定义:
(篇幅原因,这里这罗列部分函数,完整代码下载方式见文末)
# 判断文件夹是否存在,若不存在则创建
def mkdir(path):
folder = os.path.exists(path)
if not folder:
os.makedirs(path)
# 把numpy.ndarray保存成图像文件
def savearray(img_array, img_name):
scipy.misc.toimage(img_array).save(img_name)
# 调整图像尺寸
def resize(img, hw):
min = img.min()
max = img.max()
img = (img - min) / (max - min) * 255
img = np.float32(scipy.misc.imresize(img, hw))
img = img / 255 * (max - min) + min
return img
# 原始图像尺寸可能很大,从而导致内存耗尽,每次只对 tile_size * tile_size 大小的图像计算梯度,避免内存问题
def calc_grad_tiled(img, t_grad, tile_size=512):
接下来便是主程序部分。关于这部分的图片风格定义,其实是笔者将许多张图片分别用12个卷积层进行处理,然后把处理结果拿给一位艺术细胞和审美能力比较良好的小姐姐,让她帮忙将风格相似的图片整理在一起,并给对应风格取一个文艺又小清新的名字,也就是说,尽管每次选择同样的风格,但是结果还是会有细微的差别,这就是惊喜了。总之,,在此再次感谢她~
# 打印提示信息
print('嗨,我是你的小小魔法师,我可以为你的图片生成超级梦幻的效果~')
print('请将待处理的图片放入"preprocess_image"文件夹中,复制当前要处理的图片名称')
# 图片风格定义及选择
pattern_name = input('\n请选择图像风格:1.失真印象;2.青铜秘纹;3.克鲁苏之眼;4.迷幻乐园;5.随机(输入对应数字即可):')
if pattern_name == '1':
name_list = ['conv2d0', 'conv2d1', 'conv2d2']
name = name_list[np.random.randint(0, 3)]
elif pattern_name == '2':
name_list = ['mixed3a', 'mixed3b']
name = name_list[np.random.randint(0, 2)]
elif pattern_name == '3':
name_list = ['mixed4a', 'mixed4b', 'mixed4c']
name = name_list[np.random.randint(0, 3)]
elif pattern_name == '4':
name_list = ['mixed4d', 'mixed4e', 'mixed5a', 'mixed5b']
name = name_list[np.random.randint(0, 4)]
elif pattern_name == '5':
name_list = (
'conv2d0', 'conv2d1', 'conv2d2', 'mixed3a', 'mixed3b', 'mixed4a', 'mixed4b', 'mixed4c', 'mixed4d', 'mixed4e',
'mixed5a', 'mixed5b')
name = name_list[np.random.randint(0, 12)]
else:
print('请看清楚规则!!')
# 获取卷积层对应的通道数
layer_output = graph.get_tensor_by_name('import/%s:0' % name)
# 读取预处理的图片
image_name = input('\n请输入图片名称(附带图片格式,例如:test.jpg):')
file_path = 'preprocess_image/' + image_name
img0 = PIL.Image.open(file_path)
img0 = np.float32(img0)
# 定义图片输出路径
mkdir('processed_image')
file_save_path = 'processed_image/' + image_name.split(sep='.')[0] + '_' + name + '.jpg'
print('\n图片处理过程需要1~2分钟,请耐心等待……')
# 调用render_deepdream函数渲染
render_deepdream(tf.square(layer_output), img0, iter_n=10, step=1.0)
print('\n已完成!生成的图片保存在“processed_image”文件夹下')
至此,图片处理部分结束。
打包
最后,我们可以将它进行简单的打包,这样没有安装python或者配置tensorflow的人也可以使用这个小工具~
我们使用conda创建虚拟环境,用pyinstaller这个库来打包。如果想详细了解Python打包,可以看我之前的文章《别再问我Python打包成exe了!(终极版)》
为什么非要创建一个虚拟环境呢?
这是因为在打包的时候,会将当前环境里所有已安装的包打包进去,如果当前环境里已经安装了很多当前代码用不到的库,这样打包好的可执行文件就显得十分臃肿,非常占地方。
接下来我将详细展示打包过程~
没有安装conda的朋友可以先装一个Anaconda或者Mini Conda
,因为用它做环境管理真的太方便了。
首先,打开终端,新建一个虚拟环境:
conda create -n tensorflow python=3.6
这里tensorflow是虚拟环境的名称,也可以按照自己的意愿自定义,python=3.6用于指定虚拟环境中python解释器的版本。
创建成功后,使用以下命令激活虚拟环境:
conda activate tensorflow
虚拟环境就相当于重新安装了一个python,此时环境中除了pip和setuptools没有其他的包。因此我们先安装需要用的几个包,这里不要忘了安装pyinstaller这个包。
pip install pyinstaller
pip install numpy==1.16.4
pip install pillow==8.2.0
pip install scipy==1.2.1
pip install matplotlib==3.3.4
pip install tensorflow==1.2.0
接下来,在终端使用命令切换到代码所在目录:
cd D:\Dreamscape\
然后就可以使用pyinstaller进行打包:
pyinstaller -F Dreamscape.py
-F表示打包成一个单独的文件,Dreamscape.py
是主程序名称。
打包成功之后,会在当前目录生成文件夹,可执行文件在dist目录下,注意,exe文件应该和其他依赖文件放在一起,如下图所示:
什么,才67.8MB??其实笔者还删掉了一些不必要的依赖库,才使得打包的exe比较小。因为tensorflow本来就是一个超级大的库,在执行该代码时候部分依赖项用不到,但是笔者也不清楚具体哪个用不到。于是使用pip list列出了该环境下的所有库,然后挨个删除,看程序能否正常能够运行,不能运行就再装回来。。。功夫不负有心人,最终,还是成功地删掉了几个库。
至此,我们完成了该小工具的制作。下面是笔者测试过程中的一些比较成功的作品:
效果图(多张,可以左右滑动):
<<< 左右滑动见更多 >>>
那么,失败的作品该长啥样呢?
原图:
效果图:
原本魅力无限的迷人小姐姐,硬是被玩坏了。。。
剧终……
Tips:如果想要获得更加细致的图像,可以修改渲染函数里的iter_n参数,增大迭代次数,或者减小step,也可以二者同时调整,使得网络学习到输入图像的更多特征。当然了,这样也会增加图像处理时长……
render_deepdream(tf.square(layer_output), img0, iter_n=10, step=1.0)
大家对本文涉及的代码感兴趣,可以扫描下面二维码👇在【快学Python】后台回复“ 梦幻 ”即可获取对应(源码+打包好的小工具)