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

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

灵感


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


风格迁移



1.简述


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


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


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


image.png


2.重点


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

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


image.pngimage.png

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


image.png


image.png


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


image.png

image.png

然后总的损失函数为image.png

image.png是内容和风格的权重因子,不同的权重比率就会得到不同的图像结果。

image.png


image.png

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


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.png

# 从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.png

当我们使用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()
复制代码


三种不同风格的图像如下,我分别叫它“爱党的孩子”,“迷幻的帅哥”,“套马的汉子”

image.png

image.pngimage.png


3.2 自己动手实现风格迁移


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

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


# 除去最后三个全连接层,只要特征提取部分网络
vgg = tf.keras.applications.VGG19(include_top=False, weights='imagenet')
# 查看各网络层
for layer in vgg.layers:
    print(layer.name)
复制代码


image.png

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


# 内容层:提取图像特征
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.png


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

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

image.png


这可以使用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,是用来图像修复复原和去噪音的,具体思想如下


image.png


所以根据这个思想,我们修改之前的  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.png

自己做出的


image.png

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

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


image.png


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


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

目录
相关文章
|
8天前
|
机器学习/深度学习 算法 计算机视觉
完全让ChatGPT写一个风格迁移的例子,不改动任何代码
完全让ChatGPT写一个风格迁移的例子,不改动任何代码
8 1
|
15天前
|
算法
轻松玩转人物风格迁移!DualStyleGAN让你一键生成各种风格人物图片!【一个有趣的开源项目】
轻松玩转人物风格迁移!DualStyleGAN让你一键生成各种风格人物图片!【一个有趣的开源项目】
|
1月前
|
存储 分布式计算 Java
软件体系结构 - 架构风格(1)批处理架构风格
【4月更文挑战第21天】软件体系结构 - 架构风格(1)批处理架构风格
82 0
|
1月前
|
存储 SQL 数据库
软件体系结构 - 架构风格(10)数据库系统架构风格
【4月更文挑战第21天】软件体系结构 - 架构风格(10)数据库系统架构风格
55 0
|
8月前
|
开发者
EasyYitian软件架构迁移工具如何使用
EasyYitian软件架构迁移工具如何使用
|
11月前
图像风格迁移
图像风格迁移
88 0
|
移动开发 自然语言处理 BI
【架构风格】架构风格演进和领域架构分类
【架构风格】架构风格演进和领域架构分类
|
存储 JSON 数据可视化
移动端可视化引擎 F2 架构设计之: 为什么要选用 JSX
移动端可视化引擎 F2 架构设计之: 为什么要选用 JSX
111 0
|
机器学习/深度学习 编解码
学习——Anycost Gan 风格迁移
学习——Anycost Gan 风格迁移
342 0
学习——Anycost Gan 风格迁移