经过阅览本文,你将获得以下收成:
1.图画滑润根底知识。
2.高斯含糊的界说以及理论根底。
3.怎么经过shader给视频增加高斯含糊作用。
上篇回顾
上一篇博文一看就懂的OpenGL ES教程——仿抖音滤镜的之改换滤镜(实践篇)将之前讲的几许改换实战了一遍,我们想必现已对运用矩阵进行改换有了比较深入的印象了吧,当然在图形学的国际里,矩阵的作用可不止对图画做做几许改换滤镜这么简略,它但是一名主力球员,不过具体作用的体现还容我讲3维渲染的时分再细细道来。
矩阵暂时不是今日的主角,不过这不代表今日就不涉及矩阵,今日会涉及一些看起来很有意思的内容。
高斯含糊理论根底
高斯含糊(英语:Gaussian Blur),也叫高斯滑润,是在Adobe Photoshop、GIMP以及Paint.NET等图画处理软件中广泛运用的处理作用,一般用它来减少图画噪声以及下降细节层次
。
作用如下图所示,左图为原图,右图为经过高斯含糊处理后的图:
要明白高斯含糊,首先就要从图画滑润说起。
图画滑润
什么是图画滑润呢,其实图画滑润便是关于某个像素点来说,让它周边的点去影响当前像素点的灰度值
,比方下图中,中心点灰度值此时为2:
最简略的滑润便是将中心点周围的像素取均匀值赋值给中心点,如下图:
所以滑润便是让一个点在周围点的影响下失去自己自身的特色
,变得有点“泯然世人”了。含糊实质便是这样,让那些与众不同的点,比方灰度值与周边明显不同的点或者边际处的点,变得和边际类似。
高斯含糊便是一种图画滑润技能
,从数学的视点来看,图画的高斯含糊进程便是图画与正态分布做卷积
。由于正态分布又叫作“高斯分布”,所以这项技能就叫作高斯含糊
。
什么意思呢?
这儿的关键词有两个,一个是卷积,别的一个高斯分布
。
图画卷积
卷积是什么呢,翻开维基百科:
接连函数的卷积,可能关于数学根底欠好的同学来说看不太懂,不过关于图画的卷积来说,由于图画实质能够看做离散信号,所以图画运用的是离散卷积
:
简略来说,就对应像素灰度值的乘积之和
。
这儿有必要讲一个图画范畴十分重要的概念,叫做卷积核
。
卷积核
卷积核便是图画处理时,给定输入图画,输入图画中一个小区域中像素加权均匀后
成为输出图画中的每个对应像素,其间权值由一个函数界说,这个函数称为卷积核
。卷积核在图画处理范畴十分重要,类似图画滑润、边际锐化等几乎一切的图画处理都离不开它
。
再次翻开维基百科的界说:
Inimage processing, akernel,convolution matrix, ormaskis a smallmatrixused for blurring, sharpening, embossing,edge detection, and more. This is accomplished by doing aconvolutionbetween the kernel and animage. Or more simply, when each pixel in the output image is a function of the nearby pixels (including itself) in the input image, the kernel is that function.
比方下图,假如用卷积核对原图画中的其间一块3* 3区域(即中心的绿色区块)进行卷积处理,则将它们对应像素相乘成果相加起来的最终成果赋值给3*3区域的中心点
,然后完成周边像素点对中心像素点的像素加权均匀
:
关于整个图画来说,便是用卷积核从第一个像素到最终一个像素顺次进行卷积处理的进程
,如下动图:
卷积核一般有以下特征:
1)滤波器的巨细应该是奇数,这样它才有一个中心,例如3×3,5×5或者7×7。有中心了,也有了半径的称呼,例如5×5巨细的核的半径便是2。
2)滤波器矩阵一切的元素之和应该要等于1,这是为了保证滤波前后图画的亮度保持不变。当然了,这不是硬性要求了。
3)假如滤波器矩阵一切元素之和大于1,那么滤波后的图画就会比原图画更亮,反之,假如小于1,那么得到的图画就会变暗。假如和为0,图画不会变黑,但也会十分暗。
4)关于滤波后的结构,可能会呈现负数或者大于255的数值。对这种状况,咱们将他们直接切断到0和255之间即可。关于负数,也能够取绝对值。
咱们的图画滑润便是用卷积完成的,显然,假如卷积核越大,则当前像素受到更多的像素的影响,则能够得出结论:含糊半径(即卷积核尺寸)越大,图画就越含糊。从数值视点看,便是数值越滑润。
以上分别是原图、含糊半径3像素、含糊半径10像素的作用。
既然是加权均匀,那么要怎么具体确认卷积核上每个小方块上的数值呢?不同的图画处理算法会有不同方法确认数值,例如均值滤波的卷积核是
所以被处理的像素的值为其周围一切像素相加的均值:
这样子会有什么问题么?假如运用简略均匀,显然不是很合理,由于图画都是接连的,越靠近的点联系越亲近,越远离的点联系越疏远。因而,加权均匀更合理,间隔越近的点权重越大,间隔越远的点权重越小
。
所以运用高斯分布的高斯含糊应运而生。
高斯分布
其实便是我们中学学过的正态分布啦,不过还是复习下界说,再再次翻开维基百科:
正态分布,是一个十分常见的接连概率分布。正态分布在核算学上十分重要,常常用在自然和社会科学来代表一个不明的随机变量。正态分布是自然科学与行为科学中的定量现象的一个便利模型。各种各样的心理学测试分数和物理现象比方光子计数都被发现近似地服从正态分布。
从上图能够看出,当x等于\mu的时分为其间心点,也便是最高点,2边逐步递减。且\sigma越小,则中心的凸起部分高度越高、宽度越窄。
高斯含糊是一种图画含糊滤波器,它用正态分布(\mu取0的状况)核算图画中每个像素的改换。
N维空间正态分布方程为:
其间r在这儿称为含糊半径,是正态分布的标准偏差。
在二维空间界说为:
这儿相当于:
在二维空间中,这个公式生成的曲面的等高线是从中心开端呈正态分布的同心圆:
也是典型的中心高,2边逐步递减,中心点(0,0)最高,即\mu=0,v=0的点最高。
那么高斯含糊为什么要运用正态分布核算卷积核的权值呢?前面现已说过,均值滤波不是很科学,加权均匀更合理,间隔越近的点权重越大,间隔越远的点权重越小。而正态分布刚好满足中心点值最大,间隔中心越远值越小
。
举个栗子?
以下例子引用于阮一峰老师的博文高斯含糊的算法 :
假定中心点的坐标是(0,0),那么间隔它最近的8个点的坐标如下:
更远的点以此类推。
为了核算权重矩阵,需求设定的值。假定=1.5,则含糊半径为1的权重矩阵如下:
这9个点的权重总和等于0.4787147,假如只核算这9个点的加权均匀,还有必要让它们的权重之和等于1,因而上面9个值还要分别除以0.4787147,得到最终的权重矩阵。
五、核算高斯含糊
有了权重矩阵,就能够核算高斯含糊的值了。
假定现有9个像素点,灰度值(0-255)如下:
每个点乘以自己的权重值:
得到
将这9个值加起来,便是中心点的高斯含糊的值。
对一切点重复这个进程,就得到了高斯含糊后的图画。假如原图是彩色图片,能够对RGB三个通道分别做高斯含糊。
滤镜晋级打怪
钻石
高斯含糊滤镜
滤镜晋级打怪继续进行~前面说了那么多理论知识,该来点实物展示了:
不是你忘了戴眼镜,也不是视频录制问题,要的便是这种类似近视的观看作用。
先看下极点着色器:
#version 300 es
layout (location = 0)
in vec4 aPosition;//输入的极点坐标,会在程序指定将数据输入到该字段
//假如传入的向量是不够4维的,主动将前三个重量设置为0.0,最终一个重量设置为1.0
layout (location = 1)
in vec2 aTextCoord;//输入的纹路坐标,会在程序指定将数据输入到该字段
out vec2 vTextCoord;//输出的纹路坐标;
uniform mat4 uMatrix;//改换矩阵
const int GAUSSIAN_SAMPLES = 9;
out vec2 blurCoordinates[GAUSSIAN_SAMPLES];
void main() {
//这儿其实是将上下翻转过来(由于安卓图片会主动上下翻转,所以转回来)
vTextCoord = vec2(aTextCoord.x, 1.0 - aTextCoord.y);
//直接把传入的坐标值作为传入渲染管线。gl_Position是OpenGL内置的
gl_Position = uMatrix * aPosition;
//横向和纵向的步长
vec2 widthStep = vec2(10.0/1080.0, 0.0);
vec2 heightStep = vec2(0.0, 10.0/1920.0);
//核算出当前片段相邻像素的纹路坐标
blurCoordinates[0] = vTextCoord.xy - heightStep - widthStep; // 左上
blurCoordinates[1] = vTextCoord.xy - heightStep; // 上
blurCoordinates[2] = vTextCoord.xy - heightStep + widthStep; // 右上
blurCoordinates[3] = vTextCoord.xy - widthStep; // 左中
blurCoordinates[4] = vTextCoord.xy; // 中
blurCoordinates[5] = vTextCoord.xy + widthStep; // 右中
blurCoordinates[6] = vTextCoord.xy + heightStep - widthStep; // 左下
blurCoordinates[7] = vTextCoord.xy + heightStep; // 下
blurCoordinates[8] = vTextCoord.xy + heightStep + widthStep; // 右下
};
乍一看有点复杂,容我解释就很快就明白了。
如上图所示,假如中心点为当前极点着色器处理的片段坐标,为了便利简略,这儿咱们取卷积核为3*3.则这儿aPosition为(0,0),widthStep和heightStep分别表示横向和纵向的步长,即用来核算高斯含糊的相邻片段中心之间的间隔,在上图这儿即为1,实践代码取了10.0/1080.0(记住屏幕是归一化的)。核算出来的这块区域9个点的坐标值按照从左到右,从上到下逐个存放在blurCoordinates数组中,最终传给片段着色器
。
(这儿为了简略便利,直接写死了widthStep和heightStep,实践上运用一般要从外部传进去更合理)
再看看片段着色器:
#version 300 es
precision mediump float;
//纹路坐标
in vec2 vTextCoord;
//输入的yuv三个纹路
uniform sampler2D yTexture;//采样器
uniform sampler2D uTexture;//采样器
uniform sampler2D vTexture;//采样器
out vec4 FragColor;
const lowp int GAUSSIAN_SAMPLES = 9;
in highp vec2 blurCoordinates[GAUSSIAN_SAMPLES];
//卷积核
mat3 kernelMatrix = mat3(
0.0947416f, 0.118318f, 0.0947416f,
0.118318f, 0.147761f, 0.118318f,
0.0947416f, 0.118318f, 0.0947416f
);
//yuv转rgb核算矩阵
mat3 colorConversionMatrix = mat3(
1.0, 1.0, 1.0,
0.0, -0.39465, 2.03211,
1.13983, -0.58060, 0.0
);
//yuv转化得到的rgb向量数据
vec3 yuv2rgb(vec2 pos)
{
vec3 yuv;
yuv.x = texture(yTexture, pos).r;
yuv.y = texture(uTexture, pos).r - 0.5;
yuv.z = texture(vTexture, pos).r - 0.5;
return colorConversionMatrix * yuv;
}
void main() {
//采样到的yuv向量数据
vec3 yuv;
//卷积处理
lowp vec3 sum = (yuv2rgb(blurCoordinates[0]) * kernelMatrix[0][0]);
sum += (yuv2rgb(blurCoordinates[1]) * kernelMatrix[0][1]);
sum += (yuv2rgb(blurCoordinates[2]) * kernelMatrix[0][2]);
sum += (yuv2rgb(blurCoordinates[3]) * kernelMatrix[1][0]);
sum += (yuv2rgb(blurCoordinates[4]) * kernelMatrix[1][1]);
sum += (yuv2rgb(blurCoordinates[5]) * kernelMatrix[1][2]);
sum += (yuv2rgb(blurCoordinates[6]) * kernelMatrix[2][0]);
sum += (yuv2rgb(blurCoordinates[7]) * kernelMatrix[2][1]);
sum += (yuv2rgb(blurCoordinates[8]) * kernelMatrix[2][2]);
FragColor = vec4(sum,1.0);
};
之前讲过的yuv转rgb就不讲了,不清楚的童鞋能够看看一看就懂的OpenGL ES教程——渲染宫崎骏动漫重拾童年。
首先kernelMatrix便是上面列出的卷积核:
经过yuv2rgb函数获取blurCoordinates上每个坐标对应的片段上的rgb数值,然后经过kernelMatrix做卷积操作,也便是乘积叠加操作。
看起来代码挺长,其实捋一捋便是这么简略。
这时分你可能会有个疑问,不是说定点着色器履行的次数和极点个数一样,这样子输出的blurCoordinates数组不就只要4个(矩形),那么到了片段着色器怎么又能每个片段都有对应的blurCoordinates数组能够供其采样相邻片段呢?假如还有这个疑问,这个时分,就要复习下之前的这篇博文了 一看就懂的OpenGL ES教程——这或许是你遇过最难画的三角形之渲染渐变色及光栅化插值原理
所谓学而时习之,不亦说乎~~
总结
不知不觉又是几千字,今日首要先从理论介绍了图画滑润技能,然后要点讲了高斯滤波的原理,最终讲了OpenGL的shader怎么对视频进行高斯含糊滤镜处理。总的来说,假如了解了高斯滤波理论以及之前的系列博文,那么了解代码便是水到渠成的事。接下来的博文,视频滤镜系列还会讲下一些酷炫的仿抖音滤镜,然后就要开端3D的征程了~
项目代码
opengl-es-study-demo 不断更新中,欢迎各位来star~
参考:
高斯含糊的算法
Gaussian blur
卷积
图画处理基本知识(了解卷积十分有用)
OpenGL.Shader:志哥教你写一个滤镜直播客户端(10)视觉滤镜:高斯滤波 / 高斯含糊 原理完成
原创不易,假如觉得本文对自己有协助,别忘了顺手点赞和关注,这也是我创造的最大动力~
系列文章目录
体系化学习系列博文,请看音视频系统学习总目录
实践项目: 介绍一个自己刚出炉的安卓音视频播映录制开源项目 欢迎各位来star~
相关专栏:
C/C++根底与进阶之路
音视频理论根底系列专栏
音视频开发实战系列专栏
一看就懂的OpenGL es教程