【CV】第 13 章:用于处理图像的高级 GAN

  🔎大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎

 

📝个人主页-Sonhhxg_柒的博客_CSDN博客 📃

🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​

📣系列专栏 - 机器学习【ML】 自然语言处理【NLP】  深度学习【DL】

 🖍foreword

✔说明⇢本人讲解主要包括Python、机器学习(ML)、深度学习(DL)、自然语言处理(NLP)等内容。

如果你对这个系列感兴趣的话,可以关注订阅哟👋

文章目录

利用 Pix2Pix GAN

利用 CycleGAN

在自定义图像上利用 StyleGAN

超分辨率GAN

建筑学

编码 SRGAN

概括

问题

在上一章中,我们了解了如何利用生成对抗网络GAN ) 来生成逼真的图像。在本章中,我们将学习如何利用 GAN 来处理图像。我们将了解使用 GAN 生成图像的两种变体——有监督和无监督方法。在监督方法中,我们将提供输入和输出对组合以基于输入图像生成图像,我们将在 Pix2Pix GAN 中了解这一点。在无监督方法中,我们将指定输入和输出,但是,我们不会提供输入和输出之间的一一对应关系,而是期望 GAN 学习这两个类的结构,并将图像从一个类到另一个,我们将在 CycleGAN 中学习。

另一类无监督图像处理涉及从随机向量的潜在空间生成图像,并查看图像如何随着潜在向量值的变化而变化,我们将在自定义图像上利用 StyleGAN部分了解这一点。最后,我们将了解如何利用预训练的 GAN – SRGAN,它有助于将低分辨率图像转换为高分辨率图像。

具体来说,我们将了解以下主题:

  • 利用 Pix2Pix GAN
  • 利用 CycleGAN
  • 在自定义图像上利用 StyleGAN
  • 超分辨率GAN

利用 Pix2Pix GAN

想象一个场景,我们有成对的相互关联的图像(例如,对象边缘的图像作为输入,对象的实际图像作为输出)。给定的挑战是,我们要在给定对象边缘的输入图像的情况下生成图像。在传统环境中,这将是输入到输出的简单映射,因此是监督学习问题。但是,假设您正在与一个创意团队合作,该团队正试图为产品提供全新的外观。在这种情况下,监督学习没有多大帮助——因为它只能从历史中学习。

在本节中,我们将了解从鞋子的手绘涂鸦(轮廓)生成鞋子图像的架构。我们将从涂鸦中生成逼真图像的策略如下:

  1. 使用标准 cv2 边缘检测技术获取大量实际图像并创建相应的轮廓。
  2. 从原始图像的补丁中采样颜色,以便生成器知道要生成的颜色。
  3. 构建一个 UNet 架构,将带有样本补丁颜色的轮廓作为输入并预测相应的图像——这就是我们的生成器。
  4. 构建一个判别器架构,获取图像并预测它是真实的还是虚假的。
  5. 一起训练生成器和判别器,直到生成器可以生成欺骗判别器的图像。

让我们编写策略代码:

1.导入数据集并安装相关包:

try:
    !wget https://bit.ly/3kiuN93
    !mv 3kiuN93 ShoeV2.zip
    !unzip ShoeV2.zip
    !unzip ShoeV2_F/ShoeV2_photo.zip
except:
    !wget https://www.dropbox.com/s/g6b6gtvmdu0h77x/ShoeV2_photo.zip
!pip install torch_snippets
from torch_snippets import *
device = 'cuda' if torch.cuda.is_available() else 'cpu'

前面的代码下载了鞋子的图片。下载图片示例如下:

对于我们的问题,我们想在给定轮廓(边缘)的情况下绘制鞋子并采样鞋子的补丁颜色。在下一步中,我们将获取给定鞋子图像的边缘。这样,我们可以训练一个模型,在给定鞋子的轮廓和样本补丁颜色的情况下重建鞋子的图像。

2.定义一个函数以从下载的图像中获取边缘:

def detect_edges(img):
    img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
    img_gray = cv2.bilateralFilter(img_gray, 5, 50, 50)
    img_gray_edges = cv2.Canny(img_gray, 45, 100)
    # invert black/white
    img_gray_edges = cv2.bitwise_not(img_gray_edges) 
    img_edges=cv2.cvtColor(img_gray_edges,cv2.COLOR_GRAY2RGB)
    return img_edges

在前面的代码中,我们利用 OpenCV 包中可用的各种方法来获取图像中的边缘(有关 OpenCV 方法如何工作的更多详细信息,请参见第 18 章使用 OpenCV 实用程序进行图像分析)。

3.定义图像转换管道(preprocess和normalize):

IMAGE_SIZE = 256
preprocess = T.Compose([
                    T.Lambda(lambda x: torch.Tensor(x.copy())\
                             .permute(2, 0, 1).to(device))
                ])
normalize = lambda x: (x - 127.5)/127.5

4.定义数据集类 ( ShoesData)。该数据集类返回原始图像和带边缘的图像。我们将传递给网络的另一个细节是随机选择的区域中出现的色块。通过这种方式,我们使用户能够拍摄手绘轮廓图像,在图像的不同部分撒上所需的颜色,并生成新图像。此处显示了示例输入(第三张图像)和输出(第一张图像)(最好以彩色查看):

然而,我们在步骤 1 中获得的输入图像只是鞋子(第一张图像)——我们将使用它来提取鞋子的边缘(第二张图像)。此外,我们将在下一步中撒上颜色以获取前面图像的输入(第三张图像)-输出(第一张图像)组合。

在下面的代码中,我们将构建获取轮廓图像的类,洒上颜色,并返回一对颜色洒上的图像和原始鞋子图像(生成轮廓的图像):

  • 定义ShoesData类、__init__方法和__len__方法:
class ShoesData(Dataset):
    def __init__(self, items):
        self.items = items
    def __len__(self): return len(self.items)
  • 定义__getitem__方法。在这种方法中,我们将处理输入图像以获取具有边缘的图像,然后将原始图像中存在的颜色洒在图像上。在这里,我们正在获取给定图像的边缘:
    def __getitem__(self, ix):
        f = self.items[ix]
        try: im = read(f, 1)
        except:
            blank = preprocess(Blank(IMAGE_SIZE, \
                                     IMAGE_SIZE, 3))
            return blank, blank
        edges = detect_edges(im)
  • 一旦我们获取图像中的边缘,调整图像大小并规范化图像:
        im, edges = resize(im, IMAGE_SIZE), \
                    resize(edges, IMAGE_SIZE)
        im, edges = normalize(im), normalize(edges)
  • edges在图像和preprocess原件和图像上撒上颜色edges:
        self._draw_color_circles_on_src_img(edges, im)
        im, edges = preprocess(im), preprocess(edges)
        return edges, im
  • 定义洒颜色的函数:
    def _draw_color_circles_on_src_img(self, img_src, \
                                       img_target):
        non_white_coords = self._get_non_white_coordinates\
                                    (img_target)
        for center_y, center_x in non_white_coords:
            self._draw_color_circle_on_src_img(img_src, \
                        img_target, center_y, center_x)

    def _get_non_white_coordinates(self, img):
        non_white_mask = np.sum(img, axis=-1) < 2.75
        non_white_y, non_white_x = np.nonzero(non_white_mask)
        # randomly sample non-white coordinates
        n_non_white = len(non_white_y)
        n_color_points = min(n_non_white, 300)
        idxs = np.random.choice(n_non_white, n_color_points, \
                                replace=False)
        non_white_coords = list(zip(non_white_y[idxs], \
                                    non_white_x[idxs]))
        return non_white_coords

    def _draw_color_circle_on_src_img(self, img_src, \
                            img_target, center_y, center_x):
        assert img_src.shape == img_target.shape
        y0, y1, x0, x1 = self._get_color_point_bbox_coords(\
                                        center_y, center_x)
        color = np.mean(img_target[y0:y1, x0:x1],axis=(0, 1))
        img_src[y0:y1, x0:x1] = color

    def _get_color_point_bbox_coords(self, center_y,center_x):
        radius = 2
        y0 = max(0, center_y-radius+1)
        y1 = min(IMAGE_SIZE, center_y+radius)
        x0 = max(0, center_x-radius+1)
        x1 = min(IMAGE_SIZE, center_x+radius)
        return y0, y1, x0, x1

    def choose(self): return self[randint(len(self))]

5.定义训练和验证数据对应的数据集和数据加载器:

from sklearn.model_selection import train_test_split 
train_items, val_items = train_test_split(\ 
                        Glob('ShoeV2_photo/*.png'), \ 
                        test_size=0.2, random_state=2) 
trn_ds, val_ds = ShoesData(train_items), ShoesData(val_items) 

trn_dl = DataLoader( trn_ds, batch_size=32, shuffle=True) 
val_dl = DataLoader(val_ds, batch_size=32, shuffle=True)

6.定义生成器和鉴别器架构,它们利用权重初始化 ( weights_init_normal)UNetDown和UNetUp架构,就像我们在第 9 章图像分割第 10 章对象检测和分割的应用中所做的那样,来定义GeneratorUNet和Discriminator架构。

  • 初始化权重,使其服从正态分布:
def weights_init_normal(m): 
    classname = m.__class__.__name__ 
    if classname.find("Conv") != -1: 
        torch.nn.init.normal_(m.weight.data, 0.0, 0.02) 
    elif classname.find( "BatchNorm2d") != -1: 
        torch.nn.init.normal_(m.weight.data, 1.0, 0.02) 
        torch.nn.init.constant_(m.bias.data, 0.0)
  • 定义UNetwDown和UNetUp类:
class UNetDown(nn.Module):
    def __init__(self, in_size, out_size, normalize=True, \
                 dropout=0.0):
        super(UNetDown, self).__init__()
        layers = [nn.Conv2d(in_size, out_size, 4, 2, 1, \
                            bias=False)]
        if normalize:
            layers.append(nn.InstanceNorm2d(out_size))
        layers.append(nn.LeakyReLU(0.2))
        if dropout:
            layers.append(nn.Dropout(dropout))
        self.model = nn.Sequential(*layers)

    def forward(self, x):
        return self.model(x)

class UNetUp(nn.Module):
    def __init__(self, in_size, out_size, dropout=0.0):
        super(UNetUp, self).__init__()
        layers = [
            nn.ConvTranspose2d(in_size, out_size, 4, 2, 1, \
                               bias=False),
            nn.InstanceNorm2d(out_size),
            nn.ReLU(inplace=True),
        ]
        if dropout:
            layers.append(nn.Dropout(dropout))

        self.model = nn.Sequential(*layers)

    def forward(self, x, skip_input):
        x = self.model(x)
        x = torch.cat((x, skip_input), 1)

        return x
  • 定义GeneratorUNet类:
class GeneratorUNet(nn.Module):
    def __init__(self, in_channels=3, out_channels=3):
        super(GeneratorUNet, self).__init__()

        self.down1 = UNetDown(in_channels,64,normalize=False)
        self.down2 = UNetDown(64, 128)
        self.down3 = UNetDown(128, 256)
        self.down4 = UNetDown(256, 512, dropout=0.5)
        self.down5 = UNetDown(512, 512, dropout=0.5)
        self.down6 = UNetDown(512, 512, dropout=0.5)
        self.down7 = UNetDown(512, 512, dropout=0.5)
        self.down8 = UNetDown(512, 512, normalize=False, \
                              dropout=0.5)

        self.up1 = UNetUp(512, 512, dropout=0.5)
        self.up2 = UNetUp(1024, 512, dropout=0.5)
        self.up3 = UNetUp(1024, 512, dropout=0.5)
        self.up4 = UNetUp(1024, 512, dropout=0.5)
        self.up5 = UNetUp(1024, 256)
        self.up6 = UNetUp(512, 128)
        self.up7 = UNetUp(256, 64)

        self.final = nn.Sequential(
            nn.Upsample(scale_factor=2),
            nn.ZeroPad2d((1, 0, 1, 0)),
            nn.Conv2d(128, out_channels, 4, padding=1),
            nn.Tanh(),
        )

    def forward(self, x):
        d1 = self.down1(x)
        d2 = self.down2(d1)
        d3 = self.down3(d2)
        d4 = self.down4(d3)
        d5 = self.down5(d4)
        d6 = self.down6(d5)
        d7 = self.down7(d6)
        d8 = self.down8(d7)
        u1 = self.up1(d8, d7)
        u2 = self.up2(u1, d6)
        u3 = self.up3(u2, d5)
        u4 = self.up4(u3, d4)
        u5 = self.up5(u4, d3)
        u6 = self.up6(u5, d2)
        u7 = self.up7(u6, d1)
        return self.final(u7)
  • 定义Discriminator类:
class Discriminator(nn.Module):
    def __init__(self, in_channels=3):
        super(Discriminator, self).__init__()

        def discriminator_block(in_filters, out_filters, \
                                normalization=True):
            """Returns downsampling layers of each 
            discriminator block"""
            layers = [nn.Conv2d(in_filters, out_filters, \
                                4, stride=2, padding=1)]
            if normalization:
                layers.append(nn.InstanceNorm2d(out_filters))
            layers.append(nn.LeakyReLU(0.2, inplace=True))
            return layers

        self.model = nn.Sequential(
            *discriminator_block(in_channels * 2, 64, \
                                 normalization=False),
            *discriminator_block(64, 128),
            *discriminator_block(128, 256),
            *discriminator_block(256, 512),
            nn.ZeroPad2d((1, 0, 1, 0)),
            nn.Conv2d(512, 1, 4, padding=1, bias=False)
        )

    def forward(self, img_A, img_B):
        img_input = torch.cat((img_A, img_B), 1)
        return self.model(img_input)

7.定义generator和discriminator模型对象并获取摘要:

generator = GeneratorUNet().to(device)
discriminator = Discriminator().to(device)
!pip install torch_summary
from torchsummary import summary
print(summary(generator, torch.zeros(3, 3, IMAGE_SIZE, \
                            IMAGE_SIZE).to(device)))
print(summary(discriminator, torch.zeros(3, 3, IMAGE_SIZE, \
                IMAGE_SIZE).to(device), torch.zeros(3, 3, \
                IMAGE_SIZE, IMAGE_SIZE).to(device)))

生成器架构总结如下:

判别器架构总结如下:

8.定义训练判别器的函数 ( discriminator_train_step):

  • 鉴别器函数将源图像 ( real_src)、真实目标 ( real_trg) 和假目标 ( fake_trg) 作为输入:
def discriminator_train_step(real_src, real_trg, fake_trg):
    d_optimizer.zero_grad()
  • 通过error_real比较真实目标(real_trgreal_srctorch.ones
    prediction_real = discriminator(real_trg, real_src)
    error_real = criterion_GAN(prediction_real, \
                    torch.ones(len(real_src), 1, 16, 16)\
                               .to(device))
    error_real.backward()
  • 计算error_fake与假图像( )对应的鉴别器损失( ) fake_trg,其中期望鉴别器将假目标分类为假图像(由 表示torch.zeros),然后执行反向传播:
    prediction_fake = discriminator(real_src, \ 
                                    fake_trg.detach()) 
    error_fake = criteria_GAN(prediction_fake, \ 
                               torch.zeros(len(real_src), 1, \ 
                                           16, 16).to(device)) 
    error_fake.backward()
  • 执行优化器步骤并返回预测的真实和虚假目标的总体误差和损失值:
    d_optimizer.step()
    return error_real + error_fake

9.定义函数来训练生成器generator_train_step(fake_trg

def generator_train_step(real_src, fake_trg): 
    g_optimizer.zero_grad() 
    prediction = discriminator(fake_trg, real_src) 

    loss_GAN = criteria_GAN(prediction, torch.ones(\ 
                            len(real_src), 1, 16, 16)\ .to 
                             (device)) 
    loss_pixel = criteria_pixelwise(fake_trg, real_trg) 
    loss_G = loss_GAN + lambda_pixel * loss_pixel 

    loss_G.backward() 
    g_optimizer.step() 
    return loss_G

请注意,在前面的代码中,除了生成器损失之外,我们还获取与loss_pixel给定轮廓的生成图像和真实图像之间的差异相对应的像素损失 ( ):

  • 定义一个函数来获取预测样本:
denorm = T.Normalize((-1, -1, -1), (2, 2, 2))
def sample_prediction():
    """Saves a generated sample from the validation set"""
    data = next(iter(val_dl))
    real_src, real_trg = data
    fake_trg = generator(real_src)
    img_sample = torch.cat([denorm(real_src[0]), \
                            denorm(fake_trg[0]), \
                            denorm(real_trg[0])], -1)
    img_sample = img_sample.detach().cpu()\
                           .permute(1,2,0).numpy()
    show(img_sample, title='Source::Generated::GroundTruth', \
         sz=12)

10.将权重初始化 ( weights_init_normal) 应用于生成器和判别器模型对象:

generator.apply(weights_init_normal) 
discriminator.apply(weights_init_normal)

11.指定损失标准和优化方法(criterion_GAN和criterion_pixelwise):

criterion_GAN = torch.nn.MSELoss()
criterion_pixelwise = torch.nn.L1Loss()

lambda_pixel = 100
g_optimizer = torch.optim.Adam(generator.parameters(), \
                               lr=0.0002, betas=(0.5, 0.999))
d_optimizer = torch.optim.Adam(discriminator.parameters(), \
                               lr=0.0002, betas=(0.5, 0.999))

12.训练模型超过 100 个 epoch:

epochs = 100 
log = Report(epochs) 
for epoch in range(epochs): 
    N = len(trn_dl) 
    for bx, batch in enumerate(trn_dl): 
        real_src, real_trg = batch 
        fake_trg = generator(real_src) 
        errD = discriminator_train_step(real_src, real_trg, \ 
                                        fake_trg) 
        errG = generator_train_step(real_src, fake_trg) 
        log.record(pos=epoch+(1+bx)/N, errD=errD.item(), \ 
                   errG=errG.item(), end='\r ') 
    [sample_prediction() for _ in range(2)]

13.在样本手绘轮廓上生成:

[sample_prediction() for _ in range(2)]

上述代码生成以下输出:

请注意,在前面的输出中,我们生成了与原始图像颜色相似的图像。

在本节中,我们学习了如何利用图像的轮廓来生成图像。但是,这需要我们成对提供输入和输出,这有时可能是一个乏味的过程。在下一节中,我们将了解未配对的图像翻译,其中网络将计算翻译,而无需我们指定图像的输入和输出映射。

利用 CycleGAN

想象一个场景,我们要求您执行从一个类到另一个类的图像转换,但不提供输入和相应的输出图像来训练模型。但是,我们在两个不同的文件夹中为您提供这两个类别的图像。CycleGAN 在这种情况下就派上用场了。

在本节中,我们将学习如何训练 CycleGAN 将苹果的图像转换为橙子的图像,反之亦然。CycleGAN 中的循环是指我们将图像从一个类转换(转换)到另一个类并返回到原始类的事实。

在高层次上,我们将在此架构中具有三个单独的损失值(此处提供了更多详细信息):

  • 鉴别器损失(Discriminator loss):这可确保在训练模型时修改对象类(如上一节所示)。
  • Cycle loss:将一张图片从生成的图片循环到原图的损失,以保证周围像素不发生变化。
  • 身份损失(Identity loss):当一个类别的图像通过生成器时的损失,该生成器预期将另一类的图像转换为输入图像的类别。

在这里,我们将从高层次了解构建 CycleGAN 的步骤:

1.导入和预处理数据集

2.构建生成器和判别器网络 UNet 架构

3.定义两个生成器:

  • G_AB : 将 A 类图像转换为 B 类图像的生成器
  • G_BA:将 B 类图像转换为 A 类图像的生成器

4.定义身份损失

  • 如果您要将橙色图像发送到橙色生成器,理想情况下,如果生成器了解有关橙色的所有内容,则它不应更改图像并应“生成”完全相同的图像。因此,我们使用这些知识创建了一个身份。
  • 当 A 类 (real_A) 的图像通过 G_BA 并与 real_A 进行比较时,身份损失应该是最小的。
  • 当 B 类 (real_B) 的图像通过 G_AB 并与 real_B 进行比较时,标识 l oss 应该是最小的。

5.定义GAN 损失:

  • real_A 和 fake_A 的判别器和生成器损失(当 real_B 图像通过 G_BA 时得到 fake_A)
  • real_B 和 fake_B 的判别器和生成器损失(当 real_A 图像通过 G_AB 时得到 fake_B)

6.定义循环 损失:

  • 考虑这样一个场景,一个苹果的图像将被一个橙子生成器转换为一个假橙子,而这个假橙子将被苹果生成器转换回一个苹果。
  • fake_B 是 real_A 通过 G_AB 时的输出,当 fake_B 通过 G_BA 时应该重新生成 real_A。
  • fake_A 是 real_B 通过 G_BA 时的输出,当 fake_A 通过 G_AB 时应该重新生成 real_B。

7.优化三个损失的加权损失。

现在我们了解了这些步骤,让我们编写代码以将苹果转换为橙子,反之亦然,如下所示:

导入相关数据集和包:

1.下载并提取数据集:

!wget https://www.dropbox.com/s/2xltmolfbfharri/apples_oranges.zip 
!unzip apples_oranges.zip

我们将处理的图像示例:

请注意,苹果和橙色图像之间没有一一对应的关系(与我们在利用 Pix2Pix GAN部分了解的轮廓到鞋子生成用例不同)。

  • 导入所需的包:
!pip install torch_snippets torch_summary 
import itertools 
from PIL import Image 
from torch_snippets import * 
from torchvision import transforms 
from torchvision.utils import make_grid 
from torchsummary import summary

2.定义图像转换管道 ( transform):

IMAGE_SIZE = 256 
device = 'cuda' if torch.cuda.is_available() else 'cpu' 
transform = transforms.Compose([ 
    transforms.Resize(int(IMAGE_SIZE*1.33)), 
    transforms.RandomCrop((IMAGE_SIZE,IMAGE_SIZE)), 
    transforms.RandomHorizo​​ntalFlip(), 
    transforms.ToTensor(), 
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)), 
])

3.定义数据集CycleGANDataset类apple(orange

class CycleGANDataset(Dataset):
    def __init__(self, apples, oranges):
        self.apples = Glob(apples)
        self.oranges = Glob(oranges)

    def __getitem__(self, ix):
        apple = self.apples[ix % len(self.apples)]
        orange = choose(self.oranges)
        apple = Image.open(apple).convert('RGB')
        orange = Image.open(orange).convert('RGB')
        return apple, orange

    def __len__(self): return max(len(self.apples), \
                                  len(self.oranges))
    def choose(self): return self[randint(len(self))]

    def collate_fn(self, batch):
        srcs, trgs = list(zip(*batch))
        srcs=torch.cat([transform(img)[None] for img in srcs]\
                         , 0).to(device).float()
        trgs=torch.cat([transform(img)[None] for img in trgs]\
                         , 0).to(device).float()
        return srcs.to(device), trgs.to(device)

4.定义训练和验证数据集和数据加载器:

trn_ds = CycleGANDataset('apples_train', 'oranges_train') 
val_ds = CycleGANDataset('apples_test', 'oranges_test') 

trn_dl = DataLoader(trn_ds, batch_size=1, shuffle=True, \ collat​​e_fn= 
                    trn_ds.collat​​e_fn) 
val_dl = DataLoader(val_ds , batch_size=5, shuffle=True, \ 
                    collat​​e_fn=val_ds.collat​​e_fn)

5.定义网络的权重初始化方法 ( weights_init_normal) 如前几节所定义:

def weights_init_normal(m): 
    classname = m.__class__.__name__ 
    if classname.find("Conv") != -1: 
        torch.nn.init.normal_(m.weight.data, 0.0, 0.02) 
        if hasattr(m, "bias") 和 m.bias 不是 None: 
            torch.nn.init.constant_(m.bias.data, 0.0) 
    elif classname.find("BatchNorm2d") != -1: 
        torch.nn.init.normal_( m.weight.data, 1.0, 0.02) 
        torch.nn.init.constant_(m.bias.data, 0.0)

6.定义残差块网络 ( ResidualBlock),在本例中,我们将利用 ResNet:

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

        self.block = nn.Sequential(
            nn.ReflectionPad2d(1),
            nn.Conv2d(in_features, in_features, 3),
            nn.InstanceNorm2d(in_features),
            nn.ReLU(inplace=True),
            nn.ReflectionPad2d(1),
            nn.Conv2d(in_features, in_features, 3),
            nn.InstanceNorm2d(in_features),
        )

    def forward(self, x):
        return x + self.block(x)

7.定义生成器网络 ( GeneratorResNet):

class GeneratorResNet(nn.Module):
    def __init__(self, num_residual_blocks=9):
        super(GeneratorResNet, self).__init__()
        out_features = 64
        channels = 3
        model = [
            nn.ReflectionPad2d(3),
            nn.Conv2d(channels, out_features, 7),
            nn.InstanceNorm2d(out_features),
            nn.ReLU(inplace=True),
        ]
        in_features = out_features
        # Downsampling
        for _ in range(2):
            out_features *= 2
            model += [
                nn.Conv2d(in_features, out_features, 3, \
                          stride=2, padding=1),
                nn.InstanceNorm2d(out_features),
                nn.ReLU(inplace=True),
            ]
            in_features = out_features

        # Residual blocks
        for _ in range(num_residual_blocks):
            model += [ResidualBlock(out_features)]

        # Upsampling
        for _ in range(2):
            out_features //= 2
            model += [
                nn.Upsample(scale_factor=2),
                nn.Conv2d(in_features, out_features, 3, \
                          stride=1, padding=1),
                nn.InstanceNorm2d(out_features),
                nn.ReLU(inplace=True),
            ]
            in_features = out_features

        # Output layer
        model += [nn.ReflectionPad2d(channels), \
                  nn.Conv2d(out_features, channels, 7), \
                  nn.Tanh()]
        self.model = nn.Sequential(*model)
        self.apply(weights_init_normal)
    def forward(self, x):
        return self.model(x)

8.定义鉴别器网络 ( Discriminator):

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

        channels, height, width = 3, IMAGE_SIZE, IMAGE_SIZE

        def discriminator_block(in_filters, out_filters, \
                                normalize=True):
            """Returns downsampling layers of each 
            discriminator block"""
            layers = [nn.Conv2d(in_filters, out_filters, \
                                4, stride=2, padding=1)]
            if normalize:
                layers.append(nn.InstanceNorm2d(out_filters))
            layers.append(nn.LeakyReLU(0.2, inplace=True))
            return layers

        self.model = nn.Sequential(
            *discriminator_block(channels,64,normalize=False),
            *discriminator_block(64, 128),
            *discriminator_block(128, 256),
            *discriminator_block(256, 512),
            nn.ZeroPad2d((1, 0, 1, 0)),
            nn.Conv2d(512, 1, 4, padding=1)
        )
        self.apply(weights_init_normal)

    def forward(self, img):
        return self.model(img)
  • 定义生成图像样本的函数 – generate_sample:
@torch.no_grad()
def generate_sample():
    data = next(iter(val_dl))
    G_AB.eval()
    G_BA.eval()    
    real_A, real_B = data
    fake_B = G_AB(real_A)
    fake_A = G_BA(real_B)
    # Arange images along x-axis
    real_A = make_grid(real_A, nrow=5, normalize=True)
    real_B = make_grid(real_B, nrow=5, normalize=True)
    fake_A = make_grid(fake_A, nrow=5, normalize=True)
    fake_B = make_grid(fake_B, nrow=5, normalize=True)
    # Arange images along y-axis
    image_grid = torch.cat((real_A,fake_B,real_B,fake_A), 1)
    show(image_grid.detach().cpu().permute(1,2,0).numpy(), \
         sz=12)

9.定义训练生成器的函数 ( generator_train_step):

  • 该函数将两个生成器模型(G_AB 和 G_BA 作为Gs)、optimizer和两个类的真实图像 -real_A和real_B- 作为输入:
def generator_train_step(Gs, optimizer, real_A, real_B):
  • 指定生成器:
    G_AB, G_BA = Gs
  • 将优化器的梯度设置为零:
    optimizer.zero_grad()
  • 如果您要将橙色图像发送到橙色生成器,理想情况下,如果生成器了解有关橙色的所有内容,则它不应该对图像进行任何更改,并且应该“生成”确切的图像。因此,我们使用这些知识创建了一个身份。对应的损失函数criterion_identity将在训练模型之前给出。loss_identity计算类型 A(苹果)和类型 B(橙子)的图像的身份损失 ( ):
    loss_id_A = criterion_identity(G_BA(real_A), real_A)
    loss_id_B = criterion_identity(G_AB(real_B), real_B)

    loss_identity = (loss_id_A + loss_id_B) / 2
  • 计算图像通过生成器时的 GAN 损失,并且生成的图像应尽可能接近其他类(np.ones在这种情况下,我们在训练生成器时有,因为我们将一个类的假图像传递给同类别的鉴别器):
    fake_B = G_AB(real_A)
    loss_GAN_AB = criterion_GAN(D_B(fake_B), \
                torch.Tensor(np.ones((len(real_A), 1, \
                                      16, 16))).to(device))
    fake_A = G_BA(real_B)
    loss_GAN_BA = criterion_GAN(D_A(fake_A), \
                torch.Tensor(np.ones((len(real_A), 1, \
                                      16, 16))).to(device))

    loss_GAN = (loss_GAN_AB + loss_GAN_BA) / 2
  • 计算循环损失。考虑这样一个场景,一个苹果的图像将被一个橙子生成器转换为一个假橙子,而这样一个假橙子将被苹果生成器转换回一个苹果。如果生成器是完美的,这个过程应该返回原始图像,这意味着以下循环损失应该为零:
    recov_A = G_BA(fake_B) 
    loss_cycle_A = criteria_cycle(recov_A, real_A) 
    recov_B = G_AB(fake_A) 
    loss_cycle_B = criteria_cycle(recov_B, real_B) 

    loss_cycle = (loss_cycle_A + loss_cycle_B) / 2
  • 在返回计算值之前计算总体损失并执行反向传播:
    loss_G = loss_GAN + lambda_cyc * loss_cycle + \
            lambda_id * loss_identity
    loss_G.backward()
    optimizer.step()
    return loss_G, loss_identity, loss_GAN, loss_cycle, \
            loss_G, fake_A, fake_B

10.定义训练判别器的函数 ( discriminator_train_step):

def discriminator_train_step(D, real_data, fake_data, \
                             optimizer):
    optimizer.zero_grad()
    loss_real = criterion_GAN(D(real_data), \
             torch.Tensor(np.ones((len(real_data), 1, \
                                   16, 16))).to(device))
    loss_fake = criterion_GAN(D(fake_data.detach()), \
             torch.Tensor(np.zeros((len(real_data), 1, \
                                   16, 16))).to(device))
    loss_D = (loss_real + loss_fake) / 2
    loss_D.backward()
    optimizer.step()
    return loss_D

11.定义生成器、鉴别器对象、优化器和损失函数:

G_AB = GeneratorResNet().to(device)
G_BA = GeneratorResNet().to(device)
D_A = Discriminator().to(device)
D_B = Discriminator().to(device)

criterion_GAN = torch.nn.MSELoss()
criterion_cycle = torch.nn.L1Loss()
criterion_identity = torch.nn.L1Loss()

optimizer_G = torch.optim.Adam(
    itertools.chain(G_AB.parameters(), G_BA.parameters()), \
    lr=0.0002, betas=(0.5, 0.999))
optimizer_D_A = torch.optim.Adam(D_A.parameters(), \
                        lr=0.0002, betas=(0.5, 0.999))
optimizer_D_B = torch.optim.Adam(D_B.parameters(), \
                        lr=0.0002, betas=(0.5, 0.999))

lambda_cyc, lambda_id = 10.0, 5.0

12.在越来越多的时期训练网络:

n_epochs = 10
log = Report(n_epochs)
for epoch in range(n_epochs):
    N = len(trn_dl)
    for bx, batch in enumerate(trn_dl):
        real_A, real_B = batch

        loss_G, loss_identity, loss_GAN, loss_cycle, \
        loss_G, fake_A, fake_B = generator_train_step(\
                                  (G_AB,G_BA), optimizer_G, \
                                  real_A, real_B)
        loss_D_A = discriminator_train_step(D_A, real_A, \
                                    fake_A, optimizer_D_A)
        loss_D_B = discriminator_train_step(D_B, real_B, \
                                    fake_B, optimizer_D_B)
        loss_D = (loss_D_A + loss_D_B) / 2
        
        log.record(epoch+(1+bx)/N, loss_D=loss_D.item(), \
            loss_G=loss_G.item(), loss_GAN=loss_GAN.item(), \
            loss_cycle=loss_cycle.item(), \
           loss_identity=loss_identity.item(), end='\r')
        if bx%100==0: generate_sample()

    log.report_avgs(epoch+1)

13.训练模型后生成图像:

generate_sample()

上述代码生成以下输出:

从前面可以看出,我们成功地将苹果转换为橙子(前两行)和橙子转换为苹果(最后两行)。

到目前为止,我们已经通过 Pix2Pix GAN 了解了成对的图像到图像的翻译,以及通过 CycleGAN 了解了不成对的图像到图像的翻译。在下一节中,我们将学习如何利用 StyleGAN 将一种风格的图像转换为另一种风格的图像。

在自定义图像上利用 StyleGAN

我们先来了解一下 StyleGAN 发明之前的一些历史发展。正如我们所知,从前一章生成假人脸涉及到 GAN 的使用。研究面临的最大问题是可以生成的图像很小(通常为 64 x 64)。任何生成更大尺寸图像的努力都会导致生成器或鉴别器陷入局部最小值,这将停止训练并产生乱码。生成高质量图像的主要飞跃之一涉及一篇名为 ProGAN(Progressive GAN 的缩写)的研究论文,其中涉及一个巧妙的技巧。

生成器和鉴别器的大小都在逐渐增加。在第一步中,您创建一个生成器和鉴别器,以从潜在向量生成 4 x 4 图像。在此之后,额外的卷积(和放大)层被添加到经过训练的生成器和鉴别器中,它们将负责接受 4 x 4 图像(由步骤 1 中的潜在向量生成)并生成/鉴别 8 x 8 图像。一旦这一步也完成了,在生成器和鉴别器中再次创建新的层,以训练生成更大的图像。一步一步(逐步),图像大小以这种方式增加。逻辑是向已经运行良好的网络添加新层比尝试从头开始学习所有层更容易。以这种方式,https://arxiv.org/pdf/1710.10196v3.pdf):

尽管它成功了,但控制生成图像的各个方面(例如性别和年龄)相当困难,主要是因为网络只得到一个输入(在上图中:网络顶部的潜在)。StyleGAN 解决了​​这种情况。

StyleGAN 使用类似的训练方案,逐步生成图像,但每次网络增长时都会添加一组潜在输入。这意味着网络现在以生成的图像大小的规则间隔接受多个潜在向量。在生成阶段给出的每个潜在值都决定了在该网络的那个阶段将要生成的特征(样式)。让我们在这里更详细地讨论 StyleGAN 的工作细节:

在上图中,我们可以对比传统的图像生成方式和基于样式的生成器。在传统的生成器中,只有一个输入。但是,在基于样式的生成器中有一种机制。让我们在这里了解详细信息:

1.创建大小为 1 x 512 的随机噪声向量z

2.将其馈送到称为样式网络(或映射网络)的辅助网络,该网络创建大小为 18 x 512 的张量w

3.生成器(合成)网络包含 18 个卷积层。每一层都将接受以下输入:

  • w ('A')的对应行
  • 随机噪声向量 ('B')
  • 上一层的输出

请注意,噪声 ('B') 仅用于正则化目的。

前面三个组合将创建一个管道,该管道接收一个 1 x 512 向量并创建一个 1024 x 1024 图像。

现在让我们了解从映射网络生成的 18 x 512 向量中的 18 个 1 x 512 向量中的每一个如何对图像的生成做出贡献。在合成网络的前几层添加的 1 x 512 向量有助于图像中存在的整体姿势和大规模特征,例如姿势、面部形状等,(因为它们负责生成4 x 4、8 x 8 的图像,等等——这是前几张图像,将在后面的层中进一步增强)。中间层添加的向量对应于小尺度特征,例如发型、睁眼/闭眼(因为它们负责生成 16 x 16、32 x 32 和 64 x 64 图像). 最后几层添加的向量对应于图像的颜色方案和其他微观结构。当我们到达最后几层时,图像结构被保留,面部特征被保留,但只有图像级别的细节(例如照明条件)发生了变化。

在本节中,我们将利用预训练的 StyleGAN2 模型来定制我们感兴趣的图像以具有不同的风格。

为了我们的目标,我们将使用 StyleGAN2 模型执行风格迁移。在高层次上,以下是面部风格转换的工作原理(通过代码结果,以下内容会更加清晰):

  • 假设 w 1样式向量用于生成 face-1,而 w 2样式向量用于生成 face-2。它们都是 18 x 512。
  • w 2中的 18 个向量中的前几个(负责从 4 x 4 到 8 x 8 分辨率生成图像)被替换为 w 1中的相应向量。然后,我们将非常粗略的特征(例如姿势从 face-1 转移到 face-2)。
  • 如果后面的样式向量(比如 18 x 512 中的第三个到第十五个——负责生成 64 x 64 到 256 x 256 维的批量图像)在 w 2中被替换为w 1中的样式向量,那么我们转移特征例如眼睛、鼻子和其他面部中级特征。
  • 如果最后几个样式向量(负责生成 512 x 512 到 1024 x 1024 维度的批量图像)被替换,则肤色和背景等精细特征(不会显着影响整体面部)被转移。

了解了风格转移是如何完成的,现在让我们了解如何使用 StyleGAN2 在自定义图像上执行风格转移:

  1. 拍摄自定义图像。
  2. 对齐自定义图像,以便仅存储图像的面部区域。
  3. 获取可能生成自定义对齐图像的潜在向量。
  4. 通过将随机潜在向量 (1 x 512) 传递到映射网络来生成图像。

通过这一步,我们有两个图像——我们的自定义对齐图像和 StyleGAN2 网络生成的图像。我们现在想将自定义图像的一些特征转移到生成的图像中,反之亦然。

让我们编写前面的策略。

请注意,我们正在利用从 GitHub 存储库中获取的预训练网络,因为训练这样的网络需要几天甚至几周的时间:

1.克隆存储库,安装需求,并获取预训练的权重:

import os
if not os.path.exists('pytorch_stylegan_encoder'):
    !git clone https://github.com/jacobhallberg/pytorch_stylegan_encoder.git
    %cd pytorch_stylegan_encoder
    !git submodule update --init --recursive
    !wget -q https://github.com/jacobhallberg/pytorch_stylegan_encoder/releases/download/v1.0/trained_models.zip
    !unzip -q trained_models.zip
    !rm trained_models.zip
    !pip install -qU torch_snippets
    !mv trained_models/stylegan_ffhq.pth InterFaceGAN/models/pretrain
else:
    %cd pytorch_stylegan_encoder
    
from torch_snippets import *

2.加载预训练的生成器和合成网络,映射网络的权重:

from InterFaceGAN.models.stylegan_generator import StyleGANGenerator 
from models.latent_optimizer import PostSynthesisProcessing 

synthesizer=StyleGANGenerator("stylegan_ffhq").model.synthesis 
mapper = StyleGANGenerator("stylegan_ffhq").model.mapping 
trunc = StyleGANGenerator("stylegan_ffhq").model.truncation

3.定义从随机向量生成图像的函数:

post_processing = PostSynthesisProcessing()
post_process = lambda image: post_processing(image)\
                .detach().cpu().numpy().astype(np.uint8)[0]

def latent2image(latent):
    img = post_process(synthesizer(latent))
    img = img.transpose(1,2,0)
    return img

4.生成一个随机向量:

rand_latents = torch.randn(1,512).cuda()

在前面的代码中,我们通过映射和截断网络传递随机的 1 x 512 维向量以生成 1 x 18 x 512 的向量。这些 18 x 512 的向量决定了生成图像的样式。

5.从随机向量生成图像:

show(latent2image(trunc(mapper(rand_latents))), sz=5)

上述代码生成以下输出:

至此,我们已经生成了一张图片。在接下来的几行代码中,您将了解如何在前面生成的图像和您选择的图像之间执行样式转换。

6.获取自定义图像 ( MyImage.jpg) 并对齐它。对齐对于生成适当的潜在向量很重要,因为 StyleGAN 中生成的所有图像都以人脸为中心并且特征明显可见:

!wget https://www.dropbox.com/s/lpw10qawsc5ipbn/MyImage.JPG\
 -O MyImage.jpg
!git clone https://github.com/Puzer/stylegan-encoder.git
!mkdir -p stylegan-encoder/raw_images
!mkdir -p stylegan-encoder/aligned_images
!mv MyImage.jpg stylegan-encoder/raw_images

7.对齐自定义图像:

!python stylegan-encoder/align_images.py \ 
stylegan-encoder/raw_images/ \ 
stylegan-encoder/aligned_images/ 
!mv stylegan-encoder/aligned_images/* ./MyImage.jpg

8.使用对齐的图像生成可以完美再现对齐图像的潜在值。这是一个识别潜在向量组合的过程,该组合使对齐图像与从潜在向量生成的图像之间的差异最小化:

from PIL import Image
img = Image.open('MyImage.jpg')
show(np.array(img), sz=4, title='original')

!python encode_image.py ./MyImage.jpg\
 pred_dlatents_myImage.npy\
 --use_latent_finder true\
 --image_to_latent_path ./trained_models/image_to_latent.pt

pred_dlatents = np.load('pred_dlatents_myImage.npy')
pred_dlatent = torch.from_numpy(pred_dlatents).float().cuda()
pred_image = latent2image(pred_dlatent)
show(pred_image, sz=4, title='synthesized')

上述代码生成以下输出:

Python 脚本encode_image.py在高层次上执行以下操作:

  1. 空间中创建一个随机向量。
  2. 使用此向量合成图像。
  3. 使用 VGG 的感知损失(与神经风格迁移中使用的损失相同)将合成图像与原始输入图像进行比较。
  4. 对随机向量执行反向传播,以减少固定迭代次数的损失。
  5. 优化后的向量现在将合成一张 VGG 给出与输入图像几乎相同的特征的图像,因此合成图像看起来与输入图像相似。

现在我们有了与感兴趣的图像相对应的潜在向量,让我们在下一步中执行图像之间的风格转换。

9.执行风格转移:

如前所述,风格转移背后的核心逻辑实际上是风格张量的部分转移,即 18 x 512 风格张量中的 18 个子集。在这里,我们将在一种情况下传输前两行(18 x 512),在一种情况下传输 3-15 行,在一种情况下传输 15-18 行。由于每组向量负责生成图像的不同方面,因此每组交换的向量交换图像中的不同特征:

idxs_to_swap = slice(0,3)
my_latents=torch.Tensor(np.load('pred_dlatents_myImage.npy', \
                                  allow_pickle=True))

A, B = latent2image(my_latents.cuda()), latent2image(trunc(mapper(rand_latents)))
generated_image_latents = trunc(mapper(rand_latents))

x = my_latents.clone()
x[:,idxs_to_swap] = generated_image_latents[:,idxs_to_swap]
a = latent2image(x.float().cuda())

x = generated_image_latents.clone()
x[:,idxs_to_swap] = my_latents[:,idxs_to_swap]
b = latent2image(x.float().cuda())

subplots([A,a,B,b], figsize=(7,8), nc=2, \
         suptitle='Transfer high level features')

前面的代码生成这个:

这是分别带有idxs_to_swapasslice(4,15)和的输出slice (15,18)。

10.接下来,我们推断一个样式向量,这样新的向量只会改变我们自定义图像的笑脸。为此,您需要计算移动潜在向量的正确方向。我们可以通过首先创建大量假图像来实现这一点。然后使用 SVM 分类器来训练并找出图像中的人是否微笑。因此,这个 SVM 创建了一个超平面,将笑脸与非笑脸分开。移动所需的方向将垂直于这个超平面,表示为stylegan_ffhq_smile_w_boundary.npy。实现细节可以在InterfaceGAN/edit.py代码本身中找到:

!python InterFaceGAN/edit.py\
 -m stylegan_ffhq\
 -o results_new_smile\
 -b InterFaceGAN/boundaries/stylegan_ffhq_smile_w_boundary.npy\
 -i pred_dlatents_myImage.npy\
 -s WP\
 --steps 20

generated_faces = glob.glob('results_new_smile/*.jpg')

subplots([read(im,1) for im in sorted(generated_faces)], \
         figsize=(10,10))

以下是生成的图像的外观:

 总之,我们已经了解了这项研究在使用 GAN 生成非常高分辨率的人脸图像方面的进展情况。诀窍是在增加分辨率的步骤中增加生成器和判别器的复杂性,以便在每一步中,两个模型都能很好地完成任务。我们了解了如何通过确保每个分辨率的特征由称为样式向量的独立输入来控制生成图像的样式。我们还学习了如何通过将样式从一张图像切换到另一张来操作不同图像的样式。

现在我们已经了解了如何利用预训练的 StyleGAN2 模型来执行样式迁移,在下一节中,我们将利用预训练的超分辨率 GAN 模型生成高分辨率图像。

超分辨率GAN

在上一节中,我们看到了一个场景,我们利用预训练的 StyleGAN 生成给定样式的图像。在本节中,我们将更进一步,了解如何利用预训练模型来执行图像超分辨率。在将其应用于图像之前,我们将了解超分辨率 GAN 模型的架构。

首先,我们将了解为什么 GAN 是超分辨率任务的良好解决方案。想象一个场景,给您一张图像并要求您提高其分辨率。直观地说,您会考虑使用各种插值技术来执行超分辨率。这是一个示例低分辨率图像以及各种技术的输出(图像来源:https ://arxiv.org/pdf/1609.04802.pdf ):

 从上图中我们可以看出,在从低分辨率(原始图像的 4 倍缩小图像)重建图像时,双三次插值等传统插值技术并没有太大帮助。

虽然基于 ResNet 的超分辨率 UNet 可以在这种情况下派上用场,但 GAN 可能更有用,因为它们可以模拟人类感知。鉴于判别器知道典型的超分辨率图像的外观,它可以检测生成的图像具有不一定看起来像高分辨率图像的属性的场景。

随着对 GAN 的超分辨率需求的确立,让我们了解并利用预训练模型。

建筑学

虽然可以从头开始编码和训练超分辨率 GAN,但我们将尽可能利用预训练模型。因此,在本节中,我们将利用由 Christian Ledig 及其团队开发并发表在题为“使用生成对抗网络的 Photo-Realistic Single Image Super-Resolution ”的论文中的模型。

SRGAN的架构如下(图片来源:https ://arxiv.org/pdf/1609.04802.pdf ):

从上图中,我们看到判别器将高分辨率图像作为输入来训练预测图像是高分辨率图像还是低分辨率图像的模型。生成器网络将低分辨率图像作为输入,生成高分辨率图像。在训练模型时,内容损失和对抗性损失都被最小化了。要详细了解模型训练的细节以及比较用于生成高分辨率图像的各种技术的结果,我们建议您阅读本文。

凭借对模型构建方式的高层次理解,我们将编写利用预训练 SRGAN 模型将低分辨率图像转换为高分辨率图像的方法。

编码 SRGAN

以下是加载预训练的 SRGAN 并进行预测的步骤:

1.导入相关包和预训练模型:

import os
if not os.path.exists('srgan.pth.tar'):
    !pip install -q torch_snippets
    !wget -q https://raw.githubusercontent.com/sizhky/a-PyTorch-Tutorial-to-Super-Resolution/master/models.py -O models.py
    from pydrive.auth import GoogleAuth
    from pydrive.drive import GoogleDrive
    from google.colab import auth
    from oauth2client.client import GoogleCredentials

    auth.authenticate_user()
    gauth = GoogleAuth()
    gauth.credentials = \
            GoogleCredentials.get_application_default()
    drive = GoogleDrive(gauth)

    downloaded = drive.CreateFile({'id': \
                    '1_PJ1Uimbr0xrPjE8U3Q_bG7XycGgsbVo'})
    downloaded.GetContentFile('srgan.pth.tar')
    from torch_snippets import *
    device = 'cuda' if torch.cuda.is_available() else 'cpu'

2.加载模型:

model = torch.load('srgan.pth.tar', map_location='cpu')['generator'].to(device) 
model.eval()

3.获取要转换为高分辨率的图像:

!wget https://www.dropbox.com/s/nmzwu68nrl9j0lf/Hema6.JPG

4.定义函数preprocess和postprocess图像:

preprocess = T.Compose([
                T.ToTensor(),
                T.Normalize([0.485, 0.456, 0.406],
                            [0.229, 0.224, 0.225]),
                T.Lambda(lambda x: x.to(device))
            ])

postprocess = T.Compose([
                T.Lambda(lambda x: (x.cpu().detach()+1)/2),
                T.ToPILImage()
            ])

5.加载图像并对其进行预处理:

image = readPIL('Hema6.JPG') 
image.size 
# (260,181) 
image = image.resize((130,90)) 
im = preprocess(image)

请注意,在前面的代码中,我们对原始图像执行了额外的调整大小以进一步模糊图像,但这只是为了说明 - 因为当我们缩小图像时改进更加明显。

6.通过模型的加载model和postprocess输出传递预处理图像:

sr = model(im[None])[0]
sr = postprocess(sr)

7.绘制原始图像和高分辨率图像:

subplots([image, sr], nc=2, figsize=(10,10), \
         titles=['Original image','High resolution image'])

前面的代码产生以下输出:

 从上图中,我们可以看到高分辨率图像捕捉到了原始图像中模糊的细节。

请注意,如果原始图像模糊,则原始图像和高分辨率图像之间的对比度会很高。但是,如果原始图像不模糊,对比度不会那么高。我们鼓励您使用不同分辨率的图像。

概括

在本章中,我们学习了使用 Pix2Pix GAN 从给定轮廓生成图像。此外,我们了解了 CycleGAN 中用于将一类图像转换为另一类图像的各种损失函数。接下来,我们了解了 StyleGAN 如何帮助生成逼真的面孔,并根据生成器的训练方式将风格从一张图像复制到另一张图像。最后,我们了解了如何利用预训练的 SRGAN 模型生成高分辨率图像。

在下一章中,我们将转向学习如何基于很少(通常少于 20 张)图像训练图像分类模型。

问题

  1. 为什么我们需要 Pix2Pix GAN,其中像 UNet 这样的监督学习算法可以用来从轮廓生成图像?
  2. 为什么我们需要在 CycleGAN 中针对三种不同的损失函数进行优化?
  3. ProgressiveGAN 中利用的技巧如何帮助构建 StyleGAN?
  4. 我们如何识别与给定自定义图像相对应的潜在向量?
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Sonhhxg_柒

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

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

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

打赏作者

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

抵扣说明:

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

余额充值