在像素着色器中实现卷积滤波器的最有效方法是什么?

时间:2011-03-09 09:52:14

标签: optimization opengl glsl shader

在像素着色器中实现卷积对于非常多的纹理提取来说有点代价。

实现卷积滤波器的直接方法是对每个片段进行 N x N 查找,每个片段使用两个循环。一个简单的计算表明,使用4x4高斯内核模糊的1024x1024图像需要1024 x 1024 x 4 x 4 = 16M次查找。

对此可以做些什么?

  1. 可以使用一些需要较少查找的优化吗?我对特定于内核的优化感兴趣,比如高斯的优化(或者它们是特定于内核的吗?)
  2. 至少可以通过某种方式利用可以使用的像素的局部性来使这些查找更快吗?
  3. 谢谢!

4 个答案:

答案 0 :(得分:18)

高斯内核是可分离的,这意味着您可以先进行水平传递,然后进行垂直传递(或反过来)。这将O(N ^ 2)变为O(2N)。这适用于所有可分离的滤镜,不仅适用于模糊(不是所有滤镜都是可分离的,但很多都是,而且有些“和”一样好“。)

或者,在模糊滤镜(Gauss与否)的特定情况下,它们都是“加权和”,您可以利用纹理插值,对于小内核尺寸可能更快(但最终不适用于大内核)。

编辑:“线性插值”方法的图像

The "linear interpolation method"

编辑(根据Jerry Coffin的要求)总结评论:

在“纹理滤镜”方法中,线性插值将根据从样本位置到纹素中心的反距离产生相邻纹素的加权和。这是由纹理硬件免费完成的。这样,16个像素可以在4次提取中求和。除了分离内核之外,还可以利用纹理过滤。

在示例图像中,在左上角,您的样本(圆圈)会到达纹素的中心。你得到的与“最近”的过滤相同,你得到的是texel的值。在右上角,你处于两个纹素之间的中间,你得到的是它们之间的50/50平均值(由较浅的蓝色着色器图示)。在右下角,您可以在4个纹素之间进行采样,但稍微靠近左上角。这给了你所有4的加权平均值,但重量偏向左上角(最暗的蓝色)。

以下建议由 datenwolf 提供(见下文):

“我想建议的另一种方法是在傅立叶空间中运行,其中卷积变成傅立叶变换信号和傅里叶变换内核的简单乘积。虽然GPU本身的傅里叶变换实现起来非常繁琐,但至少使用OpenGL着色器。但是在OpenCL中它很容易完成。实际上我使用OpenCL实现了这些功能,现在,我的3D引擎中的大量图像处理都发生在OpenCL中。

OpenCL专为在GPU上运行而设计。快速傅立叶变换实际上是维基百科的OpenCL文章中的示例代码:en.wikipedia.org/wiki/OpenCL,是的,性能提升是巨大的。 FFT最多执行O(n log n),反之亦然。滤波器内核傅立叶表示可以预先计算。方法是FFT - >乘以内核 - > IFFT,归结为O(n + 2n log n)运算。请注意,实际的卷积只是O(n)。

在可分离的有限卷积(如高斯模糊)的情况下,分离解决方案将胜过傅立叶方法。但是对于广义的,可能的不可分离的内核,傅里叶方法可能是最快的方法。 OpenCL与OpenGL很好地集成,例如您可以将OpenGL缓冲区(纹理和顶点)用于OpenCL程序的输入和输出。“

答案 1 :(得分:4)

除了可分离之外,高斯滤波器也可以在O(1)中计算:

有像Deriche那样的递归计算:

http://hal.inria.fr/docs/00/07/47/78/PDF/RR-1893.pdf

答案 2 :(得分:3)

Rotoglup回答我的问题here我值得一读;特别是,this blog post about Gaussian blur确实帮助我理解了可分离过滤器的概念。

答案 3 :(得分:0)

另一种方法是使用逐步函数https://arxiv.org/pdf/1107.4958.pdf逼近高斯曲线(我想当然也可以使用分段线性函数)。