升维的艺术:Bilateral Grid

如题所述

最近在看一些古老的 tone mapping 文章,看到一个之前经常看到却一直没深入学习的技术:Bilateral Grid (双边网格),后面仔细了解了下,发现是个很有趣的东西。

Bilateral Filter (双边滤波)高斯滤波

故事要从高斯滤波说起。

学过图像处理的同学对高斯滤波 (高斯模糊) 都不陌生,这是一种常用于图像去噪、图像平滑的技术。和所有图像滤波算法一样,它会对每一个像素取周边像素的平均值,来对像素值进行平均。在数值上是一种平滑,表现在图像上就相当于模糊的效果。

不同的滤波算法区别就在于取平均值的时候,不同像素的权重如何分配。高斯滤波的方法是根据高斯函数 (正态分布) 来决定权重,离得近的像素分配的权重大,远的像素分配的权重小,相当于像素值只跟它附近的像素进行较大的平均,因此图像内容的基本结构能被保护下来。

高斯滤波的公式:

翻译一下就是,在当前像素的领域内,逐个计算每个像素的权重,最后加权平均起来,得到滤波后的像素值。其中 就是高斯函数:

高斯函数的输入参数表示两个像素之间的距离,距离越小,函数值越大,对应的权重越大,具体可以参照下面的函数图像:

不过,高斯滤波有个很大的问题,就是当某个像素值周围存在跟它差别很大的像素时,二者容易互相平均,导致像素值发生很大的变化。

图中离群的像素就是被「带偏」的结果,它表现在图像中就是边缘被磨没了,这是我们不愿意看到的。

产生这种现象的原因在于高斯滤波器是各向同性的,在取权重的时候,每个方向一视同仁,只跟位置有关,跟像素值无关。

因此,一种很好的改进方法是双边滤波 (Bilateral Filter)。它在计算权重的时候,把像素值的大小关系也考虑进去:

上面就是双边滤波的基本公式。读者很容易发现,它跟高斯滤波最大的区别就是加入了一个新的高斯函数,这个高斯函数的输入是相邻两个像素的差值。加入这个函数后,最终的权重就是位置和像素值共同组合的结果了,因此边缘上的像素点可以被它周围像素值接近的像素「拉回去」,实现保边的效果。

双边滤波虽然好用,但它有个致命弱点:慢!太慢了!

一张 1080P 的图片,高斯滤波秒出,双边滤波却可能跑上十分钟。

为什么差距这么大?

一个很重要的原因是:高斯滤波是线性的,二维的高斯滤波可以拆解为两个一维的高斯滤波:

而且,一旦 kernel 半径、方差 这些参数确定,高斯核就是确定的,这很利于并行处理 (GPU/SIMD/neon)。

另外,高斯滤波还可以通过卷积定理变成傅立叶运算,后者一个矩阵相乘就完事了。

但双边滤波中的另一个核函数 是依赖像素值的,不同的位置都会变化,属于非线性滤波器,无法拆解成单个滤波器,每次运算也要频繁查表 (查找对应像素值),非常不利于并行化,所以速度贼慢!

Bilateral Grid (双边网格)

上面铺垫了这么多,终于可以讲到今天的主角了。

双边滤波其实就是新增了一个高斯函数,它慢的原因在于像素值「range」这个滤波器,不像位置「space」那样固定,需要频繁去原图上寻找像素值。那有没有办法把「range」变得像「space」一样呢?

下面就请出今天的主角:Bilateral Grid。

为了方便讲解,下面仍然用一维信号举例,读者可以自行拓展到二维。

文章标题叫「升维的艺术」,聪明的读者应该领悟到了,Bilateral Grid 就是把像素也拉成一个新的维度:

为了获得更好的全局视野,我们先对双边滤波的公式做一点变换。

公式 (3) 可以拆成像素和以及权重和两个部分:

把 都拿到左边:

最终得到:

为什么要做这种看似「废话」的转换,后面我们就会明白。

现在,我们把像素这个维度也拉升上来,把像素值也变成一种「位置」:

这个升维后的东西就叫Bilateral Grid (双边网格)。它的横坐标仍然是图像信号原本的位置,纵坐标则变成像素值的「坐标」。

从这个视角看,针对「space」的高斯函数作用在横坐标上,而针对「range」的高斯函数,则是直接作用在纵坐标上!通过这个升维的方式,我们不用再查表获取像素值了,直接用 2D 的高斯滤波处理升维后的一维信号就可以 (2D 图像的话,则是用 3D 高斯滤波来处理)。

来细化一下这个过程。

我们需要求的是两个东西:和 ,前者是像素经过双边滤波的加权和,后者是加权和要除的权重和。因此,我们对原始信号升维后,需要得到两个升维后的网格:

左边的网格wi 就是直接把像素值投影到新的维度,数值越大 (越往上) 的像素点越亮,反之则越暗。这个网格是为了之后计算 。

右边的网格w 则表示当前这个位置是否有像素存在,如果有,数值就是 1 (图中白点),否则数值为 0 (图中黑色部分)。很显然,每个纵向上只会有一个白点,表示像素投影到新维度后的位置,而绝大部分区域都是 0。这个网格是为了之后计算 。

接下来,我们会用两个一样的 2D 高斯滤波分别处理这两个网格,分别得到每个像素点的 和 。

其实就是执行我们前面转换出来的公式:

紧接着,用每个像素点的 除以每个像素点的 ,就已经得到双边滤波的结果了。最后,再根据最开始创建的 w 网格,在值为 1 的地方把像素点抠出来,重新降维成一维信号即可。

整个过程串起来后,总共分为三步:网格创建、网格处理、网格切片 (slice)。

以上就是 Bilateral Grid 加速双边滤波的过程了,通过这种方式,双边滤波可以在一个更高的维度,用高斯滤波直接处理。这样一来,原本高斯滤波的所有加速手段,也都能用到双边滤波上。

最后,我们再来审视下,这种方法实现双边滤波的关键在哪里呢?

关键就在这一步:

我们创建好网格,如果用卷积对这个网格进行高斯滤波,那么,由于高斯滤波器的 kernel 大小是有限的,因此,对当前像素而言,周围那些像素值差得较远的邻居像素,高斯滤波器是作用不到,自然就不会受它们影响,也就实现了双边滤波的作用。

细心的读者可能会有疑问:如果是个 3x3 大小的滤波器,那只要邻居的像素值相差超过 2,滤波器就作用不到了,但正常的双边滤波,这样的像素值也是会参与权值运算的,这样用双边网格会不会出现误差呢?

针对这个问题,需要明确一点:双边网格实现的双边滤波,和普通双边滤波的结果不是完全一致的,会有轻微的误差,滤波器的大小以及方差无疑也是影响因素。不过,当像素的差值超过某个范围后,高斯滤波器计算的结果就很小了,对于这样的像素值,即使高斯滤波器覆盖不到,结果影响也不大。通常来说,会把高斯滤波器的大小设置为 大小,这样误差通常可以忽略了。

这篇文章主要介绍了 Bilateral Grid 这种用于加速双边滤波的算法,核心思想就是通过升维的方式,达到空间换时间的目的。这样可以把原本不可分的滤波器,拆分成几个一维的高斯滤波器 (对于二维图像,通常是三个一维滤波器),而高斯滤波器的加速算法是很成熟的。

Bilateral Grid 除了用于加速双边滤波外,在很多领域也发挥着作用,比如 Google 2017 年发表的 HDRNet:

这篇文章的作者团队,本身也是 Bilateral Grid 的发明人。

对了,本文没有提及的一点是,升维后的网格通常巨大无比,一张 1024x1024 的图片,升维后可以达到 1024x1024x256,这对内存占用是有点吃不消的。关于 Bilateral Grid 如何处理这种大图,留待读者思考。
温馨提示:答案为网友推荐,仅供参考
相似回答
大家正在搜