模糊内核分离以进行两次渲染

时间:2018-06-19 08:37:07

标签: java opengl image-processing blur gaussianblur

我有必要计算内核的双遍模糊。假设有一个模糊内核5x5,它生成如下:

public static float[][] getKernelf(int size, float sigma) {
    float[][] kernel = new float[size][size];
    int mean = size / 2;
    float sum = 0; // For accumulating the kernel values
    for (int x = 0; x < size; x++)  {
        for (int y = 0; y < size; y++) {
            kernel[x][y] = (float) (Math.exp(-0.5 * (Math.pow((x - mean) / sigma, 2.0) + 
                    Math.pow((y - mean) / sigma, 2.0))) / (2 * Geometry.PI * sigma * sigma));
            // Accumulate the kernel values
            sum += kernel[x][y];
        }
    }

    // Normalize the kernel
    for (int x = 0; x < size; x++) 
        for (int y = 0; y < size; y++)
            kernel[x][y] /= sum;

    return kernel;
}

sigma = 1且size = 5,我们有以下内核:

0.0029690173  0.013306212  0.021938235  0.013306212  0.0029690173
0.013306212   0.059634306  0.09832035   0.059634306  0.013306212
0.021938235   0.09832035   0.16210285   0.09832035   0.021938235
0.013306212   0.059634306  0.09832035   0.059634306  0.013306212
0.0029690173  0.013306212  0.021938235  0.013306212  0.0029690173

问题是如何将这个内核放入适合双遍渲染的视图中(水平和垂直,在OpenGL中实时渲染)

修改

书籍给出的内核:0.227027 0.1945946 0.1216216 0.054054 0.016216

我的完整 fragment_blur_shader.glsl:

#version 330

out vec4 fragColor;
in vec2 texCoords;

uniform sampler2D image;
uniform bool isHorizontal;
uniform float weight[5] = float[] (0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);

void main() {
    vec2 texOffset = 1.0 / textureSize(image, 0); // gets size of single texel
    vec3 result = texture(image, texCoords).rgb * weight[0]; // current fragment’s contribution
    if(isHorizontal) {
        for(int i = 1; i < 5; i++) {
            result += texture(image, texCoords + vec2(texOffset.x * i, 0.0)).rgb * weight[i];
            result += texture(image, texCoords - vec2(texOffset.x * i, 0.0)).rgb * weight[i];
        }
    } else {
        for(int i = 1; i < 5; ++i) {
            result += texture(image, texCoords + vec2(0.0, texOffset.y * i)).rgb * weight[i];
            result += texture(image, texCoords - vec2(0.0, texOffset.y * i)).rgb * weight[i];
        }
    }
    fragColor = vec4(result, 1.0);
}

此外,我发现以下图片展示了从1D内核接收2D内核以进行两阶段渲染:

Example of a 7x7 convolution kernel done using the two pass separable filter approach

但我不知道如何获得这个1D核心。我希望得到你的帮助。

修改

我理解如何获取我需要的内核,但我仍然不明白为什么本书以这种形式提供这个内核

2 个答案:

答案 0 :(得分:2)

要从已拥有的2D高斯内核开始:

float[][] kernel = new float[size][size];
int mean = size / 2;
float sum = 0; // For accumulating the kernel values
for (int x = 0; x < size; x++)  {
    for (int y = 0; y < size; y++) {
        kernel[x][y] = (float) (Math.exp(-0.5 * (Math.pow((x - mean) / sigma, 2.0) + 
                Math.pow((y - mean) / sigma, 2.0))) / (2 * Geometry.PI * sigma * sigma));
        // Accumulate the kernel values
        sum += kernel[x][y];
    }
}

// Normalize the kernel
for (int x = 0; x < size; x++) 
    for (int y = 0; y < size; y++)
        kernel[x][y] /= sum;

对于一维高斯内核,只需删除y上的循环(以及对y的所有进一步引用):

float[] kernel = new float[size];
int mean = size / 2;
float sum = 0; // For accumulating the kernel values
for (int x = 0; x < size; x++)  {
    kernel[x] = (float) Math.exp(-0.5 * Math.pow((x - mean) / sigma, 2.0));
    // Accumulate the kernel values
    sum += kernel[x];
}

// Normalize the kernel
for (int x = 0; x < size; x++) 
    kernel[x] /= sum;

更多提示:

  • 您已经注意到,着色器仅使用该内核的一半(x-mean >= 0的一部分)。由于它是对称的,所以另一半是多余的。

  • 无需使用kernel来缩放2 * Geometry.PI * sigma * sigma的值,因为稍后再对内核进行规范化,因此这种缩放是无关紧要的。

  • 将此内核与其转置相乘将产生代码的第一位生成的2D内核(如您在问题中所包含的图所示)。

答案 1 :(得分:0)

所以,我得出了最终答案。多亏了我找到的图片和您的建议,我终于编写了获取一维两遍内核的最终函数:

public static float[] getTwoPassKernelf(int size, float sigma) {
    float[] kernel = new float[size];
    float[][] fkernel = getKernelf(size, sigma);

    for (int i = 0; i < size; i++) {
        kernel[i] = (float) Math.sqrt(fkernel[i][i]);
    }

    return kernel;
}

public static float[][] getKernelf(int size, float sigma) {
    float[][] kernel = new float[size][size];
    int mean = size / 2;
    float sum = 0; // For accumulating the kernel values
    for (int x = 0; x < size; x++)  {
        for (int y = 0; y < size; y++) {
            kernel[x][y] = (float) (Math.exp(-0.5 * (Math.pow((x - mean) / sigma, 2.0) + 
                    Math.pow((y - mean) / sigma, 2.0))) / (2 * Geometry.PI * sigma * sigma));
            // Accumulate the kernel values
            sum += kernel[x][y];
        }
    }

    // Normalize the kernel
    for (int x = 0; x < size; x++) 
        for (int y = 0; y < size; y++)
            kernel[x][y] /= sum;

    return kernel;
}

总而言之,着色器不需要转移所有内核,而是转移其一部分,包括中心(在我的情况下)。因此,我在问题中引用的核心不是5x5,而是10x10。

例如:getTwoPassKernelf(10, 2f)返回

0.008890575, 0.027384898, 0.065692954, 0.1227306, 0.17857197, 0.20234855, 0.17857197, 0.1227306, 0.065692954, 0.027384898

但就我而言,我只能参加0.20234855, 0.17857197, 0.1227306, 0.065692954, 0.027384898-这与本书所提供的完全一样

A picture explaining the essence of obtaining a kernel for a two-pass rendering

一幅图片,解释了获取用于两遍渲染的内核的本质

已选择-内核元素的平方

我希望答案对某人有用