风格迁移(Style Transfer)中直方图匹配(Histogram Match)的作用

前言

风格迁移是神经网络深度学习中比较重要且有趣的一个项目。如果不知道什么是风格迁移的请参考这篇文章:https://oldpan.me/archives/pytorch-neural-transfer

本文参考论文:Stable and Controllable Neural Texture Synthesis and Style Transfer Using Histogram Losses

正文

gram matrix

风格迁移(Style Transfer)中我们使用了很多损失函数,最主要的损失函数是在内容层的L2损失以及在风格层的Gram(格拉姆矩阵)损失,Gram损失即利用原图和目标的gram矩阵进行比较得到的损失。Gram矩阵即是简单的一个数据(比如一张图片)中内部元素相乘的矩阵乘法,获取该数据的内在特征,原因很简单,一个数据的内在特征两两相乘后,特殊的特征(元素值比较大)会更大,而元素值比较小的特征在两两相乘后也会变小,所以这个矩阵得到的效果即是,放大数据的特征,得到该数据的纹理细节,从而方便比较:

上面是Gram Matrix(格拉姆矩阵),但是gram是不稳定的,在实际过程中需要人工进行调参才可以得到不错的结果:

如上图,a图为输入图像,b图为通过输入图像a经过gram矩阵仿制出来的,很明显这个gram矩阵很不稳定,导致图片纹理模糊不清楚,而c图则是在经过调参后得到的不错的效果图,但是仍然可以从其中看到一些模糊和细节丢失的痕迹。

为什么会gram矩阵会出现这些问题,原因在于gram矩阵在读取对象本身的特征同时对这个对象本身的分布并不“感冒”。

举个例子,上面的两幅图中,左边的图的分布比较均匀,可以得到该分布的均值是0.707、而方差是0。而右图的均值是1/2,方差也是1/2,这两张图我们可以当做风格迁移中某一个特征图中的一个层,代表了不同对象的特征信息。为什么要说这两张图,因为在对对这两个不同的图进行计算后,发现,这两张图的gram矩阵的值是一样的!

这两张图的gram matrix信息竟然一样,在本文参考的论文中有一些公式论证,这里直接说结论:我们可以在保持gram matrix矩阵值不变的情况下,改变这两张图的分布的方差,这也就是为什么gram matrix矩阵不稳定的原因。

Histogram Loss

这时就需要Histogram Loss来实现更好的texture transfer-风格迁移。

为什么用Histogram,之前我们说过gram loss不稳定是因为其对所提取对象的分部信息“不感冒”,所以我们利用Histogram来进行修改,因为直方图代表的信息就是分布。

这篇文章主要说直方图匹配,另外还有一篇文章是说直方图损失,可以与这篇文章进行相互补充:传送门

利用直方图提取对象分布信息再结合gram来实现风格的迁移。听起来挺酷,但是实现起来就需要稍微换一个方向。

我们利用这个公式:

直方图匹配和直方图均衡这两个概念应该都比较熟悉,在数字图像处理中是比较常见的算法,opencv就有直方图均衡的算法。

python代码的直方图匹配代码

这里给出通过python实现的直方图匹配算法与pytorch一块使用,输入为tensor型变量,patch为直方图bin分割数,stride为移动步数。

输出为input相对target的匹配。corresponding为相关参数。

该代码参考其cuda代码实现:https://github.com/luanfujun/deep-painterly-harmonization/blob/master/cuda_utils.cu

def histogram_match(input, target, patch, stride):
    n1, c1, h1, w1 = input.size()
    n2, c2, h2, w2 = target.size()
    input.resize_(h1 * w1 * h2 * w2)
    target.resize_(h2 * w2 * h2 * w2)
    conv = torch.tensor((), dtype=torch.float32)
    conv = conv.new_zeros((h1 * w1, h2 * w2))
    conv.resize_(h1 * w1*h2 * w2)
    assert c1 == c2, 'input:c{} is not equal to target:c{}'.format(c1, c2)
    size1 = h1 * w1
    size2 = h2 * w2
    N = h1 * w1 * h2 * w2
    print('N is', N)
 for i in range(0, N):
        i1 = i / size2
        i2 = i % size2
        x1 = i1 % w1
        y1 = i1 / w1
        x2 = i2 % w2
        y2 = i2 / w2
        kernal_radius = int((patch - 1) / 2)
        conv_result = 0
        norm1 = 0
        norm2 = 0
        dy = -kernal_radius
        dx = -kernal_radius
 while dy <= kernal_radius:
 while dx <= kernal_radius:
                xx1 = x1 + dx
                yy1 = y1 + dy
                xx2 = x2 + dx
                yy2 = y2 + dy
 if 0 <= xx1 < w1 and 0 <= yy1 < h1 and 0 <= xx2 < w2 and 0 <= yy2 < h2:
                    _i1 = yy1 * w1 + xx1
                    _i2 = yy2 * w2 + xx2
 for c in range(0, c1):
                        term1 = input[int(c * size1 + _i1)]
                        term2 = target[int(c * size2 + _i2)]
                        conv_result += term1 * term2
                        norm1 += term1 * term1
                        norm2 += term2 * term2
                dx += stride
            dy += stride
        norm1 = math.sqrt(norm1)
        norm2 = math.sqrt(norm2)
        conv[i] = conv_result / (norm1 * norm2 + 1e-9)
    match = torch.tensor((), dtype=torch.float32)
    match = match.new_zeros(input.size())
    correspondence = torch.tensor((), dtype=torch.int16)
    correspondence.new_zeros((h1, w1, 2))
    correspondence.resize_(h1*w1*2)
 for id1 in range(0, size1):
        conv_max = -1e20
 for y2 in range(0, h2):
 for x2 in range(0, w2):
                id2 = y2 * w2 + x2
 id = id1 * size2 + id2
                conv_result = conv[id1]
 if conv_result > conv_max:
                    conv_max = conv_result
                    correspondence[id1 * 2 + 0] = x2
                    correspondence[id1 * 2 + 1] = y2
 for c in range(0, c1):
                        match[c * size1 + id1] = target[c * size2 + id2]
    match.resize_((n1, c1, h1, w1))
 return match, correspondence

参考文章

Stable and Controllable Neural Texture Synthesis and Style Transfer Using Histogram Losses

文章来自Oldpan博客,欢迎关注!

https://oldpan.me/

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏机器学习养成记

特征工程(一):前向逐步回归(R语言)

“ 建模过程中,选择合适的特征集合,可以帮助控制模型复杂度,防止过拟合等问题。为了选取最佳的特征集合,可以遍历所有的列组合,找出效果最佳的集合,但这样需要大量的...

41111
来自专栏瓜大三哥

基于FPGA的非线性滤波器(一) 之概述

一类比较重要的非线性滤波器就是统计排序滤波器。 统计排序滤波器对窗口内的像素值进行排序并通过多路选择选择器选择排序后的值,例如中值滤波、最大/最...

1929
来自专栏数值分析与有限元编程

可视化 | MATLAB划分均匀矩形网格

之前发过一个划分均匀三角形网格的例子。下面结合一个悬臂梁说说如何在规则区域划分均匀矩形网格。 ? 将一个矩形平面区域划分成相同大小的矩形。X方向等分nex,Y方...

5659
来自专栏Python小屋

Python使用空域融合技术进行图像去噪

本文要点在于Python内置函数和扩展库pillow的用法。图像空域融合的主要思路是:把所有含有随机噪点的临时图像中对应位置像素值的平均值作为最终像素值,生成结...

4498
来自专栏人工智能LeadAI

pytorch入门教程 | 第五章:训练和测试CNN

我们 按照 pytorch入门教程(四):准备图片数据集准备好了图片数据以后,就来训练一下识别这10类图片的cnn神经网络吧。 按照 pytorch入门教程(三...

84010
来自专栏海天一树

决策树

决策树是一种特殊的树形结构,一般由节点和有向边组成。其中,节点表示特征、属性或者一个类。而有向边包含有判断条件。如图所示,决策树从根节点开始延伸,经过不同的判断...

2852
来自专栏ATYUN订阅号

重新调整Keras中长短期记忆网络的输入数据

你可能很难理解如何为LSTM模型的输入准备序列数据。你可能经常会对如何定义LSTM模型的输入层感到困惑。也可能对如何将数字的1D或2D矩阵序列数据转换为LSTM...

2494
来自专栏数据结构与算法

09:矩阵乘法

09:矩阵乘法 总时间限制: 1000ms 内存限制: 65536kB描述 计算两个矩阵的乘法。n*m阶的矩阵A乘以m*k阶的矩阵B得到的矩阵C 是n*k阶的...

4955
来自专栏瓜大三哥

K-近邻算法(KNN)概述

最简单最初级的分类器是将全部的训练数据所对应的类别都记录下来,当测试对象的属性和某个训练对象的属性完全匹配时,便可以对其进行分类。但是怎么可能所有测试对象都会找...

2648
来自专栏iOSDevLog

估计器接口小结摘自:《Python 机器学习基础教程》 第3章 无监督学习与预处理(三)

scikit-learn 中的所有算法——无论是预处理、监督学习还是无监督学习算法——都被实现为类。这些类在 scikit-learn 中叫作估计器(estim...

1532

扫码关注云+社区

领取腾讯云代金券