想打造一個神經網路,自動給黑白照片上色?這兒有一份超詳細教程

@王小新 編譯自 FloydHub Blog

量子位 出品 | 公眾號 QbitAI

昨天,你可能驚喜地看到了Adobe做了個給人像上色的軟體,然後傷心地發現只能搞定人臉,而且還沒正式推出,現在能看到的只有一篇論文……

沒關係,咱們自己做一個。

深度學習雲平台FloydHub最近在官方博客上發了一篇教你搭建神經網路,來給黑白照片上色的教程,在Twitter和Reddit論壇上都廣受好評。

FloydHub是個YC孵化的創業公司,號稱要做深度學習領域的Heroku。它在GPU系統上預裝了TensorFlow和很多其他的機器學習工具,用戶可以按時長租用,訓練自己的機器學習模型。

這份教程是基於FloydHub平台寫的,這個平台號稱深度學習領域的Heroku,在GPU系統上預裝了TensorFlow和很多其他的機器學習工具,用戶可以按時長租用,訓練自己的機器學習模型。免費版支持1個項目、每月20小時GPU時長、10G存儲空間,用上色項目練個手足夠了。

進入正題~

以下內容編譯自FloydHub官方博客:


我將分三個步驟展示如何打造你自己的著色神經網路。

第一部分介紹核心邏輯。我們將用40行代碼來構建一個簡單的神經網路,叫做「Alpha」版本著色機器人。這個代碼里沒有太多技巧,但是這有助於熟悉語法。

下一步是創建一個具備泛化能力的神經網路,叫做「Beta」版本,它能夠對以前沒見過的圖像進行著色。

最後,將神經網路與分類器相結合,得到最終版本。Inception Resnet V2是訓練120萬張圖像後得到的神經網路,我們使用了該模型。為了使著色效果更加吸引人,我們使用了來自素材網站Unsplash的人像集,來訓練這個神經網路。

Alpha版本機器人的Jupyter Notebook代碼、上述三個版本實現代碼的FloydHub和GitHub地址,以及在FloydHub的GPU雲上運行的所有實驗代碼,都在文末。

核心邏輯

在本節中,我將概述如何渲染圖像、數字顏色的基礎知識以及神經網路的主要邏輯。

黑白圖像可以在像素網格中表示。每個像素有對應於其亮度的值,取值範圍為0 - 255,從黑色到白色。

圖像與數字的對應

彩色圖像可以分為三層,分別是紅色層、綠色層和藍色層。直觀上,你可能會認為植物只存在於綠色層,這可能與你的直覺相反。想像一下,將白色背景上的綠葉分成三個圖層。

如下圖所示,葉子在三個圖層中都存在。這些層不僅可以確定顏色,還確定了亮度。

三個通道的綠葉

例如,為了得到白色這個顏色,你需要將所有顏色均勻分布。添加等量紅色和藍色後,綠色會變得更亮。因此,彩色圖像使用三個通道來編碼顏色和對比度:

RGB調色

就像黑白圖像一樣,彩色圖像中每個圖層值的範圍也是0 – 255,值為0意味著該圖層中沒有顏色。如果在所有顏色圖層中該值都為0,則該圖像像素為黑色。

神經網路能建立輸入和輸出之間的關係。更準確地說,著色任務就是讓網路找到連接灰度圖像與彩色圖像的特徵。

因此,著色神經網路,就是要尋找將灰度值網格連接到三色網格的特徵。

Alpha版本

我們從簡單版本開始,建立一個能給女性臉部著色的神經網路。這樣,當添加新特徵時,你可以熟悉已有模型的核心語法。

只需要40行代碼,就能實現下圖所示的轉換。中間圖像是用神經網路完成的,右邊圖像是原始的彩色照片。這個網路使用了相同圖像做訓練和測試,在beta版本中還會再講這一點。

顏色空間

首先,使用一種能改變顏色通道的演算法,從RGB到Lab。其中,L表示亮度,a和b分別表示顏色光譜,綠-紅和藍-黃。

如下所示,Lab編碼的圖像具有一個灰度層,並將三個顏色層壓成兩層,這意味著在最終預測中可以使用原始灰度圖像。此外,我們只需預測兩個通道。

Lab編碼圖像

科學研究表明,人類眼睛中有94%細胞確定著亮度,只有6%細胞是用來感受顏色的。如上圖所示,灰度圖像比彩色層更加清晰。這是我們在最終預測中保留灰度圖像的另一個原因。

從黑白到彩色

最終預測應該是這樣的:向網路輸入灰度層(L),然後預測Lab中的兩個顏色層ab。要創建最終輸出的彩色圖像,我們需要把輸入的灰度(L)圖像和輸出的a、b層加在一起,創建一個Lab圖像。

圖像轉化函數

我們使用卷積過濾器將一層變成兩層,可以把它們看作3D眼鏡中的藍色和紅色濾鏡。每個過濾器確定能從圖片中看到的內容,可以突出或移除某些東西,來從圖片中提取信息。這個網路可以從過濾器中創建新圖像,也可以組合多個過濾器形成新圖像。

卷積神經網路能自動調整每個濾波器,以達到預期結果。我們將從堆疊數百個濾波器開始,然後將它們縮小成兩層,即a層和b層。

在詳細介紹其工作原理之前,先介紹代碼。

在FloydHub上部署代碼

如果你剛接觸FloydHub,請看這個2分鐘安裝教程(floydhub.com/),以及手把手教學指南(blog.floydhub.com/my-fi),然後就可以開始在GPU雲上訓練深度學習模型了。

Alpha版本

安裝好FloydHub後,執行以下命令:

git clone https://github.com/emilwallner/Coloring-greyscale-images-in-Kerasn

打開文件夾並啟動FloydHub。

cd Coloring-greyscale-images-in-Keras/floydhubnfloyd init colornetn

FloydHub Web控制台將在瀏覽器中打開,系統會提示你創建一個名為colornet的FloydHub新項目。完成後,返回終端並執行相同的初始化命令。

floyd init colornetn

接下來執行本項目任務。

floyd run --data emilwallner/datasets/colornet/2:data --mode jupyter --tensorboardn

對於這個任務的一些簡單說明:

1. 我們在FloydHub的數據集目錄(—data emilwallner / datasets / colornet / 2:data)中載入了一個公開數據集,你在FloydHub上查看並使用此數據集和許多其他公開數據集;

2. 啟用了Tensorboard (—tensorboard);

3. 在Jupyter Notebook下運行代碼 (—mode jupyter);

4. 如果能使用GPU,你還可以將GPU (—gpu)添加到命令中,這樣運行速度能提高50倍。

在FloydHub網站上的選項「Jobs」下,點擊Jupyter Notebook可鏈接到以下文件:floydhub / Alpha version / working_floyd_pink_light_full.ipynb,打開它並摁下shift+enter。

逐漸增大epoch值,體會神經網路是如何學習的。

model.fit(x=X, y=Y, batch_size=1, epochs=1)n

開始時把epoch設置為1,逐漸增加到10、100、5500、1000和3000。epoch值表示神經網路從圖像中學習的次數。在訓練神經網路的過程中,你可以在主文件夾中找到這個圖像img_result.png。

# Get imagesnimage = img_to_array(load_img(woman.png))nimage = np.array(image, dtype=float)nn# Import map images into the lab colorspacenX = rgb2lab(1.0/255*image)[:,:,0]nY = rgb2lab(1.0/255*image)[:,:,1:]nY = Y / 128nX = X.reshape(1, 400, 400, 1)nY = Y.reshape(1, 400, 400, 2)nnmodel = Sequential()nmodel.add(InputLayer(input_shape=(None, None, 1)))nn# Building the neural networknmodel = Sequential()nmodel.add(InputLayer(input_shape=(None, None, 1)))nmodel.add(Conv2D(8, (3, 3), activation=relu, padding=same, strides=2))nmodel.add(Conv2D(8, (3, 3), activation=relu, padding=same))nmodel.add(Conv2D(16, (3, 3), activation=relu, padding=same))nmodel.add(Conv2D(16, (3, 3), activation=relu, padding=same, strides=2))nmodel.add(Conv2D(32, (3, 3), activation=relu, padding=same))nmodel.add(Conv2D(32, (3, 3), activation=relu, padding=same, strides=2))nmodel.add(UpSampling2D((2, 2)))nmodel.add(Conv2D(32, (3, 3), activation=relu, padding=same))nmodel.add(UpSampling2D((2, 2)))nmodel.add(Conv2D(16, (3, 3), activation=relu, padding=same))nmodel.add(UpSampling2D((2, 2)))nmodel.add(Conv2D(2, (3, 3), activation=tanh, padding=same))nn# Finish modelnmodel.compile(optimizer=rmsprop,loss=mse)nn#Train the neural networknmodel.fit(x=X, y=Y, batch_size=1, epochs=3000)nprint(model.evaluate(X, Y, batch_size=1))nn# Output colorizationsnoutput = model.predict(X)noutput = output * 128ncanvas = np.zeros((400, 400, 3))ncanvas[:,:,0] = X[0][:,:,0]ncanvas[:,:,1:] = output[0]nimsave("img_result.png", lab2rgb(cur))nimsave("img_gray_scale.png", rgb2gray(lab2rgb(cur)))n

用FloydHub命令來運行網路:

floyd run --data emilwallner/datasets/colornet/2:data --mode jupyter --tensorboardn

技術說明

總的來說,輸入是一個黑白圖像網格,輸出兩個具有顏色值的網格。在輸入和輸出之間,通過一個卷積神經網路,構建過濾器連接兩者。

訓練網路時使用的是彩色圖像,並將RGB顏色轉換成Lab顏色空間。網路輸入為黑白圖層,輸出兩個著色層。

在上圖左側,輸入為黑白網格、過濾器和神經網路的預測結果。

在同一變化間距內,比較預測值和實際值的差異。其中,該間距為[-1, 1]。為了映射這些預測值,我們使用Tanh激活函數,因為Tanh函數的輸入可為任意值,輸出為-1到1。

實際上,顏色值的分布區間為[-128, 128],這也是Lab顏色空間的默認間距。除以128後這些值也落到區間[-1, 1]中,這樣就能用來比較預測結果的誤差。

計算得到最終誤差後,網路會更新過濾器以減小全局誤差。網路會留在這個循環,直到誤差達到足夠小。

下面解釋下代碼的一些語法。

X = rgb2lab(1.0/255*image)[:,:,0]nY = rgb2lab(1.0/255*image)[:,:,1:]n

1.0 / 255表示彩色圖像使用的RGB顏色空間為24位,也就是說每個顏色通道值為整數,且從0到255變化。這是標準顏色範圍,一共有1670萬種顏色組合。由於人類只能感知到2 ~ 10萬種顏色,因此使用更大的顏色空間沒有太大意義。

Y = Y / 128n

與RGB相比,Lab顏色空間的範圍與其不同。Lab中色譜ab的取值範圍是[-128, 128]。將輸出層的所有值都除以128,它的範圍變成[-1, 1]。神經網路的輸出值範圍也為[-1, 1],兩者可進行匹配。

在用rgb2lab()函數轉換顏色空間後,我們使用[:,…,0]來選擇灰度層,這是神經網路的輸入。同時[:,…,1:]表示選擇兩個顏色層,綠-紅和藍-黃。

訓練好神經網路後,轉換回Lab圖像,作最終預測。

output = model.predict(X)noutput = output * 128n

其中,輸入為灰度圖像,經過訓練好的神經網路後,其輸出位於區間[-1, 1],再將其乘以128,得到Lab色譜中的合適顏色。

canvas = np.zeros((400, 400, 3))ncanvas[:,:,0] = X[0][:,:,0]ncanvas[:,:,1:] = output[0]n

最後,用0填充三個圖層得到一個全黑RGB畫布,然後從測試圖像中複製灰度圖層,再把兩個顏色層添加到RGB畫布中,最後把這個像素數組轉換回圖像。

打造Alpha版本的一些心得

1. 閱讀研究論文是很痛苦的。但只要你能總結出每篇論文的核心要點,就能輕鬆地閱讀論文,也讓你更關注於文中的前後關聯。

2. 關鍵是要從簡化版開始。網上大多數實現的代碼行數可達2000行到10000行,這讓我很難去理解問題的核心邏輯。只要有了一個準系統版本,之後就能更容易去閱讀實現代碼和研究論文。

3.探索開源項目。為了大體了解如何編程,我在Github上瀏覽了50-100個關於著色的項目。

4.事情並不總是像預期的那樣工作。開始時,我的網路只能創建紅色和黃色。最初,末層激活時用的是Relu激活函數。因為它只能將數字映射為正數,無法得到負值和藍綠色譜。後來,我添加了一個Tanh激活函數來映射Y值,解決了這個問題。

5.理解比速度更重要。我看到,很多實現代碼的運行速度都很快,但很難看懂。我選擇優化創新速度而不是代碼速度。

Beta版本

要了解Alpha版本的缺點,用該網路給未經訓練的圖像著色。嘗試後你就會發現它的效果不好,因為網路只記住了已有的信息,還沒有學會如何給未見過的圖像著色。而這正是我們要在Beta版本中努力的方向,即提高網路的泛化能力。

以下是Beta版本神經網路在驗證圖像上的著色結果。

我在FloydHub上創建了一個公開的高質量圖像數據集,而不是使用現有的Imagenet。Unsplash收集了大量專業攝影師的創意共享圖片,本數據集圖像來自於此,一共包括了9500張訓練圖像和500張驗證圖像。

特徵提取器

我們所構建的神經網路能找到灰度圖與其彩色版本相關聯的有效特徵。

試想一下,你必須給黑白圖像著色,但是限制你每次只能看到九個像素。你只能從左上角到右下角來掃描每張圖像,並嘗試預測每個像素可能的顏色值。

例如,這九個像素就是上面那個女性的鼻孔邊緣。想像得到,這很難得到一個良好的著色效果,所以可將它分解成多個步驟。

首先,你要尋找簡單圖案,如對角線和黑色像素等。你也可以在每個方塊中尋找相同的確切圖案,並刪除不匹配的像素。具體做法是使用最開始的64個過濾器來生成64張新圖像。

當再次掃描圖像時,你會看到已經檢測到的相同小圖案。要獲得對圖像的更深入理解,你可以將圖像解析度減半。

你仍然只用了3×3過濾器來掃描每張圖像。但是,通過將9個新像素與較低層次濾波器相結合,你可以檢測更複雜的圖案。幾個像素組合起來,可能會形成一個半圓、一個小點或是一條線。此外,你可以反覆從圖像中提取相同圖案,這樣就能生成128個過濾後的新圖像。

經過幾次操作後,過濾後的新圖像可能會像這樣:

如上所述,先提取低層特徵,如邊緣等。越靠近輸出的圖層先組合成圖案,再組合成局部細節,最終轉化為面部。如果你覺得難以理解,可觀看這個視頻教程(youtube.com/watch?)。

這個過程與用於處理圖像的神經網路相似,被稱為卷積神經網路。卷積運算與單片語合相似,通過組合若干個過濾後的圖像來理解圖像中的前後關係。

從特徵提取到著色

神經網路以誤差傳遞的方式實現。首先,它對每個像素進行隨機預測;然後基於每個像素的誤差,通過反向傳播,來改進特徵提取效果。

開始時,它會先調整具有最大誤差的位置。因此,最初只考慮兩個問題,即是否要著色和是否定位不同物體;然後給所有物體塗上棕色,因為這是與其他顏色最相似的一種顏色,能產生最小的誤差。

由於大多數訓練數據非常相似,網路只能通過調整棕色的深淺程度來區分不同物體,但是不能產生更細緻的顏色,這也是我們將在最終版本中探索的內容。

以下是beta版本的代碼,及技術說明。

# Get imagesnX = []nfor filename in os.listdir(../Train/):n X.append(img_to_array(load_img(../Train/+filename)))nX = np.array(X, dtype=float)nn# Set up training and test datansplit = int(0.95*len(X))nXtrain = X[:split]nXtrain = 1.0/255*Xtrainnn#Design the neural networknmodel = Sequential()nmodel.add(InputLayer(input_shape=(256, 256, 1)))nmodel.add(Conv2D(64, (3, 3), activation=relu, padding=same))nmodel.add(Conv2D(64, (3, 3), activation=relu, padding=same, strides=2))nmodel.add(Conv2D(128, (3, 3), activation=relu, padding=same))nmodel.add(Conv2D(128, (3, 3), activation=relu, padding=same, strides=2))nmodel.add(Conv2D(256, (3, 3), activation=relu, padding=same))nmodel.add(Conv2D(256, (3, 3), activation=relu, padding=same, strides=2))nmodel.add(Conv2D(512, (3, 3), activation=relu, padding=same))nmodel.add(Conv2D(256, (3, 3), activation=relu, padding=same))nmodel.add(Conv2D(128, (3, 3), activation=relu, padding=same))nmodel.add(UpSampling2D((2, 2)))nmodel.add(Conv2D(64, (3, 3), activation=relu, padding=same))nmodel.add(UpSampling2D((2, 2)))nmodel.add(Conv2D(32, (3, 3), activation=relu, padding=same))nmodel.add(Conv2D(2, (3, 3), activation=tanh, padding=same))nmodel.add(UpSampling2D((2, 2)))nn# Finish modelnmodel.compile(optimizer=rmsprop, loss=mse)nn# Image transformerndatagen = ImageDataGenerator(n shear_range=0.2,n zoom_range=0.2,n rotation_range=20,n horizontal_flip=True)nn# Generate training datanbatch_size = 50ndef image_a_b_gen(batch_size):n for batch in datagen.flow(Xtrain, batch_size=batch_size):n lab_batch = rgb2lab(batch)n X_batch = lab_batch[:,:,:,0]n Y_batch = lab_batch[:,:,:,1:] / 128n yield (X_batch.reshape(X_batch.shape+(1,)), Y_batch)nn# Train modelnTensorBoard(log_dir=/output)nmodel.fit_generator(image_a_b_gen(batch_size), steps_per_epoch=10000, epochs=1)n# Test imagesnXtest = rgb2lab(1.0/255*X[split:])[:,:,:,0]nXtest = Xtest.reshape(Xtest.shape+(1,))nYtest = rgb2lab(1.0/255*X[split:])[:,:,:,1:]nYtest = Ytest / 128nprint model.evaluate(Xtest, Ytest, batch_size=batch_size)nn# Load black and white imagesncolor_me = []nfor filename in os.listdir(../Test/):n color_me.append(img_to_array(load_img(../Test/+filename)))ncolor_me = np.array(color_me, dtype=float)ncolor_me = rgb2lab(1.0/255*color_me)[:,:,:,0]ncolor_me = color_me.reshape(color_me.shape+(1,))nn# Test modelnoutput = model.predict(color_me)noutput = output * 128nn# Output colorizationsnfor i in range(len(output)):n cur = np.zeros((256, 256, 3))n cur[:,:,0] = color_me[i][:,:,0]n cur[:,:,1:] = output[i]n imsave("result/img_"+str(i)+".png", lab2rgb(cur))n

用FloydHub命令來運行Beta版本神經網路:

floyd run --data emilwallner/datasets/colornet/2:data --mode jupyter --tensorboardn

技術說明

與其他視覺網路不同,它的主要區別在於像素位置的重要性。在著色網路中,圖像的解析度或比例在整個網路中保持不變。而在其他網路中,越靠近最後一層,圖像變得越扭曲。

分類網路中的最大池化層增加了信息密度,但也可能導致圖像失真。它只對信息進行估值,而未考慮到圖像布局。在著色網路中,我們將步幅改為2,每次運算使寬度和高度減半。這樣,不但增加了信息密度,也不會使圖像扭曲。

兩者差異在於上採樣層和圖像比例保持方面。而分類網路只關心最終的分類正確率,因此隨著網路深入,它會不斷減小圖像的解析度和質量。

但是,著色網路的圖像比例保持不變。這是通過添加空白填充來實現的,否則每個卷積層將削減圖像,實現代碼為:padding =』same』。

要使圖像的解析度加倍,著色網路使用了上採樣層,更多信息見:keras.io/layers/convolu

for filename in os.listdir(/Color_300/Train/):n X.append(img_to_array(load_img(/Color_300/Test+filename)))n

這個for循環首先計算了目錄中的所有文件名,然後遍歷圖像目錄,並將圖像轉換為像素數組,最後將其組合成一個大型矢量。

datagen = ImageDataGenerator(n shear_range=0.2,n zoom_range=0.2,n rotation_range=20,n horizontal_flip=True)n

在ImageDataGenerator中,我們還修改了圖像生成器的參數。這樣,圖像永遠不會相同,以改善學習效果。參數shear_range是指圖像向左或向右的傾斜程度,其他參數不需要加以說明。

batch_size = 50ndef image_a_b_gen(batch_size):n for batch in datagen.flow(Xtrain, batch_size=batch_size):n lab_batch = rgb2lab(batch)n X_batch = lab_batch[:,:,:,0]n Y_batch = lab_batch[:,:,:,1:] / 128n yield (X_batch.reshape(X_batch.shape+(1,)), Y_batch)n

我們使用了文件夾Xtrain中的圖像,根據之前設置來生成圖像。然後我們提取X_batch中的黑白層和兩個顏色層的兩種顏色。

model.fit_generator(image_a_b_gen(batch_size), steps_per_epoch=1, epochs=1000)n

擁有越高性能的GPU,則可以設置越大的batch_size值。根據現有硬體,我們設置了每批次輸入50-100張圖像。參數steps_per_epoch是通過把訓練圖像的數量除以批次大小得出的。例如,有100張圖像且批次大小為50,則steps_per_epoch值為2。參數epoch決定網路中所有圖像的訓練次數。在Tesla K80 GPU上,大約需要11小時才能完成對1萬張圖像的21次訓練。

訓練心得

1.先進行多次小批次實驗,再嘗試大批次實驗。即使經過20-30次實驗,我仍然發現很多錯誤。程序能運行並不意味著它能工作。神經網路中的問題通常比傳統編程遇到的Bug更為麻煩。

2.多樣化的數據集會使著色效果呈現棕色。如果數據集中圖像比較相似,不需要設計很複雜的架構,就可以得到一個良好效果,但是其泛化能力十分糟糕。

3.重視圖像形狀的統一性。每張圖像的解析度必須是準確的,且在整個網路中保持成比例。開始時,所使用的圖像解析度為300,將它減半三次後分別得到150、75和35.5。因此,就丟失了半個像素,這導致了很多問題,後來才意識到應該使用4、8、16、32、64、256等這種能被2整除的數。

4.創建數據集。請禁用.DS_Store文件,不然會產生很多麻煩;大膽創新,最後我用Chrome控制台腳本和擴展程序來下載文件;備份原始文件並構建清理腳本。

最終版本

最終版本的著色神經網路有四個組成部分。我們將之前的網路拆分成編碼器和解碼器,在這兩者之間還添加了一個融合層。關於分類網路的基本教程,可參考:cs231n.github.io/classi

Inception resnet v2是目前最強大的分類器之一,使用了120萬張圖像來訓練該網路。我們提取了它的分類層,並將其與編碼器的輸出進行合併。因此,輸入數據傳給編碼器的同時,也並行傳輸到resnet v2網路的分類層中。更多信息,請參考原論文:github.com/baldassarreF

信息並行傳輸示意圖

這個網路通過將分類器的學習效果遷移到著色網路上,可更好理解圖片中的內容。因此,這樣使得網路能夠把目標表徵與著色方案相匹配。

以下是在一些驗證圖像上的著色效果,僅使用20張圖像來訓練網路。

大多數圖像的著色效果不好,但是由於驗證集中有2500張圖像,我設法找到了一些良好的著色圖像。在更多圖像上進行訓練可以獲得更為穩定的結果,但是大部分都呈現棕色色調。這兩個鏈接貼出了運行試驗的完整列表,包括驗證圖像(floydhub.com/emilwallne) (floydhub.com/emilwallne)。

本文的神經網路設計參考了這篇論文(github.com/baldassarreF),以及我在Keras中的一些理解。

注意:在下面代碼中,我把Keras的序列模型變換成相應的函數調用。

# Get imagesnX = []nfor filename in os.listdir(/data/images/Train/):n X.append(img_to_array(load_img(/data/images/Train/+filename)))nX = np.array(X, dtype=float)nXtrain = 1.0/255*Xnn#Load weightsninception = InceptionResNetV2(weights=None, include_top=True)ninception.load_weights(/data/inception_resnet_v2_weights_tf_dim_ordering_tf_kernels.h5)ninception.graph = tf.get_default_graph()nnembed_input = Input(shape=(1000,))nn#Encodernencoder_input = Input(shape=(256, 256, 1,))nencoder_output = Conv2D(64, (3,3), activation=relu, padding=same, strides=2)(encoder_input)nencoder_output = Conv2D(128, (3,3), activation=relu, padding=same)(encoder_output)nencoder_output = Conv2D(128, (3,3), activation=relu, padding=same, strides=2)(encoder_output)nencoder_output = Conv2D(256, (3,3), activation=relu, padding=same)(encoder_output)nencoder_output = Conv2D(256, (3,3), activation=relu, padding=same, strides=2)(encoder_output)nencoder_output = Conv2D(512, (3,3), activation=relu, padding=same)(encoder_output)nencoder_output = Conv2D(512, (3,3), activation=relu, padding=same)(encoder_output)nencoder_output = Conv2D(256, (3,3), activation=relu, padding=same)(encoder_output)nn#Fusionnfusion_output = RepeatVector(32 * 32)(embed_input) nfusion_output = Reshape(([32, 32, 1000]))(fusion_output)nfusion_output = concatenate([encoder_output, fusion_output], axis=3) nfusion_output = Conv2D(256, (1, 1), activation=relu, padding=same)(fusion_output) nn#Decoderndecoder_output = Conv2D(128, (3,3), activation=relu, padding=same)(fusion_output)ndecoder_output = UpSampling2D((2, 2))(decoder_output)ndecoder_output = Conv2D(64, (3,3), activation=relu, padding=same)(decoder_output)ndecoder_output = UpSampling2D((2, 2))(decoder_output)ndecoder_output = Conv2D(32, (3,3), activation=relu, padding=same)(decoder_output)ndecoder_output = Conv2D(16, (3,3), activation=relu, padding=same)(decoder_output)ndecoder_output = Conv2D(2, (3, 3), activation=tanh, padding=same)(decoder_output)ndecoder_output = UpSampling2D((2, 2))(decoder_output)nnmodel = Model(inputs=[encoder_input, embed_input], outputs=decoder_output)nn#Create embeddingndef create_inception_embedding(grayscaled_rgb):n grayscaled_rgb_resized = []n for i in grayscaled_rgb:n i = resize(i, (299, 299, 3), mode=constant)n grayscaled_rgb_resized.append(i)n grayscaled_rgb_resized = np.array(grayscaled_rgb_resized)n grayscaled_rgb_resized = preprocess_input(grayscaled_rgb_resized)n with inception.graph.as_default():n embed = inception.predict(grayscaled_rgb_resized)n return embednn# Image transformerndatagen = ImageDataGenerator(n shear_range=0.4,n zoom_range=0.4,n rotation_range=40,n horizontal_flip=True)nn#Generate training datanbatch_size = 20nndef image_a_b_gen(batch_size):n for batch in datagen.flow(Xtrain, batch_size=batch_size):n grayscaled_rgb = gray2rgb(rgb2gray(batch))n embed = create_inception_embedding(grayscaled_rgb)n lab_batch = rgb2lab(batch)n X_batch = lab_batch[:,:,:,0]n X_batch = X_batch.reshape(X_batch.shape+(1,))n Y_batch = lab_batch[:,:,:,1:] / 128n yield ([X_batch, create_inception_embedding(grayscaled_rgb)], Y_batch)nn#Train model ntensorboard = TensorBoard(log_dir="/output")nmodel.compile(optimizer=adam, loss=mse)nmodel.fit_generator(image_a_b_gen(batch_size), callbacks=[tensorboard], epochs=1000, steps_per_epoch=20)nn#Make a prediction on the unseen imagesncolor_me = []nfor filename in os.listdir(../Test/):n color_me.append(img_to_array(load_img(../Test/+filename)))ncolor_me = np.array(color_me, dtype=float)ncolor_me = 1.0/255*color_mencolor_me = gray2rgb(rgb2gray(color_me))ncolor_me_embed = create_inception_embedding(color_me)ncolor_me = rgb2lab(color_me)[:,:,:,0]ncolor_me = color_me.reshape(color_me.shape+(1,))nn# Test modelnoutput = model.predict([color_me, color_me_embed])noutput = output * 128nn# Output colorizationsnfor i in range(len(output)):n cur = np.zeros((256, 256, 3))n cur[:,:,0] = color_me[i][:,:,0]n cur[:,:,1:] = output[i]n imsave("result/img_"+str(i)+".png", lab2rgb(cur))n

用FloydHub命令來運行最終版本神經網路:

floyd run --data emilwallner/datasets/colornet/2:data --mode jupyter --tensorboardn

技術說明

當你要實現模型連接或模型融合時,Keras的函數調用功能是最佳選擇。

模型融合層

首先,要下載inception resnet v2網路並載入權重。由於要並行使用兩個模型,因此必須指定當前要使用哪個模型。這個可通過Keras的後端Tensorflow來完成。

inception = InceptionResNetV2(weights=None, include_top=True)ninception.load_weights(/data/inception_resnet_v2_weights_tf_dim_ordering_tf_kernels.h5)ninception.graph = tf.get_default_graph()n

批處理方法中使用了調整後的圖像,把它們變成黑白圖像,並在inception resnet模型上運行。

grayscaled_rgb = gray2rgb(rgb2gray(batch))nembed = create_inception_embedding(grayscaled_rgb)n

首先,要調整圖像解析度以適應inception模型;然後根據模型,使用預處理程序來規範化像素和顏色值;最後,在inception網路上運行並提取模型最後一層的權重。

def create_inception_embedding(grayscaled_rgb):n grayscaled_rgb_resized = []n for i in grayscaled_rgb:n i = resize(i, (299, 299, 3), mode=constant)n grayscaled_rgb_resized.append(i)n grayscaled_rgb_resized = np.array(grayscaled_rgb_resized)n grayscaled_rgb_resized = preprocess_input(grayscaled_rgb_resized)n with inception.graph.as_default():n embed = inception.predict(grayscaled_rgb_resized)n return embedn

再講下生成器。對於每個批次,我們按照下列格式生成20張圖像,在Tesla K80 GPU上大約要運行一個小時。基於該模型,每批次最多可輸入50張圖像,且不產生內存溢出。

yield ([X_batch, create_inception_embedding(grayscaled_rgb)], Y_batch)n代碼23n

這與本項目中著色模型的格式相匹配。

model = Model(inputs=[encoder_input, embed_input], outputs=decoder_output)n

encoder_input會輸入到編碼器模型中;接著,編碼器模型的輸出會與融合層中的embed_input相融合;然後,這個融合輸出會作為解碼器模型的輸入;最終,解碼器模型會輸出預測結果decode_output。

fusion_output = RepeatVector(32 * 32)(embed_input) nfusion_output = Reshape(([32, 32, 1000]))(fusion_output)nfusion_output = concatenate([fusion_output, encoder_output], axis=3) nfusion_output = Conv2D(256, (1, 1), activation=relu)(fusion_output)n

在融合層中,首先將1000類輸出層乘以1024(即32×32),這樣就從inception模型的最後一層得到了1024行數據;接著,把2D重構成3D得到一個具有1000個類別對象的32x32網格;然後,將它與編碼器模型的輸出相連;最後,應用一個帶有254個1X1內核的卷積網路,得到了融合層的最終輸出。

一些思考

1.不要逃避難懂的術語。我花了三天時間去搜索在Keras該如何實現「融合層」。因為這聽起來很複雜,我不想面對這個問題,而是試圖找到現成代碼。

2.多在網上請教他人。在Keras論壇中,我提出的問題沒人回答,同時Stack Overflow刪除了我的提問。但是,通過分解成小問題去請教他人,這迫使我進一步理解問題,並更快解決問題。

3.多發郵件請教。雖然論壇可能沒人回應,人們關心你能否直接與他們聯繫。在Skype上與你不認識的研究人員一起探討問題,這聽起來很有趣。

4.在解決「融合層」問題之前,我決定構建出所有組件。以下是分解融合層的幾個實驗(floydhub.com/emilwallne)。

5.我以為某些代碼能夠起作用,但是在猶豫是否要運行。雖然我知道核心邏輯是行得通的,但我不認為它會奏效。經過一陣糾結,我還是選擇運行。運行完模型的第一行代碼後,就開始報錯;四天後,一共產生幾百個報錯,我也做了數千個Google搜索,模型依舊停留在「Epoch 1/22」。

下一步計劃

圖像著色是一個極其有趣的問題。這既是一個科學問題,也是一個藝術問題。我寫下這篇文章,希望你能從中有所啟發,以加快在圖像著色方面的研究。以下是一些建議:

1.微調另一個預訓練好的模型;

2.使用不同的數據集;

3.添加更多圖片來提高網路的正確率;

4.在RGB顏色空間中構建一個放大器。構建一個與著色網路類似的模型,將深色調的著色圖像作為輸入,它能微調顏色以輸出合適的著色圖像;

5.進行加權分類;

6.應用一個分類神經網路作為損失函數。網路中錯誤分類的圖片有一個相應誤差,探究每個像素對該誤差的貢獻度。

7.應用到視頻中。不要太擔心著色效果,而是要關注如何使圖像切換保持協調。你也可以通過平鋪多張小圖像來處理大型圖像。

當然,你也可以嘗試用我貼在FloydHub上的三種著色神經網路,來給你的黑白圖像著色。

1.對於Alpha版本,只需將你的圖片解析度調成400x400像素,把名稱改為woman.jpg,並替換原有文件。

2.對於Beta版本和最終版本,在你運行FloydHub命令之前,要將你的圖片放入Test文件夾。當Notebook運行時,你也可以通過命令行直接上傳到Test文件夾。要注意,這些圖像的解析度必須是256x256像素。此外,你也可以上傳彩色測試圖像集,因為這個網路會自動把它們轉換為黑白圖像。

相關鏈接

1.Alpha版本機器人的Jupyter Notebook代碼:

floydhub.com/emilwallne

2.三個版本實現代碼 - Floydhub傳送門:

floydhub.com/emilwallne

3.三個版本實現代碼 - Github傳送門:

github.com/emilwallner/

4.所有實驗代碼:

floydhub.com/emilwallne

5.作者Twitter:emilwallner twitter.com/EmilWallner

6.教程原文:blog.floydhub.com/color

歡迎大家關注我們的專欄:量子位 - 知乎專欄

誠摯招聘

量子位正在招募編輯/記者,工作地點在北京中關村。期待有才氣、有熱情的同學加入我們!相關細節,請在量子位公眾號(QbitAI)對話界面,回復「招聘」兩個字。

量子位 QbitAI


推薦閱讀:

圖片一拉大就模糊!要不要試試無損放大圖片?
1.14【OpenCV圖像處理】基本閾值操作
亮度響應與HDR基礎
如何讓模糊圖片變得更清晰?

TAG:神经网络 | 图像处理 | 深度学习DeepLearning |