风格迁移 Style transfer

一、介绍

  • 将一张图片的艺术风格应用在另外一张图片上

风格迁移结果

  • 使用深度卷积网络CNN提取一张图片的内容和提取一张图片的风格, 然后将两者结合起来得到最后的结果

二、 方法

提取图片内容和风格并重构
- 我们知道 CNN 可以捕捉图像的高层次特征,如上图所示,内容图片经过CNN可以得到对应的图像表述(representation, 就是经过卷积操作的feature map),然后经过重构可以得到近似原图的效果
- 特别是前面几层经过重构得到的结果和原图更接近,也说明前几层保留的图片细节会更多,因为后面还有pooling层,自然会丢弃调一些信息
- 这里的网络使用的是VGG-16 (如下图),包含 13 个卷积层,3 个全连接层
vgg-16结构

1、内容损失

  • 假设一个卷积层包含 Nl N l 个过滤器 filters,则可以得到 Nl N l feature maps,假设 feature map的大小是 Ml M l (长乘宽),则可以通过一个矩阵来存储 l 层的数据
    FlRNl×Ml F l ∈ R N l × M l

    • Fli,j F i , j l 表示第 l 层的第 ifilterj 位置上的激活值
  • 所以现在一张内容图片 p p → ,一张生成图片 x x → (初始值为高斯分布), 经过一层卷积层l可以得到其对应的特征表示: Pl P l Fl F l , 则对应的损失采用均方误差:
    Lcontent(p,x,l)=12ij(FlijPlij)2 L c o n t e n t ( p → , x → , l ) = 1 2 ∑ i j ( F i j l − P i j l ) 2

    • F F P是两个矩阵,大小是 Nl×Ml N l × M l ,即l层过滤器的个数 和 feature map 的长乘宽的值

2、风格损失

  • 风格的表示这里采用格拉姆矩阵(Gram Matrix): GlRNl×Nl G l ∈ R N l × N l
    Glij=kFlikFljk G i j l = ∑ k F i k l F j k l

    • 格拉姆矩阵计算的是两两特征的相关性 , 即哪两个特征是同时出现的,哪两个特征是此消彼长的等,能够保留图像的风格
    • ( 比如一幅画中有人和树,它们可以出现在任意位置,格拉姆矩阵可以衡量它们之间的关系,可以认为是这幅画的风格信息 )
  • 假设 a a → 是风格图像, x x → 是生成图像, Al A l Gl G l 表示在 l l 层的格拉姆矩阵,则这一层的损失为:

    El=14Nl2Ml2i,j(GijlAijl)2

  • 提取风格信息是我们会使用多个卷积层的输出,所以总损失为:

    Lstyle(a,x)=lLwlEl L s t y l e ( a → , x → ) = ∑ l L w l E l

    • 这里 wl w l 是每一层损失的权重

3、总损失函数

  • 通过白噪声初始化(就是高斯分布)一个输出的结果,然后通过网络对这个结果进行风格和内容两方面的约束进行修正
    Ltotal(p,a,x)=αLcontent(p,x)+βLstyle(a,x) L t o t a l ( p → , a → , x → ) = α L c o n t e n t ( p → , x → ) + β L s t y l e ( a → , x → )

三、代码实现

1、说明

  • 全部代码:点击查看
  • 图像使用一张建筑图和梵高的星空
    建筑
    星空

2、加载并预处理图片和初始化输出图片

  • 输出图片采用高斯分布初始化
import numpy as np
from keras import backend as K
from keras.applications.vgg16 import preprocess_input
from keras.preprocessing.image import load_img, img_to_array

from keras.applications import VGG16
from scipy.optimize import fmin_l_bfgs_b
from matplotlib import pyplot as plt

'''图片路径'''
content_image_path = './data/buildings.jpg'
style_image_path = './data/starry-sky.jpg'
generate_image_path = './data/output.jpg'


'''加载图片并初始化输出图片'''
target_height = 512
target_width = 512
target_size = (target_height, target_width)

content_image = load_img(content_image_path, target_size=target_size)
content_image_array = img_to_array(content_image)
content_image_array = K.variable(preprocess_input(np.expand_dims(content_image_array, 0)), dtype='float32')

style_image = load_img(style_image_path, target_size=target_size)
style_image_array = img_to_array(style_image)
style_image_array = K.variable(preprocess_input(np.expand_dims(style_image_array, 0)), dtype='float32')

generate_image = np.random.randint(256, size=(target_width, target_height, 3)).astype('float64')
generate_image = preprocess_input(np.expand_dims(generate_image, 0))
generate_image_placeholder = K.placeholder(shape=(1, target_width, target_height, 3))

3、获取网络中对应层的输出

def get_feature_represent(x, layer_names, model):
    '''图片的特征图表示

    参数
    ----------------------------------------------
    x : 输入,
        这里并没有使用,可以看作一个输入的标识
    layer_names : list
        CNN网络层的名字
    model : CNN模型

    返回值
    ----------------------------------------------
    feature_matrices : list
        经过CNN卷积层的特征表示,这里大小是(filter个数, feature map的长*宽)

    '''
    feature_matrices = []
    for ln in layer_names:
        select_layer = model.get_layer(ln)
        feature_raw = select_layer.output
        feature_raw_shape = K.shape(feature_raw).eval(session=tf_session)
        N_l = feature_raw_shape[-1]
        M_l = feature_raw_shape[1]*feature_raw_shape[2]
        feature_matrix = K.reshape(feature_raw, (M_l, N_l))
        feature_matrix = K.transpose(feature_matrix)
        feature_matrices.append(feature_matrix)
    return feature_matrices

4、内容损失函数

def get_content_loss(F, P):
    '''计算内容损失

    参数
    ---------------------------------------
    F : tensor, float32
        生成图片特征图矩阵
    P : tensor, float32
        内容图片特征图矩阵

    返回值
    ---------------------------------------
    content_loss : tensor, float32
        内容损失
    '''
    content_loss = 0.5*K.sum(K.square(F-P))
    return content_loss

5、Gram矩阵和风格损失

def get_gram_matrix(F):
    '''计算gram矩阵'''
    G = K.dot(F, K.transpose(F))
    return G

def get_style_loss(ws, Gs, As):
    '''计算风格损失

    参数
    ---------------------------------------
    ws : array
         每一层layer的权重
    Gs : list
         生成图片每一层得到的特征表示组成的list
    As : list
         风格图片每一层得到的特征表示组成的list
    '''
    style_loss = K.variable(0.)
    for w, G, A in zip(ws, Gs, As):
        M_l = K.int_shape(G)[1]
        N_l = K.int_shape(G)[0]
        G_gram = get_gram_matrix(G)
        A_gram = get_gram_matrix(A)
        style_loss += w*0.25*K.sum(K.square(G_gram-A_gram))/(N_l**2*M_l**2)
    return style_loss

6、总损失

def get_total_loss(generate_image_placeholder, alpha=1.0, beta=10000.0):
    '''总损失
    '''
    F = get_feature_represent(generate_image_placeholder, layer_names=[content_layer_name], model=gModel)[0]
    Gs = get_feature_represent(generate_image_placeholder, layer_names=style_layer_names, model=gModel)
    content_loss = get_content_loss(F, P)
    style_loss = get_style_loss(ws, Gs, As)
    total_loss = alpha*content_loss + beta*style_loss
    return total_loss


def calculate_loss(gen_image_array):
    '''调用总损失函数,计算得到总损失数值'''
    if gen_image_array != (1, target_width, target_height, 3):
        gen_image_array = gen_image_array.reshape((1, target_width, target_height, 3))
    loss_fn = K.function(inputs=[gModel.input], outputs=[get_total_loss(gModel.input)])
    return loss_fn([gen_image_array])[0].astype('float64')

7、损失函数梯度

def get_grad(gen_image_array):
    '''计算损失函数的梯度'''
    if gen_image_array != (1, target_width, target_height, 3):
        gen_image_array = gen_image_array.reshape((1, target_width, target_height, 3))
    grad_fn = K.function([gModel.input], K.gradients(get_total_loss(gModel.input), [gModel.input]))
    grad = grad_fn([gen_image_array])[0].flatten().astype('float64')
    return grad

8、生成结果后处理

  • 因为之前preprocess_input函数中做了处理,这里进行逆处理还原
def postprocess_array(x):
    '''生成图片后处理,因为之前preprocess_input函数中做了处理,这里进行逆处理还原

    '''
    if x.shape != (target_width, target_height, 3):
        x = x.reshape((target_width, target_height, 3))
    x[..., 0] += 103.939
    x[..., 1] += 116.779
    x[..., 2] += 123.68

    x = x[..., ::-1]  # BGR-->RGB
    x = np.clip(x, 0, 255)
    x = x.astype('uint8')
    return x

9、定义模型并优化

'''定义VGG模型'''
tf_session = K.get_session()
cModel = VGG16(include_top=False, input_tensor=content_image_array)
sModel = VGG16(include_top=False, input_tensor=style_image_array)
gModel = VGG16(include_top=False, input_tensor=generate_image_placeholder)
content_layer_name = 'block4_conv2'
style_layer_names = [
    'block1_conv1',
    'block2_conv1',
    'block3_conv1',
    'block4_conv1'
]

'''得到对应的representation矩阵'''
P = get_feature_represent(x=content_image_array, layer_names=[content_layer_name], model=cModel)[0]
As = get_feature_represent(x=style_image_array, layer_names=style_layer_names, model=sModel)
ws = np.ones(len(style_layer_names))/float(len(style_layer_names))

'''使用fmin_l_bfgs_b进行损失函数优化'''
iterations = 600
x_val = generate_image.flatten()
xopt, f_val, info = fmin_l_bfgs_b(func=calculate_loss, x0=x_val, fprime=get_grad, maxiter=iterations, disp=True)

x_out = postprocess_array(xopt)

10、输出结果

  • 初始化输出图片

初始化输出图片

  • 迭代优化500轮结果
    迭代500轮结果

四、总结

  • style tranfer通过白噪声初始化(就是高斯分布)一个输出的结果,然后通过优化损失对这个结果进行风格内容两方面的约束修正
  • 图片的风格信息使用的是 Gram矩阵来表示
  • 其中超参数风格损失的权重ws、内容损失和风格损失的权重 α α , β β 可以进行调整查看结果
    • 论文给出的 βα=103104 β α = 10 3 或 10 4 结果较好,可以自己适当增加看看最后的结果

Reference

  • 6
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Style Transfer Multisource是一种风格迁移的技术,它可以将多个风格融合到一张图像中。传统的风格迁移算法只能将一种风格转移到目标图像上,而Multisource风格迁移则可以通过同时应用多个源风格,将多个风格的特征融合到目标图像中,使之具有多种风格的特点。 Multisource风格迁移的实现过程主要包括两个步骤:style extraction(风格提取)和style merging(风格融合)。 在风格提取阶段,系统需要从多个源图像中提取各自的风格特征。这可以通过预训练的卷积神经网络(CNN)模型来实现,通过提取图像的低层次和高层次特征,捕捉源图像的风格信息。 在风格融合阶段,系统需要将多个源图像的风格特征进行融合,生成目标图像。这可以通过使用加权平均的方法来实现,其中每个源图像的风格特征按照权重进行叠加。权重的选择取决于各自源图像的重要性以及用户的需求。 通过style transfer multisource技术,可以创造出独特的图像,具有多个不同源图像的风格特征。这种方法在艺术创作、图像编辑等领域具有潜在的应用价值。例如,可以将多个艺术家的风格融合到一张图像中,创造出独特的艺术作品。另外,在设计领域中,也可以将多个设计方案的特点融合到一张图像上,以便更好地展示不同设计风格的可能性。 当然,style transfer multisource技术还存在一些挑战,例如如何合理选择源图像的权重,以及如何平衡不同风格特征的融合关系等问题。随着研究的深入和技术的发展,相信这些问题将得到更好的解决,使得多源风格迁移在实际应用中更加广泛和有效。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值