閱讀776 返回首頁    go 阿裏雲 go 技術社區[雲棲]


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


0?wx_fmt=gif

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

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

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

0?wx_fmt=jpeg

0?wx_fmt=jpeg

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

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

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

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

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

0?wx_fmt=jpeg

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

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

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

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

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

0?wx_fmt=jpeg

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

0?wx_fmt=jpeg

唿,上色終於完成了!照著這個勢頭下去再接著幹吧。

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

0?wx_fmt=jpeg

還不錯喲

0?wx_fmt=jpeg

不錯不錯。

0?wx_fmt=jpeg

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

0?wx_fmt=jpeg

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

0?wx_fmt=jpeg

好棒!

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

平安收工。

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

總之:

0?wx_fmt=jpeg

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

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

0?wx_fmt=jpeg0?wx_fmt=jpeg

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

0?wx_fmt=jpeg

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

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

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

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

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

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

0?wx_fmt=jpeg

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

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

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

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

借鑒的線稿原畫:

「【プリンセスロワイヤル】パンドラ」/「鉛筆工房【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):
def __init__(self):
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),
)

def calc(self,x, test = False):
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”微信公眾號

最後更新:2017-05-23 16:34:08

  上一篇:go  Akka文檔(java版)-為什麼選擇Akka
  下一篇:go  AKKA文檔(java版)—什麼是角色