OpenGL.Shader:志哥教你写一个滤镜直播客户端(10)视觉滤镜:高斯滤波 / 高斯模糊 原理实现

OpenGL.Shader:志哥教你写一个滤镜直播客户端(10)高斯滤波 / 高斯模糊 原理实现

1、不“中庸”的滤波

上一章原理实现了均值滤波。在介绍图像滤波的时候说到了,均值滤波是最简单的一种图像低通滤波器,可以滤除均匀噪声和高斯噪声,但是会对图像造成一定程度的模糊。它是将图片中指定区域内的像素点进行平均滤波的方法。均值滤波器的缺点是会使图像变得模糊,这是因为它将所有的点都进行了均值处理。而实际上,在绝大多数情况下,噪声的占比是少数,将所有的点都以同样的权值进行处理,势必会导致图像的模糊。而且,这个滤波器的宽度越大,滤波后的图片就会越模糊,也就是丢失图像的细节部分,使图像变得更加“中庸”。

那么怎样才能不“中庸”地实现图像的滤波呢?只要将滤波器的权值更改一下,将这个滤波器的参数按照高斯分布形式进行修改,那么这个滤波器就称为高斯滤波器,也称高斯模糊。

2、正太分布、高斯函数

这里简单介绍高斯正太分布的推演算法,以及其代码的演变过程。

何为正太分布,是指正态分布中,越接近中心点,取值越大,越远离中心,取值越小
计算平均值的时候,我们只需要将"中心点"作为原点,其他点按照其在正态曲线上的位置,分配权重,就可以得到一个加权平均值。正态分布显然是一种可取的权重分配模式。

OpenGL.Shader:志哥教你写一个滤镜直播客户端(10)视觉滤镜:高斯滤波 / 高斯模糊 原理实现_第1张图片

明白什么是正态分布,接下类需要使用高斯函数来实现数学转化过程。
上面的正态分布是一维的,而对于图像都是二维的,所以我们需要二维的正态分布。

OpenGL.Shader:志哥教你写一个滤镜直播客户端(10)视觉滤镜:高斯滤波 / 高斯模糊 原理实现_第2张图片

正态分布的密度函数叫做"高斯函数"(Gaussian function)。它的二维形式是:

获取权重矩阵

假定中心点的坐标是(0,0),那么距离它最近的8个点的坐标如下:

OpenGL.Shader:志哥教你写一个滤镜直播客户端(10)视觉滤镜:高斯滤波 / 高斯模糊 原理实现_第3张图片

更大的范围,更远的点以此类推。
为了计算权重矩阵,需要设定σ的值。假定σ=1.5,则模糊半径为1的权重矩阵如下:(把坐标值带入高斯公式)

OpenGL.Shader:志哥教你写一个滤镜直播客户端(10)视觉滤镜:高斯滤波 / 高斯模糊 原理实现_第4张图片

这9个点的权重总和等于0.4787147,如果只计算这9个点的加权平均,还必须让它们的权重之和等于1,因此上面9个值还要分别除以0.4787147,得到最终的权重矩阵。

OpenGL.Shader:志哥教你写一个滤镜直播客户端(10)视觉滤镜:高斯滤波 / 高斯模糊 原理实现_第5张图片

除以总值这个过程也叫做“归一问题”。目的是让滤镜的权重总值等于1。否则的话,使用总值大于1的滤镜会让图像偏亮,小于1的滤镜会让图像偏暗。至此我们得出了3阶的高斯卷积核。

3、GL着色器多索引传递

接着就是在GL.Shader上实现这个高斯模糊的滤镜了,其实实现思路和上一章节的均值模糊一样,但这一次介绍一下着色器的索引数组传递。简化一下shader代码。

首先是定点着色器。注意看引用数组的写法,没啥好说,注意矩阵位置和数组索引位置要对应好。

attribute vec4 position;
attribute vec4 inputTextureCoordinate;
uniform float widthFactor;
uniform float heightFactor;
uniform float offset; // 采样点半径
const int GAUSSIAN_SAMPLES = 9;
varying vec2 textureCoordinate[GAUSSIAN_SAMPLES];
void main()
{
    gl_Position = position;
    vec2 widthStep = vec2(offset*widthFactor, 0.0);
    vec2 heightStep = vec2(0.0, offset*heightFactor);
    textureCoordinate[0] = inputTextureCoordinate.xy - heightStep - widthStep; // 左上
    textureCoordinate[1] = inputTextureCoordinate.xy - heightStep; // 上
    textureCoordinate[2] = inputTextureCoordinate.xy - heightStep + widthStep; // 右上
    textureCoordinate[3] = inputTextureCoordinate.xy - widthStep; // 左中
    textureCoordinate[4] = inputTextureCoordinate.xy; // 中
    textureCoordinate[5] = inputTextureCoordinate.xy + widthStep; // 右中
    textureCoordinate[6] = inputTextureCoordinate.xy + heightStep - widthStep; // 左下
    textureCoordinate[7] = inputTextureCoordinate.xy + heightStep; // 下
    textureCoordinate[8] = inputTextureCoordinate.xy + heightStep + widthStep; // 右下
}

然后就是片元着色器,我们从外部引用卷积核convolutionMatrix,把刚刚计算出来的高斯滤波的卷积核心从外部C++层代码传递进来,其实可以像yuv2rgb的转化矩阵一样静态写在shader里面。只是在外部代码传递方便调试&增强阅读性。

precision highp float;
uniform sampler2D SamplerY;
uniform sampler2D SamplerU;
uniform sampler2D SamplerV;
mat3 colorConversionMatrix = mat3(
                   1.0, 1.0, 1.0,
                   0.0, -0.39465, 2.03211,
                   1.13983, -0.58060, 0.0);
vec3 yuv2rgb(vec2 pos)
{
   vec3 yuv;
   yuv.x = texture2D(SamplerY, pos).r;
   yuv.y = texture2D(SamplerU, pos).r - 0.5;
   yuv.z = texture2D(SamplerV, pos).r - 0.5;
   return colorConversionMatrix * yuv;
}
uniform mediump mat3 convolutionMatrix;
const int GAUSSIAN_SAMPLES = 9;
varying vec2 textureCoordinate[GAUSSIAN_SAMPLES];
void main()
{
    //mediump vec3 topLeftColor     = yuv2rgb(textureCoordinate[0]);
    //mediump vec3 topColor         = yuv2rgb(textureCoordinate[1]);
    //mediump vec3 topRightColor    = yuv2rgb(textureCoordinate[2]);
    //mediump vec3 leftColor        = yuv2rgb(textureCoordinate[3]);
    //mediump vec3 centerColor      = yuv2rgb(textureCoordinate[4]);
    //mediump vec3 rightColor       = yuv2rgb(textureCoordinate[5]);
    //mediump vec3 bottomLeftColor  = yuv2rgb(textureCoordinate[6]);
    //mediump vec3 bottomColor      = yuv2rgb(textureCoordinate[7]);
    //mediump vec3 bottomRightColor = yuv2rgb(textureCoordinate[8]);
    vec3 fragmentColor = (yuv2rgb(textureCoordinate[0]) * convolutionMatrix[0][0]);
    fragmentColor += (yuv2rgb(textureCoordinate[1]) * convolutionMatrix[0][1]);
    fragmentColor += (yuv2rgb(textureCoordinate[2]) * convolutionMatrix[0][2]);
    fragmentColor += (yuv2rgb(textureCoordinate[3]) * convolutionMatrix[1][0]);
    fragmentColor += (yuv2rgb(textureCoordinate[4]) * convolutionMatrix[1][1]);
    fragmentColor += (yuv2rgb(textureCoordinate[5]) * convolutionMatrix[1][2]);
    fragmentColor += (yuv2rgb(textureCoordinate[6]) * convolutionMatrix[2][0]);
    fragmentColor += (yuv2rgb(textureCoordinate[7]) * convolutionMatrix[2][1]);
    fragmentColor += (yuv2rgb(textureCoordinate[8]) * convolutionMatrix[2][2]);
    gl_FragColor = vec4(fragmentColor, 1.0);
}

部分C++代码节选:

#ifndef GPU_GAUSSIANBLUR_FILTER_HPP
#define GPU_GAUSSIANBLUR_FILTER_HPP
#include "GpuBaseFilter.hpp"

class GpuGaussianBlurFilter : public GpuBaseFilter {
public:
    virtual int getTypeId() { return FILTER_TYPE_GAUSSIANBLUR; }

    GpuGaussianBlurFilter()
    {
        GAUSSIAN_BLUR_VERTEX_SHADER ="...";
        GAUSSIAN_BLUR_FRAGMENT_SHADER ="...";
    }

    void init() {
        GpuBaseFilter::init(GAUSSIAN_BLUR_VERTEX_SHADER.c_str(), GAUSSIAN_BLUR_FRAGMENT_SHADER.c_str());
        mWidthFactorLocation = glGetUniformLocation(getProgram(), "widthFactor");
        mHeightFactorLocation = glGetUniformLocation(getProgram(), "heightFactor");
        mSampleOffsetLocation = glGetUniformLocation(getProgram(), "offset");
        mUniformConvolutionMatrix = glGetUniformLocation(getProgram(), "convolutionMatrix");
        mSampleOffset = 0.0f;
        // 高斯滤波的卷积核
        convolutionKernel = new GLfloat[9]{
                0.0947416f, 0.118318f, 0.0947416f,
                0.118318f,  0.147761f, 0.118318f,
                0.0947416f, 0.118318f, 0.0947416f,
        };
    }
    void onOutputSizeChanged(int width, int height) {
        GpuBaseFilter::onOutputSizeChanged(width, height);
        glUniform1f(mWidthFactorLocation, 1.0f / width);
        glUniform1f(mHeightFactorLocation, 1.0f / height);
    }
    void setAdjustEffect(float percent) {
        mSampleOffset = range(percent * 100.0f, 0.0f, 3.0f);
        // 动态修正采样半径
    }

    void onDraw(GLuint SamplerY_texId, GLuint SamplerU_texId, GLuint SamplerV_texId,
                void* positionCords, void* textureCords)
    {
        if (!mIsInitialized)
            return;
        glUseProgram(mGLProgId);
        // 传递高斯滤波的卷积核
        glUniformMatrix3fv(mUniformConvolutionMatrix, 1, GL_FALSE, convolutionKernel);
        glUniform1f(mSampleOffsetLocation, mSampleOffset);
        glUniform1f(mWidthFactorLocation, 1.0f / mOutputWidth);
        glUniform1f(mHeightFactorLocation, 1.0f / mOutputHeight);

        glVertexAttribPointer(mGLAttribPosition, 2, GL_FLOAT, GL_FALSE, 0, positionCords);
        glEnableVertexAttribArray(mGLAttribPosition);
        glVertexAttribPointer(mGLAttribTextureCoordinate, 2, GL_FLOAT, GL_FALSE, 0, textureCords);
        glEnableVertexAttribArray(mGLAttribTextureCoordinate);

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, SamplerY_texId);
        glUniform1i(mGLUniformSampleY, 0);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, SamplerU_texId);
        glUniform1i(mGLUniformSampleU, 1);
        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, SamplerV_texId);
        glUniform1i(mGLUniformSampleV, 2);
        // onDrawArraysPre
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
        glDisableVertexAttribArray(mGLAttribPosition);
        glDisableVertexAttribArray(mGLAttribTextureCoordinate);
        glBindTexture(GL_TEXTURE_2D, 0);
    }
};
#endif // GPU_GAUSSIANBLUR_FILTER_HPP

以上是高斯滤波的实现,视频实践效果转化成gif识辨度很低,这里就不放出来了。有兴趣可以直接跑demo工程看效果。

项目地址:https://github.com/MrZhaozhirong/NativeCppApp 均值模糊滤镜 cpp/gpufilter/filter/GpuGaussianBlurFilter.hpp

That is All.

兴趣讨论群:703531738。暗号:志哥13567

 

 

引用参考

高斯分布算法  https://www.cnblogs.com/invisible2/p/9177018.html

你可能感兴趣的:(OpenGL的视觉滤镜处理,OpenGL.Shader进阶)