我用AI给女友画了一幅画,这届算法有点强!

Posted Jack-Cui

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我用AI给女友画了一幅画,这届算法有点强!相关的知识,希望对你有一定的参考价值。

大家好,我是 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,我们下期见。

以上是关于我用AI给女友画了一幅画,这届算法有点强!的主要内容,如果未能解决你的问题,请参考以下文章

03-02

ta的第一幅画

一幅画是不是真迹?AI比专家看得更明白

我的爱情与Python不得不说的故事

退役感言

用python做数字油画或者从一幅画学习风格,去画另一幅画