图像风格迁移及代码实现

图像风格迁移其实非常好理解,就是将一张图像的“风格”(风格图像)迁移至另外一张图像(内容图像),但是这所谓的另外一张图像只是在“风格”上与之前有所不同,图像的“内容”仍要与之前相同。Luan et al. and Gatys et al. 的工作都是利用VGGNet19作为该项任务的backbone,由于VGGNet19是一种近似“金字塔”型结构,所以随着卷积操作的加深,feature maps的感受野越来越大,提取到的图像特征从局部扩展到了全局。我们为了避免合成的图像过多地保留内容信息,选取VGGNet19中位于金字塔顶部的卷积层作为内容层。整个训练过程为将生成图像初始化为内容图像,每次循环分别抽取生成图像和内容图像的内容特征,计算mse并且使之最小化,同时抽取生成图像和风格图像的样式特征,计算mse并且使之最小化。这里注意损失函数的写法:
在这里插入图片描述
总损失由两部分组成:内容损失和样式损失。内容损失即为生成图像和内容图像对应特征图的均方误差,但是样式损失需要分别计算生成图像和内容图像的格拉姆矩阵再做均方误差。另外, α \alpha α β \beta β分别为内容损失和样式损失的各项权重, Γ \Gamma Γ为样式损失的惩罚系数。我通过实发现 β \beta β Γ \Gamma Γ应该取的值大些,使得样式损失被尽可能地“惩罚”,即“放大”样式损失。

import torch
import numpy as np
from PIL import Image
from torchvision.models import vgg19
from torchvision.transforms import transforms
import torch.nn as nn
import matplotlib.pyplot as plt
from torch.nn.functional import mse_loss
from torch.autograd import Variable

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# 预处理:大小裁剪、转为张量、归一化
def preprocess(img_shape):
    transform = transforms.Compose([
        transforms.Resize(img_shape),
        transforms.ToTensor(),
        transforms.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225))
    ])
    return transform

class VGGNet19(nn.Module):
    def __init__(self):
        super(VGGNet19, self).__init__()
        self.vggnet19 = vgg19(pretrained=False)
        self.vggnet19.load_state_dict(torch.load('./vgg19-dcbb9e9d.pth'))
        self.content_layers = [25]
        self.style_layers = [0, 5, 10, 19, 28]

    def forward(self, x):
        content_features = []
        style_features = []
        for name, module in self.vggnet19.features._modules.items():
            x = module(x)
            if int(name) in self.content_layers:
                content_features.append(x)
            if int(name) in self.style_layers:
                style_features.append(x)
        return content_features, style_features

class GenerateImage(nn.Module):
    def __init__(self, img_shape):
        super(GenerateImage, self).__init__()
        self.weight = torch.nn.Parameter(torch.rand(*img_shape))

    def forward(self):
        return self.weight

# 初始化生成图像为内容图像
def generate_inits(content, device, lr):
    g_img = GenerateImage(content.shape).to(device)
    g_img.weight.data = content.data
    optimizer = torch.optim.Adam(g_img.parameters(), lr=lr, betas=(0.9, 0.999), eps=1e-08, weight_decay=0)
    return g_img(), optimizer

# 计算格拉姆矩阵
def gramMatrix(x):
    _, c, h, w = x.shape
    x = x.view(c, h*w)
    return torch.matmul(x, x.t()) / (c*h*w)

# 计算总损失:内容损失+样式损失
def compute_loss(content_g, content_y, style_g, style_y, content_weight, style_weight, gamma):
    contentlosses = [mse_loss(g, y)*content_weight for g, y in zip(content_g, content_y)]
    stylelosses = [mse_loss(gramMatrix(g), gramMatrix(y))*style_weight for g, y in zip(style_g, style_y)]
    total_loss = sum(contentlosses) + gamma * sum(stylelosses)
    return contentlosses, stylelosses, total_loss

# 用于可视化的后处理
def postprocess(img_tensor):
    rgb_mean = np.array([0.485, 0.456, 0.406])
    rgb_std = np.array([0.229, 0.224, 0.225])
    inv_normalize = transforms.Normalize(
        mean=-rgb_mean/rgb_std,
        std=1/rgb_std)
    to_PIL_image = transforms.ToPILImage()
    return to_PIL_image(inv_normalize(img_tensor[0].detach().cpu()).clamp(0, 1))

def train(lr, epoch_num, c_path, s_path, img_shape):
    ipt = Image.open(c_path)
    syl = Image.open(s_path)
    transform = preprocess(img_shape)
    content, style = transform(ipt).unsqueeze(0), transform(syl).unsqueeze(0)
    net = VGGNet19()
    net.to(device).eval()

    content = content.type(torch.FloatTensor)
    style = style.type(torch.FloatTensor)
    if torch.cuda.is_available():
        content, style = Variable(content.cuda(), requires_grad=False), Variable(style.cuda(), requires_grad=False)
    else:
        content, style = Variable(content, requires_grad=False), Variable(style, requires_grad=False)

    icontent, istyle = net(content)
    scontent, sstyle = net(style)

    input, optimizer = generate_inits(content, device, lr)

    for epoch in range(epoch_num+1):

        gcontent, gstyle = net(input)
        contentlosses, stylelosses, total_loss = compute_loss(gcontent, icontent, gstyle, sstyle, 1, 1e3, 1e2)

        optimizer.zero_grad()
        total_loss.backward(retain_graph=True)
        optimizer.step()

        print("[epoch: %3d/%3d] content loss: %3f style loss: %3f total loss: %3f" % (epoch, epoch_num, sum(contentlosses).item(), sum(stylelosses).item(), total_loss))

        if epoch % 100 == 0 and epoch != 0:
            # plt.imshow(postprocess(input))
            # plt.axis('off')
            # plt.show()

            torch.save(net.state_dict(), "itr_%d_total_loss_%3f.pth" % (epoch, total_loss))


if __name__ == "__main__":
    train(0.01, 10000, './content.jpg', './s.jpg', (500, 700)

在这里插入图片描述
在这里插入图片描述
内容图像、风格图像和生成图像(第10000次迭代的可视化)分别如上图所示,并且代码实现是Gatys et al.的工作。

  • 5
    点赞
  • 88
    收藏
    觉得还不错? 一键收藏
  • 15
    评论
### 回答1: VGG19风格迁移代码是一种用于将图像的内容与风格进行分离,并将两者合成以创建新图像的算法。该算法基于深度卷积神经网络VGG19,它是一种经典的视觉感知模型,用于图像分类和识别任务。 在实现VGG19风格迁移代码时,我们需要进行以下步骤: 1. 导入相关的Python库和模块,例如tensorflow、opencv和numpy等。 2. 加载VGG19模型的权重文件,以便使用VGG19进行图像特征提取。 3. 定义图片的内容损失函数,该函数用于衡量生成图像与原始图像之间的内容相似度。 4. 定义图片的风格损失函数,该函数用于衡量生成图像与目标风格图像之间的风格相似度。 5. 定义总体损失函数,该函数将内容损失和风格损失加权组合在一起,以平衡两者的影响。 6. 使用优化算法,如梯度下降法,来最小化总体损失函数,从而更新生成图像的像素值。 7. 重复步骤6,直到生成的图像与原始图像在内容和风格上都达到满意的程度。 需要注意的是,VGG19风格迁移代码是一种较为复杂和计算密集的算法,可能需要较长的训练时间和高性能的计算设备。因此,在实际应用,可以使用预训练的VGG19模型,以加快风格迁移的速度。 这就是VGG19风格迁移代码的一般步骤和流程。通过这种方法,我们可以将不同图像的内容与风格进行有机地融合,从而创造出独特且具有艺术感的图像。 ### 回答2: VGG19是一种深度卷积神经网络模型,经常被用于图像分类任务。而风格迁移是一种计算机视觉的技术,它可以将一幅图像的风格迁移到另一幅图像上,从而创造出具有新风格的图像。 VGG19风格迁移代码实现的基本原理如下: 1. 导入VGG19模型的权重参数,这些参数在预训练模型已经通过大规模训练集进行了优化,可以提取出图像的不同特征。 2. 加载待进行风格迁移的两个图像,一个是内容图像,一个是风格图像,通过读取图像的像素值进行处理。 3. 对内容图像和风格图像分别进行预处理,将图像缩放至合适的大小,并通过减去均值来进行归一化。 4. 将内容图像和风格图像输入到VGG19网络,分别提取出内容特征和风格特征,这些特征通过网络的不同层来表示不同等级和抽象程度的特征信息。 5. 使用内容图像的特征与风格图像的特征计算损失函数,通过最小化这个损失函数来求解风格迁移的目标图像。 6. 通过梯度下降等优化算法,对目标图像进行迭代优化,不断更新图像的像素值,使得目标图像的内容与内容图像的特征相似,同时与风格图像的特征相匹配。 7. 最后得到的目标图像即为风格迁移后的图像。 这是简单概括了VGG19风格迁移代码的运行过程。实际使用时,还需要在代码设置合适的超参数、学习率,以及选择不同层的特征来表示风格等。这是一个复杂的计算过程,需要一定的计算资源和训练时间。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值