告別PS,用神經網路給家裡的黑白老照片上色。
回家過年時,我們都會和家人一起照些團圓照,留下美好的回憶。說起照片,很多小夥伴家裡都會有幾張黑白的老照片,你有想過讓它們變成彩色嗎?如果想,怎麼實現呢?本文手把手教你怎麼給家裡的黑白照片上色。
在 Reddit 論壇上有個很有趣的版塊叫 Colorization,在這個社區,人們用 Photoshop 給黑白歷史照片上色後會分享出來。但去年,一個叫 Amir Avni 的小哥把社區里的人給驚呆了,因為他用神經網路給黑白照片上色的結果實在過於驚艷,原本要花將近一個月才能完成的工作,現在用神經網路只需幾秒就能完成,而且效果很好!
當前,給黑白照片上色的工作通常還是用 Photoshop 手工完成,儘管在很多人眼裡 PS 可以說是無所不能,但其實它的上手成本並不低,完成這個過程非常麻煩。
簡單說,一張黑白照片常常需要花一個月才能變成彩色,而且需要很深的專業知識。比如,單單是臉部就需要多達 20 多層的粉色、綠色和藍色才能調和成正確的顏色。
我(作者Emil Wallnér——譯者注)被這種能上色的神經網路給深深迷住了,所以我自己照著 Amir Avni 的神經網路複製了一個,也可以給黑白照片上色。下面我就分享一下方法。
本文是面向初學者的,不過如果你對深度學習相關的知識還是比較陌生,你可以看看我之前寫的兩篇文章,這篇(https://blog.floydhub.com/my-first-weekend-of-deep-learning/)以及這篇(https://blog.floydhub.com/coding-the-history-of-deep-learning/)。或者你也可以看看 Andrej Karpathy 的講座(https://www.youtube.com/watch?v=LxfUGhug-iQ),補充一下背景知識。
我會通過三個步驟向大家展示如何搭建你自己的「染色」神經網路。
第一步是講解其中的核心邏輯。我們會用 40 行代碼搭建一個基本的神經網路,作為一個 Alpha 版的染色機器人。這部分的代碼都稀鬆平常,但這會很好地幫我們熟悉其中的語法。
第二步是創建一個具有泛化能力的神經網路,也就是我們的 Beta 版的染色機器人。它可以讓我們對此前未見的圖片進行上色。
在最終,我們會將神經網路與分類器相結合。我們會使用已被 120 萬張照片訓練過的 Inception Resnet V2。為了讓神經網路也能給當前的流行照片上色,我們會用 Unsplash 上的照片訓練它。
全部三個版本的神經網路,以及相關代碼的鏈接,我都放在了文章末尾,大家可以參考。
核心邏輯
在這部分,我會概述怎樣給一張照片著色,數字色彩的基本知識以及我們的神經網路的主要邏輯。
黑白照片可以用像素網格來表示,每個像素都有對應其亮度的值,這些值的範圍在 0 至 255 之間,從黑到白。
彩色照片其實是三個顏色層:一個紅色層,一個綠色層和一個藍色層(即三原色光模式,又叫 RGB 顏色模型)。這一點可能不符合大部分人的直覺。我們想像一個場景,在一個白色背景上將一片綠葉分到紅、綠、藍三個通道中。憑直覺,你會認為綠葉只會出現在綠色層中。
但是,如下所示,你會發現葉子出現了全部三個通道中。這是因為每個顏色層不僅僅決定色彩,還決定亮度。
比如,如果想讓顏色為白色,你需要所有的顏色相等分布。添加相同量的紅色和藍色後,綠色就會更亮。這樣,一張彩色照片會利用這三個顏色層調配色彩和對比度:
和黑白照片一樣,彩色照片中的每個顏色層也有一個在 0-255 之間的值。如果值為 0,則意味著這一層沒有任何顏色。如果這個值在所有的顏色通道中都為 0,那麼這張照片的像素就是黑色的。
你可能知道,神經網路可以為輸入值和輸出值創建一種關係。那麼在我們要給黑白照片染色的例子中,神經網路需要找到連接灰度圖像和彩色圖像的特徵。
總而言之,用神經網路為黑白照片上色,就是尋找將灰度值網格連接到紅、綠、藍三色網格的數據特徵。
第一步:搭建Alpha版神經網路
我們首先會搭建神經網路的一個簡單版本,為一張女性的臉部照片上色。這樣,後面我們給模型添加特徵的時候,你依然能熟悉模型的核心語法。
只需 40 行代碼,我們就可以進行下面這樣的轉換。中間的照片是我們的神經網路的著色結果,右邊圖像是原始彩色照片。訓練和測試模型都是用的同一張照片,我們在後面搭建模型的「測試版」時還會再講到這點。
顏色空間
首先,我們會用演算法將顏色通道從 RGB 改為 Lab。L 代表亮度,a 和 b 分別代表顏色光譜綠-紅和藍-黃。
如下所示,Lab 編碼的照片有一個灰度層,並將三個顏色層壓縮為兩層。這意味著我們在最終預測時可以使用原始灰度照片。而且,我們只需預測兩個顏色通道。
科學研究發現,人類眼睛中 94% 的細胞是用來決定亮度的,剩下的那 6% 才是用來感受顏色。在上圖中我們可以看到,灰度圖像比彩色層更加清晰,這也是我們在最終預測時保留灰度圖像的另一個原因。
從黑白道彩色
我們的最終預測是這樣的:我們有一個灰度層作為輸入,然後預測兩個彩色層,也就是 Lab 中的 a 和 b。要想創建最終的彩色圖像,我們會將用作輸入的 L 灰度圖像和 a、b 層加在一起,創建一個 Lab 圖像。
要想將一個顏色層變為兩個層,我們就需要用到卷積過濾器,可以把它們看作 3D 眼鏡中的藍色和紅色濾鏡。每個過濾器會決定我們在照片中看到的內容,它們也可以突出或移除一些東西,以從照片中提取信息。神經網路可以從過濾器中創建一個新的照片,也可以將幾個過濾器合併成一張照片。
卷積神經網路可以自動調整每個過濾器,以達到預期效果。我們首先會堆疊幾百個過濾器,然後把它們壓縮為兩個層,即 a 層和 b 層。
在我們詳細了解其中的工作原理之前,我們運行一下代碼。
在 FloydHub 上部署代碼
如果你對 FloydHub 比較陌生,你可以看看這個兩分鐘的安裝教程(https://www.floydhub.com/),我的這份5分鐘的視頻教程(https://www.youtube.com/watch?v=byLQ9kgjTdQ&t=6s)或這篇手把手指南(https://blog.floydhub.com/my-first-weekend-of-deep-learning/)。使用 FloydHub,我們可以很容易地在雲 GPU 上訓練深度學習模型。
Alpha 版神經網路
安裝好 FloydHub 後,執行如下命令:
git clone https://github.com/emilwallner/Coloring-greyscale-images-in-Keras
打開文件夾,並啟動 FloydHub:
cd Coloring-greyscale-images-in-Keras/floydhubfloyd init colornet
FloydHub 的 Web 控制面板會在你的瀏覽器中打開。系統會提示你創建一個新的 FloydHub 項目叫做 colornet。這一步完成後,回到終端,然後運行相同的 init 命令。
floyd init colornet
好了,我們開始運行本項目任務:
floyd run --data emilwallner/datasets/colornet/2:data --mode jupyter --tensorboard
對於本任務的簡單說明:
我們用下面這行代碼在 FloydHub 的 data 目錄中載入了一個公開數據集:
--dataemilwallner/datasets/colornet/2:data
你可以在 FloydHub 上查看和使用該數據集。
- 我們用--tensorboard 啟用 Tensorboard。
- 我們用--mode jupyter 在 Jupyter Notebook 下運行代碼。
- 如果你可以使用 GPU,那麼也可以給命令添加 GPU(--gpu),真能讓運行速度提升 50倍。
打開 Jupyter notebook,在 FloydHub 網站頁面的 Jobs 標籤下,點擊 Jupyter Notebook鏈接,會跳轉至這個文件:
floydhub/Alpha version/working_floyd_pink_light_full.ipynb
打開文件,按下 Shift+Enter 鍵,運行所有代碼塊。
漸漸增加 epoch 的值,感受一下神經網路是怎麼學習的。
現以 epoch 值為 1 開始,然後將值增加到 10,100,500,1000 和 3000。Epoch 的值顯示了神經網路從圖像中學習了多少次。在訓練神經網路後,你會在主文件夾中發現照片img_result.pngin。
# Get imagesimage = img_to_array(load_img(woman.png))image = np.array(image, dtype=float)# Import map images into the lab colorspaceX = rgb2lab(1.0/255*image)[:,:,0]Y = rgb2lab(1.0/255*image)[:,:,1:]Y = Y / 128X = X.reshape(1, 400, 400, 1)Y = Y.reshape(1, 400, 400, 2)# Building the neural networkmodel = Sequential()model.add(InputLayer(input_shape=(None, None, 1)))model.add(Conv2D(8, (3, 3), activation=relu, padding=same, strides=2))model.add(Conv2D(8, (3, 3), activation=relu, padding=same))model.add(Conv2D(16, (3, 3), activation=relu, padding=same))model.add(Conv2D(16, (3, 3), activation=relu, padding=same, strides=2))model.add(Conv2D(32, (3, 3), activation=relu, padding=same))model.add(Conv2D(32, (3, 3), activation=relu, padding=same, strides=2))model.add(UpSampling2D((2, 2)))model.add(Conv2D(32, (3, 3), activation=relu, padding=same))model.add(UpSampling2D((2, 2)))model.add(Conv2D(16, (3, 3), activation=relu, padding=same))model.add(UpSampling2D((2, 2)))model.add(Conv2D(2, (3, 3), activation=tanh, padding=same))# Finish modelmodel.compile(optimizer=rmsprop,loss=mse)#Train the neural networkmodel.fit(x=X, y=Y, batch_size=1, epochs=3000)print(model.evaluate(X, Y, batch_size=1))# Output colorizationsoutput = model.predict(X)output = output * 128canvas = np.zeros((400, 400, 3))canvas[:,:,0] = X[0][:,:,0]canvas[:,:,1:] = output[0]imsave("img_result.png", lab2rgb(canvas))imsave("img_gray_scale.png", rgb2gray(lab2rgb(canvas)))
用 FloydHub 命令來運行神經網路:
floyd run --data emilwallner/datasets/colornet/2:data --mode jupyter --tensorboard
技術說明
簡單說,輸入是一個表示黑白照片的網格,輸出是用彩色值表示的兩個網格。在輸入值和輸出值之間,我們創建了很多卷積過濾器,將它們連接在一起。這就是一個卷積神經網路。
在我們訓練神經網路時,我們用到了彩色照片。我們將 RGB 顏色轉換為 Lab 顏色空間。我們的輸入是一個黑白顏色層,輸出是兩個彩色層。
自左至右依次是我們的黑白顏色輸入,過濾器,神經網路的預測結果。
我們需要在同一區間內將預測值和實際值進行映射,這樣我們可以比較這兩個值。區間範圍在 -1 和 1 之間。要想映射預測值,我們會使用 tanh 函數。給 tanh 函數輸入任何值,它都會返回 -1 到 1 之間的值。
真正的顏色值分布在 -128 和 128 之間,這是 Lab 色彩空間的默認區間。用 128 除以這些值後,就能得到分布在 -1 和 1 之間的值。這種「標準化」的操作能讓我們比較神經網路的預測誤差。
在計算最終的誤差後,神經網路會更新過濾器以減少整體誤差。神經網路會持續循環這個過程直到誤差儘可能的低。
我們描述一下代碼塊中的一些語法。
X = rgb2lab(1.0/255*image)[:,:,0]Y = rgb2lab(1.0/255*image)[:,:,1:]
1.0/255 顯示我們在用一個 24 位元組的 RGB 彩色照片。這意味著我們每個顏色通道在使用 0-255 之間的數字。這總共會產生 1670 萬個顏色組合。
因為人類只能感知 2 百萬到 1 千萬種顏色,所以沒必要用這麼大的色彩空間。
Y = Y / 128
Lab 色彩空間的範圍和 RGB 的不同。Lab 中的色譜 ab 範圍在 -128 到 128 之間。用 128 除以輸出層中的所有值,我們可以將最終結果縮小在 -1 和 1 之間。
我們將它同神經網路進行匹配,會返回在 -1 和 1 之間分布的值。
用函數 rgb2lab() 將色彩空間轉換後,我們以 [ : , : , 0] 選擇灰度層,用作神經網路的輸入。 [ : , : , 1: ] 會選擇兩個彩色層,即綠-紅層和藍-黃層。
在訓練神經網路後,我們會將最後預測轉換為圖像。
output = model.predict(X)output = output * 128
這裡我們用了一張灰度照片作為輸入,然後在我們訓練後的神經網路中運行,最後我們取所有分布在 -1 和 1 之間的值,並將每個值乘以 128。這樣我們就可以得到對應 Lab 色譜中的正確顏色。
canvas = np.zeros((400, 400, 3))canvas[:,:,0] = X[0][:,:,0]canvas[:,:,1:] = output[0]
最後,我們以三層顏色值全為 0 的網格創建一個黑色 RGB 畫布。然後我們從測試照片中複製其灰度層。接著我們將兩個彩色層添加到 RGB 畫布中,最後將這個像素值序列轉換為一張照片。
Alpha 版神經網路的幾點訓練心得:
- 讀研究論文很有挑戰性:但我總結出每篇論文的核心內容後,這件工作就簡單的多了,這也讓我學會了將細節放在特定背景中去理解。
- 以簡單的任務開始很關鍵:我在網上能找到的實現代碼大都長達幾千行,這很難讓人概覽問題的核心邏輯。而一旦我們搭建好一個基礎版本的模型,就比較容易查看實現代碼和研究論文了。
- 參考其它公開項目:為了大概明白怎樣用代碼解決這個問題,我在GitHub上瀏覽了將近100個染色項目。
- 過程總會充滿挫折:剛開始,模型智能創建紅色和黃色。首先我用Relu激活函數進行最終激活。由於它只能將數字映射為正值,因而無法創建負值,也就是藍色和綠色色譜。在添加一個tanh激活函數並映射Y值後,解決了這個問題。
- 理解為先,速度在後:我看到的很多代碼執行起來很快,卻幾乎無法使用。我更在乎模型的優化速度,而不是代碼的執行速度。
第二步:搭建 Beta 版神經網路
要想搞懂 Alpha 版神經網路哪裡不足,可以試著讓它給一張從未見過的黑白照片上色。我們會發現結果很不理想。這是因為神經網路只根據它記憶的信息工作,面對一張從未見過的照片時就不知道怎麼做了。我們會在 Beta 版中糾正這個問題,教給神經網路怎麼泛化。
下面是 Beta 版神經網路對驗證照片的染色結果。
我沒有使用 ImageNet,而是用更高質量的照片在 FloydHub 上創建了一個公開數據集,照片來自 UNsplash(https://unsplash.com/),都是由專業攝影師拍攝的很有創意的照片。這個數據集包含了 9500 張訓練照片和 500 張驗證照片。
特徵提取器
我們的神經網路能夠找到將灰度圖像連接到其彩色版圖像的數據特徵。
想像一下你現在要給一張黑白照片上色,但是有限制條件,你每次只能看到 9 個像素。你可以從左上角到右下角瀏覽每張照片,試著猜測每個像素的顏色。
例如,這 9 個像素是下圖中女性的鼻孔邊緣。你可以預料到,要給這個部位上色幾乎是不可能的,所以將它拆分為幾步。
首先,你可以尋找簡單的模式:比如對角線和所有的黑色像素等。你可以尋找每個方塊中相同的模式,然後移除不匹配的像素。最終你會從 64 個小型過濾器中生成64張新照片。
如果再次看一下照片,你會發現自己已經檢測到了相同的小範圍模式。要想更深一步理解照片,可以將照片大小縮減一半。
這時你仍然僅有一個 3X3 過濾器來查看每張照片。但是將 9 個新像素和更低一層的過濾器合併後,你可以發現更複雜的模式。一個像素合併可能形成一個半圓,一個小點,或者一條線。然後再次從照片中提取相同的模式。這次你會生成 128 張新的過濾後的照片。
這樣操作幾步後,你生成的經過過濾的照片會如下所示:
前面提到過,這個過程從低級特徵開始,比如一個物體的邊緣。和輸出更近的層會合併成模式,然後進一步合併成更具體的畫面,最後形成一張臉部照片。
這個過程和大部分解決視覺問題的神經網路很像。我們這裡用的是卷積神經網路,需要將幾個過濾後的照片合併,以理解照片中的內容。
從特徵提取到著色
神經網路是自不斷試錯中運行。首先它會對每個像素進行隨機預測,根據每個像素的錯誤在網路中反向工作來優化特徵提取的工作。
首先它會調整生成最大誤差的情形,在我們的例子中,神經網路會進行這些調整:是否要進行著色,如何定位不同的物體。
神經網路首先會將所有物體染為棕色,這也是和其餘顏色最相似的一個顏色,這樣就會將誤差控制在最小。
因為大部分訓練數據都很相似,神經網路會儘力區分不同的物體,但無法生成更細微的顏色。這是我們在最終版本的神經網路中要解決的問題。
下面是 Beta 版神經網路的代碼,接著是技術說明。
# Get imagesX = []for filename in os.listdir(../Train/): X.append(img_to_array(load_img(../Train/+filename)))X = np.array(X, dtype=float)# Set up training and test datasplit = int(0.95*len(X))Xtrain = X[:split]Xtrain = 1.0/255*Xtrain#Design the neural networkmodel = Sequential()model.add(InputLayer(input_shape=(256, 256, 1)))model.add(Conv2D(64, (3, 3), activation=relu, padding=same))model.add(Conv2D(64, (3, 3), activation=relu, padding=same, strides=2))model.add(Conv2D(128, (3, 3), activation=relu, padding=same))model.add(Conv2D(128, (3, 3), activation=relu, padding=same, strides=2))model.add(Conv2D(256, (3, 3), activation=relu, padding=same))model.add(Conv2D(256, (3, 3), activation=relu, padding=same, strides=2))model.add(Conv2D(512, (3, 3), activation=relu, padding=same))model.add(Conv2D(256, (3, 3), activation=relu, padding=same))model.add(Conv2D(128, (3, 3), activation=relu, padding=same))model.add(UpSampling2D((2, 2)))model.add(Conv2D(64, (3, 3), activation=relu, padding=same))model.add(UpSampling2D((2, 2)))model.add(Conv2D(32, (3, 3), activation=relu, padding=same))model.add(Conv2D(2, (3, 3), activation=tanh, padding=same))model.add(UpSampling2D((2, 2)))# Finish modelmodel.compile(optimizer=rmsprop, loss=mse)# Image transformerdatagen = ImageDataGenerator( shear_range=0.2, zoom_range=0.2, rotation_range=20, horizontal_flip=True)# Generate training databatch_size = 50def image_a_b_gen(batch_size): for batch in datagen.flow(Xtrain, batch_size=batch_size): lab_batch = rgb2lab(batch) X_batch = lab_batch[:,:,:,0] Y_batch = lab_batch[:,:,:,1:] / 128 yield (X_batch.reshape(X_batch.shape+(1,)), Y_batch)# Train modelTensorBoard(log_dir=/output)model.fit_generator(image_a_b_gen(batch_size), steps_per_epoch=10000, epochs=1)# Test imagesXtest = rgb2lab(1.0/255*X[split:])[:,:,:,0]Xtest = Xtest.reshape(Xtest.shape+(1,))Ytest = rgb2lab(1.0/255*X[split:])[:,:,:,1:]Ytest = Ytest / 128print model.evaluate(Xtest, Ytest, batch_size=batch_size)# Load black and white imagescolor_me = []for filename in os.listdir(../Test/): color_me.append(img_to_array(load_img(../Test/+filename)))color_me = np.array(color_me, dtype=float)color_me = rgb2lab(1.0/255*color_me)[:,:,:,0]color_me = color_me.reshape(color_me.shape+(1,))# Test modeloutput = model.predict(color_me)output = output * 128# Output colorizationsfor i in range(len(output)): cur = np.zeros((256, 256, 3)) cur[:,:,0] = color_me[i][:,:,0] cur[:,:,1:] = output[i] imsave("result/img_"+str(i)+".png", lab2rgb(cur))
下面是運行 Beta 版神經網路的 FloydHub 命令:
floyd run --data emilwallner/datasets/colornet/2:data --mode jupyter --tensorboard
技術說明
該網路和其它神經網路主要的不同之處是像素定位的重要性。在染色類神經網路中,圖像大小或比率在神經網路中會一直保持不變。而在其它類型的神經網路中,圖像里最後一層越近,會變得越扭曲。
分類神經網路中的最大池化層會增加信息密度,但是也會造成圖像扭曲。它只會估值信息,但不會估值圖像的布局。而在染色類網路中,我們會使用步幅 2,將圖像的寬度和高度減少一半。雖然這也會增加信息密度,但不會扭曲圖像。
兩處進一步的區別:上採樣層和保持圖像比例的問題。分類網路只關注最終的分類問題,因此圖像在神經網路中移動時,圖像大小和質量都不會變化。
染色類神經網路也會將圖像比例保持一致,也是和上面一樣通過增加白色填充完成。另外,每個卷積層會裁切圖像,通過 *padding=same* 參數完成。
最後,神經網路使用下採樣層將圖像大小增大一倍。
for filename in os.listdir(/Color_300/Train/):X.append(img_to_array(load_img(/Color_300/Test+filename)))
該 for 循環會首先計算目錄中的文件名字,然後通過圖像目錄進行迭代,將圖像轉換為一個像素數組,最後將它們合併為一大很大的向量。
datagen = ImageDataGenerator( shear_range=0.2, zoom_range=0.2, rotation_range=20, horizontal_flip=True)
通過 ImageDataGenerator,我們可以調整我們圖像生成器的設置。這樣以來,每張圖像就永遠不會一樣了,從而提高學習效率。Shear_rangetilts 讓圖像向左或向右傾斜,其它設置為放大、旋轉和水平翻轉。
batch_size = 50def image_a_b_gen(batch_size): for batch in datagen.flow(Xtrain, batch_size=batch_size): lab_batch = rgb2lab(batch) X_batch = lab_batch[:,:,:,0] Y_batch = lab_batch[:,:,:,1:] / 128 yield (X_batch.reshape(X_batch.shape+(1,)), Y_batch)
根據上面的設置,我們使用文件夾 Xtrain 中的圖像來生成圖像。然後,為 X_batch 提取黑白層,為兩個顏色層提取兩種顏色。
model.fit_generator(image_a_b_gen(batch_size), steps_per_epoch=1, epochs=1000)
你用的 GPU 越強,可擬合的圖像就越多。在我們上面的設置中,你可以使用 50 到 100 張圖像。用你的批量大小除以訓練圖像的數目,就可以計算出 steps_per_epoch。
例如:批量大小為 50 的 100 張照片,那麼每個 epoch 就是 2 步。Epoch 的數目決定了你想訓練照片多少次。在 Tesla K80 GPU上,1萬張照片,21 個 epoch,訓練時間為 11 個小時。
Beta 版神經網路的幾點訓練心得:
- 在大批量運行前先進行大量的小批次實驗:即便在20-30次實驗後,還是有錯誤存在。網路在運行並不代表網路在起作用。相比傳統的編程錯誤,神經網路中的bug會更加細微。
- 數據集越多樣會讓圖像更能呈現棕色:如果你有很相似的圖像,你可以在無需更加複雜的架構下獲得良好的結果。不過代價是神經網路的泛化表現更糟糕了。
- 形狀、形狀還是形狀:在神經網路中,每張圖像的大小必須很準確,且比例保持一致。在剛開始時,我用了大小為300的圖像。將圖像切割三次後,得到150,75和35.5的圖像。結果就是丟失了很多像素!這導致了很多糟糕的結果,直到我意識到最好是用2的冪次方:2, 8, 16, 32, 64, 256等等。
- 創建一個數據集:a) 停用.DS_Store文件 b) 創造性解決問題,我最終用Chrome console腳本和擴展程序ImageSpark下載的文件 c) 拷貝抓取的原始文件,使你的清理腳本結構化。
第三步:搭建最終版神經網路
我們用於染色的最終版神經網路包含 4 個組件。我們將之前搭建的神經網路分割位一個編碼器和一個解碼器,在它們之間我們添加一個融合層。如果你對分類神經網路不是很熟悉,我建議你看看這篇教程。
除了編碼器外,輸入圖像同時也在 Inception ResNet v2 上運行,這是當前最強大的分類器之一,該神經網路由 120 萬張圖像訓練而成。我們提取出分類層,將它和編碼器的輸出融合起來。
通過從分類器到染色網路的遷移學習,神經網路能夠理解照片中都有什麼。這樣,就能讓神經網路通過上色機制匹配目標表示(object representation)。
這裡是部分驗證圖像,僅使用 20 張圖像用於網路訓練。
大部分照片的著色效果都不好。但這個驗證集很大(2500張照片),我還是找出了幾張不錯的。在更多圖像上訓練模型可以給出更一致的結果,但是大多數圖像經過處理後呈棕色。我使用多個圖像(包括驗證圖像)進行了一些實驗,全部實驗列表:https://www.floydhub.com/emilwallner/projects/color
這裡是之前研究中最常見的架構:
- 向圖像中手動添加色點來指導神經網路(http://www.cs.huji.ac.il/~yweiss/Colorization/)
- 找出匹配圖像,進行色彩遷移(https://dl.acm.org/citation.cfm?id=2393402、https://arxiv.org/abs/1505.05192)
- 殘差編碼器和融合分類層(http://tinyclouds.org/colorize/)
- 融合分類網路中的超列(https://arxiv.org/pdf/1603.08511.pdf、https://arxiv.org/pdf/1603.06668.pdf)
- 融合編碼器和解碼器之間的最終分類(http://hi.cs.waseda.ac.jp/~iizuka/projects/colorization/data/colorization_sig2016.pdf、https://github.com/baldassarreFe/deep-koalarization/blob/master/report.pdf)
- 常見色彩空間:Lab、YUV、HSV 和 LUV
- 常見損失函數:均方差、分類、加權分類
我選擇了融合層架構(上述列表的第五個)。
因為融合層的輸出結果最好,而且在 Keras 中理解和復現都更加容易。儘管它不是最強大的上色網路,但很適合初學者,而且最適合理解上色問題的動態。
我使用 Federico Baldassarre 等人論文中設計的神經網路(https://github.com/baldassarreFe/deep-koalarization/blob/master/report.pdf),在 Keras 中進行了自己的操作。
# Get imagesX = []for filename in os.listdir(/data/images/Train/): X.append(img_to_array(load_img(/data/images/Train/+filename)))X = np.array(X, dtype=float)Xtrain = 1.0/255*X#Load weightsinception = InceptionResNetV2(weights=None, include_top=True)inception.load_weights(/data/inception_resnet_v2_weights_tf_dim_ordering_tf_kernels.h5)inception.graph = tf.get_default_graph()embed_input = Input(shape=(1000,))#Encoderencoder_input = Input(shape=(256, 256, 1,))encoder_output = Conv2D(64, (3,3), activation=relu, padding=same, strides=2)(encoder_input)encoder_output = Conv2D(128, (3,3), activation=relu, padding=same)(encoder_output)encoder_output = Conv2D(128, (3,3), activation=relu, padding=same, strides=2)(encoder_output)encoder_output = Conv2D(256, (3,3), activation=relu, padding=same)(encoder_output)encoder_output = Conv2D(256, (3,3), activation=relu, padding=same, strides=2)(encoder_output)encoder_output = Conv2D(512, (3,3), activation=relu, padding=same)(encoder_output)encoder_output = Conv2D(512, (3,3), activation=relu, padding=same)(encoder_output)encoder_output = Conv2D(256, (3,3), activation=relu, padding=same)(encoder_output)#Fusionfusion_output = RepeatVector(32 * 32)(embed_input) fusion_output = Reshape(([32, 32, 1000]))(fusion_output)fusion_output = concatenate([encoder_output, fusion_output], axis=3) fusion_output = Conv2D(256, (1, 1), activation=relu, padding=same)(fusion_output)#Decoderdecoder_output = Conv2D(128, (3,3), activation=relu, padding=same)(fusion_output)decoder_output = UpSampling2D((2, 2))(decoder_output)decoder_output = Conv2D(64, (3,3), activation=relu, padding=same)(decoder_output)decoder_output = UpSampling2D((2, 2))(decoder_output)decoder_output = Conv2D(32, (3,3), activation=relu, padding=same)(decoder_output)decoder_output = Conv2D(16, (3,3), activation=relu, padding=same)(decoder_output)decoder_output = Conv2D(2, (3, 3), activation=tanh, padding=same)(decoder_output)decoder_output = UpSampling2D((2, 2))(decoder_output)model = Model(inputs=[encoder_input, embed_input], outputs=decoder_output)#Create embeddingdef create_inception_embedding(grayscaled_rgb): grayscaled_rgb_resized = [] for i in grayscaled_rgb: i = resize(i, (299, 299, 3), mode=constant) grayscaled_rgb_resized.append(i) grayscaled_rgb_resized = np.array(grayscaled_rgb_resized) grayscaled_rgb_resized = preprocess_input(grayscaled_rgb_resized) with inception.graph.as_default(): embed = inception.predict(grayscaled_rgb_resized) return embed# Image transformerdatagen = ImageDataGenerator( shear_range=0.4, zoom_range=0.4, rotation_range=40, horizontal_flip=True)#Generate training databatch_size = 20def image_a_b_gen(batch_size): for batch in datagen.flow(Xtrain, batch_size=batch_size): grayscaled_rgb = gray2rgb(rgb2gray(batch)) embed = create_inception_embedding(grayscaled_rgb) lab_batch = rgb2lab(batch) X_batch = lab_batch[:,:,:,0] X_batch = X_batch.reshape(X_batch.shape+(1,)) Y_batch = lab_batch[:,:,:,1:] / 128 yield ([X_batch, create_inception_embedding(grayscaled_rgb)], Y_batch)#Train model tensorboard = TensorBoard(log_dir="/output")model.compile(optimizer=adam, loss=mse)model.fit_generator(image_a_b_gen(batch_size), callbacks=[tensorboard], epochs=1000, steps_per_epoch=20)#Make a prediction on the unseen imagescolor_me = []for filename in os.listdir(../Test/): color_me.append(img_to_array(load_img(../Test/+filename)))color_me = np.array(color_me, dtype=float)color_me = 1.0/255*color_mecolor_me = gray2rgb(rgb2gray(color_me))color_me_embed = create_inception_embedding(color_me)color_me = rgb2lab(color_me)[:,:,:,0]color_me = color_me.reshape(color_me.shape+(1,))# Test modeloutput = model.predict([color_me, color_me_embed])output = output * 128# Output colorizationsfor i in range(len(output)): cur = np.zeros((256, 256, 3)) cur[:,:,0] = color_me[i][:,:,0] cur[:,:,1:] = output[i] imsave("result/img_"+str(i)+".png", lab2rgb(cur))
下面是 FloydHub 運行完整版神經網路的代碼:
floyd run --data emilwallner/datasets/colornet/2:data --mode jupyter --tensorboard
技術說明
當我們將幾個模型融合在一起,Keras 的功能性 API 是個理想的選擇。
首先,我們下載 Inception ResNet v2 神經網路,並載入權重值。因為我們會並行使用兩個模型,所以需要指明我們在用哪個模型。這一步用 TensorFlow 和 Keras 後端完成。
inception = InceptionResNetV2(weights=None, include_top=True)inception.load_weights(/data/inception_resnet_v2_weights_tf_dim_ordering_tf_kernels.h5)inception.graph = tf.get_default_graph()
我們使用圖像微調來創建批次,然後將它們轉換成黑白圖像,在 Inception ResNet 模型中運行。
grayscaled_rgb = gray2rgb(rgb2gray(batch))embed = create_inception_embedding(grayscaled_rgb)
首先,我們必須重新調整照片大小以匹配 Inception 模型,然後根據模型對象,用預處理器將像素和色彩值進行格式處理。在最後一步,在 Inception 模型中運行該圖像,並提取模型的最後一層。
def create_inception_embedding(grayscaled_rgb): grayscaled_rgb_resized = [] for i in grayscaled_rgb: i = resize(i, (299, 299, 3), mode=constant) grayscaled_rgb_resized.append(i) grayscaled_rgb_resized = np.array(grayscaled_rgb_resized) grayscaled_rgb_resized = preprocess_input(grayscaled_rgb_resized) with inception.graph.as_default(): embed = inception.predict(grayscaled_rgb_resized)return embed
我們回到生成器那裡,對於每個批次,我們生成 20 張低格式的照片。在 Tesla K80 GPU 上會花費一個小時。如果不出現內存問題,在這個模型上每次最多能處理 50 張照片。
yield ([X_batch, create_inception_embedding(grayscaled_rgb)], Y_batch)
這和我們的 colornet 模型的格式相匹配。
model = Model(inputs=[encoder_input, embed_input], outputs=decoder_output)
將 encoder_input 輸入編碼器模型,然後在融合層中把編碼器模型的輸出和 embed_input 融合起來;接著將融合層的輸出作為解碼器模型的輸入,它會返回最終輸出 decoder_output。
fusion_output = RepeatVector(32 * 32)(embed_input) fusion_output = Reshape(([32, 32, 1000]))(fusion_output)fusion_output = concatenate([fusion_output, encoder_output], axis=3) fusion_output = Conv2D(256, (1, 1), activation=relu)(fusion_output)
在融合層,我們首先將帶有 1000 個類別的層級乘 1024 (32 * 32)。這樣,我們就使用 Inception 模型的最後一層得到了 1024 個單元。
然後將它們從 2D 重塑為 3D,即將維度更改為 32 x 32x1000 的張量。然後把它們和編碼器模型的輸出連接起來。我們使用 256 個 1X1 卷積核的卷積網路,輸入 ReLU 激活函數後作為融合層的最終輸出。
下一步工作
為黑白照片上色是個非常迷人的問題,這既是一個科學問題,也是一個藝術問題。我寫這篇文章的目的就是使大家可以了解圖像上色,並且繼續開發相關技術。以下是一些初學建議:
- 使用另一個預訓練模型實現它
- 嘗試一個不同的數據集
- 使用更多圖像來提高神經網路的準確率
- 在 RGB 色彩空間內構建一個放大器。創建一個與上色網路類似的模型,該模型使用高飽和彩色圖像作為輸入,正確的彩色圖像作為輸出。
- 實現加權分類
- 將該網路應用到視頻。不用擔心上色問題,但是要保持圖像轉換時的一致性。你還可以過平鋪多張小圖像來處理大型圖像。
你還可以使用 FloydHub,用這三個版本的上色神經網路為黑白圖像上色。
- 使用 Alpha 版本時,只需將你的圖片解析度調成 400x400 像素,將 woman.jpg 並替換你自己的同名文件。
- 使用 Beta 版本和最終版本時,在你運行 FloydHub 命令之前,要將你的圖片放入 Test 文件夾。當 Notebook 運行時,你也可以通過命令行直接上傳到 Test 文件夾。要注意,這些圖像的解析度必須是 256x256 像素。此外,你也可以上傳彩色測試圖像集,因為這個網路會自動把它們轉換為黑白圖像。
通過這三步,相信你也可以自己動手給家裡的黑白照片穿上花衣服了,快去試試吧!
附模型及代碼傳送門:
1.Alpha 版本機器人的 Jupyter Notebook 代碼:
https://www.floydhub.com/emilwallner/projects/color/43/code/Alpha-version/alpha_version.ipynb
2.三個版本實現代碼 - Floydhub 鏈接:
https://www.floydhub.com/emilwallner/projects/color/43/code
3.三個版本實現代碼 - Github 鏈接:
https://github.com/emilwallner/Coloring-greyscale-images-in-Keras
4.所有實驗代碼:
FloydHub - Deep Learning Platform - Cloud GPU參考資料:
https://medium.freecodecamp.org/colorize-b-w-photos-with-a-100-line-neural-network-53d9b4449f8d
集智AI課堂將於 4 月 9 號停止售課,需要報名的同學不要錯過,這可能是今年最好的轉入人工智慧行業的契機了,等你~
推薦閱讀:
※你們幾個,明早可別忘了來集智拿1070ti啊!
※昨晚AI送給我一首歌,我很感動接著告訴他有待提高。
※今年最後一次發車!
※2017無人駕駛智能車Hackathon(太庫站)正在進行中!
※3分鐘好像太狂了,那5分鐘幫你開啟機器學習的大門吧!