Практическая анимация аватара

GAN

предисловие

Давненько я ничего не развлекался. Я полистал гитхаб и подумал, что мультяшный аватар, который я недавно видел, был очень милым, поэтому я решил найти похожие проекты, чтобы поиграть с ними. В конце концов, было бы скучно напрямую звонить в Baidu. API

текст

Я уже писал несколько блогов о GAN, и вы должны иметь базовое представление о GAN. Самый основной контент основан на идее игры с нулевой суммой.СтроительСгенерированные поддельные изображения достаточно реалистичны, чтобы обманутьдискриминатор, а суть в том, что генератор постоянно учится на конфронтации и учит распределение данных на реальном изображении. Но после реальных практических занятий вы обнаружите, что обучение GAN на самом деле довольно сложно, сложность в том, что трудно сходиться, и бывают случаи, когда режим рушится.

Возвращаясь к цели этого времени, если вы хотите реализовать анимацию аватара, это должна быть стилистическая передача изображения в концепции, то самое основное нужно продуматьПотеря контентаипотеря стиля,конечно потерь будет больше и разных для конкретных задач. Найдите на github, и вы найдете эти имена

  • CartoonGAN
  • AnimeGAN
  • AnimeGAN2

Просмотрев соответствующие статьи и ссылки на github, вы обнаружите, что описанная выше последовательность — это процесс постепенной оптимизации. Я остановился на модели SOTA.AnimeGAN2-tensorflow

Конечно, есть и версия pytorchAnimeGAN2-pytorch

Я выбрал версию pytorch, потому что tensorflow реализован в версии 1.X, и вся моя среда была преобразована в TF2, и я все больше и больше чувствую, что мне не нравится сложный и запутанный API TF и ​​другие сетевые реализации. основаны на 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 в исходном проекте. Ниже приведен мой сгенерированный код, вам нужно заранее поместить модель в тот же каталог и создать папку с образцами для хранения сгенерированных изображений.

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Вы можете выбрать процессор или 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)

Эффект

在这里插入图片描述Эффект довольно хороший, и скорость создания изображений относительно высока, но я чувствую, что есть еще некоторые проблемы.

  • Поскольку изображение будет перехвачено в коде, возможна потеря информации в исходном изображении.
  • Возможно проблема с обучающим набором данных, снимки при плохом освещении получаются не очень, а контурные кривые сильно размыты.