大家好,我是 Jack。
小时候,我其实还是有点艺术细胞的,喜欢看火影忍者和七龙珠的我,虽然没学过绘画,但也笨手笨脚地画了不少作品。
特意叫我妈,把我收藏多年的小破本拿出来,分享下我儿时的快乐。
小学几年级画的记不清了,只记得一画就是小半天,还拿去学校显摆了一番。
如今,再让我拿起铅笔,画个素描,我是画不出来了。
不过,我另辟蹊径,用起了算法。我lbw,没有开挂!
Anime2Sketch
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,我们下期见。
转载:https://blog.csdn.net/c406495762/article/details/116760610