当前位置: 首页 > news >正文

神经风格迁移(Neural Style Transfer)

神经风格迁移(Neural Style Transfer)

    • 0. 前言
    • 1. 神经风格迁移
    • 2. 使用 VGG 提取特征
    • 3. VGG 预处理
    • 4. 重建内容
    • 5. 用 Gram 矩阵重建风格
    • 6. 执行神经风格迁移

0. 前言

变分自编码器 (Variational Autoencoder, VAE)和生成对抗网络 (Generative Adversarial Network, GAN) 等生成模型非常适合生成逼真的图像。但是我们对潜编码了解得很少,更不用说如何在图像生成方面控制它们了。研究人员开始探索除像素分布外还能更好地表示图像的方法,发现图像可以分解为内容和风格。内容描述了图像中的构图,例如图像中间的一栋高楼。另一方面,风格是指图片的细节,例如墙壁的砖块或石材纹理或屋顶的颜色。在一天的不同时间显示同一栋建筑物的图像具有不同的色调和亮度,可以被视为具有相同的内容但风格不同。在本节中,我们将介绍神经风格迁移 (Neural Style Transfer),以迁移图像的艺术风格。

1. 神经风格迁移

当卷积神经网络 (convolutional neural networks, CNN) 在 ImageNet 图像分类竞赛中胜过其他算法时,人们开始意识到它的潜力,并开始探索将其用于其他计算机视觉任务。在 Gatys 等人于 2015 年发表的《A Neural Algorithm of Artistic Style》论文中,演示了使用 CNN 将一幅图像的艺术风格转移到另一幅图像的方式:

风格迁移

与大多数需要大量训练数据的深度学习模型不同,神经风格迁移仅需要两个图像-内容图像和风格图像。我们可以使用经过训练的 CNN (例如 VGG) 将风格从风格图像迁移到内容图像上。如上图所示,A 是内容图像,B–D 是风格图像和风格化图像。
接下来,我们将从如何使用 CNN 提取图像特征入手,详细探讨有关如何实现神经风格迁移。

2. 使用 VGG 提取特征

分类 CNN 可以分为两部分。第一部分称为特征提取器 (feature extractor),主要由卷积层组成。后一部分由几个全连接层组成,输出类别分数,称为分类器头 (classifier head)。同时,在 ImageNet 上为分类任务预先训练的 CNN 也可以用于其他任务。
例如,如果要为只有 10 个类别而不是 ImageNet1000 个类别的其他数据集创建分类 CNN,则可以保留特征提取器,而只用一个新的分类器替换掉原分类器头。这就是迁移学习 (transfer learning),我们可以转移或重用一些学到的知识到新的网络或应用中。许多用于计算机视觉任务(包括目标检测 (object detection) 和姿势估计 (pose estimation) )的深度神经网络都包括特征提取器,可以重用权重也可以从头开始训练。
CNN 中,随着对输出的深入了解,与详细像素值相比,它越来越了解图像内容的表示形式。为了更好地理解这一点,我们将构建一个网络来重建网络层看到的图像。图像重建的两个步骤如下:

  1. 通过 CNN 向前计算图像以提取特征
  2. 使用随机初始化的输入,我们训练输入,以便其重新创建与步骤 1 中的参考特征最匹配的特征

在正常的网络训练中,输入图像是固定的,并且使用反向传播的梯度来更新网络权重。在神经风格迁移中,所有网络层都被冻结,而我们使用梯度来修改输入。原始论文使用的是 VGG19,可以使用 Keras 中的预训练模型。VGG 的特征提取器部分由五个块组成,每个块的末尾都有一个下采样。每个块都有 24 个卷积层,整个 VGG19 具有 16 个卷积层和 3 个全连接层,因此 VGG19 中的数字 19 代表 19 个可训练权重的层。下图显示了不同的 VGG 配置:

VGG不同配置

接下来,将使用一个更简单的代码来实现内容重构,同时将进行扩展以执行风格。使用预训练的 VGG 提取 block4_conv2 的输出层:

vgg = tf.keras.applications.VGG19(include_top=False, weights='imagenet')
content_layers = ['block4_conv2']
content_outputs = [vgg.get_layer(x).output for x in content_layers]
model = Model(vgg.input, content_outputs)

预训练的 Keras CNN 模型分为两部分。底部由卷积层组成,通常称为特征提取器,而顶部是由全连接层组成的分类器头。因为我们只想提取特征而不关心分类器,所以在实例化 VGG 模型时设置 include_top = False

3. VGG 预处理

Keras 预训练模型期望输入图像的 BGR 范围为 [0, 255]。因此,第一步是反转颜色通道,以将 RGB 转换为 BGRVGG 对不同的颜色通道使用不同的平均值。在 preprocess_input() 内部,分别为 BGR 通道的像素值减去 103.939116.779123.68 的值。
执行前向计算,在对图像进行处理之前先对其进行预处理,然后再将其输入模型以返回内容特征。然后,我们提取内容特征并将其用作我们的目标:

def extract_features(image):image = tf.keras.applications.vgg19.preprocess_input(image *255.)content_ref = model(image)return content_ref
content_image = tf.reverse(content_image, axis=[-1])
content_ref = extract_features(content_image)

需要注意的是,由于图像已标准化为 [0., 1.],因此我们需要通过将其乘以 255 将其恢复为 [0., 255.]。然后创建一个随机初始化的输入,该输入也将成为风格化的图像:

image = tf.Variable(tf.random.normal( shape=content_image.shape))

接下来,我们将使用反向传播从内容特征中重建图像。

4. 重建内容

在训练步骤中,我们将图像馈送到冻结的 VGG 中以提取内容特征,然后使用 L2 损失针对目标内容特征进行度量。自定义损失函数用于计算每个特征层的 L2 损失:

def calc_loss(y_true, y_pred):loss = [tf.reduce_sum((x-y)**2) for x, y in zip(y_pred, y_true)]return tf.reduce_mean(loss)

使用 tf.GradientTape() 计算梯度。在正常的神经网络训练中,将梯度应用于可训练变量,即神经网络的权重。但是,在神经风格迁移中,将梯度应用于图像。之后,将图像值剪裁在 [0., 1.] 之间:

for i in range(1,steps+1):with tf.GradientTape() as tape:content_features = self.extract_features(image)loss = calc_loss(content_features, content_ref)grad = tape.gradient(loss, image)optimizer.apply_gradients([(grad, image)])image.assign(tf.clip_by_value(image, 0., 1.))

使用 block1_1 重建图像,训练了 2000 步后,得到重构后的内容图像:

重构后的内容图像

使用 block4_1 重建图像,训练了 2000 步后,得到重构后的内容图像:

重构后的内容图像

可以看到使用层 block4_1 时,开始丢失细节,例如树叶的形状。当我们使用 block5_1 时,我们看到几乎所有细节都消失了,并充满了一些随机噪声:

重构后的内容图像

如果我们仔细观察,树叶的结构和边缘仍然得到保留,并在其应有的位置。现在,我们已经提取了内容,提取内容特征后,下一步是提取风格特征。

5. 用 Gram 矩阵重建风格

正如我们在风格重建中所看到的那样,特征图(尤其是前几层)既包含风格又包含内容。我们可以使用 Gram 矩阵从图像中提取风格表示,该矩阵可计算不同卷积核响应之间的相关性。假设卷积层 1 的激活形状为 (H, W, C),其中 HW 是空间尺寸,C 是通道数,等于卷积核的数量,每个卷积核检测不同的图像特征。
当具有一些共同的特征(例如颜色和边缘)时,则认为它们具有相同的纹理。例如,如果我们将草地的图像输入到卷积层中,则检测垂直线和绿色的滤波器将在其特征图中产生更大的响应。因此,我们可以使用特征图之间的相关性来表示图像中的纹理。
要通过形状为 (H, W, C) 的激活来创建 Gram 矩阵,我们首先将其重塑为 C 个向量。每个向量都是大小为 H×W 的一维特征图。对 C 个向量执行点积运算,以获得对称的 C×C Gram 矩阵。在 TensorFlow 中计算 Gram 矩阵的详细步骤如下:

  1. 使用 tf.squeeze() 将批尺寸 (1, H, W, C) 修改为 (H, W, C)
  2. 转置张量以将形状从 (H, W, C) 转换为 (C, H, W)
  3. 将最后两个维度展平为 (C, H×W)
  4. 执行特征的点积以创建形状为 (C, C)Gram 矩阵
  5. 通过将矩阵除以每个展平的特征图中的元素数 (H×W) 进行归一化

通过一次卷积层激活来计算 Gram 矩阵:

def gram_matrix(x):x = tf.transpose(tf.squeeze(x), (2,0,1));x = tf.keras.backend.batch_flatten(x)num_points = x.shape[-1]gram = tf.linalg.matmul(x, tf.transpose(x))/num_pointsreturn gram

我们可以使用此函数为指定的风格层的每个 VGG 层获取 Gram 矩阵。然后,我们对来自目标图像和参考图像的 Gram 矩阵使用 L2 损失。损失函数与内容重建相同。创建 Gram 矩阵列表:

def extract_features(image):image = tf.keras.applications.vgg19.preprocess_input(image *255.)styles = self.model(image)styles = [self.gram_matrix(s) for s in styles]return styles

以下图像是从不同 VGG 图层的风格特征中重构得到的:

风格特征重构
在从 block1_1 重建的风格图像中,内容信息完全消失,仅显示高频纹理细节。较高的层 block3_1,显示了一些卷曲的形状:

风格特征重构
这些形状捕获了输入图像中风格的较高层次。Gram 矩阵的损失函数是平方误差之和而不是均方误差。因此,层次风格较高的层具有较高的固有权重。这允许传输更高级的风格表示形式,例如笔触。如果使用均方误差,则低层次的风格特征(例如纹理)将在视觉上更加突出,并且可能看起来像高频噪声。

6. 执行神经风格迁移

接下来,我们可以合并内容和风格重构中的代码,以执行神经风格迁移。
我们首先创建一个模型,该模型提取两个特征块,一个用于内容,另一个用于风格。内容重建使用 block5_conv1 层,从 block1_conv1block5_conv1 的五层用于捕获来自不同层次结构的风格:

vgg = tf.keras.applications.VGG19(include_top=False,   weights='imagenet')
default_content_layers = ['block5_conv1']
default_style_layers = ['block1_conv1','block2_conv1','block3_conv1','block4_conv1','block5_conv1']
content_layers = content_layers if content_layers else default_content_layers
style_layers = style_layers if style_layers else default_style_layers
self.content_outputs = [vgg.get_layer(x).output for x in content_layers]
self.style_outputs = [vgg.get_layer(x).output for x in style_layers]
self.model = Model(vgg.input, [self.content_outputs, self.style_outputs])

在训练循环开始之前,我们从各自的图像中提取内容和风格特征以用作目标。虽然我们可以使用随机初始化的输入来进行内容和风格重建,但从内容图像开始进行训练会更快:

content_ref, _ = self.extract_features(content_image)
_, style_ref = self.extract_features(style_image)

然后,计算并添加内容和风格损失:

def train_step(self, image, content_ref, style_ref):with tf.GradientTape() as tape:content_features, style_features = self.extract_features(image)content_loss = self.content_weight * self.calc_loss(content_ref, content_features)style_loss = self.style_weight*self.calc_loss( style_ref, style_features)loss = content_loss + style_lossgrad = tape.gradient(loss, image)self.optimizer.apply_gradients([(grad, image)])image.assign(tf.clip_by_value(image, 0., 1.))return content_loss, style_loss  

以下是使用不同权重和内容层生成的风格化图像:

生成结果

我们可以通过更改权重和层以创建所需的风格。

http://www.dtcms.com/a/392375.html

相关文章:

  • Chromium 138 编译指南 Ubuntu 篇:源码获取与版本管理(四)
  • R 语言入门实战|第九章 循环与模拟:用自动化任务解锁数据科学概率思维
  • [论文阅读] 人工智能 + 软件工程 | 4907个用户故事验证!SEEAgent:解决敏捷估计“黑箱+不协作”的终极方案
  • 鸿蒙Next ArkTS卡片开发指南:从入门到实战
  • 【绕过disable_function】
  • 使用云手机运行手游的注意事项有哪些?
  • 【数据结构】利用堆解决 TopK 问题
  • 2025陇剑杯现场检测
  • openharmony之充电空闲状态定制开发
  • 【开题答辩全过程】以 python的线上订餐系统为例,包含答辩的问题和答案
  • (附源码)基于Spring Boot的校园心理健康服务平台的设计与实现
  • 微信小程序开发教程(十八)
  • 寰宇光锥舟架构图
  • Spring Bean生命周期全面解析
  • [vibe code追踪] 侧边栏UI管理器 | showSidebarContent
  • 嵌入式ARM架构学习9——IIC
  • 多线程——线程安全的练习加感悟
  • 使用 TwelveLabs 的 Marengo 视频嵌入模型与 Amazon Bedrock 和 Elasticsearch
  • Windows 11 下 Notepad++ 等应用无法启动问题排查修复
  • 面向口齿不清者的语音识别新突破:用大模型拯救“听不懂”的声音
  • 服装企业优化信息化管理系统的最佳软件选择
  • 多阶段构建镜像
  • 推荐一个开源服务器一键自动重装系统脚本:reinstall
  • 【C++进阶】C++11 的新特性 | lambda | 包装器
  • 2.【QT 5.12.12 安装 Windows 版本】
  • Rust_2025:阶段1:day6.3 macro
  • 【Qt开发】输入类控件(一)-> QLineEdit
  • python10——组合数据类型(集合)
  • 分布式专题——14 RabbitMQ之集群实战
  • WEEX唯客的多维度安全守护