【百战GAN】定制属于二次元宅们的专属动漫头像,这款GAN正好!

pytorch BN函数说明 pytorch中的BN继承自: class _BatchNorm(Module): 这个类的代码: class _BatchNorm(Module): def __init__(self, num_features, eps=1e-5, momentum=0.1, affine=True, track_running_stats=True):... 阅读详情

大家好,欢迎来到专栏《百战GAN》,在这个专栏里,我们会进行GAN相关项目的核心思想讲解,代码的详解,模型的训练和测试等内容。

作者&编辑 | 言有三

3f778bcf720e16caf51c5e8d77e170e6.jpeg

本文资源与生成结果展示

本文篇幅:7000字

背景要求:会使用Python和Pytorch

附带资料:参考论文和项目

1 项目背景

如今二次元文化拥有数以亿计的群众基础,有三自己也是一个动漫宅,这些年醉心于国产动漫,在知乎写的第一篇文章还是给国产动漫打call。二次元宅们,给自己做一个专属动漫头像可好!作为有技术的动漫粉,我们当然不能满足于看看,有机会就要自己亲自参与创作一把。本次我们使用GAN来完成定制个人专属动漫头像的任务。

b4c51d5322af1c9749b3e846fbf32fe5.jpeg

2 项目解读

本次我们要完成的任务就是从一张真人头像,变成高质量的动漫风格,并且要保证身份信息不被篡改,这样才能实现个性化的需求。

简单来说就是实现下面的转换过程:

a09a1792238a15c705d64858af647279.jpeg

左边是我们的真人人脸图,右边是二次元头像,这是一个风格化问题或者说图像翻译问题。

当前有CycleGAN等框架可以实现,但是效果是不行的,本次我们给大家介绍一个更合适做这个问题的框架,原理图如下:

dc1c6312e1a7d82d695f634eeb04bfe8.png

2691a22a406866725f9da186c1391723.png

上图就是框架的生成器和判别器,名为UGATIT,下面我们首先来解读一下该框架的主要特点:

UGATIT是一个基于GAN的无监督风格化模型,它包含了一个判别器和一个生成器,在生成器和判别器中都添加了注意力机制了保证模型的效果,具体实现就是全局和平均池化下的类激活图(Class Activation Map-CAM)。

左边是生成器,右边是判别器,首先我们看生成器:

输入两个域的图经过下采样,编码器提取得到特征图,然后对特征图通道应用注意力机制,学习输出各个通道的权重。这个注意力机制模型的目标是学习到那些能够区分源域和目标域区别的重要区域。

得到了每个通道的权重后,再应用AdaLIN层,输入生成器进行生成。AdaLIN层是Instance Normalization(简称IN)和Layer Normalization(简称LN)的结合。IN因为对各个图像特征图单独进行归一化,会保留较多的内容结构,LN与IN相比,使用了多个通道进行归一化,能够更好地获取全局特征。

两者的计算以及融合方式如下:

ffb5e652d884760a1dcb35cd8b4415ae.png

判别器的设计采用一个全局判别器加一个局部判别器,区别就在于全局判别器更深,达到了32倍的步长,全局判别器的感受野已经超过256×256。

3 模型训练

接下来我们来实现U-GAT-IT模型,剖析完整的工程代码。

3.1 数据预处理

首先是数据集的处理,我们使用作者开源的数据,https://github.com/nagadomi/lbpcascade_animeface。女性动漫头像图共3500张,其中3400张作为训练集trainA,100张作为测试集testA。真实的女性人脸肖像图也是3500张,其中3400张作为训练trainB,100张作为测试testB,目录结构如下:

f09cf61cbaf8f209f45d8b2ba6c6029a.png

我们可以使用pytorch的ImageFolder来完成数据集的读取,训练预处理函数和测试预处理函数如下:

## 训练预处理函数

train_transform = transforms.Compose([

            transforms.RandomHorizontalFlip(),

            transforms.Resize((self.img_size + 30, self.img_size+30)),

            transforms.RandomCrop(self.img_size),

            transforms.ToTensor(),

            transforms.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5))

        ])

## 测试预处理函数

test_transform = transforms.Compose([

            transforms.Resize((self.img_size, self.img_size)),

            transforms.ToTensor(),

            transforms.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5))

        ])

self.trainA = ImageFolder(os.path.join('dataset', self.dataset, 'trainA'), train_transform)

self.trainB = ImageFolder(os.path.join('dataset', self.dataset, 'trainB'), train_transform)

self.testA = ImageFolder(os.path.join('dataset', self.dataset, 'testA'), test_transform)

self.testB = ImageFolder(os.path.join('dataset', self.dataset, 'testB'), test_transform)

self.trainA_loader = DataLoader(self.trainA, batch_size=self.batch_size, shuffle=True)

self.trainB_loader = DataLoader(self.trainB, batch_size=self.batch_size, shuffle=True)

self.testA_loader = DataLoader(self.testA, batch_size=1, shuffle=False)

self.testB_loader = DataLoader(self.testB, batch_size=1, shuffle=False)

训练的时候将图像大小缩放到了self.img_size + 30,测试时大小缩放为self.img_size,使用了随机裁剪数据增强操作。

3.2 模型定义

接下来我们再查看模型的定义,首先是生成器的一些基本模块,包括残差网络模块和AdaILN模块:

## AdaILN网络层实现

class adaILN(nn.Module):

    def __init__(self, num_features, eps=1e-5):

        super(adaILN, self).__init__()

        self.eps = eps

        self.rho = Parameter(torch.Tensor(1, num_features, 1, 1)) #参数

        self.rho.data.fill_(0.9)

def forward(self, input, gamma, beta):

        in_mean, in_var = torch.mean(input, dim=[2, 3], keepdim=True), torch.var(input, dim=[2, 3], keepdim=True) ##计算通道均值和方差

        out_in = (input - in_mean) / torch.sqrt(in_var + self.eps) ##通道归一化

        ln_mean, ln_var = torch.mean(input, dim=[1, 2, 3], keepdim=True), torch.var(input, dim=[1, 2, 3], keepdim=True) ##计算层均值和方差

        out_ln = (input - ln_mean) / torch.sqrt(ln_var + self.eps) ##层归一化

        out = self.rho.expand(input.shape[0], -1, -1, -1) * out_in + (1-self.rho.expand(input.shape[0], -1, -1, -1)) * out_ln 

        ##得到AdaLIN归一化结果

        out = out * gamma.unsqueeze(2).unsqueeze(3) + beta.unsqueeze(2).unsqueeze(3)

        return out

## AdaLIN残差块,其中归一化层为AdaLIN,包含两个卷积层

class ResnetAdaILNBlock(nn.Module):

    def __init__(self, dim, use_bias):

        super(ResnetAdaILNBlock, self).__init__()

        self.pad1 = nn.ReflectionPad2d(1)

        self.conv1 = nn.Conv2d(dim, dim, kernel_size=3, stride=1, padding=0, bias=use_bias)

        self.norm1 = adaILN(dim)

        self.relu1 = nn.ReLU(True)

        self.pad2 = nn.ReflectionPad2d(1)

        self.conv2 = nn.Conv2d(dim, dim, kernel_size=3, stride=1, padding=0, bias=use_bias)

        self.norm2 = adaILN(dim)

    def forward(self, x, gamma, beta):

        out = self.pad1(x)

        out = self.conv1(out)

        out = self.norm1(out, gamma, beta)

        out = self.relu1(out)

        out = self.pad2(out)

        out = self.conv2(out)

        out = self.norm2(out, gamma, beta)

        return out + x

然后是整个生成器的定义:

class ResnetGenerator(nn.Module):

    def __init__(self, input_nc, output_nc, ngf=64, n_blocks=6, img_size=256, light=False):

        assert(n_blocks >= 0)

        super(ResnetGenerator, self).__init__()

        self.input_nc = input_nc ##输入通道

        self.output_nc = output_nc ##输出通道

        self.ngf = ngf ##基准通道数

        self.n_blocks = n_blocks ##残差块数量

        self.img_size = img_size ##输入图像大小

        self.light = light ##是否使用轻量级网络

        ## 第一个卷积层,卷积核大小为7,步长为1

        DownBlock = []

        DownBlock += [nn.ReflectionPad2d(3),

                      nn.Conv2d(input_nc, ngf, kernel_size=7, stride=1, padding=0, bias=False),

                      nn.InstanceNorm2d(ngf),

                      nn.ReLU(True)]

        ## n_downsampling个下采样卷积网络层

        n_downsampling = 2

        for i in range(n_downsampling):

            mult = 2**I  ## 该层通道乘因子

            ## 下采样卷积层,输入通道数ngf*mult,输出ngf*mult*2

            DownBlock += [nn.ReflectionPad2d(1),

                          nn.Conv2d(ngf * mult, ngf * mult * 2, kernel_size=3, stride=2, padding=0, bias=False),

                          nn.InstanceNorm2d(ngf * mult * 2),

                          nn.ReLU(True)]

        ## n_block个残差网络层,输入输出通道相等

        mult = 2**n_downsampling

        for i in range(n_blocks):

            DownBlock += [ResnetBlock(ngf * mult, use_bias=False)]

        # CAM激活图(Class Activation Map)

        self.gap_fc = nn.Linear(ngf * mult, 1, bias=False) ##均值池化全连接层

        self.gmp_fc = nn.Linear(ngf * mult, 1, bias=False) ##最大池化全连接层

        self.conv1x1 = nn.Conv2d(ngf * mult * 2, ngf * mult, kernel_size=1, stride=1, bias=True)

        self.relu = nn.ReLU(True)

    ## 参数和学习模块

        if self.light: ##如果使用轻量级模型,则包含两个全连接层,输入输出大小都是ngf*mul

            FC = [nn.Linear(ngf * mult, ngf * mult, bias=False),

                  nn.ReLU(True),

                  nn.Linear(ngf * mult, ngf * mult, bias=False),

                  nn.ReLU(True)]

        else: ##使用更大的模型,则包含两个全连接层,第一个输入大小为img_size // mult * img_size // mult * ngf * mult,输出为ngf * mult,

            FC = [nn.Linear(img_size // mult * img_size // mult * ngf * mult, ngf * mult, bias=False),

                  nn.ReLU(True),

                  nn.Linear(ngf * mult, ngf * mult, bias=False),

                  nn.ReLU(True)]

        self.gamma = nn.Linear(ngf * mult, ngf * mult, bias=False) ##得到

        self.beta = nn.Linear(ngf * mult, ngf * mult, bias=False) ##得到

        # n_block个自适应残差瓶颈模块

        for i in range(n_blocks):

            setattr(self, 'UpBlock1_' + str(i+1), ResnetAdaILNBlock(ngf * mult, use_bias=False))

        # n_downsampling个上采样模块,与下采样模型对应

        UpBlock2 = []

        for i in range(n_downsampling):

            mult = 2**(n_downsampling - i)

            UpBlock2 += [nn.Upsample(scale_factor=2, mode='nearest'),

                         nn.ReflectionPad2d(1),

                         nn.Conv2d(ngf * mult, int(ngf * mult / 2), kernel_size=3, stride=1, padding=0, bias=False),

                         ILN(int(ngf * mult / 2)), ##使用ILN层

                         nn.ReLU(True)]

        UpBlock2 += [nn.ReflectionPad2d(3),

                     nn.Conv2d(ngf, output_nc, kernel_size=7, stride=1, padding=0, bias=False),

                     nn.Tanh()]

        self.DownBlock = nn.Sequential(*DownBlock)

        self.FC = nn.Sequential(*FC)

        self.UpBlock2 = nn.Sequential(*UpBlock2)

    def forward(self, input):

        x = self.DownBlock(input) ##卷积特征图

        gap = torch.nn.functional.adaptive_avg_pool2d(x, 1) ##自适应均值池化

        gap_logit = self.gap_fc(gap.view(x.shape[0], -1))

        gap_weight = list(self.gap_fc.parameters())[0]

        gap = x * gap_weight.unsqueeze(2).unsqueeze(3)

        gmp = torch.nn.functional.adaptive_max_pool2d(x, 1) ##自适应最大池化

        gmp_logit = self.gmp_fc(gmp.view(x.shape[0], -1))

        gmp_weight = list(self.gmp_fc.parameters())[0]

        gmp = x * gmp_weight.unsqueeze(2).unsqueeze(3)

        cam_logit = torch.cat([gap_logit, gmp_logit], 1) ##均值池化和最大池化CAM图拼接

        x = torch.cat([gap, gmp], 1) ##均值池化和最大池化特征图拼接

        x = self.relu(self.conv1x1(x)) ##1*1卷积变换

        heatmap = torch.sum(x, dim=1, keepdim=True) ##得到heatmap

        if self.light:

            x_ = torch.nn.functional.adaptive_avg_pool2d(x, 1) ##进行均值池化

            x_ = self.FC(x_.view(x_.shape[0], -1))

        else:

            x_ = self.FC(x.view(x.shape[0], -1))

        gamma, beta = self.gamma(x_), self.beta(x_)

        for i in range(self.n_blocks):

            x = getattr(self, 'UpBlock1_' + str(i+1))(x, gamma, beta)

        out = self.UpBlock2(x)

        return out, cam_logit, heatmap ##返回特征图,CAM图以及heatmap图

判别器的定义则比较简单,其中AdaIN层的应用类似,如下:

class Discriminator(nn.Module):

    def __init__(self, input_nc, ndf=64, n_layers=5):

        super(Discriminator, self).__init__()

        model = [nn.ReflectionPad2d(1),

                 nn.utils.spectral_norm(

                 nn.Conv2d(input_nc, ndf, kernel_size=4, stride=2, padding=0, bias=True)),

                 nn.LeakyReLU(0.2, True)]

        for i in range(1, n_layers - 2):

            mult = 2 ** (i - 1)

            model += [nn.ReflectionPad2d(1),

                      nn.utils.spectral_norm(

                      nn.Conv2d(ndf * mult, ndf * mult * 2, kernel_size=4, stride=2, padding=0, bias=True)),

                      nn.LeakyReLU(0.2, True)]

        mult = 2 ** (n_layers - 2 - 1)

        model += [nn.ReflectionPad2d(1),

                  nn.utils.spectral_norm(

                  nn.Conv2d(ndf * mult, ndf * mult * 2, kernel_size=4, stride=1, padding=0, bias=True)),

                  nn.LeakyReLU(0.2, True)]

        ## CAM(Class Activation Map)图

        mult = 2 ** (n_layers - 2)

        self.gap_fc = nn.utils.spectral_norm(nn.Linear(ndf * mult, 1, bias=False)) ##均值池化全连接层

        self.gmp_fc = nn.utils.spectral_norm(nn.Linear(ndf * mult, 1, bias=False)) ##最大池化全连接层

        self.conv1x1 = nn.Conv2d(ndf * mult * 2, ndf * mult, kernel_size=1, stride=1, bias=True)

        self.leaky_relu = nn.LeakyReLU(0.2, True)

        self.pad = nn.ReflectionPad2d(1)

        self.conv = nn.utils.spectral_norm(

            nn.Conv2d(ndf * mult, 1, kernel_size=4, stride=1, padding=0, bias=False))

        self.model = nn.Sequential(*model)

    def forward(self, input):

        x = self.model(input) ##卷积特征图

        gap = torch.nn.functional.adaptive_avg_pool2d(x, 1) ##自适应均值池化

        gap_logit = self.gap_fc(gap.view(x.shape[0], -1))

        gap_weight = list(self.gap_fc.parameters())[0]

        gap = x * gap_weight.unsqueeze(2).unsqueeze(3)

        gmp = torch.nn.functional.adaptive_max_pool2d(x, 1) ##自适应最大池化

        gmp_logit = self.gmp_fc(gmp.view(x.shape[0], -1))

        gmp_weight = list(self.gmp_fc.parameters())[0]

        gmp = x * gmp_weight.unsqueeze(2).unsqueeze(3)

        cam_logit = torch.cat([gap_logit, gmp_logit], 1) ##均值池化和最大池化拼接

        x = torch.cat([gap, gmp], 1)

        x = self.leaky_relu(self.conv1x1(x))

        heatmap = torch.sum(x, dim=1, keepdim=True) ##得到heatmap

        x = self.pad(x)

        out = self.conv(x)

        return out, cam_logit, heatmap ##返回特征图,CAM图以及heatmap图

3.3 优化目标

首先我们看生成器的损失定义,令fake_GA_logit,fake_GB_logit表示生成器A和B的输出,fake_A2B2A和fake_B2A2B表示A和B经过一次完整的循环后的输出,fake_A2A和fake_B2B表示将A输入B到A方向的生成器输出,以及将B输入A到B方向的生成器的输出,所有的生成器损失定义如下:

G_ad_loss_GA = self.MSE_loss(fake_GA_logit, torch.ones_like(fake_GA_logit).to(self.device)) ##全局生成器A损失

G_ad_cam_loss_GA = self.MSE_loss(fake_GA_cam_logit, torch.ones_like(fake_GA_cam_logit).to(self.device)) #全局CAM生成器A损失

G_ad_loss_LA = self.MSE_loss(fake_LA_logit, torch.ones_like(fake_LA_logit).to(self.device)) #局部生成器A损失

G_ad_cam_loss_LA = self.MSE_loss(fake_LA_cam_logit, torch.ones_like(fake_LA_cam_logit).to(self.device)) #局部CAM生成器A损失

G_ad_loss_GB = self.MSE_loss(fake_GB_logit, torch.ones_like(fake_GB_logit).to(self.device)) #全局生成器B损失

G_ad_cam_loss_GB = self.MSE_loss(fake_GB_cam_logit, torch.ones_like(fake_GB_cam_logit).to(self.device)) #全局CAM生成器B损失

G_ad_loss_LB = self.MSE_loss(fake_LB_logit, torch.ones_like(fake_LB_logit).to(self.device)) 局部生成器B损失

G_ad_cam_loss_LB = self.MSE_loss(fake_LB_cam_logit, torch.ones_like(fake_LB_cam_logit).to(self.device)) 局部CAM生成器B损失

G_recon_loss_A = self.L1_loss(fake_A2B2A, real_A) ##A的循环损失

G_recon_loss_B = self.L1_loss(fake_B2A2B, real_B) ##B的循环损失

G_identity_loss_A = self.L1_loss(fake_A2A, real_A) ##A的身份保持损失

G_identity_loss_B = self.L1_loss(fake_B2B, real_B) ##B的身份保持损失

## A的CAM损失

G_cam_loss_A = self.BCE_loss(fake_B2A_cam_logit, torch.ones_like(fake_B2A_cam_logit).to(self.device)) + self.BCE_loss(fake_A2A_cam_logit, torch.zeros_like(fake_A2A_cam_logit).to(self.device))

## B的CAM损失

G_cam_loss_B = self.BCE_loss(fake_A2B_cam_logit, torch.ones_like(fake_A2B_cam_logit).to(self.device)) + self.BCE_loss(fake_B2B_cam_logit, torch.zeros_like(fake_B2B_cam_logit).to(self.device))

## 生成器A的损失

G_loss_A =  self.adv_weight * (G_ad_loss_GA + G_ad_cam_loss_GA + G_ad_loss_LA + G_ad_cam_loss_LA) + self.cycle_weight * G_recon_loss_A + self.identity_weight * G_identity_loss_A + self.cam_weight * G_cam_loss_A

## 生成器B的损失

G_loss_B = self.adv_weight * (G_ad_loss_GB + G_ad_cam_loss_GB + G_ad_loss_LB + G_ad_cam_loss_LB) + self.cycle_weight * G_recon_loss_B + self.identity_weight * G_identity_loss_B + self.cam_weight * G_cam_loss_B

# 总的损失

Generator_loss = G_loss_A + G_loss_B

接下来我们看判别器的损失定义,如下:

D_ad_loss_GA = self.MSE_loss(real_GA_logit, torch.ones_like(real_GA_logit).to(self.device)) + self.MSE_loss(fake_GA_logit, torch.zeros_like(fake_GA_logit).to(self.device)) #全局判别器A损失

D_ad_cam_loss_GA = self.MSE_loss(real_GA_cam_logit, torch.ones_like(real_GA_cam_logit).to(self.device)) + self.MSE_loss(fake_GA_cam_logit, torch.zeros_like(fake_GA_cam_logit).to(self.device)) #全局CAM判别器A损失

D_ad_loss_LA = self.MSE_loss(real_LA_logit, torch.ones_like(real_LA_logit).to(self.device)) + self.MSE_loss(fake_LA_logit, torch.zeros_like(fake_LA_logit).to(self.device)) #局部判别器A损失

D_ad_cam_loss_LA = self.MSE_loss(real_LA_cam_logit, torch.ones_like(real_LA_cam_logit).to(self.device)) + self.MSE_loss(fake_LA_cam_logit, torch.zeros_like(fake_LA_cam_logit).to(self.device)) #局部CAM判别器A损失

D_ad_loss_GB = self.MSE_loss(real_GB_logit, torch.ones_like(real_GB_logit).to(self.device)) + self.MSE_loss(fake_GB_logit, torch.zeros_like(fake_GB_logit).to(self.device)) #全局判别器B损失

D_ad_cam_loss_GB = self.MSE_loss(real_GB_cam_logit, torch.ones_like(real_GB_cam_logit).to(self.device)) + self.MSE_loss(fake_GB_cam_logit, torch.zeros_like(fake_GB_cam_logit).to(self.device)) #全局CAM判别器B损失

D_ad_loss_LB = self.MSE_loss(real_LB_logit, torch.ones_like(real_LB_logit).to(self.device)) + self.MSE_loss(fake_LB_logit, torch.zeros_like(fake_LB_logit).to(self.device)) #局部判别器B损失

D_ad_cam_loss_LB = self.MSE_loss(real_LB_cam_logit, torch.ones_like(real_LB_cam_logit).to(self.device)) + self.MSE_loss(fake_LB_cam_logit, torch.zeros_like(fake_LB_cam_logit).to(self.device)) #局部CAM判别器B损失

## 判别器A损失

D_loss_A = self.adv_weight * (D_ad_loss_GA + D_ad_cam_loss_GA + D_ad_loss_LA + D_ad_cam_loss_LA)

## 判别器B损失

D_loss_B = self.adv_weight * (D_ad_loss_GB + D_ad_cam_loss_GB + D_ad_loss_LB + D_ad_cam_loss_LB)

## 判别器损失

Discriminator_loss = D_loss_A + D_loss_B

4 模型训练与测试

接下来我们对模型进行训练和测试,主要包括模型结构参数,训练优化参数,损失权重。

4.1 模型训练

结构相关的参数主要是输入图像大小,网络通道数的配置,残差块的数目。

ch=32, 即通道单元数为32。

n_res=4, 即生成器中的残差模块数量为4。

n_dis=4, 即生成器中的残差模块数量为4。

img_size=256,即训练图像大小为256。

优化相关的参数主要是优化器,学习率,损失的权重。本次训练使用了Adam,一阶动量项系数为0.5,一阶动量项系数为0.999,固定学习率大小为0.0001。

损失权重为:

weight_decay==0.0001,权重正则项参数。

adv_weight=1,GAN的损失权重。

cycle_weight=10,CycleGAN损失权重。

identity_weight=10,身份一致性的损失权重。

cam_weight=1000,CAM的损失权重。

下图是训练的中间结果。

a86cf6a22866736c073ef1e3859f8d76.jpeg

从上到下分别表示real_B,fake_B2B_heatmap,fake_B2B,fake_B2A_heatmap,fake_B2A,fake_B2A2B_heatmap,fake_B2A2B。

4.2 模型推理

训练完模型后接下来我们对模型进行测试,需要完成模型的载入,数据的预处理,结果后处理等操作。

## 后处理函数

def denorm(x):

    return x * 0.5 + 0.5

## tensor到numpy变量转换

def tensor2numpy(x):

    return x.detach().cpu().numpy().transpose(1,2,0)

## RGB到BGR转换

def RGB2BGR(x):

return cv2.cvtColor(x, cv2.COLOR_RGB2BGR)

if __name__ == '__main__':

    modelpath = 'selfie2anime_params_latest.pt'

    input_nc = 3 ##生成器输入通道

    output_nc = 3 ##判别器输出通道

    ch = 32 ##基准通道数

    n_res = 4 ##生成器残差块数量

    img_size = 256 ##测试图像大小

    device = 'cpu' ##CPU模式

    result_dir = 'results' ##结果文件夹

    input_dir = 'images' ##测试文件夹

    genA2B = ResnetGenerator(input_nc=input_nc, output_nc=output_nc, ngf=ch, n_blocks=n_res, img_size=img_size, light=True).to(device) ##生成器结构

    params = torch.load(modelpath,map_location='cpu') ##载入参数

    genA2B.load_state_dict(params['genA2B']) ##载入生成器参数

    genA2B.eval() ##测试模式

    if not os.path.isdir(result_dir):

        os.makedirs(result_dir)

    imagepaths = os.listdir(input_dir) ##遍历文件夹

    transform = transforms.Compose([transforms.Resize((img_size, img_size)),transforms.ToTensor(),transforms.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5))]) ##预处理函数

    with torch.no_grad():

        for imagepath in imagepaths:

            image = Image.open(os.path.join(input_dir,imagepath)) ##读取图片

            image = image.convert('RGB') ##转换为RGB

            image = transform(image) ##预处理

            image.requires_grad = False

            image = image.unsqueeze(0).to(device) ##维度扩充

            print(type(image))

            fake_A2B, _,_ = genA2B(image) ##前向预测

            result = RGB2BGR(tensor2numpy(denorm(fake_A2B[0]))) ##得到BGR格式结果

            cv2.imwrite(os.path.join(result_dir,imagepath), result * 255.0)

实验结果如下图展示:

48b939fd7a04589dc8735ebe6079ee9a.jpeg

本文参考的文献如下:

[1] Kim J, Kim M, Kang H, et al. U-GAT-IT: Unsupervised Generative Attentional Networks with Adaptive Layer-Instance Normalization for Image-to-Image Translation[C]. international conference on learning representations, 2020.

本文视频讲解和代码,请大家移步:

【项目实战课】基于Pytorch的UGATIT人脸动漫风格化实战

0b6df05072e2e746dd66157326482670.gif

15760c3e5fdf28a0982a2ff3e3e2ad92.jpeg

总结

本次我们使用U-GAT-IT完成了动漫头像生成的任务,这是生成对抗网络非常有意思的一个应用,欢迎大家以后持续关注《百战GAN专栏》。

如何系统性地学习生成对抗网络GAN

欢迎大家关注有三AI-CV秋季划GAN小组,可以系统性学习GAN相关的内容,包括GAN的基础理论,《深度学习之图像生成GAN:理论与实践篇》,《深度学习之图像翻译GAN:理论与实践篇》以及各类GAN任务的实战。

介绍如下:【CV秋季划】生成对抗网络GAN有哪些研究和应用,如何循序渐进地学习好(2022年言有三一对一辅导)?

0a79e2bfb440b72f619251c0d858a7fb.png

转载文章请后台联系

侵权必究

1dedd565735e7d38f904be0dc8ed1339.gif

6ae3ea3dda6e0b1244825cb9cd9ba5b6.png

b3fb4dc7bf80f47e57e67a05b4008a80.png

opencv 照片动漫风格化 风景照片的动漫画,针对人脸效果不怎么好。 立即下载

相关推荐

动漫头像数据集

是一个动漫头像的数据集,可以利用GAN来训练学习。如Tensorflow中的DCGAN

Pytorch——生成式对抗网络的实例

生成器的最终目标是要欺骗判别器,混淆真伪图像;而判别器的目标是发现他何时被欺骗了,同时告知生成器在生成图像的过程中可识别的错误。注意无论是判别器获胜还是生成器获胜,都不是字面意义上的获胜。两个网络都是基于彼此的训练结果来推动参数优化的。 这项技术已经被证明可以产生一个生成器,他只从一个噪声和一个条件信号生成逼真的图像。这里的条件信号是指属性或者其他图片,例如:年轻的、女性的、戴着眼镜的。这样的生成器产生的图片可以满足人的审美。

lqm1094583745的博客 1044

GAN动漫头像数据集

5W张96*96的日本动漫头像,用于GAN生成对抗模型的训练。 https://blog.youkuaiyun.com/bu_fo/article/details/109800054

U-GAT-IT笔记

目录 前言 模型结构 生成器 鉴别器 损失函数 实验结果 结语 由于博客园有时候公式显示不出来,建议在https://github.com/FangYang970206/PaperNote/b...

weixin_30834783的博客 1772

NRF52832串口通信避坑指南:从SDK配置到硬件流控(RTS/CTS)实战

本文详细介绍了NRF52832串口通信的实战指南,从SDK配置到硬件流控(RTS/CTS)的完整实现。通过分析UARTE模块特性、解决SDK版本差异问题、正确配置硬件引脚以及实现稳定通信的代码示例,帮助开发者避开常见陷阱,确保高效可靠的串口通信。特别强调了硬件流控在高速通信中的重要性及其实践方法。

weixin_33739646的博客 283

告别迷茫!用C#和Windows.Devices.Bluetooth搞定BLE设备连接与数据收发(附完整代码)

本文详细介绍了如何使用C#和Windows.Devices.Bluetooth命名空间实现BLE设备连接与数据收发。从开发环境配置到设备扫描、服务发现、数据通信及资源管理,提供完整代码示例和实战技巧,帮助开发者快速掌握低功耗蓝牙开发,解决连接稳定性和异常处理等常见问题。

weixin_29248653的博客 506

CycleGan人脸转为漫画脸,牛掰的知识又增加了!| 附代码

作者 |李秋键责编 | Carol出品 | AI科技大本营(ID:rgznai100)近几天一个GitHub项目火遍了朋友圈,那就是卡通头像AI生成小程序。如下图所见:而这个项目的基本...

AI科技大本营 1万+

从零到一:手把手教你用海康VisionMaster完成第一个字符识别项目(附完整流程与避坑点)

本文详细介绍了如何使用海康VisionMaster完成首个字符识别项目,涵盖从环境搭建、图像预处理到系统集成的完整流程。特别分享了Blob分析配置、字符识别优化等实战技巧,帮助开发者避开常见陷阱,快速掌握VisionMaster的基本操作,提升工业自动化场景下的字符识别效率。

weixin_29313423的博客 672

图像风格迁移中的AdaIN,LIN,AdaLIN

在上篇(关于归一化BN IN LN GN相关梳理_TranSad的博客-优快云博客)整理过IN,LN,GN等归一化操作。但还有一些操作名词和它们密切相关,如AdaIN,LIN,AdaLIN等,因为它们主要是出现在图像风格迁移任务中,所以这篇单独拿出来讲一下这几个名词。 AdaIN 首先回顾一下IN: 通过上篇我们已经知道IN是对于特征图的每一个通道的H、W求均值和标准差,IN的示意图和公式回顾如下: 我们的IN在进行初步“减均值比标准差”操作过后,还需要通过γ和β来进行线...

TranSad的博客 8539

基于CNN的表情识别(FER)pytorch实现

Report for FER Jian Tang Abstract 我们使用卷积神经网络(CNN)实现了baby的面部表情识别(FER)。 数据上,我们获取了开源FER数据集FER2013[1],besides我们使用爬虫获取了baby相关的人脸图片,进行了手工分类标注以形成一个全新的baby FER数据集。 模型上,我们首先复现了Luan[2]的工作,即参考Kuo[3]在CVPR 2018上的工作并实现一个轻量的CNN网络,在FER2013上进行训练并得到了XXXX%的平均分类准确率(avg a

qq_20241587的博客 3685

VxWorks系列 快速导航

Vxworks系列快速阅览导航

Mr.Sugarcane 1315

torch.Tensor和torch.tensor的区别

在Pytorch中,Tensor和tensor都用于生成新的张量。 >>> a = torch.Tensor([1, 2]) >>> a tensor([1., 2.]) >>> a=torch.tensor([1,2]) >>> a tensor([1, 2]) 首先我们从根源上来看看torch.Tensor()和to...

Vic_Hao的博客 11万+

通过傅里叶变换进行音频变声变调

在游戏或者一些特殊场景下为了提高娱乐性或者保护声音的特征,我们会对音频进行变声变调处理。

yang1fei2的博客 1821

【百战GAN】二次元宅们,给自己做一个专属动漫头像可好!

大家好,欢迎来到专栏《百战GAN》,在这个专栏里,我们会进行算法的核心思想讲解,代码的详解,模型的训练和测试等内容。作者&编辑 | 言有三本文资源与生成结果展示本文篇幅:6800...

hacker_long的专栏 1031

JavaScript实现量化交易策略(附完整源码)

JavaScript实现量化交易策略(附完整源码)

希望我的博客,能帮上你解决学习中工作中所遇到的问题 722

YOLOv11改进 | 损失函数改进篇 | 2024最新高质量的目标检测边界框回归损失Unified-IoU、FocalUIoU、FocalInvUIoU(设置动态epoch参数)

本文给大家带来的改进机制是最近新提出的高质量的目标检测边界框回归损失,其通过动态调整模型对不同质量预测框的关注,优化目标检测中的边界框回归精度。UIoU引入了FocalBox方法,通过缩放预测框与真实框分配权重,并采用了退火策略(引入动态参数epoch),逐渐将模型的注意力从低质量预测框转移到高质量预测框,平衡了训练速度与检测精度。其还有一定的解决样本不平衡问题,同时该损失函数可以和现有的任何边界框回归损失函数进行结合,例如ShapeIoU和其结合可以形成二次创新,它是一种类似于之前的Inner的机制.

Snu77的博客 4591

【总结】有三AI所有原创GAN相关的学习资料汇总(2022年12月)

GAN的研究和应用在这几年发展可以说是非常迅猛,无疑是这几年深度学习计算机视觉领域里落地性最酷的技术之一,包括图像与视频生成,数据仿真与增强,各种各样的图像风格化任务,人脸与人体图像编辑,图像质量提升等。GAN最早期也是最经典的任务,就是高质量图像生成,当前已经可以生成1024分辨率以上的高清逼真图像,如下图生成了一些假明星脸。图像翻译与风格化则是另外一个应用极其广泛的领域,因为从图像到图像的任务...

hacker_long的专栏 1040

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

torch.nn.Parameter理解

Pytorch官网对torch.nn.Parameter()的解释: torch.nn.Parameter是继承自torch.Tensor的子类,其主要作用是作为nn.Module中的参数使用。它与torch.Tensor的区别就是nn.Parameter会自动被认为是module的可训练参数,即加入到parameter()这个迭代器中去;而module中非nn.Parameter()得普通te...

Stoneplay 4万+

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

pytorch: Tensor 常用操作

pytorch: Tensor 常用操作

吕爽 8万+

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

PyTorch:模型训练-parameters和optimizer

迭代打印model.named_parameters()将会打印每一次迭代元素的名字和paramparam.requires_grad=False # 顺便改下属性。

皮皮blog 9890

Pytorch之parameters

目录 1.预构建网络  网络结构 2.net.parameters() 逐列表项输出列表元素和index   1.预构建网络 class Net(nn.Module): def __init__(self): super(Net, self).__init__() # 1 input image channel, 6 output chann...

qq_37385726的博客 1万+

和tan的转换_[综述]基于对抗学习的图像间转换问题-2

《基于对抗学习的图像间转换问题-1》的链接地址:https://zhuanlan.zhihu.com/p/2636920121.4、CycleGAN更加具有普适性的无监督图像转换方案,目前来看是基于对偶学习(Dual Learning)的概念。很经典的就是文献[1]提出的CycleGAN结构,给定两个域X和Y,在将X域映射到Y域的同时,又将Y域映射回X域,这里用到了循环一致性(Cycle-cons...

weixin_35735088的博客 458

生成对抗网络消除图像模糊(Keras)

2017年,乌克兰天主教大学、布拉格捷克理工大学和解决方案提供商Eleks联手公布了一篇论文,文章标题为《DeblurGAN: Blind Motion Deblurring Using Conditional Adversarial Networks》。 这篇文章中,研究人员提出一种基于条件对抗式生成网络和内容损失(content loss)的端对端学习法DeblurGAN,用来去除图像上因为相...

牧野的博客 9892

【githubboy】前端内参,涵盖了技术面试、JavaScript 特性解析、数据结构与算法、主流框架、开发工具、编程范式

推荐一款比较有意思的工具:ProsePainter,可基于机器学习,通过文字描述来生成图像。 GitHub:github.com/Morphogens/ProsePainter ​ 一个开源的 SQLite 在线工具:SQLime,可用于调试与分享 SQL 片段,跟前端开发工具 JSFiddle 有点像。 数据库支持从本地或远程导入,远程导入可使用 GitHub 的 URL 链接。有需要的话,你还可以将数据库与查询语句保存到 GitHub 上,与项目开发者共享。 整个项目针对移动端进行了适配,因此操

m0_68038546的博客 5362

【杂谈】计算机视觉新手如何选读论文?这100多篇你必须收下。

从事计算机视觉领域的工作或者研究,必不可少的一件事情就是读论文,目前论文这么多,对于新手朋友们来说,该如何进行选读呢?本次来汇总一下我们的论文推荐专栏,给大家推荐各个方向,超过100篇文...

hacker_long的专栏 2448

基于DCGAN实现动漫头像生成(Pytorch)

基于DCGAN实现动漫头像生成(Pytorch) 参考代码:https://github.com/eriklindernoren/PyTorch-GAN 数据集链接:https://www.kaggle.com/soumikrakshit/anime-faces 实验环境 ① 建议使用Anaconda配置环境 ② 本实验采用的Package版本: torch = 1.9.0 torchvision = 0.10.0 matplotlib = 3.3.4 numpy = 1.19.5 scipy = 1.5.

m0_54551680的博客 482
上一篇: 【百战GAN】SRGAN人脸低分辨率老照片修复代码实战
下一篇: 【项目实战课】基于Pytorch的BiseNet表面缺陷分割实战
言有三
言有三 领域专家: 人工智能技术领域 领域专家: 人工智能技术领域
博客等级 码龄15年 3635粉丝 976原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

言有三

三人行必有AI

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

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

打赏作者

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

抵扣说明:

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

余额充值