【Deep Learning】初学者用chainer为线描上色,效果意外的好(翻译自Qiita)
日语原文:初心者がchainerで線画着色してみた。わりとできた。 - Qiita。原作者:taizan
本文为(不完全)翻译版本。日语滥请多包涵。以下为正文
最近一直有人说深度学习(Deep Learning)的附加价值高,于是我也在一两个月前开始学习chainer了。机会难得就想试着用chainer做一些各种各样的尝试,比如写个给线描上色的小程序之类的。
线描上色这个任务的性质是监督式学习(supervised learning),因此需要大量的线稿和上完色的图片,越多越好。
这次是用opencv用角色的画像把线稿生成了出来。生成例子如下:
→
收集了角色们的画像,将其转化为线稿之后数据集(dataset)就完成了。这次用了约60万张图片。
关于神经网络(neural network)的结构,我用了一种叫做U-net的网络。它的特点是会把卷积(convolution)和反卷积(deconvolution)的层混合着连接在一起。这样就可以做到参照着一开始的线稿来上色。(译者注:有兴趣的可以看一下unet的论文以及文末提供的神经网络代码。)
这样生成出来的图像和原来上完色的图像对比然后取平方差,神经网络的训练让平方差最小即可。
只要保证网络每一层的输入和下一层的输出相匹配就大体ok,但是自己定义的数据如何制作这方面,因为没有什么例子所以可能对各位来说有些难懂。
花了整整一晚上时间使劲用data喂饱了neural net(掩面)的结果↓
嗯~神经网络这边大概是想说“肌肤的颜色我大概能搞懂但是除此之外的实在不知道啊,发色啊衣服颜色啊我当然不可能知道吧。”
这里要登场的是叫对抗网络(adversarial net)的神经网络,简称“怼”。
“怼”要做的是学习真正的图像和被神经网络生成出来的图像之间的颜色差别,然后找出两个图像中的那个叛徒。
所以如果神经网络一直生成老照片那样颜色的图像的话“怼”只要学一会儿就能准确的找出哪一张是神经网络生成的。
但是如果“怼”太用力的话上色的神经网络会拼命反抗导致上色失败请多加注意。
这样的颜色都已经说不上是线稿上色后的东西而更加接近艺术了。(嘛,顺着这条道走,把上色用神经网络怼成艺术生也不是不可以。。。现在暂时还是回到学习和原画之间的差别上面吧)
呼,上色终于完成了!照着这个势头下去再接着干吧。
第一阶段是学习了128x128的图像,第二阶段是给512x512的图像学习上色。以下是没“怼”过的训练结果↓
还不错哟
不错不错。
把实际的线稿拿来喂给神经网络如何?我从pixiv上借用来了线稿类的画
(因为神经网络大部分都是卷积神经网络(cnn),宽高比有一些变化也没关系)
赞!
变成了彩色的怪物了,嘛这种怪物也是有的。
平安收工。
说回来,果然还是会想亲手在线稿上上一些色吧?于是稍微改变一下输入,和一阶段不同的是在原来的线稿之外多加了三个输入层(rgb),给神经网络一些用色上的提示吧。
总之:
茶色的头发淡蓝的水手服和藏青的裙子,之类的要求也可以提了。
稍微霸气的像这样画上一笔也是可以的
不管是大概的提示也好非常用心的每个细节都提示也好效果都不错。(可能有些难懂,就是用不同的颜色在各种地方点一下来提示,比如下面)
圣诞快乐!!(译者:晚了。。都快过年了)
这样我也从工程师升职成画师了!
至此,我觉得线稿的自动上色和带提示的上色已经做的还不错了。
虽然还不如画师们认真画出来的,如果想随随便便涂个色还是非常方便的(译者:比如在经费不够的情况下。。。)。
漫画之类的也是,比起用网点贴纸(Screen tone)还是大致的上一个色比较快速方便的。(这次的神经网络非常擅长给肤色上色。。。我想说的各位懂得吧~)
顺便补充一下,弱点还是有几个的。
例如同时用对抗网络和上色提示一起训练的时候,上色提示会干涉到对抗网络,有时会导致训练结果不稳定。
↑明明只是想给泳装上一个不同的颜色,结果其他部分的颜色也跟着变了。
如果只是作为一个简单的上色工具的话,只加提示来训练神经网络可能会更加稳定。
另外,如果线稿的线太粗或者太细的情况下,线会崩坏掉导致结果不怎么样的情况也有,仔细的给了上色提示但是没有反应在结果上的情况也有。
不同的细节都用同一个神经网络来对付虽然比较厉害,但是作为工具使用的时候需要根据用途来做一些调整。
借鉴的线稿原画:
「【プリンセスロワイヤル】パンドラ」/「鉛筆工房【IRITH】」[pixiv]
※生成的线稿的训练材料和原画一时半会儿找不到,实在抱歉,还请多包涵。
顺便,这次的神经网络第一阶段和第二阶段的构造都是一样的,基本上感觉如下:
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)
来自: <初心者がchainerで線画着色してみた。わりとできた。 - Qiita>
译者注:
最近自己也在做类似的东西,但总做不出好的结果,偶尔看到这个帖子被吓到了。看上去效果非常厉害。最近在用tensorflow把程序写出来。写的过程中觉得这篇blog描述的还是不够详细,我自己怎么做都做不出他的效果,不知道是训练的数据问题还是其他问题。写完了的话会把结果一并发到这里。(原作者并没有公布自己的源代码。)
日语业余也不会画画所以有些翻译不太准确,如果有日语大神请务必指出翻译不正确的地方。专有名词的中文翻译也有可能有问题,如果有不准的地方请参照括号内的英文。如果有不懂的地方也可以回复,我尽量答。。。
转载请务必著名原作者(taizan)的名字和链接。
2017/1/11
--By 李嘉铭
---------
今天得知了作者终于公开了源代码的消息,以及提供了一个测试这个上色网络的网站。在此提供一下地址:
顺手用英文也翻译了一下,地址如下:
Using Chainer to color sketches yields surprising results (Translated) - Qiita
转载要求与中文相同,请著名原作者(taizan)的名字和链接。
最近在用tensorflow在没有源代码的情况下尝试重新根据这个blog写一个,写了快一个月还是失败了。。。不过现在有了源代码应该很快就会用tensorflow重现出来。到时候结果和训练集会一并放在这里。