tf2.X-神经风格迁移,人人都是艺术家
点点关注不迷路
什么是神经风格迁移
神经风格迁移是将一幅图片的内容和另一幅艺术图片的风格结合,生成一张艺术化的图片的过程。输入是一张内容图和一张风格图,输出是风格化的结果。例如下面这样:
代码解析
下面我们就用tensorflow2.X来实现最早的神经风格迁移,实现我们自己的艺术创作。
基本设置
首先我们导入需要的库,影像以及设置一些需要的参数。图片来自于http://www.pexels.com/。
importnumpyasnp
importtensorflowastf
fromtensorflowimportkeras
fromtensorflow.keras.applicationsimportvgg19
base_image_path ='building.jpg'
style_reference_image_path ='dids.jpg'
result_prefix ="result"
#损失分量的加权平均所使用的权重
total_variation_weight =1e-6
style_weight =1e-6
content_weight =2.5e-8
# 确保处理后的影像具有相似尺寸
width, height = keras.preprocessing.image.load_img(base_image_path).size
img_nrows =400
img_ncols = int(width * img_nrows / height)
所使用的基础图片与风格图片如下所示。
图像预处理与去处理操作
defpreprocess_image(image_path):
# 处理图像,确保为VGG19接受的输入张量
img = keras.preprocessing.image.load_img(
image_path, target_size=(img_nrows, img_ncols)
)
img = keras.preprocessing.image.img_to_array(img)
img = np.expand_dims(img, axis=0)
img = vgg19.preprocess_input(img)
returntf.convert_to_tensor(img)
defdeprocess_image(x):
# vgg19.preprocess_input的作用是减去ImageNet的平均像素值,使其中心为0。这里相当于vgg19.preprocess_input的逆操作
x = x.reshape((img_nrows, img_ncols,3))
x[:, :,0] +=103.939
x[:, :,1] +=116.779
x[:, :,2] +=123.68
# 'BGR'->'RGB'将图像由BGR格式转换为RGB格式。这也是vgg19.preprocess_input 逆操作的一部分
x = x[:, :, ::-1]
# 裁剪x中元素到指定范围
x = np.clip(x,0,255).astype("uint8")
returnx
定义损失函数
我们需要定义 4 个相关函数:
gram_matrix (用于计算风格损失)
style_loss,使生成的图像风格接近参考图像的局部纹理
content_loss,使生成的图像的基本内容与基础图像的基本内容保持接近
total_variation_loss,也称为总变差正则化,使图像平滑
defgram_matrix(x):
x = tf.transpose(x, (2,0,1))
features = tf.reshape(x, (tf.shape(x)[0],-1))
gram = tf.matmul(features, tf.transpose(features))
returngram
# 定义风格损失,确保生成图像保持参考图像的风格
defstyle_loss(style, combination):
S = gram_matrix(style)
C = gram_matrix(combination)
channels =3
size = img_nrows * img_ncols
returntf.reduce_sum(tf.square(S - C)) / (4.0* (channels **2) * (size **2))
# 内容损失,确保基准影像的内容不丢失
defcontent_loss(base, combination):
returntf.reduce_sum(tf.square(combination - base))
# 定义总变差损失:它对生成的组合图像的像素进行操作。它促使生成图像具有空间连续性,从而避免结果过度像素化。可以将其理解为正则化损失。
deftotal_variation_loss(x):
a = tf.square(
x[:, : img_nrows -1, : img_ncols -1, :] - x[:,1:, : img_ncols -1, :]
)
b = tf.square(
x[:, : img_nrows -1, : img_ncols -1, :] - x[:, : img_nrows -1,1:, :]
)
returntf.reduce_sum(tf.pow(a + b,1.25))
# 这里我们加载在imagenet上预训练好的VGG19
model = vgg19.VGG19(weights="imagenet", include_top=False)
# 获得层的名称与对应输出的字典
outputs_dict = dict([(layer.name, layer.output)forlayerinmodel.layers])
# 创建模型,并返回上述字典内的层的结果
feature_extractor = keras.Model(inputs=model.inputs, outputs=outputs_dict)
# 用于风格损失的层
style_layer_names = [
"block1_conv1",
"block2_conv1",
"block3_conv1",
"block4_conv1",
"block5_conv1",
]
# 用于内容损失的层
content_layer_name ="block5_conv2"
defcompute_loss(combination_image, base_image, style_reference_image):
input_tensor = tf.concat(
[base_image, style_reference_image, combination_image], axis=0
)
features = feature_extractor(input_tensor)
# 初始化损失
loss = tf.zeros(shape=())
# 添加内容损失
layer_features = features[content_layer_name]
base_image_features = layer_features[0, :, :, :]
combination_features = layer_features[2, :, :, :]
loss = loss + content_weight * content_loss(
base_image_features, combination_features
)
# 添加风格损失
forlayer_nameinstyle_layer_names:
layer_features = features[layer_name]
style_reference_features = layer_features[1, :, :, :]
combination_features = layer_features[2, :, :, :]
sl = style_loss(style_reference_features, combination_features)
loss += (style_weight / len(style_layer_names)) * sl
# 添加总变差损失
loss += total_variation_weight * total_variation_loss(combination_image)
returnloss
损失与梯度计算
@tf.function
defcompute_loss_and_grads(combination_image, base_image, style_reference_image):
withtf.GradientTape()astape:
loss = compute_loss(combination_image, base_image, style_reference_image)
grads = tape.gradient(loss, combination_image)
returnloss, grads
训练循环
每100次迭代,减小学习率,并保存一次结果图片
# 定义优化器
optimizer = keras.optimizers.SGD(
keras.optimizers.schedules.ExponentialDecay(
initial_learning_rate=100.0, decay_steps=100, decay_rate=0.96
)
)
base_image = preprocess_image(base_image_path)
style_reference_image = preprocess_image(style_reference_image_path)
combination_image = tf.Variable(preprocess_image(base_image_path))
iterations =4000
foriinrange(1, iterations +1):
loss, grads = compute_loss_and_grads(
combination_image, base_image, style_reference_image
)
optimizer.apply_gradients([(grads, combination_image)])
# 每隔100次迭代,打印损失,输出一次结果图片
ifi %100==0:
print("Iteration %d: loss=%.2f"% (i, loss))
img = deprocess_image(combination_image.numpy())
fname = result_prefix +"_at_iteration_%d.png"% i
# 保存结果图片
keras.preprocessing.image.save_img(fname, img)
结果展示
到了这里,我们所有的操作都完成了,下面我们来看一下结果。
emmm,风格有些独特。
总结
今天我们介绍了图像风格迁移的先驱之一Leon A. Gatys在2015年的神经风格迁移网络,感兴趣的同学可以自己试一试,创作自己的艺术图片。