20 风格转换

发布时间 2023-10-08 17:04:13作者: 王哲MGG_AI
import os
import tensorflow as tf
# Load compressed models from tensorflow_hub
os.environ['TFHUB_MODEL_LOAD_FORMAT'] = 'COMPRESSED'

import IPython.display as display

import matplotlib.pyplot as plt
import matplotlib as mpl
mpl.rcParams['figure.figsize'] = (12, 12)
mpl.rcParams['axes.grid'] = False

import numpy as np
import PIL.Image
import time
import functools

这段代码是Python脚本的开头,它主要是为后续的深度学习任务准备工作环境。

  1. import os: 这是Python中的一个标准库,用于与操作系统进行交互。在这里,它被用来设置环境变量,具体来说是设置了一个名为TFHUB_MODEL_LOAD_FORMAT的环境变量,将其值设为'COMPRESSED'。这个变量的作用是告诉TensorFlow从TensorFlow Hub加载模型时要使用压缩格式。

  2. import tensorflow as tf: 这是导入TensorFlow库的语句,TensorFlow是一个用于机器学习和深度学习的强大框架。它通常用于构建、训练和部署机器学习模型。

  3. import IPython.display as display: 这里导入了IPython库的display模块,它用于在IPython环境中显示图像和其他媒体内容。

  4. import matplotlib.pyplot as plt: 这是导入Matplotlib库的语句,Matplotlib是一个用于绘制图表和图形的库。plt是一个常用的别名,用于简化Matplotlib函数的调用。

  5. import matplotlib as mpl: 这是导入Matplotlib库的另一个别名,mpl,它用于设置Matplotlib的全局参数。

  6. mpl.rcParams['figure.figsize'] = (12, 12): 这一行代码设置了Matplotlib全局参数,指定了图形的默认大小为12x12英寸。

  7. mpl.rcParams['axes.grid'] = False: 这一行代码也是设置Matplotlib的全局参数,禁用了图形的坐标网格。

  8. import numpy as np: 这是导入NumPy库的语句,NumPy是一个用于数值计算的库,通常用于处理数组和矩阵。

  9. import PIL.Image: 这是导入Python Imaging Library(PIL)的Image模块,它用于处理图像。

  10. import time: 这是导入Python的time模块,用于在代码中测量时间或进行时间相关的操作。

  11. import functools: 这是导入Python的functools模块,它提供了一些函数式编程工具,例如functools.partial,用于创建函数的部分应用。

这段代码的主要目的是导入所需的Python库和设置一些全局参数,以便后续的深度学习任务可以顺利进行。

# 工具函数,从给定地址加载图片
def load_img(path_to_img):
  max_dim = 512
  img = tf.io.read_file(path_to_img)
  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

这段代码定义了一个名为load_img的工具函数,用于加载图像并对其进行预处理。

  1. def load_img(path_to_img): 这是Python函数的定义,它接受一个参数path_to_img,表示要加载的图像文件的路径。

  2. max_dim = 512: 这一行定义了一个变量max_dim,它表示加载的图像在任何维度上的最大允许尺寸,这里设置为512像素。

  3. img = tf.io.read_file(path_to_img): 这一行代码使用TensorFlow的tf.io.read_file函数从指定路径读取图像文件的二进制数据,并将结果存储在img变量中。

  4. img = tf.image.decode_image(img, channels=3): 接下来,使用tf.image.decode_image函数将二进制图像数据解码为具有3个通道(RGB)的图像。解码后的图像数据存储在img变量中。

  5. img = tf.image.convert_image_dtype(img, tf.float32): 这一行代码将图像数据的数据类型转换为tf.float32,以便后续的计算可以在浮点数表示中进行。

  6. shape = tf.cast(tf.shape(img)[:-1], tf.float32): 这一行代码计算了图像的形状(高度和宽度),然后使用tf.cast函数将形状转换为浮点数类型。

  7. long_dim = max(shape): 这一行计算了图像的长边,即高度和宽度中的最大值。

  8. scale = max_dim / long_dim: 这一行计算了一个缩放因子,将图像的长边缩放到最大允许尺寸max_dim

  9. new_shape = tf.cast(shape * scale, tf.int32): 这一行根据缩放因子计算了新的图像形状,将其转换为整数类型。

  10. img = tf.image.resize(img, new_shape): 最后,使用tf.image.resize函数将图像按照新的形状进行缩放。

  11. img = img[tf.newaxis, :]: 最后一行将图像的维度扩展一个维度,以便它可以作为批次中的单个图像进行处理。

这个函数的主要目的是加载图像并将其准备好,以便后续可以将其输入到深度学习模型中进行处理。

# 工具函数,将图片显示出来
def imshow(image, title=None):
  if len(image.shape) > 3:
    image = tf.squeeze(image, axis=0)

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

这段代码定义了一个名为imshow的工具函数,用于在Matplotlib中显示图像。

  1. def imshow(image, title=None): 这是Python函数的定义,它接受两个参数,image表示要显示的图像,title是可选的标题。

  2. if len(image.shape) > 3:: 这一行代码检查图像的维度是否大于3。如果图像具有多个通道(例如,RGB图像),它将通过squeeze操作将批次维度去除,以便在Matplotlib中正确显示。

  3. image = tf.squeeze(image, axis=0): 如果图像具有批次维度(例如,形状为(1, height, width, channels)),这一行代码将批次维度去除,以便在Matplotlib中正确显示单个图像。

  4. plt.imshow(image): 这一行使用Matplotlib的imshow函数来显示图像。它将image作为输入,并将其显示在当前的Matplotlib图形中。

  5. if title:: 这一行检查是否提供了标题。如果提供了标题,它将执行以下操作。

  6. plt.title(title): 如果提供了标题,这一行设置Matplotlib图形的标题为title的值。

这个函数的主要目的是方便显示图像,可以用于可视化深度学习模型的输入、输出或中间结果。

content_path = tf.keras.utils.get_file('YellowLabradorLooking_new.jpg', 'https://storage.googleapis.com/download.tensorflow.org/example_images/YellowLabradorLooking_new.jpg')
style_path = tf.keras.utils.get_file('kandinsky5.jpg','https://storage.googleapis.com/download.tensorflow.org/example_images/Vassily_Kandinsky%2C_1913_-_Composition_7.jpg')

这段代码用于从指定的URL下载两张图像并将它们保存到本地。

  1. content_path = tf.keras.utils.get_file('YellowLabradorLooking_new.jpg', 'https://storage.googleapis.com/download.tensorflow.org/example_images/YellowLabradorLooking_new.jpg'):这一行代码使用tf.keras.utils.get_file函数从给定的URL下载一张名为'YellowLabradorLooking_new.jpg'的图像,并将其保存在本地。如果本地已经存在同名文件,它会跳过下载。

    • 'YellowLabradorLooking_new.jpg'是要保存的本地文件名。
    • 'https://storage.googleapis.com/download.tensorflow.org/example_images/YellowLabradorLooking_new.jpg'是要下载的图像的URL地址。
  2. style_path = tf.keras.utils.get_file('kandinsky5.jpg','https://storage.googleapis.com/download.tensorflow.org/example_images/Vassily_Kandinsky%2C_1913_-_Composition_7.jpg'):这一行代码与上面的类似,但是它下载了另一张名为'kandinsky5.jpg'的图像,同样将其保存在本地。

    • 'kandinsky5.jpg'是要保存的本地文件名。
    • 'https://storage.googleapis.com/download.tensorflow.org/example_images/Vassily_Kandinsky%2C_1913_-_Composition_7.jpg'是要下载的第二张图像的URL地址。

这段代码的目的是准备深度学习任务中所需的内容图像和风格图像,以便后续可以加载并将其输入到深度学习模型中进行处理。

content_image = load_img(content_path)
style_image = load_img(style_path)

plt.subplot(1, 2, 1)
imshow(content_image, 'Content Image')

plt.subplot(1, 2, 2)
imshow(style_image, 'Style Image')

 

这段代码加载了之前下载的内容图像和风格图像,并使用Matplotlib在两个子图中显示它们。

  1. content_image = load_img(content_path): 这一行代码使用之前定义的load_img函数加载内容图像。content_path是内容图像的文件路径,它被传递给load_img函数。加载后的图像被存储在content_image变量中。

  2. style_image = load_img(style_path): 类似于上一行,这一行代码使用load_img函数加载风格图像。style_path是风格图像的文件路径,加载后的图像被存储在style_image变量中。

  3. plt.subplot(1, 2, 1): 这一行代码创建一个Matplotlib子图,将其分成1行2列,并选择第一个子图作为当前活动子图。

  4. imshow(content_image, 'Content Image'): 这一行代码调用之前定义的imshow函数来在第一个子图中显示内容图像。同时,它还为子图设置了标题"Content Image"。

  5. plt.subplot(1, 2, 2): 这一行代码创建第二个Matplotlib子图,将其分成1行2列,并选择第二个子图作为当前活动子图。

  6. imshow(style_image, 'Style Image'): 这一行代码调用imshow函数来在第二个子图中显示风格图像,并为子图设置标题"Style Image"。

最终,这段代码的效果是在一个Matplotlib图形中显示了内容图像和风格图像的对比,使您可以直观地查看它们。

# 一个工具函数,将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)

这段代码定义了一个名为tensor_to_image的工具函数,其主要功能是将TensorFlow张量转换为图像,并在Matplotlib中显示它。

  1. def tensor_to_image(tensor): 这是Python函数的定义,它接受一个参数tensor,表示要转换为图像的TensorFlow张量。

  2. tensor = tensor*255: 这一行将输入的张量中的所有值乘以255,以将图像的像素值范围从[0, 1]映射到[0, 255],以便于显示。

  3. tensor = np.array(tensor, dtype=np.uint8): 这一行将张量转换为NumPy数组,并将数据类型设置为np.uint8,这是8位无符号整数类型,用于表示图像的像素值。

  4. if np.ndim(tensor)>3:: 这一行代码检查张量的维度是否大于3,如果是,则执行以下操作。

  5. assert tensor.shape[0] == 1: 这一行代码断言如果张量具有多个批次维度,则批次维度的大小必须为1。这是因为通常情况下,tensor_to_image函数处理的是单个图像而不是批次中的多个图像。这个检查确保了输入的张量是符合预期的。

  6. tensor = tensor[0]: 如果通过了上述断言,那么将张量中的批次维度去除,以得到单个图像的数据。

  7. return PIL.Image.fromarray(tensor): 最后,这一行代码将NumPy数组转换为PIL图像对象,并返回它。这使得可以使用PIL库的功能来保存、显示或进一步处理图像。

这个函数的主要目的是将TensorFlow张量转换为可视化的图像,以便查看深度学习模型的输出或中间结果。

# 需要先安装pip install tensorflow-hub-0.12.0
import tensorflow_hub as hub
hub_model = hub.load('https://hub.tensorflow.google.cn/google/magenta/arbitrary-image-stylization-v1-256/2')
stylized_image = hub_model(tf.constant(content_image), tf.constant(style_image))[0]
tensor_to_image(stylized_image)

这段代码使用TensorFlow Hub加载了一个预训练的模型,该模型用于图像的风格转移。接下来,它将内容图像和风格图像传递给该模型进行风格转移,并将结果显示出来。

  1. import tensorflow_hub as hub: 这一行代码导入了TensorFlow Hub库,它是一个用于共享和使用预训练模型的库。

  2. hub_model = hub.load('https://hub.tensorflow.google.cn/google/magenta/arbitrary-image-stylization-v1-256/2'): 这一行代码使用hub.load函数加载了一个预训练的图像风格转移模型。模型的URL指定了模型的位置。

  3. stylized_image = hub_model(tf.constant(content_image), tf.constant(style_image))[0]: 这一行代码使用加载的模型对内容图像和风格图像进行风格转移。它首先使用tf.constant将内容图像和风格图像转换为TensorFlow常量张量,然后将它们传递给hub_model进行风格转移。结果被存储在stylized_image变量中,其中[0]是为了获取批次中的第一个图像。

  4. tensor_to_image(stylized_image): 最后,这一行代码调用之前定义的tensor_to_image函数,将风格转移后的图像(stylized_image)转换为PIL图像对象,并显示出来。

总之,这段代码的目的是使用预训练的模型将内容图像与风格图像相结合,生成具有风格的新图像,并将其显示出来。

# vgg就代表了一个训练好了的VGG19模型
# include_top=False代表不需要最后一层。因为咱们只用它来风格转换,不需要最后一层,最后一层是用来识别图片的。
vgg = tf.keras.applications.VGG19(include_top=False, weights='imagenet')

# 将每层的名字打印出来
for layer in vgg.layers:
  print(layer.name)

这段代码使用TensorFlow的tf.keras.applications.VGG19函数加载了一个预训练的VGG19模型,并打印了该模型的每一层的名称。

  1. vgg = tf.keras.applications.VGG19(include_top=False, weights='imagenet'): 这一行代码使用tf.keras.applications.VGG19函数加载了一个VGG19模型。参数include_top设置为False表示不包括模型的顶层(最后一层),因为在这个上下文中,我们不需要用它来进行图像分类。参数weights设置为'imagenet'表示使用在ImageNet数据集上预训练的权重。

  2. for layer in vgg.layers:: 这是一个循环,用于迭代遍历VGG19模型的所有层。

  3. print(layer.name): 在循环中,这一行代码打印了每个层的名称。这将列出VGG19模型的所有层的名称,包括卷积层、池化层、全连接层等。

这段代码的目的是加载VGG19模型,并列出模型的所有层的名称,以便进一步的使用和分析。

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)

这段代码定义了一些参数,用于指定内容图像和风格图像在VGG19模型中的层级。

  1. content_layers = ['block5_conv2']: 这一行定义了一个列表content_layers,其中包含一个字符串 'block5_conv2'。这表示在VGG19模型中,我们将使用'block5_conv2'这一层来捕获内容图像的特征。

  2. style_layers = ['block1_conv1', 'block2_conv1', 'block3_conv1', 'block4_conv1', 'block5_conv1']: 这一行定义了一个列表style_layers,其中包含多个字符串,表示在VGG19模型中我们将使用这些层来捕获风格图像的特征。这些层的选择是基于它们通常能够捕获到不同尺度的纹理和风格信息。

  3. num_content_layers = len(content_layers): 这一行计算了内容图像层的数量,将其存储在num_content_layers变量中。在这个示例中,只有一个内容图像层。

  4. num_style_layers = len(style_layers): 这一行计算了风格图像层的数量,将其存储在num_style_layers变量中。在这个示例中,有五个风格图像层。

这些参数的目的是用于后续的风格转移任务,其中我们将使用VGG19模型的不同层来捕获内容和风格特征。

# 创建一个自定义的模型
# 在输入vgg.input后(也就是一张图片后),这个函数会返回上面定义的那些网络层的激活值。
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

这段代码定义了一个自定义的模型,该模型基于VGG19模型,并根据提供的层名称返回相应层的激活值。

  1. vgg = tf.keras.applications.VGG19(include_top=False, weights='imagenet'): 这一行代码与之前的代码相似,它使用tf.keras.applications.VGG19函数加载了一个VGG19模型,并设置了include_topFalse,以及使用在ImageNet数据集上预训练的权重。

  2. vgg.trainable = False: 这一行代码将加载的VGG19模型设置为不可训练,这意味着模型的权重不会在后续的训练中被更新。

  3. outputs = [vgg.get_layer(name).output for name in layer_names]: 这一行代码创建了一个包含要提取的层激活的列表。它遍历了layer_names中的每个层名称,并使用vgg.get_layer(name).output来获取相应层的输出张量,并将这些输出张量存储在outputs列表中。

  4. model = tf.keras.Model([vgg.input], outputs): 最后,这一行代码创建了一个新的Keras模型,接受VGG19模型的输入,并输出outputs列表中的层激活。这个新模型用于提取输入图像在指定层中的激活值。

这个自定义模型的目的是根据给定的层名称提取图像在VGG19模型中的激活值,以便后续用于计算内容损失和风格损失,用于风格转移任务。

def gram_matrix(input_tensor):
  result = tf.linalg.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)

这段代码定义了一个名为gram_matrix的函数,用于计算输入张量的格拉姆矩阵。

  1. def gram_matrix(input_tensor): 这是Python函数的定义,它接受一个输入张量input_tensor作为参数,该张量通常包含了某一层的特征映射。

  2. result = tf.linalg.einsum('bijc,bijd->bcd', input_tensor, input_tensor): 这一行代码使用tf.linalg.einsum函数计算格拉姆矩阵。格拉姆矩阵的计算涉及到两个相同形状的张量的点积。这里,input_tensor被自己相乘,产生了一个形状为(batch, height, width, channels)的结果矩阵。

  3. input_shape = tf.shape(input_tensor): 这一行代码获取输入张量的形状,并将其存储在input_shape变量中。

  4. num_locations = tf.cast(input_shape[1]*input_shape[2], tf.float32): 这一行代码计算了特征映射中的像素位置数量,并将其存储在num_locations变量中。它通过将特征映射的高度(input_shape[1])和宽度(input_shape[2])相乘得到。

  5. 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}

这段代码定义了一个自定义的Keras模型,名为StyleContentModel,用于同时计算图像的风格和内容特征。

  1. class StyleContentModel(tf.keras.models.Model): 这是一个Python类的定义,它继承自tf.keras.models.Model,表示创建了一个Keras模型。

  2. def __init__(self, style_layers, content_layers): 这是类的构造函数,接受两个参数:style_layerscontent_layers,它们分别是指定用于计算风格和内容的层的列表。

    • self.vgg = vgg_layers(style_layers + content_layers): 这一行代码创建了一个VGG模型(已经定义的vgg_layers函数),该模型将包含了指定的风格层和内容层。

    • self.style_layers = style_layersself.content_layers = content_layers:这两行代码将输入的style_layerscontent_layers存储在对象的属性中,以供后续使用。

    • self.num_style_layers = len(style_layers): 这一行代码计算了风格层的数量,并将其存储在self.num_style_layers属性中。

    • self.vgg.trainable = False: 这一行代码将创建的VGG模型设置为不可训练,以防止在后续的训练中更新它的权重。

  3. def call(self, inputs): 这是模型的调用方法,它接受输入张量inputs,并计算模型的输出。

    • inputs = inputs*255.0: 这一行代码将输入的张量缩放到范围[0, 255],因为VGG模型在训练时使用的像素值范围是[0, 255]。

    • preprocessed_input = tf.keras.applications.vgg19.preprocess_input(inputs): 这一行代码使用VGG19模型的预处理函数来对输入进行预处理,以使其适合VGG19的预训练权重。

    • outputs = self.vgg(preprocessed_input): 这一行代码将预处理后的输入传递给VGG模型,计算出一系列层的输出。

    • style_outputs, content_outputs = (outputs[:self.num_style_layers], outputs[self.num_style_layers:]): 这一行代码将VGG模型的输出分成风格层的输出和内容层的输出。

    • style_outputs = [gram_matrix(style_output) for style_output in style_outputs]: 这一行代码计算了风格层的格拉姆矩阵,以捕获图像的风格信息。

    • content_dictstyle_dict:这两行代码分别创建了字典,将内容层的输出和风格层的格拉姆矩阵输出存储在这些字典中,以便后续使用。

    • 最后,函数返回一个字典,包含了内容和风格信息,结构为{'content': content_dict, 'style': style_dict}

这个自定义模型的主要目的是在一次前向传播中计算图像的内容和风格信息,以便在风格转移任务中使用。

extractor = StyleContentModel(style_layers, content_layers)

这行代码创建了一个StyleContentModel类的实例,名为extractor,用于提取图像的风格和内容信息。这个模型已经在之前的代码中定义并初始化了,它会根据提供的style_layerscontent_layers来计算风格和内容信息。这个extractor实例可以在后续的风格转移任务中使用,以从图像中提取相关特征。

style_targets = extractor(style_image)['style'] # 获取风格图片的风格矩阵
content_targets = extractor(content_image)['content'] # 获取内容图片的内容激活值矩阵

这段代码使用之前创建的extractor模型从风格图像和内容图像中提取了风格和内容信息,分别存储在style_targetscontent_targets变量中。

  1. style_targets = extractor(style_image)['style']: 这一行代码将风格图像(style_image)传递给extractor模型,并通过['style']获取模型的输出中的风格信息。style_targets变量将包含风格图像的风格信息,通常是格拉姆矩阵或类似的特征表示。

  2. content_targets = extractor(content_image)['content']: 这一行代码将内容图像(content_image)传递给extractor模型,并通过['content']获取模型的输出中的内容信息。content_targets变量将包含内容图像的内容信息,通常是激活值矩阵或类似的特征表示。

这些目标信息(style_targetscontent_targets)将在后续的风格转移任务中与生成的图像进行比较,以计算风格损失和内容损失,从而指导生成的图像向期望的风格和内容靠近。

# 复制内容图片到image。
# 后面会不断的根据content_image和style_image来改变image,时image的风格越来越像style_image
image = tf.Variable(content_image)

这段代码创建了一个名为image的可变张量(Variable),并将其初始化为内容图像(content_image)的值。这个可变张量将用作生成的图像,而后续的风格转移算法将不断地根据内容图像和风格图像来修改image,使其逐渐具有与风格图像相似的风格。这是风格转移算法的核心部分。

通过在image上应用风格转移算法,您可以生成一个新的图像,该图像在内容上与内容图像相似,但在风格上与风格图像相似。这种方式允许您融合两张图像的特征,创造出具有独特艺术风格的图像。

# 一个工具函数,用来修剪图片的数值
def clip_0_1(image):
  return tf.clip_by_value(image, clip_value_min=0.0, clip_value_max=1.0)

这段代码定义了一个名为clip_0_1的工具函数,其主要目的是将图像的像素值修剪到指定范围内,即[0, 1]。

  1. def clip_0_1(image): 这是Python函数的定义,它接受一个输入参数image,表示待修剪的图像。

  2. return tf.clip_by_value(image, clip_value_min=0.0, clip_value_max=1.0): 这一行代码使用tf.clip_by_value函数,将图像的像素值修剪到指定的范围内。clip_value_min参数设置为0.0,表示将小于0.0的像素值变为0.0,而clip_value_max参数设置为1.0,表示将大于1.0的像素值变为1.0。这确保了图像的像素值在[0, 1]范围内,符合通常的图像表示要求。

这个工具函数通常用于确保生成的图像的像素值范围合法,以便正确显示或保存图像。在风格转移等任务中,由于图像的像素值可能会在优化过程中发生变化,因此需要定期使用这种函数来修剪像素值范围。

opt = tf.optimizers.Adam(learning_rate=0.02, beta_1=0.99, epsilon=1e-1)

这段代码创建了一个Adam优化器的实例,用于在风格转移任务中更新生成的图像以逐渐使其逼近期望的目标风格和内容。

  1. opt = tf.optimizers.Adam(learning_rate=0.02, beta_1=0.99, epsilon=1e-1): 这一行代码创建了一个Adam优化器的实例,并将其赋值给变量opt。Adam是一种常用的优化算法,用于调整生成的图像,使其最小化风格损失和内容损失。

    • learning_rate=0.02: 这个参数设置了学习率,它控制了每次优化步骤中权重更新的大小。学习率越大,权重更新越大,但可能导致不稳定的训练过程。通常需要根据具体任务进行调整。

    • beta_1=0.99: 这是Adam优化器的超参数之一,控制了梯度的指数加权平均过程中的衰减率。通常保持在接近1的值,以平稳梯度更新。

    • epsilon=1e-1: 这是Adam优化器的超参数之一,用于防止分母为零的情况。它是一个很小的正数,通常设置为较小的值,以确保数值稳定性。

这个Adam优化器将用于在风格转移算法中迭代地更新生成的图像,以使其逼近期望的目标风格和内容。学习率、beta_1和epsilon等参数的值可以根据具体任务进行调整,以获得最佳的优化性能。

style_weight=1e-2 # 可以通过这个值来控制风格化到什么程度
content_weight=1e4 # 控制内容保留的程度

这两个参数style_weightcontent_weight用于控制风格转移算法中风格和内容的相对权重,影响最终生成图像的风格和内容之间的平衡。

  1. style_weight = 1e-2: 这个参数控制了生成图像的风格化程度。较大的style_weight值将导致更多的风格被应用于生成的图像,使其更接近目标风格图像。较小的值将减弱风格的影响,使生成的图像更接近内容图像。调整这个参数可以控制风格化效果的强度。

  2. content_weight = 1e4: 这个参数控制了生成图像的内容保留程度。较大的content_weight值将导致更多的内容信息被保留在生成的图像中,使其更接近内容图像。较小的值将减弱内容的影响,使生成的图像更接近目标风格图像。调整这个参数可以控制内容的保留程度。

这两个参数的具体值通常需要根据具体的风格转移任务和期望的效果进行调整。增加style_weight将增强风格效果,减少style_weight将减弱风格效果。增加content_weight将增强内容保留,减少content_weight将减弱内容保留。

调整这些权重是风格转移算法中的重要部分,因为它们可以影响最终生成的图像的外观和风格。通常需要进行一些试验来找到最佳的权重组合,以满足特定的需求和美学偏好。

# 用这个函数来对比最终图片image与内容图片content_image风格图片style_image的差别。
# 也就是我们所说的损失函数。差别越大损失就越大。
# 当iamge的内容越来越像content_image,风格越来越像style_image,那么损失就会越来越小。
def style_content_loss(outputs):
    style_outputs = outputs['style'] # image当前的风格矩阵
    content_outputs = outputs['content'] # image当前的内容激活值矩阵
    
    # 计算风格损失
    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

这个函数style_content_loss用于计算图像的风格损失和内容损失,以评估生成的图像与目标内容图像和目标风格图像之间的差别。

  1. style_outputs = outputs['style']content_outputs = outputs['content']:这两行代码从outputs字典中提取了当前生成图像的风格和内容信息。

  2. style_loss计算了风格损失,它是通过比较生成图像的风格矩阵与目标风格图像的风格矩阵之间的均方差来计算的。tf.add_n函数用于将多个层的风格损失相加,并乘以style_weightnum_style_layers来权衡和标准化损失值。

  3. content_loss计算了内容损失,它是通过比较生成图像的内容激活值矩阵与目标内容图像的内容激活值矩阵之间的均方差来计算的。同样,tf.add_n函数用于将多个层的内容损失相加,并乘以content_weightnum_content_layers来权衡和标准化损失值。

  4. 最后,loss将风格损失和内容损失相加,得到总的损失值,这个总损失值用于指导优化器更新生成的图像,以减小与目标内容和风格之间的差距。

这个损失函数的目的是在优化过程中,帮助生成的图像逐渐向目标内容图像和目标风格图像靠近。通过调整style_weightcontent_weight参数,可以控制风格和内容之间的权衡,以获得所需的效果。

@tf.function()
def train_step(image):
  # tape会记录下前向传播的每一个步骤,后面好自动执行反向传播
  with tf.GradientTape() as tape:
    outputs = extractor(image) # 获取当前image的内容激活值矩阵和风格矩阵
    loss = style_content_loss(outputs) # 计算损失。即与内容图片content_image风格图片style_image的差距

  # 获取image相对于loss的梯度。这里的image就相当于w和b参数一样。
  grad = tape.gradient(loss, image)
  # 使用梯度来改变image,也即是说image会变得越来越像content_image风格图片style_image
  opt.apply_gradients([(grad, image)]) 
  
  image.assign(clip_0_1(image))

这段代码定义了一个train_step函数,该函数用于执行一次训练迭代,优化生成的图像,使其逼近目标内容图像和目标风格图像。

  1. @tf.function(): 这是一个装饰器,用于将函数编译为TensorFlow图,以提高计算性能。这个装饰器通常用于加速训练过程。

  2. with tf.GradientTape() as tape:: 这是一个上下文管理器,用于记录前向传播中的计算操作,以便后续计算梯度。在这个上下文中,前向传播的计算步骤被记录下来。

  3. outputs = extractor(image): 这一行代码将生成的图像(image)传递给extractor模型,以获取当前生成图像的内容激活值矩阵和风格矩阵。

  4. loss = style_content_loss(outputs): 这一行代码计算了当前生成图像与目标内容图像和目标风格图像之间的总损失,使用了之前定义的style_content_loss函数。

  5. grad = tape.gradient(loss, image): 这一行代码使用梯度磁带(tape)计算了生成图像(image)相对于总损失(loss)的梯度。这些梯度将用于更新生成图像以减小损失。

  6. opt.apply_gradients([(grad, image)]): 这一行代码使用优化器(opt)来根据计算的梯度来更新生成图像(image)的像素值。这个步骤是风格转移算法的核心,通过反向传播和梯度下降来调整生成图像,使其逼近期望的内容和风格。

  7. image.assign(clip_0_1(image)): 最后,这一行代码使用之前定义的clip_0_1函数将生成图像的像素值修剪到合法的范围[0, 1]内,以确保图像的像素值在有效范围内。

这个train_step函数是风格转移算法中的一次训练迭代,它不断地更新生成图像以减小损失,从而使生成图像逐渐向期望的内容和风格靠近。这个过程会在多次迭代中进行,直到生成的图像达到满意的效果。

# 下面我们只训练3步。从结果可以看出,图片已经有了一点点风格图片的感觉了
train_step(image)
train_step(image)
train_step(image)
tensor_to_image(image)

这段代码展示了对生成图像进行三次训练迭代的过程,并通过tensor_to_image函数将生成的图像转换为可显示的图像。通过这三次迭代,可以观察到生成的图像已经开始具有一些类似于目标风格图像的特征,这表明风格转移算法已经开始生效。

在实际应用中,通常需要执行更多的训练迭代,以获得更好的结果。训练的次数和每次训练的步骤可以根据任务和期望的效果进行调整。风格转移是一个迭代过程,随着训练的进行,生成的图像将逐渐逼近期望的内容和风格。

如果您想要更进一步的改进结果,可以尝试增加训练的次数或者调整style_weightcontent_weight等参数来调整风格和内容的平衡。

import time
start = time.time()

epochs = 10
steps_per_epoch = 100

step = 0
for n in range(epochs):
  for m in range(steps_per_epoch):
    step += 1
    train_step(image)
    print(".", end='', flush=True)
  display.clear_output(wait=True)
  display.display(tensor_to_image(image))
  print("Train step: {}".format(step))

end = time.time()
print("Total time: {:.1f}".format(end-start))

这段代码执行了更多的训练迭代,具体来说,它进行了10个epochs,每个epoch包含100个训练步骤。

  1. start = time.time(): 这一行代码记录了训练开始的时间。

  2. epochs = 10steps_per_epoch = 100:这两行代码分别指定了训练的总轮数(epochs)和每个epoch的训练步骤数量。在这个例子中,总共进行了10个epochs,每个epoch包含100个训练步骤。

  3. step = 0:这一行代码初始化了一个变量step,用于跟踪训练的总步骤数。

  4. 外部的两个嵌套循环,分别用于遍历epochs和每个epoch中的训练步骤。

  5. 在内部循环中,train_step(image)被调用,以执行一次训练迭代。

  6. display.clear_output(wait=True)display.display(tensor_to_image(image))用于在每个epoch结束时清除输出并显示当前生成的图像。这样可以观察到训练过程中图像的逐渐变化。

  7. print("Train step: {}".format(step))用于在每个epoch结束时打印当前训练步骤的数量。

  8. 最后,通过计算end - start来获取整个训练过程所花费的时间,并打印出来。

这段代码执行了更多的训练迭代,因此生成的图像将更接近期望的内容和风格。根据任务的要求,您可以进一步增加训练轮数或调整其他参数来改进结果。