我正在参与「兔了个兔」创意投稿大赛,详情请看:「兔了个兔」创意投稿大赛
一、图画风格化简介
说起图画风格化(image stylization),你或许会感觉到陌生。虽然这项技能,现已深入你的日子很久了。
专业名词,有时分,沟通起来不方便。
记得高中时,我看见同桌带了一个书包。很特别。我就问他这是什么资料的。他说是PP的。我摇了摇头。他又说,便是聚丙烯。其时我很自卑,他说了两遍我仍然不了解,感觉我知识太匮乏了。即便如此,我仍是虚伪地址了允许。
多少年之后。我才了解到,本来聚丙烯的袋子从我出生时,我就见过了,便是下图这样的:
从那一刻起,我发誓,关于专业名词,我要做到尽量不提。
可是不提,同行又认为我不专业。因而,我现在便是,说完了通俗的,再总结专业的。我会说编织袋或许蛇皮袋,文雅一点能够称为:聚丙烯可延展包装容器。
而关于图画风格化,其实就相似你的相片加梵高的画作合成梵高风格的你。又或许你的相片直接转为动漫头像。
风格化需求有两个参数。一个叫 content
原内容,另一个叫 style
风格参照。两者经过模型,能够将原内容变为参照的风格。
举个例子。假如 content
是一只兔子,style
是上面的聚丙烯编织袋,那么两者交融会发生什么呢?
那肯定是一个带有编织袋风格的……兔子!
上面的风格交融,多少有点下里巴人。
我再来一个阳春白雪的。让兔子和康定斯基的笼统画做一次交融。
看着还不错,虽然没有笼统感,可是最少风格是有的。
咱们幻想一下,在兔年降临之际,假如兔子和年画、剪纸、焰火这类新年元素交融起来,会是怎样的作用呢?从技能上(不调用API接口)又该怎样完结呢?
下面,跟随我的镜头,咱们来一探终究(我现已探完了,不然不能有上面的图)。
二、技能完结解说
首要说啊,咱们不调用网络API。其次,咱们是依据开源项目。
调用第三方API,会实时依附于服务供给商。一般来说,它处于自主产品轻视链的底层。
我了解一些大牛,尤其是领导,宣称完结了许多高档功能。成果一深究,是调用了别家的才能。这类人,把购买接口的年租费用,称为“研发投入”。把忘记续费,归咎于“服务器故障”。
那么,轻视链再上升一层。便是拿国外的开源项目,自己布置服务用。虽然这种行为仍然不露脸。可是,这在国内现已算很棒的了。因为他们会把布置好的服务,再卖给上面的大牛领导,然后还轻视他只能调API。
今日,我要运用的,便是从开源项目本地布置这条路。
因而,你学会了也不要自豪,这并没有什么自主的知识产权。学不会也不必自卑,你还能够试试调用API。
咱们选用的开源项目便是TensorFlow Hub。地址是 github.com/tensorflow/… 。
2.1 TensorFlow Hub库
能够说我对 TensorFlow
很熟,并且是它的铁杆粉丝。铁到我的昵称“TF男孩”的TF
指的便是 TensorFlow
。
TensorFlow
现已很简略和人性化了。简略到几十行代码,就能够完结数字识别的全流程(我都不好意写这个教程)。
可是,它仍然不满足于此。代码调用现已够简略了。可是关于练习的样本数据、设备性能这些条件,仍然是约束普通人去进入的门槛。
所以,TensorFlow
就推出了一个 TensorFlow Hub
来解决上面的问题。你能够使用它练习好的模型和权重,自己再做微调,以此适配成自己的成果。这节省了许多的人力和物力的投入。
Hub
是轮毂的意思。
这让咱们很简略就联想到“重复造轮子”这个论题。可是,它又很清晰,不是轮子,是轮毂。这阐明,它把最硬的部件做好了,你只需求往上放轮胎就行。到这儿,我开始感觉,虽然我很讨厌有些人说一句话,又是带中文,又是带英文的。可是,这个 Hub
,很难翻译,仍是叫 TensorFlow Hub
更为贴切。
2.2 加载image-stylization模型
假如你打算运用 hub
预练习好的风格化模型,自己不做任何改动的话,作用就像下面这样。这是我搞的一个梵高《星空》风格的兔子:
而代码其实很简略,也便是下面几行:
# 导入hub库
import tensorflow_hub as hub
# 加载练习好的风格化模型
hub_model = hub.load('https://tfhub.dev/google/magenta/arbitrary-image-stylization-v1-256/2')
# 将content和style传入
stylized_image = hub_model(content_image, style_image)[0]
# 获取风格化后的图片并打印出来
tensor_to_image(stylized_image)
这,看起来很简略。似乎人工智能的工作很简略干。
事实,并非如此。
我主张咱们都来学习人工智能,使用老练的代码或工具,解决日子中遇到的问题。
可是,我不主张你着急转行到人工智能的工作岗位中来。
因为在学习进程中,你会发现,相比于其他言语,人工智能具有更多的约束和基础学科要求。因而,作为运用体会者和制造开发者,会是两个不同的心境。
随着下面的解说,上面的问题咱们会逐一碰到。
首要,上面的 hub.load('https://tfhub.dev/……')
你就加载不下来。而这个地址,正是图画风格化的模型文件。咔,晴天霹雳啊,刚起头便是波折。
其实TensorFlow
是谷歌的开源项目。因而他们许多项目的资源是同享的。你能够替换 tfhub.dev
为 storage.googleapis.com/tfhub-modules
。并且在末尾加上后缀 .tar.gz
。
下载完结之后,解压文件,然后指定加载途径。其实这一步操作,也是结构的操作。它也是先下载到本地某处,然后从本地加载。
比方,我将 .tar.gz
解压到同级目录下。然后调用 hub.load('image-stylization-v1-256_2')
即可完结 hub
的加载。
这便是我说的约束。
相比较而言,Java
或许 Php
这类情况也会有,可是频率没有这么高。
下面,咱们继续。还会有其他惊喜。
2.3 输入图片转为tensor格局
hub_model = hub.load(……)
是加载模型。咱们是加载了图画风格化的模型。
赋值的称号随便起就行,上面我起的名是 hub_model
。之所以说这句话,是因为我发现有些人感觉改个名字,代码就会运转不起来。其实,变一变,更有利于了解代码。而项目运转不起来,向天主祈求不起作用,是需求看报错信息的。
假如彻底不更改 hub
预置模型的话,再一行代码就完工了。
这行代码便是 stylized_images = hub_model(content_image, style_image)
。
这行代码是把内容图片 content_image
和风格参照图片 style_image
传给加载好的模型 hub_model
。模型就会输出风格化后的成果图片stylized_images
。
哇哦,瞬间感觉自己能卖API了。
可是,这个图片参数的格局,却并没有那么简略。
前面说了,TensorFlow Hub
是 TensorFlow
的轮毂,不是轮子,更不是自动驾驶。它的参数和回来值,都是一个 flow
流的形式。
TensorFlow
中的 flow
是什么?这很像《道德经》里的“道”是什么一样。它们只能在自己的言语系统里能说清楚。
可是在这儿,你只需求知道调用一个 tf.constant(……)
,或许其他 tf
开头的函数,就可把一个字符、数组或许结构体,包装成为 tensor flow
的格局。
那么下面,咱们就要把图片文件包装成这个格局。
先放代码:
import tensorflow as tf
# 依据途径加载图片,并缩小至512像素,转为tensor
max_dim = 512
img = tf.io.read_file(path_to_img)
img = tf.image.decode_image(img, channels=3)
img = tf.image.convert_image_dtype(img, tf.float32)
shape = tf.cast(tf.shape(img)[:-1], tf.float32)
long_dim = max(shape)
scale = max_dim / long_dim
new_shape = tf.cast(shape * scale, tf.int32)
img = tf.image.resize(img, new_shape)
img = img[tf.newaxis, :]
tf_img = tf.constant(img)
首要,模型在练习和预测时,是有固定尺度的。比方,宽高统一是512像素。
然后,关于用户的输入,咱们是不能约束的。比方,用户输入一个高度为863像素的图,这时咱们不能让用户裁剪好了再上传。应该是用户上传后,咱们来处理。
最终,要搞成tensorflow
需求的格局。
上面的代码片段,把这三条都搞定了。
read_file
从途径读入文件。然后经过 decode_image
将文件解析成数组。
这时,假如打印img,详细如下:
shape=(434, 650, 3), dtype=uint8
array([[[219, 238, 245],
...,
[219, 238, 245]],
[[219, 238, 245],
...,
[219, 238, 245]]])
shape=(434, 650, 3)
阐明这是一个三维数组,看数据这是一张650434像素且具有RGB三个通道的图片。其间的array是详细像素的数值,在某个颜色通道内,255表明纯白,0表明纯黑。
接着 convert_image_dtype(img, tf.float32)
把img转成了float形式。
此时,img的信息为:
shape=(434, 650, 3), dtype=float32
array([[[0.8588236, 0.9333334, 0.9607844],
...,
[0.8588236, 0.9333334, 0.9607844]],
[[0.8588236, 0.9333334, 0.9607844],
...,
[0.8588236, 0.9333334, 0.9607844]]])
为啥要把int转为float呢?初学者往往会有这样的疑问。
因为他们发现,只要是核算,就要求搞成float类型。就算明明是9个分类,也不能用1、2、3、4来表明,也要转为一堆的小数点。
今日这个图片的像素,也是如此,255个色值多好辨认,为什么非要转为看不了解的小数呢?
别拦着我,我今日非要要解释一下。
这并不是算法没事找事,伪装高档。其实,这是为了更好地对应到许多基础学科的知识。
比方,我在《详解激活函数》中讲过许多激活函数。激活函数决定算法怎样做决策,能够说是算法的指导思想。
你看几个就知道了。不管是sigmoid仍是tanh,它的值都是以0或许1为鸿沟的。
也便是说你的模型做数字识别的时分,核算的成果并不是1、2、3、4,而是0到1之间的小数。最终,算法依据概率得出属于哪个分类。哎,你看概率的表明也是0到1之间的数。
除此之外,核算机的二进制也是0或许1。芯片的核算需求精度,整数类型不如小数精确。
各种原因,导致仍是浮点型的小数更适合算法的核算。甚至,人工智能的系统中,还具有float64类型,也便是64位的小数。
变为小数之后,后边便是将图片数组做缩放。依据数据的shape,找到最长的边。然后缩放到512像素以内。
这就到了 resize(img, new_shape)
这行代码。
到这一步时,img的数据如下:
shape=(341, 512, 3), dtype=float32
array([[[0.8588236, 0.9333334, 0.9607844],
...,
[0.8588236, 0.9333334, 0.9607844]],
[[0.8588236, 0.9333334, 0.9607844],
...,
[0.8588236, 0.9333334, 0.9607844]]])
本来的 (434, 650, 3)
图片被从头定义成了 (341, 512, 3)
。仍然是3通道的颜色,可是长宽尺度经过核算,最大现已不超过512像素了。
为什么做缩放?除了模型要求,还要防止用户有或许上传一张1亿像素的图片,这时你的服务器就冒烟了。
(434, 650, 3)
代表的是一张图。可是纵观所有算法模型,不管是 model.fit(train_ds)
练习阶段,仍是 model.predict(tf_imgs)
预测阶段。就没有处理单张图片的代码逻辑,全都是批量处理。
它不能处理单张图片的结构,你别说它不人性化,不必跟他杠。兄弟,模型要的仅仅一个数组结构,它并不关怀里边图片的数量。一张图片能够是 ["a.png"]
这种形式。
提到这儿,我又忍不住想谈谈关于接口设计的论题了。
我给事务方供给了一个算法接口才能,便是查询一张图上存在的特定方针信息。我也是回来多个成果的结构。虽然样本中只要一个方针。事务方非要回来一个。从长远来讲,谁也不敢确保以后场景中只要一个方针。我必需要照实回来,有一个回来一个,有两个回来两个,你能够只取第一个。可是,结构肯定是要支撑多个的。
从本钱和风险权衡的角度,从列表中取一条数据的本钱,要远小于程序出错或许失灵的风险。可是事务方比较坚持回来一个就行。
后来,他们让我把图片的base64回来值带上 data:image/jpeg;base64,
以便于前端直接展现。那一刻,我就了解了,跟他们较这个真,是我冲动了。
而关于 TensorFlow
的要求,你必需要包装成批量的形式。我认为这很规范。
这句代码 img = img[tf.newaxis, :]
便是将维度上升一层。能够将 1
变为 [1]
,也能够将 [[1],[2]]
变为 [[[1],[2]]]
。
此时再打印img,它现已变为了如下结构:
shape=(1, 341, 512, 3), dtype=float32
array([[[[0.8588236, 0.9333334, 0.9607844],
...,
[0.8588236, 0.9333334, 0.9607844]],
[[0.8588236, 0.9333334, 0.9607844],
...,
[0.8588236, 0.9333334, 0.9607844]]]])
shape=(1, 341, 512, 3)
表明有1张512341的彩图。那么,这个结构它也能够承载100张这样的图,那时便是shape=(100, 341, 512, 3)
。这就做到了,以不变应万变。
最终一步的 tf_img = tf.constant(img)
,作用是经过 tf.constant
把图片数据,包装成 TensorFlow
需求的格局。
这个格局,就能够传给hub_model去处理了。
经过 stylized_images = hub_model(tf_img_content, tf_img_style)
这行代码的处理。它会将处理成果放到 stylized_images
中。你马上就能够看到交融成果了。
不过,如同也没有那么简略。这个成果的出现,实际上是图片到tensor格局的逆向进程。
咱们下面就来处理它。
2.4 tensor格局成果转为图片
上一步经过 hub_model
转化,咱们获取到了 stylized_images
。这是咱们辛苦那么久的产物。你是否会猎奇 stylized_images
到底是怎样的结构。
咱们来打印一下:
[<tf.Tensor: shape=(1, 320, 512, 3), dtype=float32, numpy=
array([[[[0.31562978, 0.47748038, 0.7790847 ],
...,
[0.7430198 , 0.733053 , 0.6921962 ]],
[[0.76158 , 0.6912774 , 0.5468565 ],
...,
[0.69527835, 0.70888966, 0.6492392 ]]]], dtype=float32)>]
厉害了,它是一个 shape=(1, 320, 512, 3)
形状的 tf.Tensor
的数组。
不要和我说这些,我要把它转为图片看成果。
来,先上代码:
import numpy as np
import PIL.Image
tensor = stylized_images[0]
tensor = tensor*255
tensor_arr = np.array(tensor, dtype=np.uint8)
img_arr = tensor_arr[0]
img = PIL.Image.fromarray(img_arr)
img.save(n_path)
信任有了上面图片转 tensor
的进程,这个反着转化的进程,你很简略就能了解。
- 第1步:取成果中的第一个
stylized_images[0]
,那是shape=(1, 320, 512, 3)
。 - 第2步:小数转为255色值的整数数组
tensor*255
、np.array(tensor, dtype=np.uint8)
。 - 第3步:取出
shape=(1, 320, 512, 3)
中的那个1,也便是512320的那张图。 - 第4步:经过
fromarray(img_arr)
加载图片的数组数据,保存为图片文件。
我敢确保,后边的事情,你只管享受就好了。
源码在这儿 github.com/hlwgy/image… 。你能够亲身运转试验下作用。
不过,多数人仍是会选择看完文章再试。
三、全部皆可兔图的作用
新年就要到了,新的一年是兔年(抱歉,我如同说过了)。
下面,我就把小兔子画面和一些新年元素,做一个风格交融。
3.1 年画兔
当然,我只说我这个年龄段的新年场景。
年画,过年是有必要贴的,在我老家(倒装句暴露了家乡)。并且年画种类很丰富。
有这样的:
还有这样的:
它们的制造工艺不同,作用不同,贴的方位也大不相同。
我最喜欢贴的是门神。老家的门是木头门。搞一盆浆糊,拿扫帚往门上抹。然后把年画一放,就粘上了。纸的质量不是很好,浆糊又是湿的,浆糊交融着彩纸还会把染料扩散开来。估计现在的孩子很少再见到了。
咱们看一下,心爱的小兔子遇到门神年画,会发生怎样的反响:
你们知道年画是怎样制造的吗?在没有印刷机的年代,年画的制造彻底靠手工。
需求先雕琢模子,其作用相似于印章。有用木头雕琢的模具,印出来的便是木板年画。
好了,雕琢完了。最终的模子是这样的。
模板里放上不同的染料,然后印在纸上,年画就出来了。
假如兔子遇到这种木板模具,会是什么风格呢?我有点猎奇,咱们看一下:
我想,这个图,再结合3D打印机,是不是就不必工匠雕琢了。
3.2 剪纸兔
剪纸,也是过新年的一项风俗。
我老家有一种特殊的剪纸的工艺,叫“挂门笺”。当地叫“门吊子”,意思便是吊在门下的旗子。
其实这个风俗来源于南宋。那时分过年,大户人家都挂丝绸旗帜,以示喜庆。可是普通百姓买不起啊,就改成了彩纸。
跟年画比,这个工艺现在仍然活跃,乡村大集还有卖的。
假如小兔子遇到剪纸,会是什么风格呢?揭晓一下作用:
3.3 焰火兔
说起焰火,就不是哪个年龄段的专利了。现如今,即便是小孩子,也很喜欢看焰火。
假如兔子遇到焰火,会发生什么样的交融呢?放图揭晓答案:
的确很美丽。
四、无限遐想
最终,我仍然意犹未尽。
我尝试自己画了个小兔子,和的吉祥物们做了一个交融:
这……我感觉比较丢失。
可是,转念一想,其实作为笼统画也能够,反正大多数人都看不了解。
我又交融了一张,裱起来,打上落款,如同也过得去。
上面的例子,咱们是 hub.load
了 https://tfhub.dev/……image-stylization-v1-256/2
这个模型。
其实,你能够试试 https://tfhub.dev/……image-stylization-v1-256/1
这个模型,它是一个带有发光作用的模型。
嗨,技能人,不管你是前端仍是后端,假如新年没事干,想跨界、想打破,试试这个人工智能的项目吧。搞个小程序,给亲友用一下,也挺好的。
我是@TF男孩,一位讲代码进程中,多少带点人文气味的编程表演艺术家。