Используйте перенос стиля, чтобы превратить человека в человека с лошадью

GAN

вдохновение

Я недавно влюбилась в набор жердей мистера Туи. Ложка дегтя в том, что она всегда "прячет мужчину" Может быть, это потому, что мужчина недостаточно красив, чтобы привлечь ее? Тогда сегодня я сделаю красавца "мужик с лошадью". Подготовка очень проста, достаточно скачать изображение красивого мальчика и изображение лошади.

передача стиля

1. Краткое описание

передача стиляКак следует из названия, стиль одного изображения A переносится на другое изображение B и смешивается вместе; конечным эффектом является создание нового изображения, которое выглядит как содержимое изображения B, но использует стиль изображения A.

До того, как глубокое обучение стало популярным, эта проблема часто решалась путем извлечения особенностей текстуры изображений стиля и особенностей текстуры изображений контента, В конечном счете, это фактически статистический метод. Этот традиционный метод не очень эффективен, а полученная модель имеет ограниченную способность к обобщению и может использоваться только на конкретных изображениях.

В горячую эру глубокого обучения, с введением различных моделей глубоких сверточных нейронных сетей (CNN) с лучшими эффектами, есть хорошие экстракторы признаков. В это время появилась инновационная точка.Метод глубокого обучения был использован для замены традиционного метода извлечения признаков текстуры в качестве средства извлечения признаков изображения.Это самая важная идея в статье.Ниже приводится изображение содержания и работы разные художественные стили в статье комбинированные результаты.在这里插入图片描述

2. Фокус

Было предложено использовать сверточные нейронные сети для замены традиционного извлечения локальных признаков текстуры, но взаимосвязь между признаками текстуры и стилем изображения определяет, возможна ли передача стиля.

Давайте посмотрим на метод, упомянутый в статье.

image-20210705153954061

Используйте VGG19 в качестве экстрактора признаков и замените максимальный пул на средний пул для получения более привлекательных результатов;

image-20210705154822018

Функция потерь использует среднеквадратичную ошибку, а затем обучает параметры сети с помощью градиентного спуска с обратным распространением, так что исходное изображение и сгенерированное изображение более похожи по содержанию; сходство по стилю заключается в том, чтобы сделать исходное изображение и сгенерированное image Матрица Грама изображения более похожа, среднеквадратичное расстояние между ними самое близкое.

  • Потеря контента

    Lcontent(p,x,l)=12i,j(FijlPijl)2L_{content}(\vec{p},\vec{x},l)=\frac{1}{2}\sum\limits_{i,j}(F_{ij}^{l}-P_{ij}^{l})^2

  • потеря стиля

    Lstyle(a,x)=l=0LюlElL_{style}(\vec{a},\vec{x})=\sum\limits_{l=0}^{L}\omega_{l}E_{l}

    вEl=14Nl2Ml2i.j(GijlAijl)2E_{l}=\frac{1}{4N_{l}^2M_{l}^2}\sum\limits_{i.j}(G_{ij}^{l}-A_{ij}^{l})^2

Тогда функция полных потерьLtotal(p,a,x)=αLcontent(p,x)+βLstyle(a,x)L_{total}(\vec{p},\vec{a},\vec{x})=\alpha L_{content}(\vec{p},\vec{x})+\beta L_{style}(\vec{a},\vec{x})

α\alphaиβ\beta— это весовой коэффициент контента и стиля, и разные весовые коэффициенты приведут к разным результатам изображения.

image-20210705161919145

Горизонтальное сравнение заключается в том, что разные соотношения соответствуют разным графикам результатов.

3. Демонстрация кода

Создайте несколько интересных изображений для переноса стиля с помощью 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

Содержимое изображения представлено значениями промежуточных карт признаков.

Оказывается, стиль изображения можно описать средним значением и корреляцией по разным картам признаков. Матрица Грама, содержащая эту информацию, может быть вычислена путем вычисления внешнего произведения вектора признаков в каждом месте и усреднения этого внешнего произведения во всех местах. Для матрицы Грама определенного слоя конкретный метод расчета выглядит следующим образом:

Gcdl=ijFijcl(x)Fijdl(x)IJG^l_{cd} = \frac{\sum_{ij} F^l_{ijc}(x)F^l_{ijd}(x)}{IJ}

Это можно использовать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, Total Variation Loss используется для восстановления изображения, восстановления и шумоподавления Конкретные идеи заключаются в следующем.

preview

Итак, согласно этой идее, мы модифицируем предыдущий loss = style_loss + content_loss, а также ограничение регуляризации для полной потери вариации, которое можно использовать в тензорном потоке.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.

  • версия тензорного потока

    image-20210706114415172

  • сделано мной

    image-20210706124337042

Для сравнения, созданные мной глаза более размыты, цвета не согласованы, а текстура одежды не ясна. Самое главное — это крайне неподходящая зелень на заднем плане, как могучий и сильный человек на коне мог быть зеленым?

Глядя на представленный hub_module, я обнаружил, что InceptionV3 использовался в качестве экстрактора признаков изображения, поэтому я попытался использовать InceptionV3 для извлечения признаков, а затем выполнить передачу стиля, но результат был очень неудовлетворительным, мало того, что новое изображение не научилось стиль, но и изображение содержимого становятся размытыми.

image-20210706170436922

После многократного изменения соотношения весов и слоя выбора объектов эффект все еще очень плохой. Я собираюсь более подробно рассмотреть конкретную реализацию модели в tensorflow-hub, загрузить модель в tensorflow-hub, распаковать ее и открыть для чтения файла .pb, но синтаксический анализ сообщает об ошибках и не может быть решено. Увы, в следующий раз, когда я попытаюсь использовать GAN для передачи стиля, я снова сравню эффект.

В конце концов, интересно, скрывается ли до сих пор такой человек, Учитель Туя?