我正在参与「兔了个兔」创意投稿大赛,详情请看:「兔了个兔」创意投稿大赛

一、图画风格化简介

说起图画风格化(image stylization),你或许会感觉到陌生。虽然这项技能,现已深入你的日子很久了。

专业名词,有时分,沟通起来不方便。

记得高中时,我看见同桌带了一个书包。很特别。我就问他这是什么资料的。他说是PP的。我摇了摇头。他又说,便是聚丙烯。其时我很自卑,他说了两遍我仍然不了解,感觉我知识太匮乏了。即便如此,我仍是虚伪地址了允许

多少年之后。我才了解到,本来聚丙烯的袋子从我出生时,我就见过了,便是下图这样的:

兔年了,利用AI风格化实现剪纸兔、年画兔、烟花兔

从那一刻起,我发誓,关于专业名词,我要做到尽量不提。

可是不提,同行又认为我不专业。因而,我现在便是,说完了通俗的,再总结专业的。我会说编织袋或许蛇皮袋,文雅一点能够称为:聚丙烯可延展包装容器

而关于图画风格化,其实就相似你的相片加梵高的画作合成梵高风格的你。又或许你的相片直接转为动漫头像。

风格化需求有两个参数。一个叫 content 原内容,另一个叫 style 风格参照。两者经过模型,能够将原内容变为参照的风格。

举个例子。假如 content 是一只兔子,style 是上面的聚丙烯编织袋,那么两者交融会发生什么呢?

兔年了,利用AI风格化实现剪纸兔、年画兔、烟花兔

那肯定是一个带有编织袋风格的……兔子!

上面的风格交融,多少有点下里巴人。

我再来一个阳春白雪的。让兔子和康定斯基的笼统画做一次交融。

兔年了,利用AI风格化实现剪纸兔、年画兔、烟花兔

看着还不错,虽然没有笼统感,可是最少风格是有的。

咱们幻想一下,在兔年降临之际,假如兔子和年画、剪纸、焰火这类新年元素交融起来,会是怎样的作用呢?从技能上(不调用API接口)又该怎样完结呢?

下面,跟随我的镜头,咱们来一探终究(我现已探完了,不然不能有上面的图)。

二、技能完结解说

首要说啊,咱们不调用网络API。其次,咱们是依据开源项目。

调用第三方API,会实时依附于服务供给商。一般来说,它处于自主产品轻视链的底层。

我了解一些大牛,尤其是领导,宣称完结了许多高档功能。成果一深究,是调用了别家的才能。这类人,把购买接口的年租费用,称为“研发投入”。把忘记续费,归咎于“服务器故障”。

那么,轻视链再上升一层。便是拿国外的开源项目,自己布置服务用。虽然这种行为仍然不露脸。可是,这在国内现已算很棒的了。因为他们会把布置好的服务,再卖给上面的大牛领导,然后还轻视他只能调API。

今日,我要运用的,便是从开源项目本地布置这条路。

因而,你学会了也不要自豪,这并没有什么自主的知识产权。学不会也不必自卑,你还能够试试调用API。

咱们选用的开源项目便是TensorFlow Hub。地址是 github.com/tensorflow/… 。

2.1 TensorFlow Hub库

能够说我对 TensorFlow 很熟,并且是它的铁杆粉丝。铁到我的昵称“TF男孩”的TF指的便是 TensorFlow

TensorFlow 现已很简略和人性化了。简略到几十行代码,就能够完结数字识别的全流程(我都不好意写这个教程)。

可是,它仍然不满足于此。代码调用现已够简略了。可是关于练习的样本数据、设备性能这些条件,仍然是约束普通人去进入的门槛。

所以,TensorFlow 就推出了一个 TensorFlow Hub 来解决上面的问题。你能够使用它练习好的模型和权重,自己再做微调,以此适配成自己的成果。这节省了许多的人力和物力的投入。

Hub 是轮毂的意思

兔年了,利用AI风格化实现剪纸兔、年画兔、烟花兔

这让咱们很简略就联想到“重复造轮子”这个论题。可是,它又很清晰,不是轮子,是轮毂。这阐明,它把最硬的部件做好了,你只需求往上放轮胎就行。到这儿,我开始感觉,虽然我很讨厌有些人说一句话,又是带中文,又是带英文的。可是,这个 Hub ,很难翻译,仍是叫 TensorFlow Hub 更为贴切。

2.2 加载image-stylization模型

假如你打算运用 hub 预练习好的风格化模型,自己不做任何改动的话,作用就像下面这样。这是我搞的一个梵高《星空》风格的兔子:

兔年了,利用AI风格化实现剪纸兔、年画兔、烟花兔

而代码其实很简略,也便是下面几行:

# 导入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)

这,看起来很简略。似乎人工智能的工作很简略干。

事实,并非如此

兔年了,利用AI风格化实现剪纸兔、年画兔、烟花兔

我主张咱们都来学习人工智能,使用老练的代码或工具,解决日子中遇到的问题。

可是,我不主张你着急转行到人工智能的工作岗位中来。

因为在学习进程中,你会发现,相比于其他言语,人工智能具有更多的约束和基础学科要求。因而,作为运用体会者和制造开发者,会是两个不同的心境。

随着下面的解说,上面的问题咱们会逐一碰到。

首要,上面的 hub.load('https://tfhub.dev/……') 你就加载不下来。而这个地址,正是图画风格化的模型文件。咔,晴天霹雳啊,刚起头便是波折

其实TensorFlow 是谷歌的开源项目。因而他们许多项目的资源是同享的。你能够替换 tfhub.devstorage.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了。

兔年了,利用AI风格化实现剪纸兔、年画兔、烟花兔

可是,这个图片参数的格局,却并没有那么简略。

前面说了,TensorFlow HubTensorFlow 的轮毂,不是轮子,更不是自动驾驶。它的参数和回来值,都是一个 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为鸿沟的。

兔年了,利用AI风格化实现剪纸兔、年画兔、烟花兔

也便是说你的模型做数字识别的时分,核算的成果并不是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*255np.array(tensor, dtype=np.uint8)
  • 第3步:取出 shape=(1, 320, 512, 3) 中的那个1,也便是512320的那张图。
  • 第4步:经过 fromarray(img_arr) 加载图片的数组数据,保存为图片文件。

我敢确保,后边的事情,你只管享受就好了

源码在这儿 github.com/hlwgy/image… 。你能够亲身运转试验下作用。

不过,多数人仍是会选择看完文章再试。

三、全部皆可兔图的作用

新年就要到了,新的一年是兔年(抱歉,我如同说过了)。

下面,我就把小兔子画面和一些新年元素,做一个风格交融。

3.1 年画兔

当然,我只说我这个年龄段的新年场景。

年画,过年是有必要贴的,在我老家(倒装句暴露了家乡)。并且年画种类很丰富。

有这样的:

兔年了,利用AI风格化实现剪纸兔、年画兔、烟花兔

还有这样的:

兔年了,利用AI风格化实现剪纸兔、年画兔、烟花兔

它们的制造工艺不同,作用不同,贴的方位也大不相同。

我最喜欢贴的是门神。老家的门是木头门。搞一盆浆糊,拿扫帚往门上抹。然后把年画一放,就粘上了。纸的质量不是很好,浆糊又是湿的,浆糊交融着彩纸还会把染料扩散开来。估计现在的孩子很少再见到了。

兔年了,利用AI风格化实现剪纸兔、年画兔、烟花兔

咱们看一下,心爱的小兔子遇到门神年画,会发生怎样的反响:

兔年了,利用AI风格化实现剪纸兔、年画兔、烟花兔

你们知道年画是怎样制造的吗?在没有印刷机的年代,年画的制造彻底靠手工。

需求先雕琢模子,其作用相似于印章。有用木头雕琢的模具,印出来的便是木板年画。

兔年了,利用AI风格化实现剪纸兔、年画兔、烟花兔

好了,雕琢完了。最终的模子是这样的。

兔年了,利用AI风格化实现剪纸兔、年画兔、烟花兔

模板里放上不同的染料,然后印在纸上,年画就出来了。

假如兔子遇到这种木板模具,会是什么风格呢?我有点猎奇,咱们看一下:

兔年了,利用AI风格化实现剪纸兔、年画兔、烟花兔

我想,这个图,再结合3D打印机,是不是就不必工匠雕琢了。

3.2 剪纸兔

剪纸,也是过新年的一项风俗。

我老家有一种特殊的剪纸的工艺,叫“挂门笺”。当地叫“门吊子”,意思便是吊在门下的旗子。

兔年了,利用AI风格化实现剪纸兔、年画兔、烟花兔

其实这个风俗来源于南宋。那时分过年,大户人家都挂丝绸旗帜,以示喜庆。可是普通百姓买不起啊,就改成了彩纸。

跟年画比,这个工艺现在仍然活跃,乡村大集还有卖的。

兔年了,利用AI风格化实现剪纸兔、年画兔、烟花兔

假如小兔子遇到剪纸,会是什么风格呢?揭晓一下作用:

兔年了,利用AI风格化实现剪纸兔、年画兔、烟花兔

3.3 焰火兔

说起焰火,就不是哪个年龄段的专利了。现如今,即便是小孩子,也很喜欢看焰火。

兔年了,利用AI风格化实现剪纸兔、年画兔、烟花兔

假如兔子遇到焰火,会发生什么样的交融呢?放图揭晓答案:

兔年了,利用AI风格化实现剪纸兔、年画兔、烟花兔

的确很美丽。

四、无限遐想

最终,我仍然意犹未尽。

我尝试自己画了个小兔子,和的吉祥物们做了一个交融:

兔年了,利用AI风格化实现剪纸兔、年画兔、烟花兔

这……我感觉比较丢失。

可是,转念一想,其实作为笼统画也能够,反正大多数人都看不了解。

我又交融了一张,裱起来,打上落款,如同也过得去。

兔年了,利用AI风格化实现剪纸兔、年画兔、烟花兔

上面的例子,咱们是 hub.loadhttps://tfhub.dev/……image-stylization-v1-256/2 这个模型。

其实,你能够试试 https://tfhub.dev/……image-stylization-v1-256/1 这个模型,它是一个带有发光作用的模型。

兔年了,利用AI风格化实现剪纸兔、年画兔、烟花兔

嗨,技能人,不管你是前端仍是后端,假如新年没事干,想跨界、想打破,试试这个人工智能的项目吧。搞个小程序,给亲友用一下,也挺好的。

我是@TF男孩,一位讲代码进程中,多少带点人文气味的编程表演艺术家。