大家好,我是 Jack。
小時候,我其實還是有點藝術細胞的,喜歡看火影忍者和七龍珠的我,雖然沒學過繪畫,但也笨手笨腳地畫了不少作品。
特意叫我媽,把我收藏多年的小破本拿出來,分享下我兒時的快樂。
小學幾年級畫的記不清了,只記得一畫就是小半天,還拿去學校顯擺了一番。
如今,再讓我拿起鉛筆,畫個素描,我是畫不出來了。
不過,我另闢蹊徑,用起了演演算法。我lbw,沒有開掛!
Anime2Sketch 是一個動畫、漫畫、插畫等藝術作品的素描提取器。
給我個藝術作品,我直接把它變成素描作品:
耗時1秒臨摹的素描作品:
Anime2Sketch 演演算法也非常簡單,就是一個 UNet 結構,生成素描作品,可以看下它的網路結構:
import torch
import torch.nn as nn
import functools
class UnetGenerator(nn.Module):
"""Create a Unet-based generator"""
def __init__(self, input_nc, output_nc, num_downs, ngf=64, norm_layer=nn.BatchNorm2d, use_dropout=False):
"""Construct a Unet generator
Parameters:
input_nc (int) -- the number of channels in input images
output_nc (int) -- the number of channels in output images
num_downs (int) -- the number of downsamplings in UNet. For example, # if |num_downs| == 7,
image of size 128x128 will become of size 1x1 # at the bottleneck
ngf (int) -- the number of filters in the last conv layer
norm_layer -- normalization layer
We construct the U-Net from the innermost layer to the outermost layer.
It is a recursive process.
"""
super(UnetGenerator, self).__init__()
# construct unet structure
unet_block = UnetSkipConnectionBlock(ngf * 8, ngf * 8, input_nc=None, submodule=None, norm_layer=norm_layer, innermost=True) # add the innermost layer
for _ in range(num_downs - 5): # add intermediate layers with ngf * 8 filters
unet_block = UnetSkipConnectionBlock(ngf * 8, ngf * 8, input_nc=None, submodule=unet_block, norm_layer=norm_layer, use_dropout=use_dropout)
# gradually reduce the number of filters from ngf * 8 to ngf
unet_block = UnetSkipConnectionBlock(ngf * 4, ngf * 8, input_nc=None, submodule=unet_block, norm_layer=norm_layer)
unet_block = UnetSkipConnectionBlock(ngf * 2, ngf * 4, input_nc=None, submodule=unet_block, norm_layer=norm_layer)
unet_block = UnetSkipConnectionBlock(ngf, ngf * 2, input_nc=None, submodule=unet_block, norm_layer=norm_layer)
self.model = UnetSkipConnectionBlock(output_nc, ngf, input_nc=input_nc, submodule=unet_block, outermost=True, norm_layer=norm_layer) # add the outermost layer
def forward(self, input):
"""Standard forward"""
return self.model(input)
class UnetSkipConnectionBlock(nn.Module):
"""Defines the Unet submodule with skip connection.
X -------------------identity----------------------
|-- downsampling -- |submodule| -- upsampling --|
"""
def __init__(self, outer_nc, inner_nc, input_nc=None,
submodule=None, outermost=False, innermost=False, norm_layer=nn.BatchNorm2d, use_dropout=False):
"""Construct a Unet submodule with skip connections.
Parameters:
outer_nc (int) -- the number of filters in the outer conv layer
inner_nc (int) -- the number of filters in the inner conv layer
input_nc (int) -- the number of channels in input images/features
submodule (UnetSkipConnectionBlock) -- previously defined submodules
outermost (bool) -- if this module is the outermost module
innermost (bool) -- if this module is the innermost module
norm_layer -- normalization layer
use_dropout (bool) -- if use dropout layers.
"""
super(UnetSkipConnectionBlock, self).__init__()
self.outermost = outermost
if type(norm_layer) == functools.partial:
use_bias = norm_layer.func == nn.InstanceNorm2d
else:
use_bias = norm_layer == nn.InstanceNorm2d
if input_nc is None:
input_nc = outer_nc
downconv = nn.Conv2d(input_nc, inner_nc, kernel_size=4,
stride=2, padding=1, bias=use_bias)
downrelu = nn.LeakyReLU(0.2, True)
downnorm = norm_layer(inner_nc)
uprelu = nn.ReLU(True)
upnorm = norm_layer(outer_nc)
if outermost:
upconv = nn.ConvTranspose2d(inner_nc * 2, outer_nc,
kernel_size=4, stride=2,
padding=1)
down = [downconv]
up = [uprelu, upconv, nn.Tanh()]
model = down + [submodule] + up
elif innermost:
upconv = nn.ConvTranspose2d(inner_nc, outer_nc,
kernel_size=4, stride=2,
padding=1, bias=use_bias)
down = [downrelu, downconv]
up = [uprelu, upconv, upnorm]
model = down + up
else:
upconv = nn.ConvTranspose2d(inner_nc * 2, outer_nc,
kernel_size=4, stride=2,
padding=1, bias=use_bias)
down = [downrelu, downconv, downnorm]
up = [uprelu, upconv, upnorm]
if use_dropout:
model = down + [submodule] + up + [nn.Dropout(0.5)]
else:
model = down + [submodule] + up
self.model = nn.Sequential(*model)
def forward(self, x):
if self.outermost:
return self.model(x)
else: # add skip connections
return torch.cat([x, self.model(x)], 1)
def create_model(gpu_ids=[]):
"""Create a model for anime2sketch
hardcoding the options for simplicity
"""
norm_layer = functools.partial(nn.InstanceNorm2d, affine=False, track_running_stats=False)
net = UnetGenerator(3, 1, 8, 64, norm_layer=norm_layer, use_dropout=False)
ckpt = torch.load('weights/netG.pth')
for key in list(ckpt.keys()):
if 'module.' in key:
ckpt[key.replace('module.', '')] = ckpt[key]
del ckpt[key]
net.load_state_dict(ckpt)
if len(gpu_ids) > 0:
assert(torch.cuda.is_available())
net.to(gpu_ids[0])
net = torch.nn.DataParallel(net, gpu_ids) # multi-GPUs
return net
UNet 應該都很熟悉了,就不多介紹了。
專案地址:https://github.com/Mukosame/Anime2Sketch
環境部署也很簡單,只需要安裝以下三個庫:
torch>=0.4.1
torchvision>=0.2.1
Pillow>=6.0.0
然後下載權重檔案,即可。
權重檔案放在了GoogleDrive,為了方便大家,我將程式碼和權重檔案,還有一些測試圖片,都打包好了。
直接下載,即可執行(提取碼:a7r4):
https://pan.baidu.com/s/1h6bqgphqUUjj4fz61Y9HCA
進入專案根目錄,直接執行命令:
python3 test.py --dataroot test_samples --load_size 512 --output_dir results
執行效果:
「畫」得非常快,我在網上找了一些圖片進行測試。
鳴人和帶土:
柯南和灰原哀:
使用演演算法前:
這樣的素描,沒有靈魂!
使用演演算法後:
拿了一些真人的圖片進行了測試,發現效果很差,果然真人的線條還是要複雜一些的。
最後再送大家一本,幫助我拿到 BAT 等一線大廠 offer 的資料結構刷題筆記,是一位 Google 大神寫的,對於演演算法薄弱或者需要提高的同學都十分受用(提起碼:m19c):
BAT 大佬分類總結的 Leetcode 刷題模版,助你搞定 90% 的面試
以及我整理的 BAT 演演算法工程師學習路線,書籍+視訊,完整的學習路線和說明,對於想成為演演算法工程師的,絕對能有所幫助(提取碼:jack):
我是 Jack,我們下期見。