三步教你搭建給黑白照片上色的神經網絡 !(附代碼)
深度學習雲平台FloydHub最近在官方博客上發了一篇通過搭建神經網絡,來給黑白照片上色的教程,在Twitter和Reddit論壇上都廣受好評。
FloydHub是個YC孵化的創業公司,號稱要做深度學習領域的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,從黑色到白色。
彩色圖像可以分為三層,分別是紅色層、綠色層和藍色層。直觀上,你可能會認為植物隻存在於綠色層,但事實可能與你的直覺相反。想象一下,將白色背景上的綠葉分成三個圖層。
如下圖所示,葉子在三個圖層中都存在。這些層不僅可以確定顏色,還確定了亮度。
例如,為了得到白色這個顏色,你需要將所有顏色均勻分布。添加等量紅色和藍色後,綠色會變得更亮。因此,彩色圖像使用三個通道來編碼顏色和對比度:
就像黑白圖像一樣,彩色圖像中每個圖層值的範圍也是0 – 255,值為0意味著該圖層中沒有顏色。如果在所有顏色圖層中該值都為0,則該圖像像素為黑色。
神經網絡能建立輸入和輸出之間的關係。更準確地說,著色任務就是讓網絡找到連接灰度圖像與彩色圖像的特征。
因此,著色神經網絡,就是要尋找將灰度值網格連接到三色網格的特征。
Alpha版本
我們從簡單版本開始,建立一個能給女性臉部著色的神經網絡。這樣,當添加新特征時,你可以熟悉已有模型的核心語法。
隻需要40行代碼,就能實現下圖所示的轉換。中間圖像是用神經網絡完成的,右邊圖像是原始的彩色照片。這個網絡使用了相同圖像做訓練和測試,在beta版本中還會再講這一點。
1、顏色空間
首先,使用一種能改變顏色通道的算法,從RGB到Lab。其中,L表示亮度,a和b分別表示顏色光譜,綠-紅和藍-黃。
如下所示,Lab編碼的圖像具有一個灰度層,並將三個顏色層壓成兩層,這意味著在最終預測中可以使用原始灰度圖像。此外,我們隻需預測兩個通道。
科學研究表明,人類眼睛中有94%細胞確定著亮度,隻有6%細胞是用來感受顏色的。如上圖所示,灰度圖像比彩色層更加清晰。這是我們在最終預測中保留灰度圖像的另一個原因。
2、從黑白到彩色
最終預測應該是這樣的:向網絡輸入灰度層(L),然後預測Lab中的兩個顏色層ab。要創建最終輸出的彩色圖像,我們需要把輸入的灰度(L)圖像和輸出的a、b層加在一起,創建一個Lab圖像。
我們使用卷積過濾器將一層變成兩層,可以把它們看作3D眼鏡中的藍色和紅色濾鏡。每個過濾器確定能從圖片中看到的內容,可以突出或移除某些東西,來從圖片中提取信息。這個網絡可以從過濾器中創建新圖像,也可以組合多個過濾器形成新圖像。
卷積神經網絡能自動調整每個濾波器,以達到預期結果。我們將從堆疊數百個濾波器開始,然後將它們縮小成兩層,即a層和b層。
在詳細介紹其工作原理之前,先介紹代碼。
3、在FloydHub上部署代碼
如果你剛接觸FloydHub,請看這個2分鍾安裝教程(https://www.floydhub.com/),以及手把手教學指南(https://blog.floydhub.com/my-first-weekend-of-deep-learning/),然後就可以開始在GPU雲上訓練深度學習模型了。
4、Alpha版本
安裝好FloydHub後,執行以下命令:
git clone https://github.com/emilwallner/Coloring-greyscale-images-in-Keras
打開文件夾並啟動FloydHub。
cd Coloring-greyscale-images-in-Keras/floydhub
floyd init colornet
FloydHub Web控製台將在瀏覽器中打開,係統會提示你創建一個名為colornet的FloydHub新項目。完成後,返回終端並執行相同的初始化命令。
floyd init colornet
接下來執行本項目任務。
floyd run --data emilwallner/datasets/colornet/2:data --mode jupyter --tensorboard
對於這個任務的一些簡單說明:
- 我們在FloydHub的數據集目錄(—data emilwallner / datasets / colornet / 2:data)中載入了一個公開數據集,你在FloydHub上查看並使用此數據集和許多其他公開數據集;
- 啟用了Tensorboard (—tensorboard);
- 在Jupyter Notebook下運行代碼 (—mode jupyter);
- 如果能使用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)
開始時把epoch設置為1,逐漸增加到10、100、5500、1000和3000。epoch值表示神經網絡從圖像中學習的次數。在訓練神經網絡的過程中,你可以在主文件夾中找到這個圖像img_result.png。
# Get images
image = img_to_array(load_img('woman.png'))
image = np.array(image, dtype=float)
# Import map images into the lab colorspace
X = rgb2lab(1.0/255*image)[:,:,0]
Y = rgb2lab(1.0/255*image)[:,:,1:]
Y = Y / 128
X = X.reshape(1, 400, 400, 1)
Y = Y.reshape(1, 400, 400, 2)
model = Sequential()
model.add(InputLayer(input_shape=(None, None, 1)))
# Building the neural network
model = 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 model
model.compile(optimizer='rmsprop',loss='mse')
#Train the neural network
model.fit(x=X, y=Y, batch_size=1, epochs=3000)
print(model.evaluate(X, Y, batch_size=1))
# Output colorizations
output = model.predict(X)
output = output * 128
canvas = np.zeros((400, 400, 3))
canvas[:,:,0] = X[0][:,:,0]
canvas[:,:,1:] = output[0]
imsave("img_result.png", lab2rgb(cur))
imsave("img_gray_scale.png", rgb2gray(lab2rgb(cur)))
用FloydHub命令來運行網絡:
floyd run --data emilwallner/datasets/colornet/2:data --mode jupyter --tensorboard
5、技術說明
與其他視覺網絡不同,它的主要區別在於像素位置的重要性。在著色網絡中,圖像的分辨率或比例在整個網絡中保持不變。而在其他網絡中,越靠近最後一層,圖像變得越扭曲。
分類網絡中的最大池化層增加了信息密度,但也可能導致圖像失真。它隻對信息進行估值,而未考慮到圖像布局。在著色網絡中,我們將步幅改為2,每次運算使寬度和高度減半。這樣,不但增加了信息密度,也不會使圖像扭曲。
兩者差異在於上采樣層和圖像比例保持方麵。而分類網絡隻關心最終的分類正確率,因此隨著網絡深入,它會不斷減小圖像的分辨率和質量。
但是,著色網絡的圖像比例保持不變。這是通過添加空白填充來實現的,否則每個卷積層將削減圖像,實現代碼為:padding =’same’。
要使圖像的分辨率加倍,著色網絡使用了上采樣層,更多信息見:https://keras.io/layers/convolutional/#upsampling2d。
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_range是指圖像向左或向右的傾斜程度,其他參數不需要加以說明。
batch_size = 50
def 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,則可以設置越大的batch_size值。根據現有硬件,我們設置了每批次輸入50-100張圖像。參數steps_per_epoch是通過把訓練圖像的數量除以批次大小得出的。例如,有100張圖像且批次大小為50,則steps_per_epoch值為2。參數epoch決定網絡中所有圖像的訓練次數。在Tesla K80 GPU上,大約需要11小時才能完成對1萬張圖像的21次訓練。
6、訓練心得
- 先進行多次小批次實驗,再嚐試大批次實驗。即使經過20-30次實驗,我仍然發現很多錯誤。程序能運行並不意味著它能工作。神經網絡中的問題通常比傳統編程遇到的Bug更為麻煩。
- 多樣化的數據集會使著色效果呈現棕色。如果數據集中圖像比較相似,不需要設計很複雜的架構,就可以得到一個良好效果,但是其泛化能力十分糟糕。
- 重視圖像形狀的統一性。每張圖像的分辨率必須是準確的,且在整個網絡中保持成比例。開始時,所使用的圖像分辨率為300,將它減半三次後分別得到150、75和35.5。因此,就丟失了半個像素,這導致了很多問題,後來才意識到應該使用4、8、16、32、64、256等這種能被2整除的數。
- 創建數據集。請禁用.DS_Store文件,不然會產生很多麻煩;大膽創新,最後我用Chrome控製台腳本和擴展程序來下載文件;備份原始文件並構建清理腳本。
最終版本
最終版本的著色神經網絡有四個組成部分。我們將之前的網絡拆分成編碼器和解碼器,在這兩者之間還添加了一個融合層。關於分類網絡的基本教程,可參考:https://cs231n.github.io/classification/。
Inception resnet v2是目前最強大的分類器之一,使用了120萬張圖像來訓練該網絡。我們提取了它的分類層,並將其與編碼器的輸出進行合並。因此,輸入數據傳給編碼器的同時,也並行傳輸到resnet v2網絡的分類層中。更多信息,請參考原論文:https://github.com/baldassarreFe/deep-koalarization。
這個網絡通過將分類器的學習效果遷移到著色網絡上,可更好理解圖片中的內容。因此,這樣使得網絡能夠把目標表征與著色方案相匹配。
以下是在一些驗證圖像上的著色效果,僅使用20張圖像來訓練網絡。
大多數圖像的著色效果不好,但是由於驗證集中有2500張圖像,我設法找到了一些良好的著色圖像。在更多圖像上進行訓練可以獲得更為穩定的結果,但是大部分都呈現棕色色調。這兩個鏈接貼出了運行試驗的完整列表,包括驗證圖像(https://www.floydhub.com/emilwallner/projects/color) (https://www.floydhub.com/emilwallner/projects/color_full)。
本文的神經網絡設計參考了這篇論文(https://github.com/baldassarreFe/deep-koalarization/blob/master/report.pdf),以及我在Keras中的一些理解。
注意:在下麵代碼中,我把Keras的序列模型變換成相應的函數調用。
# Get images
X = []
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 weights
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()
embed_input = Input(shape=(1000,))
#Encoder
encoder_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)
#Fusion
fusion_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)
#Decoder
decoder_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 embedding
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
# Image transformer
datagen = ImageDataGenerator(
shear_range=0.4,
zoom_range=0.4,
rotation_range=40,
horizontal_flip=True)
#Generate training data
batch_size = 20
def 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 images
color_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_me
color_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 model
output = model.predict([color_me, color_me_embed])
output = output * 128
# Output colorizations
for 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的函數調用功能是最佳選擇。
首先,要下載inception resnet v2網絡並加載權重。由於要並行使用兩個模型,因此必須指定當前要使用哪個模型。這個可通過Keras的後端Tensorflow來完成。
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)
代碼23
這與本項目中著色模型的格式相匹配。
model = Model(inputs=[encoder_input, embed_input], outputs=decoder_output)
e```
ncoder_input會輸入到編碼器模型中;接著,編碼器模型的輸出會與融合層中的embed_input相融合;然後,這個融合輸出會作為解碼器模型的輸入;最終,解碼器模型會輸出預測結果decode_output。
```javascript
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得到一個具有1000個類別對象的32x32網格;然後,將它與編碼器模型的輸出相連;最後,應用一個帶有254個1X1內核的卷積網絡,得到了融合層的最終輸出。
一些思考
1. 不要逃避難懂的術語。我花了三天時間去搜索在Keras該如何實現“融合層”。因為這聽起來很複雜,我不想麵對這個問題,而是試圖找到現成代碼。
2. 多在網上請教他人。在Keras論壇中,我提出的問題沒人回答,同時Stack Overflow刪除了我的提問。但是,通過分解成小問題去請教他人,這迫使我進一步理解問題,並更快解決問題。
3. 多發郵件請教。雖然論壇可能沒人回應,人們關心你能否直接與他們聯係。在Skype上與你不認識的研究人員一起探討問題,這聽起來很有趣。
4. 在解決“融合層”問題之前,我決定構建出所有組件。以下是分解融合層的幾個實驗(https://www.floydhub.com/emilwallner/projects/color/24/code/Experiments/transfer-learning-examples)。
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代碼:
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.所有實驗代碼:
https://www.floydhub.com/emilwallner/projects/color/jobs
5.作者Twitter:emilwallner
https://twitter.com/EmilWallner
6.教程原文:
https://blog.floydhub.com/colorizing-b&w-photos-with-neural-networks/
原文發布時間為:2017-10-28
本文來自雲棲社區合作夥伴“數據派THU”,了解相關信息可以關注“數據派THU”微信公眾號
最後更新:2017-10-30 16:34:32