兔年了,利用AI風格化實現剪紙兔、年畫兔、煙花兔
theme: Chinese-red
我正在參加「兔了個兔」創意投稿大賽,詳情請看:「兔了個兔」創意投稿大賽
一、影象風格化簡介
說起影象風格化(image stylization),你可能會感覺到陌生。儘管這項技術,已經深入你的生活很久了。
專業名詞,有時候,溝通起來不方便。
記得高中時,我看見同桌帶了一個書包。很特別。我就問他這是什麼材料的。他說是PP的。我搖了搖頭。他又說,就是聚丙烯。當時我很自卑,他說了兩遍我依然不懂,感覺我知識太貧乏了。即便如此,我還是虛偽地點了點頭。
多少年之後。我才瞭解到,原來聚丙烯的袋子從我出生時,我就見過了,就是下圖這樣的:
從那一刻起,我發誓,對於專業名詞,我要做到儘量不提。
但是不提,同行又以為我不專業。因此,我現在就是,說完了通俗的,再總結專業的。我會說編織袋或者蛇皮袋,文雅一點可以稱為:聚丙烯可延展包裝容器。
而對於影象風格化,其實就類似你的照片加梵高的畫作合成梵高風格的你。又或者你的照片直接轉為動漫頭像。
風格化需要有兩個引數。一個叫 content
原內容,另一個叫 style
風格參照。兩者經過模型,可以將原內容變為參照的風格。
舉個例子。如果 content
是一隻兔子,style
是上面的聚丙烯編織袋,那麼兩者融合會發生什麼呢?
那肯定是一個帶有編織袋風格的……兔子!
上面的風格融合,多少有點下里巴人。
我再來一個陽春白雪的。讓兔子和康定斯基的抽象畫做一次融合。
看著還不錯,雖然沒有抽象感,但是起碼風格是有的。
大家想象一下,在兔年來臨之際,如果兔子和年畫、剪紙、煙花這類春節元素融合起來,會是怎樣的效果呢?從技術上(不呼叫API介面)又該如何實現呢?
下面,跟隨我的鏡頭,我們來一探究竟(我已經探完了,不然不能有上面的圖)。
二、技術實現講解
首先說啊,咱們不呼叫網路API。其次,我們是基於開源專案。
呼叫第三方API,會實時依附於服務提供商。一般來說,它處於自主產品鄙視鏈的底層。
我瞭解一些大牛,尤其是領導,聲稱實現了很多高階功能。結果一深究,是呼叫了別家的能力。這類人,把購買介面的年租費用,稱為“研發投入”。把忘記續費,歸咎於“伺服器故障”。
那麼,鄙視鏈再上升一層。就是拿國外的開源專案,自己部署服務用。儘管這種行為依然不露臉。但是,這在國內已經算很棒的了。因為他們會把部署好的服務,再賣給上面的大牛領導,然後還鄙視他只能調API。
今天,我要使用的,就是從開源專案本地部署這條路。
因此,你學會了也不要驕傲,這並沒有什麼自主的智慧財產權。學不會也不用自卑,你還可以試試呼叫API。
我們選用的開源專案就是TensorFlow Hub。地址是 http://github.com/tensorflow/hub 。
2.1 TensorFlow Hub庫
可以說我對 TensorFlow
很熟,而且是它的鐵桿粉絲。鐵到我的暱稱“TF男孩”的TF
指的就是 TensorFlow
。
TensorFlow
已經很簡單和人性化了。簡單到幾十行程式碼,就可以實現數字識別的全流程(我都不好意寫這個教程)。
但是,它依然不滿足於此。程式碼呼叫已經夠簡單了。但是對於訓練的樣本資料、裝置效能這些條件,仍然是限制普通人去涉足的門檻。
於是,TensorFlow
就推出了一個 TensorFlow Hub
來解決上面的問題。你可以利用它訓練好的模型和權重,自己再做微調,以此適配成自己的成果。這節省了大量的人力和物力的投入。
Hub
是輪轂的意思。
這讓我們很容易就聯想到“重複造輪子”這個話題。但是,它又很明確,不是輪子,是輪轂。這說明,它把最硬的部件做好了,你只需要往上放輪胎就行。到這裡,我開始感覺,雖然我很討厭有些人說一句話,又是帶中文,又是帶英文的。但是,這個 Hub
,很難翻譯,還是叫 TensorFlow Hub
更為貼切。
2.2 載入image-stylization模型
如果你打算使用 hub
預訓練好的風格化模型,自己不做任何改動的話,效果就像下面這樣。這是我搞的一個梵高《星空》風格的兔子:
而程式碼其實很簡單,也就是下面幾行:
``` python
匯入hub庫
import tensorflow_hub as hub
載入訓練好的風格化模型
hub_model = hub.load('http://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('http://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
的格式。
那麼下面,我們就要把圖片檔案包裝成這個格式。
先放程式碼:
``` python 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,具體如下:
python
shape=(434, 650, 3), dtype=uint8
array([[[219, 238, 245],
...,
[219, 238, 245]],
[[219, 238, 245],
...,
[219, 238, 245]]])
shape=(434, 650, 3)
說明這是一個三維陣列,看資料這是一張650×434畫素且具有RGB三個通道的圖片。其中的array是具體畫素的數值,在某個顏色通道內,255表示純白,0表示純黑。
接著 convert_image_dtype(img, tf.float32)
把img轉成了float形式。
此時,img的資訊為:
python
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的資料如下:
python
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,它已經變為了如下結構:
python
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張512×341的彩圖。那麼,這個結構它也可以承載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
到底是怎樣的結構。
我們來列印一下:
python
[<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
的陣列。
不要和我說這些,我要把它轉為圖片看結果。
來,先上程式碼:
``` python 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,也就是512×320的那張圖。 - 第4步:通過
fromarray(img_arr)
載入圖片的陣列資料,儲存為圖片檔案。
我敢保證,後面的事情,你只管享受就好了。
原始碼在這裡 http://github.com/hlwgy/image-stylization 。你可以親自執行試驗下效果。
不過,多數人還是會選擇看完文章再試。
三、一切皆可兔圖的效果
春節就要到了,新的一年是兔年(抱歉,我好像說過了)。
下面,我就把小兔子畫面和一些春節元素,做一個風格融合。
3.1 年畫兔
當然,我只說我這個年齡段的春節場景。
年畫,過年是必須貼的,在我老家(倒裝句暴露了家鄉)。而且年畫種類很豐富。
有這樣的:
還有這樣的:
它們的製作工藝不同,作用不同,貼的位置也大不相同。
我最喜歡貼的是門神。老家的門是木頭門。搞一盆漿糊,拿掃帚往門上抹。然後把年畫一放,就粘上了。紙的質量不是很好,漿糊又是溼的,漿糊融合著彩紙還會把染料擴散開來。估計現在的孩子很少再見到了。
我們看一下,可愛的小兔子遇到門神年畫,會發生怎樣的反應:
你們知道年畫是怎麼製作的嗎?在沒有印刷機的年代,年畫的製作完全靠手工。
需要先雕刻模子,其作用類似於印章。有用木頭雕刻的模具,印出來的就是木板年畫。
好了,雕刻完了。最終的模子是這樣的。
模板裡放上不同的染料,然後印在紙上,年畫就出來了。
如果兔子遇到這種木板模具,會是什麼風格呢?我有點好奇,我們看一下:
我想,這個圖,再結合3D印表機,是不是就不用工匠雕刻了。
3.2 剪紙兔
剪紙,也是過春節的一項民俗。
我老家有一種特殊的剪紙的工藝,叫“掛門箋”。當地叫“門吊子”,意思就是吊在門下的旗子。
其實這個習俗來源於南宋。那時候過年,大戶人家都掛絲綢旗幟,以示喜慶。但是普通百姓買不起啊,就改成了彩紙。
跟年畫比,這個工藝現在依然活躍,農村大集還有賣的。
如果小兔子遇到剪紙,會是什麼風格呢?揭曉一下效果:
3.3 煙花兔
說起煙花,就不是哪個年齡段的專利了。現如今,即便是小孩子,也很喜歡看煙花。
如果兔子遇到煙花,會產生什麼樣的融合呢?放圖揭曉答案:
確實很美麗。
四、無限遐想
最後,我仍然意猶未盡。
我嘗試自己畫了個小兔子,和掘金的吉祥物們做了一個融合:
這……我感覺比較失落。
但是,轉念一想,其實作為抽象畫也可以,反正大多數人都看不懂。
我又融合了一張,裱起來,打上落款,好像也過得去。
上面的例子,我們是 hub.load
了 http://tfhub.dev/……image-stylization-v1-256/2
這個模型。
其實,你可以試試 http://tfhub.dev/……image-stylization-v1-256/1
這個模型,它是一個帶有發光效果的模型。
嗨,技術人,不管你是前端還是後端,如果春節沒事幹,想跨界、想突破,試試這個人工智慧的專案吧。搞個小程式,給親友用一下,也挺好的。
我是掘金@TF男孩,一位講程式碼過程中,多少帶點人文氣息的程式設計表演藝術家。
- 60分鐘的文心一言釋出會:我帶你5分鐘看完
- 程式設計師寫小說:我甘心當韭菜
- docx格式文件詳解:xml解析並用html還原
- 評論二則:年齡大了能否寫程式碼、上大學不喜歡IT專業咋整
- ChatGPT火了,我連夜詳解AIGC原理,並實戰生成動漫頭像
- 咱不吃虧,也不能過度自衛
- 兔年了,利用AI風格化實現剪紙兔、年畫兔、煙花兔
- 傻嗎?談男人們飯桌的拼酒現象
- 他扔出一張對話截圖:王總說的
- 老張說:快過年了,搞個AI作曲,用TensorFlow訓練midi檔案
- 為什麼大家都看中學歷?
- 年底了,裁兄弟當職員那哥們兒,如今咋樣了?
- 老張讓我用TensorFlow識別語音命令:前進、停止、左轉、右轉
- 在掘金第一次記錄失眠
- 十五分鐘簡介人工智慧,以聽懂為目的
- 認知史 12:積累階段,沒資格攀比
- 認知史 16:人過留名,雁過留聲
- 認知史 4:普遍共識
- 一文講通OCR文字識別原理與技術全流程(科普版)
- 認知史 7:選擇和努力