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

点击上方“小白学视觉”,选择加"星标"或“置顶

重磅干货,第一时间送达

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

39d77453df64f60747038dbe64d1ac8a.jpeg
波士顿天际线和梵高的繁星之夜混合效果

风格迁移

 

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

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

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

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

工作方式

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

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

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

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

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

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

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

最后设置梯度并使用 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

这就是旧金山的天际线

08c6ffd9141c46e87ae3263c3374c507.jpeg

风格

然后定义一个风格图像。

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的景色。

286517b3455b1bcdcb2fdd4541aeb81e.jpeg

预处理

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

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模型,如下所示。

b846afac57fb313fc8c3cb28813008ba.jpeg
VGG16架构

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

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

b16725f8cc478ee4fcb21cbdb9c2566a.jpeg
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, (2, 0, 1)))
 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, :, :, :]
17    style_loss = compute_style_loss(style_features, combination_features)
18    loss += (STYLE_WEIGHT / len(style_layers)) * style_loss

总变化损失

最后定义一个总变化损失,它作为一个空间平滑器来规范图像并防止去噪。

1def total_variation_loss(x):
2    a = backend.square(x[:, :IMAGE_HEIGHT-1, :IMAGE_WIDTH-1, :] - x[:, 1:, :IMAGE_WIDTH-1, :])
3    b = backend.square(x[:, :IMAGE_HEIGHT-1, :IMAGE_WIDTH-1, :] - x[:, :IMAGE_HEIGHT-1, 1:, :])
4    return backend.sum(backend.pow(a + b, TOTAL_VARIATION_LOSS_FACTOR))
5
6loss += TOTAL_VARIATION_WEIGHT * total_variation_loss(combination_image)

优化 - 损失和梯度

设置了内容损失,样式损失和总变化损失之后,就可以将风格转移过程转化为优化问题,最大限度地减少全局损失(内容,风格和总变化损失的组合) 。

在每次迭代中,我们将创建一个输出图像,以便最小化相应像素输出和输入/样式之间的距离(差异)。

1outputs = [loss]
 2outputs += backend.gradients(loss, combination_image)
 3
 4def evaluate_loss_and_gradients(x):
 5    x = x.reshape((1, IMAGE_HEIGHT, IMAGE_WIDTH, CHANNELS))
 6    outs = backend.function([combination_image], outputs)([x])
 7    loss = outs[0]
 8    gradients = outs[1].flatten().astype("float64")
 9    return loss, gradients
10
11class Evaluator:
12
13    def loss(self, x):
14        loss, gradients = evaluate_loss_and_gradients(x)
15        self._gradients = gradients
16        return loss
17
18    def gradients(self, x):
19        return self._gradients
20
21evaluator = Evaluator()
1bc00c4f450256d08ad119b987db06e8.gif
梯度下降可视化

结果

最后,使用 L-BFGS 算法进行优化并可视化结果。

1x = np.random.uniform(0, 255, (1, IMAGE_HEIGHT, IMAGE_WIDTH, 3)) - 128.
 2
 3for i in range(ITERATIONS):
 4    x, loss, info = fmin_l_bfgs_b(evaluator.loss, x.flatten(), fprime=evaluator.gradients, maxfun=20)
 5    print("Iteration %d completed with loss %d" % (i, loss))
 6
 7x = x.reshape((IMAGE_HEIGHT, IMAGE_WIDTH, CHANNELS))
 8x = x[:, :, ::-1]
 9x[:, :, 0] += IMAGENET_MEAN_RGB_VALUES[2]
10x[:, :, 1] += IMAGENET_MEAN_RGB_VALUES[1]
11x[:, :, 2] += IMAGENET_MEAN_RGB_VALUES[0]
12x = np.clip(x, 0, 255).astype("uint8")
13output_image = Image.fromarray(x)
14output_image.save(output_image_path)
15output_image
7e8d993893506a177caa0a62a3f650a0.jpeg
在1,2和5次迭代后输出图像
0555afbecf1e685ccb407ce650390104.jpeg
10次迭代后结果

将输入图像,样式图像和输出图像放在一起。

37160d05800ec83d6ec4ea683608902a.jpeg

效果还是非常不错的。

我们可以清楚地看到,既保留了输入图像(旧金山天际线)的原始内容,也成功地将新样式(Tytus Brzozowski)应用到了新的输出图像。

其他一些例子

6a027d75a56ff6746fed34fe518f47cf.jpeg 11400a0bf7632b67ccf8cf5203dc45ec.jpeg 1856cab3fab9d1cefd2b43b184f6715c.jpeg 61f3ba0b468b3b6b7855d1eb7133e4c2.jpeg
 
 

好消息!

小白学视觉知识星球

开始面向外开放啦👇👇👇

 
 

8134667dde2a849ec11adb565cb248c8.jpeg

下载1:OpenCV-Contrib扩展模块中文版教程

在「小白学视觉」公众号后台回复:扩展模块中文教程,即可下载全网第一份OpenCV扩展模块教程中文版,涵盖扩展模块安装、SFM算法、立体视觉、目标跟踪、生物视觉、超分辨率处理等二十多章内容。


下载2:Python视觉实战项目52讲
在「小白学视觉」公众号后台回复:Python视觉实战项目,即可下载包括图像分割、口罩检测、车道线检测、车辆计数、添加眼线、车牌识别、字符识别、情绪检测、文本内容提取、面部识别等31个视觉实战项目,助力快速学校计算机视觉。


下载3:OpenCV实战项目20讲
在「小白学视觉」公众号后台回复:OpenCV实战项目20讲,即可下载含有20个基于OpenCV实现20个实战项目,实现OpenCV学习进阶。


交流群

欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器、自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN、算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~
基于深度学习的图像风格迁移是一种能够将一幅图像的风格转移到另外一幅图像上的技术。它使用了深度神经网络来实现图像的风格化。Python作为一种流行的编程语言,也提供了许多库和框架来实现图像风格迁移实现图像风格迁移的一种常用方法是使用卷积神经网络(CNN)。CNN可以从图像中提取各种特征,包括颜色、纹理和形状等。通过训练一个CNN模型,我们可以用于捕捉图像风格的特征,比如一幅画作的风格。 在Python中,有许多流行的深度学习框架可以使用,比如TensorFlow、PyTorch和Keras等。这些框架提供了许多预训练的模型,包括一些用于图像风格迁移模型,如VGG19、ResNet等。这些模型可以很方便地用来提取图像的特征,并用于图像的风格化。 图像风格迁移的核心思想是将输入图像的内容和风格分离开来,然后将风格迁移到目标图像上。为了实现这个过程,我们需要定义一个损失函数来衡量输入图像和目标图像之间的差异,并使用梯度下降算法来最小化这个损失,从而产生一个风格化的图像。 在Python中,我们可以使用CNN模型来提取输入图像和目标图像的特征,并通过梯度下降算法调整输入图像,使其与目标图像的特征尽可能地接近。通过迭代多次训练,我们可以得到一个具有目标图像风格的风格化图像。 总之,Python提供了许多工具和库来实现基于深度学习的图像风格迁移。通过使用CNN模型和梯度下降算法,我们可以将一幅图像的风格迁移到另外一幅图像上,从而实现图像的风格化。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值