1. 磐创AI首页
  2. 机器学习
  3. TensorFlowNews

图像风格迁移实战(附Python实战)


图像风格迁移实战(附Python实战)


作者 | 小韩

编辑 | 安可

出品 | 磐创AI技术团队

在今天的文章中,我们会建立一个很棒的风格迁移网络。为了做到这一点,我们需要深入地了解 CNN 和卷积层的工作原理。在文章结束时,你将会创建一个风格迁移网络,这个网络能够在保留原始图像的同时将新样式应用到它上面。

图像风格迁移实战(附Python实战)
波士顿天际线和梵高的繁星之夜混合效果

风格迁移

在开始之前,先明确一下我们的目标。

我们将风格迁移定义为改变图像风格同时保留它的内容的过程

给定一张输入图像和样式图像,我们就可以得到既有原始内容又有新样式的输出图像。在 Leon A. Gaty 的论文 A Neural Algorithm of Artistic Style 中有所描述。

输入图像 + 样式图像 -> 输出图像(风格化)

工作方式

  1. 准备输入图像和风格图像并将它们调整为相同的大小。

  2. 加载预训练的卷积神经网络(VGG16)。

  3. 区分负责样式的卷积(基本形状,颜色等)和负责内容的卷积(特定于图像的特征),将卷积分开可以单独地处理内容和样式。

  4. 优化问题,也就是最小化:

    • 内容损失(输入和输出图像之间的距离 – 尽力保留内容)

    • 风格损失(风格和输出图像之间的距离 – 尽力应用新风格)

    • 总变差损失(正则化 – 对输出图像进行去噪的空间平滑度)

  5. 最后设置梯度并使用 L-BFGS 算法进行优化。

实现

输入

1# 旧金山
2san_francisco_image_path = "https://www.economist.com/sites/default/files/images/print-edition/20180602_USP001_0.jpg"
3
4# 输入可视化
5input_image = Image.open(BytesIO(requests.get(san_francisco_image_path).content))
6input_image = input_image.resize((IMAGE_WIDTH, IMAGE_HEIGHT))
7input_image.save(input_image_path)
8input_image

这就是旧金山的天际线

图像风格迁移实战(附Python实战)

风格

然后定义一个风格图像。

1# Tytus Brzozowski
2tytus_image_path = "http://meetingbenches.com/wp-content/flagallery/tytus-brzozowski-polish-architect-and-watercolorist-a-fairy-tale-in-warsaw/tytus_brzozowski_13.jpg"
3
4# 风格图像可视化
5style_image = Image.open(BytesIO(requests.get(tytus_image_path).content))
6style_image = style_image.resize((IMAGE_WIDTH, IMAGE_HEIGHT))
7style_image.save(style_image_path)
8style_image

这是Tytus Brzozowski的景色。

图像风格迁移实战(附Python实战)

预处理

接下来对两个图像调整大小和均值归一化。

 1input_image_array = np.asarray(input_image, dtype="float32")
2input_image_array = np.expand_dims(input_image_array, axis=0)
3input_image_array[:, :, :, 0] -= IMAGENET_MEAN_RGB_VALUES[2]
4input_image_array[:, :, :, 1] -= IMAGENET_MEAN_RGB_VALUES[1]
5input_image_array[:, :, :, 2] -= IMAGENET_MEAN_RGB_VALUES[0]
6input_image_array = input_image_array[:, :, :, ::-1]
7
8style_image_array = np.asarray(style_image, dtype="float32")
9style_image_array = np.expand_dims(style_image_array, axis=0)
10style_image_array[:, :, :, 0] -= IMAGENET_MEAN_RGB_VALUES[2]
11style_image_array[:, :, :, 1] -= IMAGENET_MEAN_RGB_VALUES[1]
12style_image_array[:, :, :, 2] -= IMAGENET_MEAN_RGB_VALUES[0]
13style_image_array = style_image_array[:, :, :, ::-1]

CNN模型

随着图像准备完成,我们可以继续建立 CNN 模型。

1# 模型
2input_image = backend.variable(input_image_array)
3style_image = backend.variable(style_image_array)
4combination_image = backend.placeholder((1, IMAGE_HEIGHT, IMAGE_SIZE, 3))
5
6input_tensor = backend.concatenate([input_image,style_image,combination_image], axis=0)
7model = VGG16(input_tensor=input_tensor, include_top=False)

在这个项目中,我们将使用预先训练的VGG16模型,如下所示。

图像风格迁移实战(附Python实战)
VGG16架构

我们不使用全连接(蓝色)和 softmax (黄色),因为这里不需要分类器。我们仅使用特征提取器,即卷积(黑色)和最大池(红色)。

下面是在ImageNet数据集上训练的 VGG16 的图像特征。

图像风格迁移实战(附Python实战)
VGG16 特征

我们不会可视化每个CNN,对于内容,我们应该选择 block2_conv2 ,样式应该选择 [block1_conv2,block2_conv2,block3_conv3,block4_conv3,block5_conv3]

虽然这种组合被证明是有效的,但也可以尝试不同的卷积层。

内容损失

定义了CNN模型后,还需要定义一个内容损失函数。为了保留图像原始内容,我们将最小化输入图像和输出图像之间的距离。

 1def content_loss(content, combination):
2    return backend.sum(backend.square(combination - content))
3
4layers = dict([(layer.name, layer.output) for layer in model.layers])
5
6content_layer = "block2_conv2"
7layer_features = layers[content_layer]
8content_image_features = layer_features[0, :, :, :]
9combination_features = layer_features[2, :, :, :]
10
11loss = backend.variable(0.)
12loss += CONTENT_WEIGHT * content_loss(content_image_features,
13                                      combination_features)

样式损失

与内容损失类似,样式损失也被定义为两个图像之间的距离。但是,为了应用新风格,样式损失被定义为风格图像和输出图像之间的距离。

 1def gram_matrix(x):
2    features = backend.batch_flatten(backend.permute_dimensions(x, (201)))
3    gram = backend.dot(features, backend.transpose(features))
4    return gram
5
6def compute_style_loss(style, combination):
7    style = gram_matrix(style)
8    combination = gram_matrix(combination)
9    size = IMAGE_HEIGHT * IMAGE_WIDTH
10    return backend.sum(backend.square(style - combination)) / (4. * (CHANNELS ** 2) * (size ** 2))
11
12style_layers = ["block1_conv2""block2_conv2""block3_conv3""block4_conv3""block5_conv3"]
13for layer_name in style_layers:
14    layer_features = layers[layer_name]
15    style_features = layer_features[1, :, :, :]
16    combination_features = layer_features[2, :, :, :]