动手玩玩头像动漫化

4 篇文章 1 订阅
本文档介绍了如何使用 AnimeGAN2 模型将真人头像转换为动漫风格。作者首先简述了 GAN 的工作原理,然后详细展示了如何在 PyTorch 环境中搭建和运行 AnimeGAN2 模型。通过下载预训练模型并编写生成图像的代码,实现了快速生成动漫头像。虽然存在图片截取导致的信息丢失和光照条件差的图片效果不佳等问题,但整体效果令人满意。
摘要由CSDN通过智能技术生成

前言

很久没有弄点好玩的东西了,逛逛github,想到最近看到那种动漫化的头像很可爱,于是决定也去找找相关的项目玩玩,毕竟直接调用百度啥的api太没意思了。

正文

之前写过一些有关GAN的博客,大家应该对GAN有了基本的了解。最基础的内容就是基于零和博弈的思想使得生成器生成的假图像逼真的可以足够骗过判别器,更本质就是生成器从对抗中不断学习,学习到了真实图像中的数据分布。但是真正动手之后会发现训练GAN其实还是比较困难的,难点就在于难以收敛,而且还有模式崩塌的情况出现。

再说回这次的目的,想要实现头像动漫化,这在概念上应该是图像的风格迁移,那最基本就应该会想到内容损失风格损失,当然对具体的问题会提出更多不同的损失。在github上搜索下就会发现这几个名字

  • CartoonGAN
  • AnimeGAN
  • AnimeGAN2
    再去看看相关的论文和github链接,会发现上面的顺序就是逐渐优化的过程,具体的论文阅读部分我会放在论文专栏,今天的注意力还是集中在实现上。我最终还是选择了SOTA模型AnimeGAN2-tensorflow

当然也有pytorch版本AnimeGAN2-pytorch

我选择的是pytorch版本,因为tensorflow是1.X版本实现,而我环境已经全部转为TF2了,而且越来越觉得TF复杂而杂乱的api很不喜欢,更多的网络实现都是基于keras,有时候又失去了TF本身的灵活性。好啦,又扯远了,现在开始正式动手实现。

开始实现

进去AnimeGAN2-pytorch,如果只需要实现的话不需要下载整个项目,只需要下载model.py

import torch
from torch import nn
import torch.nn.functional as F


class ConvNormLReLU(nn.Sequential):
    def __init__(self, in_ch, out_ch, kernel_size=3, stride=1, padding=1, pad_mode="reflect", groups=1, bias=False):
        pad_layer = {
            "zero": nn.ZeroPad2d,
            "same": nn.ReplicationPad2d,
            "reflect": nn.ReflectionPad2d,
        }
        if pad_mode not in pad_layer:
            raise NotImplementedError

        super(ConvNormLReLU, self).__init__(
            pad_layer[pad_mode](padding),
            nn.Conv2d(in_ch, out_ch, kernel_size=kernel_size, stride=stride, padding=0, groups=groups, bias=bias),
            nn.GroupNorm(num_groups=1, num_channels=out_ch, affine=True),
            nn.LeakyReLU(0.2, inplace=True)
        )


class InvertedResBlock(nn.Module):
    def __init__(self, in_ch, out_ch, expansion_ratio=2):
        super(InvertedResBlock, self).__init__()

        self.use_res_connect = in_ch == out_ch
        bottleneck = int(round(in_ch * expansion_ratio))
        layers = []
        if expansion_ratio != 1:
            layers.append(ConvNormLReLU(in_ch, bottleneck, kernel_size=1, padding=0))

        # dw
        layers.append(ConvNormLReLU(bottleneck, bottleneck, groups=bottleneck, bias=True))
        # pw
        layers.append(nn.Conv2d(bottleneck, out_ch, kernel_size=1, padding=0, bias=False))
        layers.append(nn.GroupNorm(num_groups=1, num_channels=out_ch, affine=True))

        self.layers = nn.Sequential(*layers)

    def forward(self, input):
        out = self.layers(input)
        if self.use_res_connect:
            out = input + out
        return out


class Generator(nn.Module):
    def __init__(self, ):
        super().__init__()

        self.block_a = nn.Sequential(
            ConvNormLReLU(3, 32, kernel_size=7, padding=3),
            ConvNormLReLU(32, 64, stride=2, padding=(0, 1, 0, 1)),
            ConvNormLReLU(64, 64)
        )

        self.block_b = nn.Sequential(
            ConvNormLReLU(64, 128, stride=2, padding=(0, 1, 0, 1)),
            ConvNormLReLU(128, 128)
        )

        self.block_c = nn.Sequential(
            ConvNormLReLU(128, 128),
            InvertedResBlock(128, 256, 2),
            InvertedResBlock(256, 256, 2),
            InvertedResBlock(256, 256, 2),
            InvertedResBlock(256, 256, 2),
            ConvNormLReLU(256, 128),
        )

        self.block_d = nn.Sequential(
            ConvNormLReLU(128, 128),
            ConvNormLReLU(128, 128)
        )

        self.block_e = nn.Sequential(
            ConvNormLReLU(128, 64),
            ConvNormLReLU(64, 64),
            ConvNormLReLU(64, 32, kernel_size=7, padding=3)
        )

        self.out_layer = nn.Sequential(
            nn.Conv2d(32, 3, kernel_size=1, stride=1, padding=0, bias=False),
            nn.Tanh()
        )

    def forward(self, input, align_corners=True):
        out = self.block_a(input)
        half_size = out.size()[-2:]
        out = self.block_b(out)
        out = self.block_c(out)

        if align_corners:
            out = F.interpolate(out, half_size, mode="bilinear", align_corners=True)
        else:
            out = F.interpolate(out, scale_factor=2, mode="bilinear", align_corners=False)
        out = self.block_d(out)

        if align_corners:
            out = F.interpolate(out, input.size()[-2:], mode="bilinear", align_corners=True)
        else:
            out = F.interpolate(out, scale_factor=2, mode="bilinear", align_corners=False)
        out = self.block_e(out)

        out = self.out_layer(out)
        return out

其中包含了生成器代码,这也是生成动漫风格图片的关键部分。
然后就是需要去对应的网盘下载不同风格下训练好的模型,可能无法访问,所以我把我下载的放到网盘分享。
模型链接 密码: n78v

然后就是自己写个生成图像的代码,当然可以根据原项目中的test_faces.ipynb来修改
下面是我的生成代码,需要提前将模型放在同一目录下,并且创建samples文件夹用来存放生成图片

import os
import cv2
import matplotlib.pyplot as plt
import torch
import random
import numpy as np

from model import Generator


def load_image(path, size=None):
    image = image2tensor(cv2.cvtColor(cv2.imread(path), cv2.COLOR_BGR2RGB))

    w, h = image.shape[-2:]
    if w != h:
        crop_size = min(w, h)
        left = (w - crop_size) // 2
        right = left + crop_size
        top = (h - crop_size) // 2
        bottom = top + crop_size
        image = image[:, :, left:right, top:bottom]

    if size is not None and image.shape[-1] != size:
        image = torch.nn.functional.interpolate(image, (size, size), mode="bilinear", align_corners=True)

    return image


def image2tensor(image):
    image = torch.FloatTensor(image).permute(2, 0, 1).unsqueeze(0) / 255.
    return (image - 0.5) / 0.5


def tensor2image(tensor):
    tensor = tensor.clamp_(-1., 1.).detach().squeeze().permute(1, 2, 0).cpu().numpy()
    return tensor * 0.5 + 0.5


def imshow(img, size=5, cmap='jet'):
    plt.figure(figsize=(size, size))
    plt.imshow(img, cmap=cmap)
    plt.axis('off')
    plt.show()


if __name__ == '__main__':
    device = 'cuda'
    torch.set_grad_enabled(False)
    image_size = 300
    img=input("")
    model = Generator().eval().to(device)

    ckpt = torch.load(f"./new.pth", map_location=device)
    model.load_state_dict(ckpt)

    result=[]
    image = load_image(f"./face/{img}", image_size)
    output = model(image.to(device))

    result.append(torch.cat([image, output.cpu()], 3))
    result = torch.cat(result, 2)

    imshow(tensor2image(result), 40)
    cv2.imwrite(f'./samples/new+{img}', cv2.cvtColor(255 * tensor2image(result), cv2.COLOR_BGR2RGB))

device中可以选择cpu或者cuda(使用GPU)

可能遇到的问题

当电脑中pytorch版本低于1.6时,载入模型时torch.load会报错,但是受限于显卡算力好像不支持安装高版本torch,那只能找其他解决办法。

找个笔记本,一般都可以安装高版本pytorch(大于1.6就行),配好环境之后运行下面代码, 用新生成的模型文件去载入,也就是我代码中的new.pth
修改模型文件的代码

import torch
weight = torch.load("高版本的模型地址")
torch.save(weight, '自定义新的模型地址', _use_new_zipfile_serialization=False)

效果

在这里插入图片描述
效果还是比较不错的,生成图片的速度也比较快,但是我感觉还是存在一些问题

  • 由于代码中会对图片进行截取,所以有可能丢失原图中的信息
  • 可能是训练数据集的问题,传入光照不好的图片出现的效果很不好,轮廓曲线十分模糊。
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shelgi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值