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

OpenGL.Shader:志哥教你写一个滤镜直播客户端(9)

 

上篇学习卷积和写出了一个简单的拉式锐化滤镜,这一章记录模糊算法——均值模糊,也叫均值滤波,也叫方形模糊(BoxBlur)。

1、信号与噪声

说模糊之前,我感觉有必要学习计算机视觉当中的信号与噪声这一组概念知识。我们在打电话中如果觉得杂音特别多,那么也就是此时通话数据中的噪声特别多,已经达到了影响正常通话的程度,甚至噪声特别大的时候,信号容易淹没在噪声中。图像也是一种数据,图像中也存在信号和噪声。通俗的说,信号与噪声是一对敌人,图像的空间是有限的,信号多一点,噪声就少一点,反之亦然。

1.1 信号

信号是一个好东西,因为这是我们想要的数据。信号越多,噪声的干扰便会越少,数据的质量也就越高。我们可以使用信噪比这个概念来衡量数据质量的高低。所谓信噪比就是指信号与噪声二者能量之比值。直观来讲,噪声越少,信噪比越大,数据的质量越佳。我们可以看到图a展示的是经过高斯噪声干扰的图像,而图b是未经噪声干扰的原图。

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

1.2 噪声

在上图中已经看到经过噪声干扰和未经过噪声干扰的两幅图片的差异。经过噪声干扰的图像令我们难以获取图片所要表达的原始信息,使得图像所表达信息的确定程度减少,也就是所谓的信息熵增大。

而在实际生活中,通过图像采集设备获取到的图片也或多或少会引入噪声,这主要是由摄像机等图像采集设备的感光元件受到干扰产生的噪声表现在图像上而形成的,主要表现为黑白杂点等。

图像中随机出现的黑白杂点称为椒盐噪声,“椒”代表黑色,“盐”代表白色,故而用椒盐噪声这个概念来表示图像中存在的黑白杂点,其在图片中出现的位置是随机的。而图像中也可能会随机出现某些颜色的改变。造成此类杂点最典型的就是高斯噪声,这是由于在原图片的基础上叠加了高斯噪声而造成的。

所谓高斯噪声是指图像叠加的噪声概率密度服从高斯分布,也就是正态分布这是自然界中最为常见的一种噪声类别,例如夜晚通过照相机拍照获得的照片就可能存在该类噪声。

2、图像滤波

前面提到了噪声是我们不想要的一类数据。但是在实际操作中往往会引入噪声,例如图片经过低质量的信道传输,引入了信道中存在的噪声;图像采集设备由于某些电子学原因而引入了噪声等。噪声的存在必然会对我们正常的图像处理造成干扰,尽可能多地滤除噪声是我们进行图像预处理的一个重要步骤。下面介绍常见的滤除噪声的方法——均值滤波。

这里提到的均值滤波器更确切地说是算数均值滤波器,这是最简单的一种图像低通滤波器,可以滤除均匀噪声和高斯噪声,但是会对图像造成一定程度的模糊。它是将图片中指定区域内的像素点进行平均滤波的方法,如下图所示。

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

均值滤波器的缺点是会使图像变得模糊,这是因为它将所有的点都进行了均值处理。而实际上,在绝大多数情况下,噪声的占比是少数,将所有的点都以同样的权值进行处理,势必会导致图像的模糊。而且,这个滤波器的宽度越大,滤波后的图片就会越模糊,也就是丢失图像的细节部分,使图像变得更加“中庸”。

当然,根据这个特点,也可以将这个滤波器的权值更改一下,以便达到有所侧重的效果。一种可行的实现方法便是将这个滤波器的参数按照高斯分布形式进行修改,那么这个滤波器就称为高斯滤波器,这个留着下章学习。

这里穿插一组概念:为何高通滤波器(HPF)和低通滤波器(LPF)?
高通滤波器:根据像素与周围的像素的亮度差值来提升修改像素的亮度。
主要作用是锐化,但会带来噪化
低通滤波器:在像素与周围像素的亮度差值小于一个特定值时,平滑修改像素的亮度。
主要作用是模糊化 / 去噪 。

3、GPU.Shader均值滤波的简单实现

接下来就是在OpenGL的shader上实现均值滤波,废话不说,直接上码:

attribute vec4 position;
attribute vec4 inputTextureCoordinate;
uniform float widthFactor; // 宽度因子 = 1 / 屏幕宽度
uniform float heightFactor; // 高度因子 = 1 / 屏幕高度
uniform float blurSize; // 中心偏移量,结合屏幕宽高因子,动态调节均值滤波的卷积核的大小
varying vec2 textureCoordinate;
varying vec2 leftTextureCoordinate;
varying vec2 rightTextureCoordinate;
varying vec2 topTextureCoordinate;
varying vec2 topLeftTextureCoordinate;
varying vec2 topRightTextureCoordinate;
varying vec2 bottomTextureCoordinate;
varying vec2 bottomLeftTextureCoordinate;
varying vec2 bottomRightTextureCoordinate;
// 9宫格矩阵对应的坐标位置。
void main()
{
    gl_Position = position;
    vec2 widthStep = vec2(blurSize*widthFactor, 0.0);
    vec2 heightStep = vec2(0.0, blurSize*heightFactor);
    // factor大小为屏幕长宽的倒数,根据blurSize可以动态调节step的偏移大小
    textureCoordinate = inputTextureCoordinate.xy;
    leftTextureCoordinate = inputTextureCoordinate.xy - widthStep;
    rightTextureCoordinate = inputTextureCoordinate.xy + widthStep;
    topTextureCoordinate = inputTextureCoordinate.xy - heightStep;
    topLeftTextureCoordinate = inputTextureCoordinate.xy - heightStep - widthStep;
    topRightTextureCoordinate = inputTextureCoordinate.xy - heightStep + widthStep;
    bottomTextureCoordinate = inputTextureCoordinate.xy + heightStep;
    bottomLeftTextureCoordinate = inputTextureCoordinate.xy + heightStep - widthStep;
    bottomRightTextureCoordinate = inputTextureCoordinate.xy + heightStep + widthStep;
}

顶点着色器负责生成卷积矩阵的9宫格所需要的顶点索引位置。卷积运算交给下面的片元着色器

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;
}
varying vec2 textureCoordinate;
varying vec2 leftTextureCoordinate;
varying vec2 rightTextureCoordinate;
varying vec2 topTextureCoordinate;
varying vec2 topLeftTextureCoordinate;
varying vec2 topRightTextureCoordinate;
varying vec2 bottomTextureCoordinate;
varying vec2 bottomLeftTextureCoordinate;
varying vec2 bottomRightTextureCoordinate;
void main()
{
    mediump vec3 textureColor = yuv2rgb(textureCoordinate);
    mediump vec3 leftTextureColor = yuv2rgb(leftTextureCoordinate);
    mediump vec3 rightTextureColor = yuv2rgb(rightTextureCoordinate);
    mediump vec3 topTextureColor = yuv2rgb(topTextureCoordinate);
    mediump vec3 topLeftTextureColor = yuv2rgb(topLeftTextureCoordinate);
    mediump vec3 topRightTextureColor = yuv2rgb(topRightTextureCoordinate);
    mediump vec3 bottomTextureColor = yuv2rgb(bottomTextureCoordinate);
    mediump vec3 bottomLeftTextureColor = yuv2rgb(bottomLeftTextureCoordinate);
    mediump vec3 bottomRightTextureColor = yuv2rgb(bottomRightTextureCoordinate);
    // 提起色值,然后进行平均加权,记得最后输出要除以9
    vec3 fragmentColor = (leftTextureColor + textureColor + rightTextureColor);
    fragmentColor += (topLeftTextureColor + topTextureColor + topRightTextureColor);
    fragmentColor += (bottomLeftTextureColor + bottomTextureColor + bottomRightTextureColor);
    gl_FragColor = vec4(fragmentColor/9.0, 1.0);
}

以上是比较直观简单的均值滤波的实现,相对前一篇文章的拉普拉斯锐化,应该是更为容易理解。

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

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

That is All.

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

 

 

 

参考引用:

从计算机视觉到人脸识别:一文看懂颜色模型、信号与噪声

 

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