OpenGL.Shader:志哥教你写一个滤镜直播客户端(10)高斯滤波 / 高斯模糊 原理实现
1、不“中庸”的滤波
上一章原理实现了均值滤波。在介绍图像滤波的时候说到了,均值滤波是最简单的一种图像低通滤波器,可以滤除均匀噪声和高斯噪声,但是会对图像造成一定程度的模糊。它是将图片中指定区域内的像素点进行平均滤波的方法。均值滤波器的缺点是会使图像变得模糊,这是因为它将所有的点都进行了均值处理。而实际上,在绝大多数情况下,噪声的占比是少数,将所有的点都以同样的权值进行处理,势必会导致图像的模糊。而且,这个滤波器的宽度越大,滤波后的图片就会越模糊,也就是丢失图像的细节部分,使图像变得更加“中庸”。
那么怎样才能不“中庸”地实现图像的滤波呢?只要将滤波器的权值更改一下,将这个滤波器的参数按照高斯分布形式进行修改,那么这个滤波器就称为高斯滤波器,也称高斯模糊。
2、正太分布、高斯函数
这里简单介绍高斯正太分布的推演算法,以及其代码的演变过程。
何为正太分布,是指正态分布中,越接近中心点,取值越大,越远离中心,取值越小。
计算平均值的时候,我们只需要将"中心点"作为原点,其他点按照其在正态曲线上的位置,分配权重,就可以得到一个加权平均值。正态分布显然是一种可取的权重分配模式。
明白什么是正态分布,接下类需要使用高斯函数来实现数学转化过程。
上面的正态分布是一维的,而对于图像都是二维的,所以我们需要二维的正态分布。
正态分布的密度函数叫做"高斯函数"(Gaussian function)。它的二维形式是:
获取权重矩阵
假定中心点的坐标是(0,0),那么距离它最近的8个点的坐标如下:
更大的范围,更远的点以此类推。
为了计算权重矩阵,需要设定σ的值。假定σ=1.5,则模糊半径为1的权重矩阵如下:(把坐标值带入高斯公式)
这9个点的权重总和等于0.4787147,如果只计算这9个点的加权平均,还必须让它们的权重之和等于1,因此上面9个值还要分别除以0.4787147,得到最终的权重矩阵。
除以总值这个过程也叫做“归一问题”。目的是让滤镜的权重总值等于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
引用参考