вдохновение
Я недавно влюбилась в набор жердей мистера Туи. Ложка дегтя в том, что она всегда "прячет мужчину" Может быть, это потому, что мужчина недостаточно красив, чтобы привлечь ее? Тогда сегодня я сделаю красавца "мужик с лошадью". Подготовка очень проста, достаточно скачать изображение красивого мальчика и изображение лошади.
передача стиля
1. Краткое описание
передача стиляКак следует из названия, стиль одного изображения A переносится на другое изображение B и смешивается вместе; конечным эффектом является создание нового изображения, которое выглядит как содержимое изображения B, но использует стиль изображения A.
До того, как глубокое обучение стало популярным, эта проблема часто решалась путем извлечения особенностей текстуры изображений стиля и особенностей текстуры изображений контента, В конечном счете, это фактически статистический метод. Этот традиционный метод не очень эффективен, а полученная модель имеет ограниченную способность к обобщению и может использоваться только на конкретных изображениях.
В горячую эру глубокого обучения, с введением различных моделей глубоких сверточных нейронных сетей (CNN) с лучшими эффектами, есть хорошие экстракторы признаков. В это время появилась инновационная точка.Метод глубокого обучения был использован для замены традиционного метода извлечения признаков текстуры в качестве средства извлечения признаков изображения.Это самая важная идея в статье.Ниже приводится изображение содержания и работы разные художественные стили в статье комбинированные результаты.
2. Фокус
Было предложено использовать сверточные нейронные сети для замены традиционного извлечения локальных признаков текстуры, но взаимосвязь между признаками текстуры и стилем изображения определяет, возможна ли передача стиля.
Давайте посмотрим на метод, упомянутый в статье.
Используйте VGG19 в качестве экстрактора признаков и замените максимальный пул на средний пул для получения более привлекательных результатов;
Функция потерь использует среднеквадратичную ошибку, а затем обучает параметры сети с помощью градиентного спуска с обратным распространением, так что исходное изображение и сгенерированное изображение более похожи по содержанию; сходство по стилю заключается в том, чтобы сделать исходное изображение и сгенерированное image Матрица Грама изображения более похожа, среднеквадратичное расстояние между ними самое близкое.
-
Потеря контента
-
потеря стиля
в
Тогда функция полных потерь
и— это весовой коэффициент контента и стиля, и разные весовые коэффициенты приведут к разным результатам изображения.
Горизонтальное сравнение заключается в том, что разные соотношения соответствуют разным графикам результатов.
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__
# 从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')
Когда мы используем модель 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)
Выберите наше извлечение контента и извлечение стиля из этих сверточных слоев.
# 内容层:提取图像特征
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()
Содержимое изображения представлено значениями промежуточных карт признаков.
Оказывается, стиль изображения можно описать средним значением и корреляцией по разным картам признаков. Матрица Грама, содержащая эту информацию, может быть вычислена путем вычисления внешнего произведения вектора признаков в каждом месте и усреднения этого внешнего произведения во всех местах. Для матрицы Грама определенного слоя конкретный метод расчета выглядит следующим образом:
Это можно использовать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 используется для восстановления изображения, восстановления и шумоподавления Конкретные идеи заключаются в следующем.
Итак, согласно этой идее, мы модифицируем предыдущий 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.
-
версия тензорного потока
-
сделано мной
Для сравнения, созданные мной глаза более размыты, цвета не согласованы, а текстура одежды не ясна. Самое главное — это крайне неподходящая зелень на заднем плане, как могучий и сильный человек на коне мог быть зеленым?
Глядя на представленный hub_module, я обнаружил, что InceptionV3 использовался в качестве экстрактора признаков изображения, поэтому я попытался использовать InceptionV3 для извлечения признаков, а затем выполнить передачу стиля, но результат был очень неудовлетворительным, мало того, что новое изображение не научилось стиль, но и изображение содержимого становятся размытыми.
После многократного изменения соотношения весов и слоя выбора объектов эффект все еще очень плохой. Я собираюсь более подробно рассмотреть конкретную реализацию модели в tensorflow-hub, загрузить модель в tensorflow-hub, распаковать ее и открыть для чтения файла .pb, но синтаксический анализ сообщает об ошибках и не может быть решено. Увы, в следующий раз, когда я попытаюсь использовать GAN для передачи стиля, я снова сравню эффект.
В конце концов, интересно, скрывается ли до сих пор такой человек, Учитель Туя?