Keras深度学习实战(21)——神经风格迁移详解

0. 前言

DeepDream 图像生成算法的学习中,我们通过修改像素值试图使神经网络中卷积核的激活最大化。但是,这并不具备灵活生成指定风格图像的功能,因此,本节我们继续学习神经风格迁移算法。在神经风格迁移中,我们需要一个内容图像和一个风格图像,我们的目标是保持内容图像的同时融和风格图像中的风格样式,以组合这两个图像生成全新图像。

1. 神经风格迁移原理

在进行实战前,我们首先了解神经风格迁移的相关原理。我们以与 DeepDream 算法类似的方式修改原始图像,但是,不同的是,在神经风格迁移模型中需要将损失值分为内容损失和风格损失。内容损失用于衡量生成的图像与内容图像之间的差异程度。风格损失用于衡量风格图像与生成的图像之间的关联程度。
尽管上述损失可以根据图像间的差异进行计算,但实际上,我们使用图像在网络层中的激活而不是原始图像来计算损失值。例如,第 2 层的内容损失是内容图像和生成的图像通过第 2 层神经网络层得到的激活之间的平方差。
计算内容损失较为简单,接下来我们继续学习如何计算生成图像和风格图像之间的相似度。我们将使用一种称为 gram 矩阵的技术,gram 矩阵用于计算生成图像和风格图像之间的相似度,相似度的计算方法如下:

L G M ( S , G , l ) = 1 4 N l 2 M l 2 ∑ i j ( G M [ l ] ( S ) i j − G M [ l ] ( G ) i j ) 2 L_{GM}(S,G,l)=\frac1 {4N_l^2M_l^2}\sum_{ij}(GM[l](S)_{ij}-GM[l](G)_{ij})^2 LGM(S,G,l)=4Nl2Ml21ij(GM[l](S)ijGM[l](G)ij)2

其中 G M [ l ] GM[l] GM[l] 是风格图像 S S S 和生成图像 G G G 在第 l l l 层的 gram 矩阵值。gram 矩阵是通过将矩阵与其自身的转置矩阵相乘得到的。
计算得到风格损失和内容损失后,最终生成的修改图像是使总损失最小的图像,即令风格和内容损失的加权平均值最小。

2. 模型分析

在了解了神经风格迁移的基本原理之后,我们继续对模型的运行流程进行分析,主要通过以下步骤实现神经风格迁移:

  • 通过预训练的模型处理图像,并在预定义的网络层上提取图像特征
    • 将内容图像输入到预训练模型中,并在预定义的内容网络层上提取图像特征
    • 计算内容损失
    • 将风格图像输入到预训练模型中,并在预定义的风格网络层上提取图像特征,然后计算风格图像的 gram 矩阵值
  • 将生成图像传递给风格图像所经过的同样的网络层,并计算对应的 gram 矩阵值
  • 提取两个图像的 gram 矩阵值的平方差,得到的结果即为风格损失
  • 总损失为风格损失和内容损失的加权平均值
  • 根据损失修改输入图像,得到令总损失最小的图像即为最终的图像

3. 使用 Keras 实现神经风格迁移

了解了模型原理和运算流程后,在本节中,我们利用 Keras 实现神经风格迁移。

(1) 导入所需要的库,以及用于神经风格迁移的内容图像和风格图像:

from keras.applications import vgg19
from keras import backend as K
import numpy as np
import cv2
from PIL import Image
from matplotlib import pyplot as plt
import tensorflow as tf
tf.compat.v1.disable_eager_execution()

style_img = cv2.imread('Vincent_van_Gogh_779.jpg')
style_img = cv2.cvtColor(style_img, cv2.COLOR_BGR2RGB)
style_shape = style_img.shape

content_img = cv2.imread('3.jpeg')
content_img = cv2.cvtColor(content_img, cv2.COLOR_BGR2RGB)
content_shape = content_img.shape

(2) 为了便于理解神经风格迁移算法的效果,在生成图像前先查看风格和内容图像:

plt.subplot(211)
# 为了进行对比,将风格图像进行缩放,以与内容图像具有相同的尺寸
plt.imshow(cv2.resize(style_img, (content_shape[1], content_shape[0])))
plt.title('Style image')
plt.axis('off')
plt.subplot(212)
plt.imshow(content_img)
plt.title('Content image')
plt.axis('off')
plt.show()

风格图像与内容图像

(3) 初始化 VGG19 模型,以便获取输入图像在网络层中的特征输出:

from keras.applications import vgg19
model = vgg19.VGG19(include_top=False, weights='imagenet')

(4) 定义图像预处理和逆处理函数:

def preprocess_image(img):
    img = np.array(img).astype(float)
    img = np.expand_dims(img, axis=0)
    # 数据预处理
    img[:, :, :, 0] -= 103.939
    img[:, :, :, 1] -= 116.779
    img[:, :, :, 2] -= 123.68
    img = img[:, :, :, ::-1] / 255
    return img

def deprocess_image(x):
    x = x[:, :, :, ::-1] * 225
    x[:, :, :, 0] += 103.939
    x[:, :, :, 1] += 116.779
    x[:, :, :, 2] += 123.68
    x = np.clip(x, 0, 255).astype('uint8')[0]
    x = Image.fromarray(x).resize((content_shape[1], content_shape[0]))
    return x

(5) 使用定义的 preprocess_image 函数对内容图像进行预处理,并将其输入到模型中以提取 VGG19 模型 block3_conv4 层的特征值:

content_img = preprocess_image(content_img)

get_3rd_layer_output = K.function([model.layers[0].input],[model.get_layer('block3_conv4').output])
layer_output_base = get_3rd_layer_output([content_img])[0]

在以上代码中,我们定义了一个函数 get_3rd_layer_output 用于获取输入图像并提取预定义层的输出特征。

(6) 接下来,定义需要提取用于计算内容和风格损失的神经网络图层,并为每一网络层分配相应权重:

layer_contributions_content = {
    
    'block3_conv4': 0.1}

layer_contributions_style = {
    
    'block1_conv1':5,
                        'block2_conv1':10,
                        'block3_conv1':10,
                        'block4_conv1':15,
                        'block5_conv1':15}

在以上代码中,我们定义了用于计算内容和风格损失的网络层,并为这些网络分配了不同权重,用于计算总损失。

(7) 定义 gram 矩阵和风格损失函数:
我们首先定义函数 gram_matrix,用于计算输入的 gram 矩阵:

def gram_matrix(x):
    # 对图像进行展平
    features = K.batch_flatten(K.permute_dimensions(x, (2, 0 ,1)))
    # 计算特征矩阵和特征转置矩阵的点积
    gram = K.dot(features, K.transpose(features))
    return gram

在以下代码中,我们将按照“神经风格迁移原理”小节中介绍的风格损失方程的定义计算风格损失:

def style_loss(style, combination):
    S = gram_matrix(style)
    C = gram_matrix(combination)
    channels = 3
    size = content_img.shape[0] * content_img.shape[1]
    return K.sum(K.square(S - C)) / (4. * (pow(channels, 2)) * (pow(size, 2)))

(8) 初始化损失值函数,计算内容损失:

layer_dict = dict([(layer.name, layer) for layer in model.layers])
loss = K.variable(0.)

for layer_name in layer_contributions_content:
    coeff = layer_contributions_content[layer_name]
    activation = layer_dict[layer_name].output
    scaling = K.prod(K.cast(K.shape(activation), 'float32'))
    loss = loss + coeff * K.sum(K.square(activation - layer_output_base)) / scaling

在以上代码中,根据内容神经网络层 layer_contributions_content 的损失来更新损失值,layer_output_base 是将原始图像通过内容神经网络层时的输出。激活 activations (即基于修改后的图像提取到的特征)和 layer_output_base 之间的差异越大,则图像内容损失越大。
然后,计算风格损失:

for layer_name in layer_contributions_style:
    coeff = layer_contributions_style[layer_name]
    activation = layer_dict[layer_name].output
    scaling = K.prod(K.cast(K.shape(activation), 'float32'))
    style_layer_output = K.function([model.layers[0].input], [model.get_layer(layer_name).output])
    layer_output_style = style_layer_output([preprocess_image(style_img)])[0][0]
    loss = loss + style_loss(layer_output_style, activation[0])

在以上代码中,我们以与计算内容损失相同的方式计算风格损失,不同的是,风格损失的计算使用不同的神经网络层。

(9) 构建函数将输入图像映射到损失值和相应的梯度变化值:

input_image = model.input
grads = K.gradients(loss, input_image)[0]
grads /= K.maximum(K.mean(K.abs(grads)), 1e-7)
outputs = [loss, grads]
fetch_loss_and_grads = K.function([input_image], outputs)

def eval_loss_and_grads(img):
    outs = fetch_loss_and_grads([img])
    loss_value = outs[0]
    grad_values = outs[1]
    return loss_value, grad_values

以上代码以与 DeepDream 算法类似,使用以上代码可以获取损失和梯度变化值以生成风格迁移图像。

(9) 最后,在多个 epochs 内运行模型,观察生成的图像:

img = content_img.copy()

for i in range(2000):
    step=0.001
    loss_value, grad_values = eval_loss_and_grads(img)
    print('...Loss value at', i, ':', loss_value)
    img = img - step * grad_values
    if((i+1)%100 == 0):
        img2 = img.copy()
        img2 = deprocess_image(img2)
        plt.imshow(img2)
        plt.axis('off')
        plt.show()

使用以上代码生成的图像,得到内容图像和风格图像的融合后的风格迁移图片:

结果图像

可以通过选择不同的神经网络层来计算内容和风格损失,并为不同网络层分配不同的权重系数,观察生成图像的差别。

小结

使用神经风格迁移算法生成图像的核心思想与 DeepDream 算法类似,通过获取损失和梯度变化值以生成风格迁移图像,将内容图像和风格参考图像混合在一起。在本节中,首先介绍了神经风格迁移的核心思想与风格迁移图像的生成流程,然后利用 Keras 从零开始实现了神经风格迁移算法,可以通过修改模型中的超参数来生成不同观感的图像。

系列链接

Keras深度学习实战(1)——神经网络基础与模型训练过程详解
Keras深度学习实战(2)——使用Keras构建神经网络
Keras深度学习实战(3)——神经网络性能优化技术
Keras深度学习实战(4)——深度学习中常用激活函数和损失函数详解
Keras深度学习实战(5)——批归一化详解
Keras深度学习实战(6)——深度学习过拟合问题及解决方法
Keras深度学习实战(7)——卷积神经网络详解与实现
Keras深度学习实战(8)——使用数据增强提高神经网络性能
Keras深度学习实战(9)——卷积神经网络的局限性
Keras深度学习实战(10)——迁移学习详解
Keras深度学习实战(11)——可视化神经网络中间层输出
Keras深度学习实战(12)——面部特征点检测
Keras深度学习实战(13)——目标检测基础详解
Keras深度学习实战(14)——从零开始实现R-CNN目标检测
Keras深度学习实战(15)——从零开始实现YOLO目标检测
Keras深度学习实战(16)——自编码器详解
Keras深度学习实战(17)——使用U-Net架构进行图像分割
Keras深度学习实战(18)——语义分割详解
Keras深度学习实战(19)——使用对抗攻击生成可欺骗神经网络的图像
Keras深度学习实战(20)——DeepDream模型详解

猜你喜欢

转载自blog.csdn.net/LOVEmy134611/article/details/126187368