使用风格迁移让汉子变成套马的汉子

4 篇文章 1 订阅

灵感

我最近迷上了图雅老师的套马杆,美中不足的就是她总是“躲汉子”,或许是因为这个汉子不够帅不能吸引到她?那今天我就动手做一个帅气的“套马的汉子”。准备工作很简单,去下载一张帅气的男生图片以及一张马的图片就可以了。

风格迁移

1.简述

风格迁移顾名思义就是将一个A图像的风格迁移到另一个B图像上混合在一起;最终的效果是产生一个看起来还是B图像的内容,但是用了A图像风格的新的图像。

在深度学习火热之前,解决这个问题往往是通过提取风格图像的纹理特征以及内容图像的纹理特征,说到底这其实是基于统计的方法。这种传统的方法效果不太好,而且得到的模型泛化能力有限,往往只能用于特定的图像上。

到了深度学习火热时代,随着各种深层次效果更好的卷积神经网络(CNN)模型提出,就有了很好的特征提取器。这个时候创新点就出现了,运用深度学习的方法代替传统的纹理特征提取方法作为图像的特征提取器,这也就是论文中最重要的思想,下面是论文中将内容图像与不同艺术风格的作品结合的结果。
在这里插入图片描述

2.重点

前面已经提出了使用卷积神经网络代替传统的局部纹理特征提取,但是纹理特征与图像风格之间存在着关系才决定了风格迁移是否可行。

再来看看论文中提到的方法

image-20210705153954061

使用VGG19作为特征提取器,并且用平均池化代替最大池化,获得更有吸引力的结果;

image-20210705154822018

损失函数用的还是均方误差,然后经过反向传播梯度下降训练网络参数,使原图像与生成的图像在内容上更相似;在风格上相似也就是使得原始图像与生成图像的格拉姆矩阵之间的均方距离最近。

  • 内容损失

    L c o n t e n t ( p ⃗ , x ⃗ , l ) = 1 2 ∑ i , j ( F i j l − P i j l ) 2 L_{content}(\vec{p},\vec{x},l)=\frac{1}{2}\sum\limits_{i,j}(F_{ij}^{l}-P_{ij}^{l})^2 Lcontent(p ,x ,l)=21i,j(FijlPijl)2

  • 风格损失

    L s t y l e ( a ⃗ , x ⃗ ) = ∑ l = 0 L ω l E l L_{style}(\vec{a},\vec{x})=\sum\limits_{l=0}^{L}\omega_{l}E_{l} Lstyle(a ,x )=l=0LωlEl

    其中 E l = 1 4 N l 2 M l 2 ∑ i . j ( G i j l − A i j l ) 2 E_{l}=\frac{1}{4N_{l}^2M_{l}^2}\sum\limits_{i.j}(G_{ij}^{l}-A_{ij}^{l})^2 El=4Nl2Ml21i.j(GijlAijl)2

然后总的损失函数为 L t o t a l ( p ⃗ , a ⃗ , x ⃗ ) = α L c o n t e n t ( p ⃗ , x ⃗ ) + β L s t y l e ( a ⃗ , x ⃗ ) L_{total}(\vec{p},\vec{a},\vec{x})=\alpha L_{content}(\vec{p},\vec{x})+\beta L_{style}(\vec{a},\vec{x}) Ltotal(p ,a ,x )=αLcontent(p ,x )+βLstyle(a ,x )

α \alpha α β \beta β是内容和风格的权重因子,不同的权重比率就会得到不同的图像结果。

image-20210705161919145

横着对比就是不同比率对应着不同的结果图。

3.代码demo

使用tensorflow创建一些有意思的风格迁移图片

import tensorflow as tf
import IPython.display as display
import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
import PIL.Image
import time
import functools
import warnings

mpl.rcParams['figure.figsize'] = (12,12)
mpl.rcParams['axes.grid'] = False
plt.rcParams['font.sans-serif']='SimHei'
plt.rcParams['axes.unicode_minus']=False

warnings.filterwarnings('ignore')

tf.__version__

image-20210706093036401

# 从tensor转为图像
def tensor_to_image(tensor):
    tensor = tensor*255
    tensor = np.array(tensor, dtype=np.uint8)
    if np.ndim(tensor)>3:
        assert tensor.shape[0] == 1
        tensor = tensor[0]
    return PIL.Image.fromarray(tensor)
    
 
# 加载本地图片
def load_img(img_path):
    # 最大像素值
    max_dim = 512
    # 读取图像(dtype=string),解码(从string变为tensor)并修改数据类型
    img = tf.io.read_file(img_path)
    img = tf.image.decode_image(img, channels=3)
    img = tf.image.convert_image_dtype(img, tf.float32)

    shape = tf.cast(tf.shape(img)[:-1], tf.float32)
    long_dim = max(shape)
    
    # 规范化
    scale = max_dim / long_dim
    new_shape = tf.cast(shape * scale, tf.int32)

    img = tf.image.resize(img, new_shape)
    img = img[tf.newaxis, :]
    return img



# 可视化图片
def imshow(image, title=None):
    if len(image.shape) > 3:
        image = tf.squeeze(image, axis=0)

    plt.imshow(image)
    if title:
        plt.title(title)

3.1 使用tensorflow-hub快速实现图像风格迁移

首先,我们引入内容图像和风格图像并可视化一下

content_image = load_img('./帅哥.jpeg')
style_image0 = load_img('./党.jpeg')
style_image1 = load_img('./艺术.jpeg')
style_image2 = load_img('./马.jpeg')
plt.subplot(2,2, 1)
imshow(content_image, '内容图片')

plt.subplot(2,2, 2)
imshow(style_image0, '风格图片1')

plt.subplot(2,2, 3)
imshow(style_image1, '风格图片2')

plt.subplot(2,2,4)
imshow(style_image2, '风格图片3')

image-20210706094745500

当我们使用tensorflow-hub的模型时,有可能会出现因为网络问题(无法科学上网)加载失败,这个时候我们需要把链接修改到.cn版本的网站中,具体的tensorflow-hub链接在这里,然后去找到你所需要的模型。

# 使用tensorflow_hub制作风格迁移
import tensorflow_hub as hub
hub_module = hub.load('https://hub.tensorflow.google.cn/google/magenta/arbitrary-image-stylization-v1-256/2')
for i in range(0,3):
    stylized_image=hub_module(tf.constant(content_image),tf.constant(locals()['style_image'+str(i)]))[0]
    tensor_to_image(stylized_image).show()

三种不同风格的图像如下,我分别叫它“爱党的孩子”,“迷幻的帅哥”,“套马的汉子”
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

3.2 自己动手实现风格迁移

在这里,我们需要使用VGG19中的部分网络作为我们的图像特征提取器,并且根据论文设置好网络的损失函数以及优化方法,构建自己的风格迁移网络。

开始,先看看VGG19中的网络结构,并且选出其中我们需要的部分

# 除去最后三个全连接层,只要特征提取部分网络
vgg = tf.keras.applications.VGG19(include_top=False, weights='imagenet')

# 查看各网络层
for layer in vgg.layers:
    print(layer.name)

image-20210706103505613

从这些卷积层中选出我们的内容提取和风格提取

# 内容层:提取图像特征
content_layers = ['block5_conv2'] 

# 多个风格层,每一层的输出可能是我们需要的某种风格
style_layers = ['block1_conv1',
                'block2_conv1',
                'block3_conv1', 
                'block4_conv1', 
                'block5_conv1']

num_content_layers = len(content_layers)
num_style_layers = len(style_layers)
# 根据上面的定义创建我们自己的VGG层
def vgg_layers(layer_names):
    vgg = tf.keras.applications.VGG19(include_top=False, weights='imagenet')
    vgg.trainable = False
  
    outputs = [vgg.get_layer(name).output for name in layer_names]

    model = tf.keras.Model([vgg.input], outputs)
    return model
# 风格特征提取器
style_extractor = vgg_layers(style_layers)
# 风格特征
style_outputs = style_extractor(style_image0*255)

#查看每层输出的统计信息
for name, output in zip(style_layers, style_outputs):
    print(name)
    print("  shape: ", output.numpy().shape)
    print("  min: ", output.numpy().min())
    print("  max: ", output.numpy().max())
    print("  mean: ", output.numpy().mean())
    print()

image-20210706104123767

图像的内容由中间feature maps(特征图)的值表示。

事实证明,图像的风格可以通过不同 feature maps (特征图)上的平均值和相关性来描述。 通过在每个位置计算 feature (特征)向量的外积,并在所有位置对该外积进行平均,可以计算出包含此信息的 Gram 矩阵。 对于特定层的 Gram 矩阵,具体计算方法如下所示:

G c d l = ∑ i j F i j c l ( x ) F i j d l ( x ) I J G^l_{cd} = \frac{\sum_{ij} F^l_{ijc}(x)F^l_{ijd}(x)}{IJ} Gcdl=IJijFijcl(x)Fijdl(x)

这可以使用tf.einsum函数来实现,有关于tf.einsum的使用可以看tf.einsum的使用

# 根据上面的计算公式得到Gram矩阵
def gram_matrix(input_tensor):
    # 分子
    result = tf.einsum('bijc,bijd->bcd', input_tensor, input_tensor)
    input_shape = tf.shape(input_tensor)
    # 分母
    num_locations = tf.cast(input_shape[1]*input_shape[2], tf.float32)
    
    # 结果
    return result/(num_locations)
# 创建风格内容模型
class StyleContentModel(tf.keras.models.Model):
    def __init__(self, style_layers, content_layers):
        super(StyleContentModel, self).__init__()
        self.vgg =  vgg_layers(style_layers + content_layers)
        self.style_layers = style_layers
        self.content_layers = content_layers
        self.num_style_layers = len(style_layers)
        self.vgg.trainable = False

    def call(self, inputs):
        "Expects float input in [0,1]"
        inputs = inputs*255.0
        preprocessed_input = tf.keras.applications.vgg19.preprocess_input(inputs)
        outputs = self.vgg(preprocessed_input)
        style_outputs, content_outputs = (outputs[:self.num_style_layers], 
                                          outputs[self.num_style_layers:])

        style_outputs = [gram_matrix(style_output)
                         for style_output in style_outputs]

        content_dict = {content_name:value 
                        for content_name, value 
                        in zip(self.content_layers, content_outputs)}

        style_dict = {style_name:value
                      for style_name, value
                      in zip(self.style_layers, style_outputs)}

        return {'content':content_dict, 'style':style_dict}
# 实例化风格内容模型,提取图片的风格、内容特征
extractor = StyleContentModel(style_layers, content_layers)

# 内容图像的提取特征结果
results = extractor(tf.constant(content_image))


print('Styles:')
for name, output in sorted(results['style'].items()):
    print("  ", name)
    print("    shape: ", output.numpy().shape)
    print("    min: ", output.numpy().min())
    print("    max: ", output.numpy().max())
    print("    mean: ", output.numpy().mean())
    print()

print("Contents:")
for name, output in sorted(results['content'].items()):
    print("  ", name)
    print("    shape: ", output.numpy().shape)
    print("    min: ", output.numpy().min())
    print("    max: ", output.numpy().max())
    print("    mean: ", output.numpy().mean())

# 风格图像的结果
style_targets = extractor(style_image2)['style']
content_targets = extractor(content_image)['content']

# 我们生成的风格迁移图像
image = tf.Variable(content_image)

# 维持像素值在0-1之间
def clip_0_1(image):
    return tf.clip_by_value(image, clip_value_min=0.0, clip_value_max=1.0)

# 定义优化器 这里使用Adam,版本高的也可以用LBFGS
opt = tf.optimizers.Adam(learning_rate=0.02, beta_1=0.99, epsilon=1e-1)

# 需要tensorflow>=2.5
# import tensorflow_probability as tfp
# opt_lbfgs = tfp.optimizer.lbfgs_minimize()


# 内容和风格的权重
style_weight=1e-2
content_weight=1e4


# 总的损失,这部分对应论文方法中的Ltotal
def style_content_loss(outputs):
    style_outputs = outputs['style']
    content_outputs = outputs['content']
    style_loss = tf.add_n([tf.reduce_mean((style_outputs[name]-style_targets[name])**2) 
                           for name in style_outputs.keys()])
    # 赋予风格损失权重
    style_loss *= style_weight / num_style_layers

    content_loss = tf.add_n([tf.reduce_mean((content_outputs[name]-content_targets[name])**2) 
                             for name in content_outputs.keys()])
    
    # 赋予内容损失权重
    content_loss *= content_weight / num_content_layers
    loss = style_loss + content_loss
    return loss
# 定义训练
@tf.function()
@tf.autograph.experimental.do_not_convert()
def train_step(image):
    with tf.GradientTape() as tape:
        outputs = extractor(image)
        loss = style_content_loss(outputs)

    grad = tape.gradient(loss, image)
    opt.apply_gradients([(grad, image)])
    image.assign(clip_0_1(image))
    

为了减少生成图像的噪音,使得生成图像更平滑并且保留内容图像的边缘信息,引入全变分损失。

关于全变分损失 Total Variation Loss,是用来图像修复复原和去噪音的,具体思想如下

preview

所以根据这个思想,我们修改之前的loss = style_loss + content_loss,加上一项全变分损失的正则化约束,全变分损失在tensorflow中可以用tf.image.total_variation(image)来计算

# 去除高频误差
def high_pass_x_y(image):
    x_var = image[:,:,1:,:] - image[:,:,:-1,:]
    y_var = image[:,1:,:,:] - image[:,:-1,:,:]

    return x_var, y_var
    
# 全变分损失的权重  
total_variation_weight=50


@tf.function()
def train_step(image):
    with tf.GradientTape() as tape:
        outputs = extractor(image)
        loss = style_content_loss(outputs)
        loss += total_variation_weight*tf.image.total_variation(image)

    grad = tape.gradient(loss, image)
    opt.apply_gradients([(grad, image)])
    image.assign(clip_0_1(image))
    return loss

 # 内容图片   
image = tf.Variable(content_image)

epochs = 10
steps_per_epoch = 120

step = 0
for n in range(epochs):
    for m in range(steps_per_epoch):
            step += 1
            loss=train_step(image)
            print(".", end='')
            if m%50==0:
                display.display(loss)
    display.clear_output(wait=True)
    
    display.display(tensor_to_image(image))
    print("Train step: {}".format(step))
    
# 保存图片    
file_name = 'stylized-image.png'
tensor_to_image(image).save(file_name)

最终自己动手生成的套马的汉子和使用tensorflow-hub生成的进行对比

  • tensorflow-hub版本

    image-20210706114415172

  • 自己做出的

    image-20210706124337042

相比起来,自己生成的眼睛更模糊,而且颜色不协调,衣服纹路不清晰。最重要的是背景里极其不搭的绿色,套马的汉子威武强壮怎么可能会绿绿的呢?

查看一下引入的hub_module,发现用的是InceptionV3作为图片的特征提取器,于是我就试着用InceptionV3进行特征提取再做风格迁移,结果效果十分不理想,不仅新图像没有学到风格,并且内容图像也变得模糊不清。

image-20210706170436922

更改了多次的权重比例以及特征选择层,效果依旧很差。准备仔细看看tensorflow-hub中模型的具体实现,下载了tensorflow-hub中的模型,解压打开准备读取.pb文件,但是解析一直报错无法解决。唉,下次试着用GAN来做风格迁移到时候再对比一次效果。

那最后,不知道这样的汉子图雅老师还躲不躲呢?

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shelgi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值