画风迁移

  • 简单来说就是将另一张图像的绘画风格在不改变原图图像内容的情况下加入到原图像中,从而“创造”名家风格的绘画作品。
  • 这其中牵扯到很多难题,但是很多问题已经被一一攻克,一般而言,这类问题将涉及到图像风格的捕捉、图像风格的迁移、图像风格的组合等主要流程。
  • 整体看来,卷积核特征的捕捉及应用、图像相干性的优化、画风的组合将是难题。

图像风格捕捉

  • 原理

    • 通过将格拉姆矩阵(gram matrix)应用于卷积神经网络各层能够捕获该层的样式,所以,如果从填充了随机噪声的图像开始,对其进行优化使得网络各层的格拉姆矩阵与目标图像的格拉姆矩阵相匹配,那么不难理解,生成的图像将会模仿目标图像的风格。
    • 可以定义一个style损失函数,计算两组激活输出值各自减去格拉姆矩阵之后计算平方误差。在原始图像和目标图像(莫奈的《睡莲》)上进行训练,将两幅图片输入VGG16的卷积神经网络,对每个卷积层计算上述style损失并多层累加,对损失使用lbfgs进行优化(它需要梯度值和损失值进行优化)。
    • 其实,很多观点认为,匹配图像风格的最好方式是直接匹配所有层的激活值,事实上,格拉姆矩阵效果会更好一些,这点并不容易理解。其背后的原理是,通过计算给定层的每个激活值与其他激活值的乘积,我们获得了神经元之间的相关性。这些相关性可以理解为图像风格的编码,因为他们衡量了激活值的分布情况,而不是激活值本身。
    • 这也带来了几个问题。一个就是零值问题,在任一被乘数为零的情况下,一个向量和自己的转置的点积都会是零,模型无法在零值处识别相关性。由于零值频繁出现,可以在执行点积前为特征值添加一个小的差量delta,delta去-1即可。还有一个问题就是,我们计算了所有激活值的格拉姆矩阵,难道不是应该针对像素通道计算吗?事实上,我们为每个像素的通道都计算了格拉姆矩阵,然后观察它们在整个图像上的相关性,这样做提供了一个捷径:可以计算通道均值并将其用作格拉姆矩阵,这会帮助获得一幅平均风格的图像,更有普适性。
    • 同时,添加了总变分损失(total variation loss),要求网络时刻检查相邻像素的差异,否则,图像会趋于像素化且更加不平缓。某种程度上,这种方法与用于持续检查层权重与层输出的正则化过程非常类似,整体效果相当于在输出像素上添加了一个略微模糊的滤镜。(本质上,这是一种模糊化的方法)该部分的结果是将最后一个组成成分添加到了损失函数中,使得图像整体更像内容图像而不是风格图像。这里所做的,就是有效优化生成图像,使得上层的激活值对应图像内容,下层的激活值对应图像风格,也就是网络底层对应图像风格,网络高层对应图像内容,通过这种方式实现图像风格转换。
  • 代码
    • def gram_matrix(x):if K.image_data_format() != 'channels_first':x = K.permute_dimensions(x, (2, 0, 1))features = K.batch_flatten(x)return K.dot(features - 1, K.transpose(features - 1)) - 1
      

def style_loss(layer_1, layer_2):
gr1 = gram_matrix(layer_1)
gr2 = gram_matrix(layer_2)
return K.sum(K.square(gr1 - gr2)) / (np.prod(layer_2.shape).value ** 2)

w, h = 740, 468
style_image = K.variable(preprocess_image(style1_image_path, target_size=(h, w)))
result_image = K.placeholder(style_image.shape)
input_tensor = K.concatenate([style_image, result_image], axis=0)
print(input_tensor.shape)

model = vgg16.VGG16(input_tensor=input_tensor, weights=‘imagenet’, include_top=False)

# caculate loss
feature_outputs = [layer.output for layer in model.layers if ‘_conv’ in layer.name]
loss_style = K.variable(0.)

for idx, layer_features in enumerate(feature_outputs):
loss_style += style_loss(layer_features[0, :, :, :], layer_features[1, :, :, :])

class Evaluator(object):
def init(self, loss_total, result_image, **other):
grads = K.gradients(loss_total, result_image)
outputs = [loss_total] + list(other.values()) + grads
self.iterate = K.function([result_image], outputs)
self.other = list(other.keys())
self.other_values = {}
self.shape = result_image.shape

    self<span class="token punctuation">.</span>loss_value <span class="token operator">=</span> <span class="token boolean">None</span>self<span class="token punctuation">.</span>grads_values <span class="token operator">=</span> <span class="token boolean">None</span><span class="token keyword">def</span> <span class="token function">loss</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> x<span class="token punctuation">)</span><span class="token punctuation">:</span>outs <span class="token operator">=</span> self<span class="token punctuation">.</span>iterate<span class="token punctuation">(</span><span class="token punctuation">[</span>x<span class="token punctuation">.</span>reshape<span class="token punctuation">(</span>self<span class="token punctuation">.</span>shape<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">)</span>self<span class="token punctuation">.</span>loss_value <span class="token operator">=</span> outs<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span>self<span class="token punctuation">.</span>grad_values <span class="token operator">=</span> outs<span class="token punctuation">[</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span>flatten<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>astype<span class="token punctuation">(</span><span class="token string">'float64'</span><span class="token punctuation">)</span>self<span class="token punctuation">.</span>other_values <span class="token operator">=</span> <span class="token builtin">dict</span><span class="token punctuation">(</span><span class="token builtin">zip</span><span class="token punctuation">(</span>self<span class="token punctuation">.</span>other<span class="token punctuation">,</span> outs<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">:</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token keyword">return</span> self<span class="token punctuation">.</span>loss_value<span class="token keyword">def</span> <span class="token function">grads</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> x<span class="token punctuation">)</span><span class="token punctuation">:</span><span class="token keyword">return</span> np<span class="token punctuation">.</span>copy<span class="token punctuation">(</span>self<span class="token punctuation">.</span>grad_values<span class="token punctuation">)</span>

style_evaluator = Evaluator(loss_style, result_image)

def run(evaluator, image, num_iter=50):
for i in range(num_iter):
clear_output()
image, min_val, info = fmin_l_bfgs_b(evaluator.loss, image.flatten(), fprime=evaluator.grads, maxfun=20)
showarray(deprocess_image(image.copy(), h, w))
print(“Current loss value:”, min_val)
print(’ '.join(k + ‘:’ + str(evaluator.other_values[k]) for k in evaluator.other))
return image

x = np.random.uniform(0, 255, result_image.shape) - 128.
res = run(style_evaluator, x, num_iter=50)

def total_variation_loss(x, exp=1.25):
_, d1, d2, d3 = x.shape
if K.image_data_format() == ‘channels_first’:
a = K.square(x[:, :, :d2 - 1, :d3 - 1] - x[:, :, 1:, :d3 - 1])
b = K.square(x[:, :, :d2 - 1, :d3 - 1] - x[:, :, :d2 - 1, 1:])
else:
a = K.square(x[:, :d1 - 1, :d2 - 1, :] - x[:, 1:, :d2 - 1, :])
b = K.square(x[:, :d1 - 1, :d2 - 1, :] - x[:, :d1 - 1, 1:, :])
return K.sum(K.pow(a + b, exp))

loss_variation = total_variation_loss(result_image) / 5000
loss_with_variation = loss_variation + loss_style
evaluator_with_variation = Evaluator(loss_with_variation, result_image)

x = np.random.uniform(0, 255, result_image.shape) - 128.
res = run(evaluator_with_variation, x, num_iter=100)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 结果

    • 显然,使用总变分损失后,原来的噪声图像产生的结果更像一个内容图像了。
  • 图像风格迁移

    • 原理

      • 要想将捕获到的风格从一个图像应用到另一个图像上,需要使用一个损失函数来平衡一个图像的内容和另一个图像的风格。
      • 在现有图像上运行上面的代码是不难的,但是结果并不那么令人满意,它看起来似乎将风格应用到了现有图像上,但是程序的不断执行,原始图像不断分解,坚持运行,最后生成一幅与原图独立的新图像,显然,这不是想要。可以通过在损失函数中添加第三个组成成分来解决这个问题,该成分会考虑生成图像和参考图像的差异,这就形成了内容损失(content loss),其作用于网络最后一层。最后一层包含与网络所看到的的映像最近似的内容,因此这也是真正希望保持一致的内容。
      • 通过把各层在网络中的位置纳入考量来微调风格损失(style loss),希望较低层承载更多权重,因为较低层能够捕获更多的图像纹理/风格,也希望较高层更多地参与到图像内容的捕获之中。这样,算法更容易平衡图像内容(使用最后一层)和图像风格(主要较低层)。最后,平衡损失函数的三个部分。
    • 代码
      • def content_loss(base, combination):return K.sum(K.square(combination - base))
        

    w, h = load_img(base_image_path).size
    base_image = K.variable(preprocess_image(base_image_path))
    style_image = K.variable(preprocess_image(style2_image_path, target_size=(h, w)))
    combination_image = K.placeholder(style_image.shape)
    input_tensor = K.concatenate([base_image, style_image, combination_image], axis=0)

    model = vgg16.VGG16(input_tensor=input_tensor, weights=‘imagenet’, include_top=False)

    feature_outputs = [layer.output for layer in model.layers if ‘_conv’ in layer.name]
    loss_content = content_loss(feature_outputs[-1][0, :, :, :], feature_outputs[-1][2, :, :, :])
    loss_variation = total_variation_loss(combination_image)
    loss_style = K.variable(0.)

    for idx, layer_features in enumerate(feature_outputs):
    loss_style += style_loss(layer_features[1, :, :, :], layer_features[2, :, :, :]) * (0.5 ** idx)

    loss_content /= 40
    loss_variation /= 10000
    loss_total = loss_content + loss_variation + loss_style

    combined_evaluator = Evaluator(loss_total, combination_image, loss_content=loss_content, loss_variation=loss_variation, loss_style=loss_style)
    run(combined_evaluator, preprocess_image(base_image_path), num_iter=100)

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
  • 结果
    • 素材

    • 通过梵高的《星空》作为风格图,将其风格迁移到阿姆斯特丹的老教堂图片上,效果如下(这是风格迁移很经典的图)。
  • 图像风格内插

    • 原理

      • 已经捕获了两种图像风格,希望在另一幅图像上应用一种介于两者之间的风格,如何做呢?可以使用一个包含浮点数值的损失函数,浮点数值表示每种风格应用的百分比。
      • 加载两种不同风格的图像,为每种风格创建损失值,然后引入占位符summerness,调控百分比来调整各种风格的占比。
      • 通过在损失变量中再次增加成分,使得可以指定两种不同风格的权重,当然,也可以进一步增加更多的风格图像并调整权重,但是要注意下调那些“压制”效果好的风格比重,以免其影响较大。
    • 代码
      • w, h = load_img(base_image_path).size
        base_image = K.variable(preprocess_image(base_image_path))
        winter_style_image = K.variable(preprocess_image('data/road-to-versailles-at-louveciennes.jpg', target_size=(h, w)))
        summer_style_image = K.variable(preprocess_image('data/VanGogh_Farmhouse.jpeg', target_size=(h, w)))
        combination_image = K.placeholder(summer_style_image.shape)
        input_tensor = K.concatenate([base_image, summer_style_image, winter_style_image, combination_image], axis=0)
        

    model = vgg16.VGG16(input_tensor=input_tensor, weights=‘imagenet’, include_top=False)

    feature_outputs = [layer.output for layer in model.layers if ‘_conv’ in layer.name]
    loss_content = content_loss(feature_outputs[-1][0, :, :, :], feature_outputs[-1][2, :, :, :])
    loss_variation = total_variation_loss(combination_image)

    loss_style_summer = K.variable(0.)
    loss_style_winter = K.variable(0.)
    for idx, layer_features in enumerate(feature_outputs):
    loss_style_summer += style_loss(layer_features[1, :, :, :], layer_features[-1, :, :, :]) (0.5 ** idx)
    loss_style_winter += style_loss(layer_features[2, :, :, :], layer_features[-1, :, :, :]) (0.5 ** idx)

    loss_content /= 40
    loss_variation /= 10000

    summerness = K.placeholder()
    loss_total = (loss_content + loss_variation + loss_style_summer summerness + loss_style_winter (1 - summerness))

    combined_evaluator = Evaluator(loss_total,
    combination_image,
    loss_content=loss_content,
    loss_variation=loss_variation,
    loss_style_summer=loss_style_summer,
    loss_style_winter=loss_style_winter)
    iterate = K.function([combination_image, summerness], combined_evaluator.iterate.outputs)
    combined_evaluator.iterate = lambda inputs: iterate(inputs + [1.0]) # 1.0夏天风格
    res = run(combined_evaluator, preprocess_image(base_image_path), num_iter=50)

    path = ‘data/summer_winter_%d/20.jpg’
    def save(res, step):
    img = deprocess_image(res.copy(), h, w)
    imsave(path % step, img)

    for step in range(1, 21, 1):
    combined_evaluator = Evaluator(loss_total,
    combination_image,
    loss_content=loss_content,
    loss_variation=loss_variation,
    loss_style_summer=loss_style_summer,
    loss_style_winter=loss_style_winter)
    iterate = K.function([combination_image, summerness], combined_evaluator.iterate.outputs)
    combined_evaluator.iterate = lambda inputs: iterate(inputs + [1.0 - step / 20.]) # 0.05-1.0的夏天风格的图像
    res = run(combined_evaluator, preprocess_image(base_image_path), num_iter=50)
    save(res, step)

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
  • 结果
    • 素材

    • 通过调控上述两个风格的比例,设定summer风格100%,结果如下。
    • 通过循环调控夏天风格的比例,产生多幅图像,组合形成动态图,如下。
  • 补充说明

    • 参考书籍《深度学习实战:Deep Learning Cookbook》。
    • 具体项目代码上传至我的Github,欢迎Star或者Fork。
    • 博客同步至个人博客网站,欢迎查看。
                                    </div><link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-b6c3c6d139.css" rel="stylesheet"><div class="more-toolbox"><div class="left-toolbox"><ul class="toolbox-list"><li class="tool-item tool-active is-like "><a href="javascript:;"><svg class="icon" aria-hidden="true"><use xlink:href="#csdnc-thumbsup"></use></svg><span class="name">点赞</span><span class="count">1</span></a></li><li class="tool-item tool-active is-collection "><a href="javascript:;" data-report-click="{&quot;mod&quot;:&quot;popu_824&quot;}"><svg class="icon" aria-hidden="true"><use xlink:href="#icon-csdnc-Collection-G"></use></svg><span class="name">收藏</span></a></li><li class="tool-item tool-active is-share"><a href="javascript:;"><svg class="icon" aria-hidden="true"><use xlink:href="#icon-csdnc-fenxiang"></use></svg>分享</a></li><!--打赏开始--><!--打赏结束--><li class="tool-item tool-more"><a><svg t="1575545411852" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5717" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M179.176 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5718"></path><path d="M509.684 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5719"></path><path d="M846.175 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5720"></path></svg></a><ul class="more-box"><li class="item"><a class="article-report">文章举报</a></li></ul></li></ul></div></div><div class="person-messagebox"><div class="left-message"><a href="https://blog.csdn.net/zhouchen1998"><img src="https://profile.csdnimg.cn/3/2/E/3_zhouchen1998" class="avatar_pic" username="zhouchen1998"><img src="https://g.csdnimg.cn/static/user-reg-year/1x/2.png" class="user-years"></a></div><div class="middle-message"><div class="title"><span class="tit"><a href="https://blog.csdn.net/zhouchen1998" data-report-click="{&quot;mod&quot;:&quot;popu_379&quot;}" target="_blank">周先森爱吃素</a></span><span class="flag expert"><a href="https://blog.csdn.net/home/help.html#classicfication" target="_blank"><svg class="icon" aria-hidden="true"><use xlink:href="#csdnc-blogexpert"></use></svg>博客专家</a></span></div><div class="text"><span>发布了231 篇原创文章</span> · <span>获赞 144</span> · <span>访问量 13万+</span></div></div><div class="right-message"><a href="https://im.csdn.net/im/main.html?userName=zhouchen1998" target="_blank" class="btn btn-sm btn-red-hollow bt-button personal-letter">私信</a><a class="btn btn-sm  bt-button personal-watch" data-report-click="{&quot;mod&quot;:&quot;popu_379&quot;}">关注</a></div></div></div>
    </article>
    

图像风格迁移(动手学深度学习)相关推荐

  1. 动手学深度学习之图像风格迁移

    参考伯禹学习平台<动手学深度学习>课程内容内容撰写的学习笔记 原文链接:https://www.boyuai.com/elites/course/cZu18YmweLv10OeV/less ...

  2. 李沐d2l《动手学深度学习》第二版——风格迁移源码详解

    本文是对李沐Dive to DL<动手学深度学习>第二版13.12节风格迁移的源码详解,整体由Jupyter+VSCode完成,几乎所有重要代码均给出了注释,一看就懂.需要的同学可以在文末 ...

  3. 动手学深度学习Pytorch Task07

    本节课内容目标检测基础.图像风格迁移.图像分类案例1 一.目标检测基础 锚框 目标检测算法通常会在输入图像中采样大量的区域,然后判断这些区域中是否包含我们感兴趣的目标,并调整区域边缘从而更准确地预测目 ...

  4. 资源 | 李沐等人开源中文书《动手学深度学习》预览版上线

    来源:机器之心 本文约2000字,建议阅读10分钟. 本文为大家介绍了一本交互式深度学习书籍. 近日,由 Aston Zhang.李沐等人所著图书<动手学深度学习>放出了在线预览版,以供读 ...

  5. 《动手学深度学习》PyTorch版本

    Dive-Into-Deep-Learning-PyTorch-PDF 简介   本项目对中文版<动手学深度学习>中的代码进行整理,并参考一些优秀的GitHub项目给出基于PyTorch的 ...

  6. 【深度学习】李沐《动手学深度学习》的PyTorch实现已完成

    这个项目是中文版<动手学深度学习>中的代码进行整理,用Pytorch实现,是目前全网最全的Pytorch版本. 项目作者:吴振宇博士 简介   Dive-Into-Deep-Learnin ...

  7. 送10本今年最火的《动手学深度学习》

    点击我爱计算机视觉标星,更快获取CVML新技术 52CV曾经多次介绍FlyAI机器学习竞赛平台,不少粉丝也曾在FlyAI拿到现金奖励. 本次52CV & FlyAI联合送书,CV君查找了两天, ...

  8. 【动手学深度学习】(task123)注意力机制剖析

    note 将注意力汇聚的输出计算可以作为值的加权平均,选择不同的注意力评分函数会带来不同的注意力汇聚操作. 当查询和键是不同长度的矢量时,可以使用可加性注意力评分函数.当它们的长度相同时,使用缩放的& ...

  9. 《动手学深度学习》—学习笔记

    文章目录 深度学习简介 起源 特点 小结 预备知识 获取和运行本书的代码 pytorch环境安装 方式一 方式二 数据操作 创建 运算 广播机制 索引 运算的内存开销 NDArray和NumPy相互变 ...

  10. 动手学深度学习Kaggle:图像分类 (CIFAR-10和Dog Breed Identification), StratifiedShuffleSplit,数据集划分

    目录 CIFAR-10 获取并组织数据集 下载数据集 整理数据集 组织数据集更一般的方式 图像增广 读取数据集 torchvision.datasets.ImageFolder()的特点 定义模型 定 ...

最新文章

  1. 计算机组成原理——概述3
  2. 51单片机系列——基础指令
  3. Java_案例实例1.简单的人机交互
  4. Git 之五 通信协议(HTTPS、SSH、Git)、使用远程仓库(GitHub、GitLab、Gitee等)
  5. 了解mysqlpump工具
  6. GDCM:制作模板的测试程序
  7. 初识Mysql(part14)--我需要知道的6个关于创建表的小知识
  8. C# 获取Excel中的合并单元格
  9. 可爱的朋友,你是否有很多问号
  10. 【C语言】赋值运算中的类型转换
  11. 张家口全国计算机等级考试,河北省张家口市2018年上半年计算机等级考试公告...
  12. 重磅!开放EasyCharts插件源代码!
  13. 微信小程序人脸识别认证-微信开放接口
  14. “开心偷菜”一梦8年终成空,终究还是输给了时间和规则!
  15. 王者服务器维护公告2月,王者荣耀2.28最新维护公告 2月28日更新维护到几点
  16. python 录音左右声道_Python分离立体声wav压缩文件的左右声道
  17. 嵌入式C语言学习笔记附图
  18. ARM920T内存管理单元MMU
  19. 2022-2028年中国素食行业竞争策略研究及未来前景展望报告
  20. android-sdk-windows版本下载

热门文章

  1. 查看串口波特率linux,linux查看波特率
  2. 可控硅的原理是什么,它有哪些作用
  3. 解决安装Ubuntu16.04时因NVIDIA显卡造成的卡在开机logo界面的问题
  4. 【PythonPlanet】数据清洗原则:完全合一
  5. RJ45带网络变压器
  6. 服务端性能测试-为什么一般性能测试都是在局域网中进行的
  7. oracle中的iw和ww学习
  8. 医疗器械电磁兼容(EMC)设计基础培训班整理
  9. 中国神华 [601088]:行业景气 估值合理
  10. qpython如何安装库_qpython3如何安装库