天天看點

手把手 | 初學者如何用Chainer為漫畫上色 深度學習幫你逆襲漫畫家(附代碼)

手把手 | 初學者如何用Chainer為漫畫上色 深度學習幫你逆襲漫畫家(附代碼)

最近一直有人說深度學習(deep learning)的附加價值高,于是我也在一兩個月前開始學習chainer了。機會難得就想試着用chainer做一些各種各樣的嘗試,比如寫個給線描上色的小程式之類的。

線描上色這個任務的性質是監督式學習(supervised learning),是以需要大量的線稿和上完色的圖檔,越多越好。

這次是用opencv用角色的畫像把線稿生成了出來。生成例子如下:

手把手 | 初學者如何用Chainer為漫畫上色 深度學習幫你逆襲漫畫家(附代碼)
手把手 | 初學者如何用Chainer為漫畫上色 深度學習幫你逆襲漫畫家(附代碼)

收集了角色們的畫像,将其轉化為線稿之後資料集(dataset)就完成了。這次用了約60萬張圖檔。

關于神經網絡(neural network)的結構,我用了一種叫做u-net的網絡。它的特點是會把卷積(convolution)和反卷積(deconvolution)的層混合着連接配接在一起。這樣就可以做到參照着一開始的線稿來上色。(譯者注:有興趣的可以看一下unet的論文以及文末提供的神經網絡代碼。)

這樣生成出來的圖像和原來上完色的圖像對比然後取平方差,神經網絡的訓練讓平方差最小即可。

隻要保證網絡每一層的輸入和下一層的輸出相比對就大體ok,但是自己定義的資料如何制作這方面,因為沒有什麼例子是以可能對各位來說有些難懂。

花了整整一晚上時間使勁用data喂飽了neural net(掩面)的結果↓

手把手 | 初學者如何用Chainer為漫畫上色 深度學習幫你逆襲漫畫家(附代碼)

嗯~神經網絡這邊大概是想說“肌膚的顔色我大概能搞懂但是除此之外的實在不知道啊,發色啊衣服顔色啊我當然不可能知道吧。”

這裡要登場的是叫對抗網絡(adversarial net)的神經網絡,簡稱“怼”。

“怼”要做的是學習真正的圖像和被神經網絡生成出來的圖像之間的顔色差别,然後找出兩個圖像中的那個叛徒。

是以如果神經網絡一直生成老照片那樣顔色的圖像的話“怼”隻要學一會兒就能準确的找出哪一張是神經網絡生成的。

但是如果“怼”太用力的話上色的神經網絡會拼命反抗導緻上色失敗請多加注意。

手把手 | 初學者如何用Chainer為漫畫上色 深度學習幫你逆襲漫畫家(附代碼)

這樣的顔色都已經說不上是線稿上色後的東西而更加接近藝術了。(嘛,順着這條道走,把上色用神經網絡怼成藝術生也不是不可以。。。現在暫時還是回到學習和原畫之間的差别上面吧)

手把手 | 初學者如何用Chainer為漫畫上色 深度學習幫你逆襲漫畫家(附代碼)

呼,上色終于完成了!照着這個勢頭下去再接着幹吧。

第一階段是學習了128x128的圖像,第二階段是給512x512的圖像學習上色。以下是沒“怼”過的訓練結果↓

手把手 | 初學者如何用Chainer為漫畫上色 深度學習幫你逆襲漫畫家(附代碼)

還不錯喲

手把手 | 初學者如何用Chainer為漫畫上色 深度學習幫你逆襲漫畫家(附代碼)

不錯不錯。

手把手 | 初學者如何用Chainer為漫畫上色 深度學習幫你逆襲漫畫家(附代碼)

把實際的線稿拿來喂給神經網絡如何?我從pixiv上借用來了線稿類的畫

手把手 | 初學者如何用Chainer為漫畫上色 深度學習幫你逆襲漫畫家(附代碼)

(因為神經網絡大部分都是卷積神經網絡(cnn),寬高比有一些變化也沒關系)

手把手 | 初學者如何用Chainer為漫畫上色 深度學習幫你逆襲漫畫家(附代碼)

好棒!

變成了彩色的怪物了,嘛這種怪物也是有的。

平安收工。

說回來,果然還是會想親手線上稿上上一些色吧?于是稍微改變一下輸入,和一階段不同的是在原來的線稿之外多加了三個輸入層(rgb),給神經網絡一些用色上的提示吧。

總之:

手把手 | 初學者如何用Chainer為漫畫上色 深度學習幫你逆襲漫畫家(附代碼)

茶色的頭發淡藍的水手服和藏青的裙子,之類的要求也可以提了。

稍微霸氣的像這樣畫上一筆也是可以的。

手把手 | 初學者如何用Chainer為漫畫上色 深度學習幫你逆襲漫畫家(附代碼)
手把手 | 初學者如何用Chainer為漫畫上色 深度學習幫你逆襲漫畫家(附代碼)

不管是大概的提示也好非常用心的每個細節都提示也好效果都不錯。(可能有些難懂,就是用不同的顔色在各種地方點一下來提示,比如下面)

手把手 | 初學者如何用Chainer為漫畫上色 深度學習幫你逆襲漫畫家(附代碼)

這樣我也從工程師升職成畫師了!

至此,我覺得線稿的自動上色和帶提示的上色已經做的還不錯了。

雖然還不如畫師們認真畫出來的,如果想随随便便塗個色還是非常友善的(譯者:比如在經費不夠的情況下。。。)。

漫畫之類的也是,比起用網點貼紙(screen tone)還是大緻的上一個色比較快速友善的。(這次的神經網絡非常擅長給膚色上色。。。我想說的各位懂得吧~)

順便補充一下,弱點還是有幾個的。

例如同時用對抗網絡和上色提示一起訓練的時候,上色提示會幹涉到對抗網絡,有時會導緻訓練結果不穩定。

手把手 | 初學者如何用Chainer為漫畫上色 深度學習幫你逆襲漫畫家(附代碼)

↑明明隻是想給泳裝上一個不同的顔色,結果其他部分的顔色也跟着變了。

如果隻是作為一個簡單的上色工具的話,隻加提示來訓練神經網絡可能會更加穩定。

另外,如果線稿的線太粗或者太細的情況下,線會崩壞掉導緻結果不怎麼樣的情況也有,仔細的給了上色提示但是沒有反應在結果上的情況也有。

不同的細節都用同一個神經網絡來對付雖然比較厲害,但是作為工具使用的時候需要根據用途來做一些調整。

借鑒的線稿原畫:

「【プリンセスロワイヤル】パンドラ」/「鉛筆工房【irith】」[pixiv] (http://www.pixiv.net/member_illust.php?mode=medium&illust;_id=31274285)

線畫詰め(http://www.pixiv.net/member_illust.php?mode=manga&illust;_id=43369404)

「改弐」/「炬燵魂」[pixiv](http://www.pixiv.net/member_illust.php?mode=medium&illust;_id=56689287)

「めりくり線畫」/「タマコ」[pixiv](http://www.pixiv.net/member_illust.php?mode=medium&illust;_id=40487409)

「泰1」/「20100301」[pixiv](http://www.pixiv.net/member_illust.php?mode=medium&illust;_id=10552795)

※生成的線稿的訓練材料和原畫一時半會兒找不到,實在抱歉,還請多包涵。

順便,這次的神經網絡第一階段和第二階段的構造都是一樣的,基本上感覺如下:

unet.py

class unet(chainer.chain):

def __init__(self):

super(unet, self).__init__(

c0 = l.convolution2d(4, 32, 3, 1, 1),

c1 = l.convolution2d(32, 64, 4, 2, 1),

c2 = l.convolution2d(64, 64, 3, 1, 1),

c3 = l.convolution2d(64, 128, 4, 2, 1),

c4 = l.convolution2d(128, 128, 3, 1, 1),

c5 = l.convolution2d(128, 256, 4, 2, 1),

c6 = l.convolution2d(256, 256, 3, 1, 1),

c7 = l.convolution2d(256, 512, 4, 2, 1),

c8 = l.convolution2d(512, 512, 3, 1, 1),

dc8 = l.deconvolution2d(1024, 512, 4, 2, 1),

dc7 = l.convolution2d(512, 256, 3, 1, 1),

dc6 = l.deconvolution2d(512, 256, 4, 2, 1),

dc5 = l.convolution2d(256, 128, 3, 1, 1),

dc4 = l.deconvolution2d(256, 128, 4, 2, 1),

dc3 = l.convolution2d(128, 64, 3, 1, 1),

dc2 = l.deconvolution2d(128, 64, 4, 2, 1),

dc1 = l.convolution2d(64, 32, 3, 1, 1),

dc0 = l.convolution2d(64, 3, 3, 1, 1),

bnc0 = l.batchnormalization(32),

bnc1 = l.batchnormalization(64),

bnc2 = l.batchnormalization(64),

bnc3 = l.batchnormalization(128),

bnc4 = l.batchnormalization(128),

bnc5 = l.batchnormalization(256),

bnc6 = l.batchnormalization(256),

bnc7 = l.batchnormalization(512),

bnc8 = l.batchnormalization(512),

bnd8 = l.batchnormalization(512),

bnd7 = l.batchnormalization(256),

bnd6 = l.batchnormalization(256),

bnd5 = l.batchnormalization(128),

bnd4 = l.batchnormalization(128),

bnd3 = l.batchnormalization(64),

bnd2 = l.batchnormalization(64),

bnd1 = l.batchnormalization(32)

)

def calc(self,x, test = false):

e0 = f.relu(self.bnc0(self.c0(x), test=test))

e1 = f.relu(self.bnc1(self.c1(e0), test=test))

e2 = f.relu(self.bnc2(self.c2(e1), test=test))

e3 = f.relu(self.bnc3(self.c3(e2), test=test))

e4 = f.relu(self.bnc4(self.c4(e3), test=test))

e5 = f.relu(self.bnc5(self.c5(e4), test=test))

e6 = f.relu(self.bnc6(self.c6(e5), test=test))

e7 = f.relu(self.bnc7(self.c7(e6), test=test))

e8 = f.relu(self.bnc8(self.c8(e7), test=test))

d8 = f.relu(self.bnd8(self.dc8(f.concat([e7, e8])), test=test))

d7 = f.relu(self.bnd7(self.dc7(d8), test=test))

d6 = f.relu(self.bnd6(self.dc6(f.concat([e6, d7])), test=test))

d5 = f.relu(self.bnd5(self.dc5(d6), test=test))

d4 = f.relu(self.bnd4(self.dc4(f.concat([e4, d5])), test=test))

d3 = f.relu(self.bnd3(self.dc3(d4), test=test))

d2 = f.relu(self.bnd2(self.dc2(f.concat([e2, d3])), test=test))

d1 = f.relu(self.bnd1(self.dc1(d2), test=test))

d0 = self.dc0(f.concat([e0, d1]))

return d0

“怼”

adv.py

class dis(chainer.chain):

super(dis, self).__init__(

c1 = l.convolution2d(3, 32, 4, 2, 1),

c2 = l.convolution2d(32, 32, 3, 1, 1),

c3 = l.convolution2d(32, 64, 4, 2, 1),

c4 = l.convolution2d(64, 64, 3, 1, 1),

c5 = l.convolution2d(64, 128, 4, 2, 1),

c6 = l.convolution2d(128, 128, 3, 1, 1),

c7 = l.convolution2d(128, 256, 4, 2, 1),

l8l = l.linear(none, 2, wscale=0.02*math.sqrt(8*8*256)),

bnc1 = l.batchnormalization(32),

bnc2 = l.batchnormalization(32),

bnc3 = l.batchnormalization(64),

bnc4 = l.batchnormalization(64),

bnc5 = l.batchnormalization(128),

bnc6 = l.batchnormalization(128),

bnc7 = l.batchnormalization(256),

h = f.relu(self.bnc1(self.c1(x), test=test))

h = f.relu(self.bnc2(self.c2(h), test=test))

h = f.relu(self.bnc3(self.c3(h), test=test))

h = f.relu(self.bnc4(self.c4(h), test=test))

h = f.relu(self.bnc5(self.c5(h), test=test))

h = f.relu(self.bnc6(self.c6(h), test=test))

h = f.relu(self.bnc7(self.c7(h), test=test))

return self.l8l(h)

原文釋出時間為:2017-03-04

本文來自雲栖社群合作夥伴“大資料文摘”,了解相關資訊可以關注“bigdatadigest”微信公衆号

繼續閱讀