内容简介:高斯模糊在图片处理中是比较常见的一种处理,其视觉效果就像是经过一个半透明屏幕在观察图像。这篇文章会基于webgl展示一种通用的实现,以及针对大图的优化。高斯模糊使用正态分布来计算当前像素周围和自身的权重,并将这些权重和对应的rgb值相乘,加总再除以总权重。因为是取像素周围的点,当前点为中点(即 0,0的点),所以使用二维正态分布来计算权重。二维正态分布如下图所示:
前言
高斯模糊在图片处理中是比较常见的一种处理,其视觉效果就像是经过一个半透明屏幕在观察图像。这篇文章会基于webgl展示一种通用的实现,以及针对大图的优化。
基于二维正态分布的实现
高斯模糊使用正态分布来计算当前像素周围和自身的权重,并将这些权重和对应的rgb值相乘,加总再除以总权重。因为是取像素周围的点,当前点为中点(即 0,0的点),所以使用二维正态分布来计算权重。二维正态分布如下图所示:
其中x,y分别是目标像素与中心像素水平和垂直方向的差值,σ是正态分布的标准偏差,一般为模糊直径的1/3,模糊直径越大,则画面越模糊。那根据模糊直径计算当前像素的rgba值,在glsl中的实现如下图所示:
// diameter 模糊直径 // sampler 图片纹理 // width 图片宽度 // height 图片长度 vec4 blur(int diameter,sampler2D sampler,float width,float height){ const float PI = 3.14159265; // 最大模糊直径 const int maxBlur = 100; // 保证模糊直径为奇数 if(mod(float(diameter), 2.0) == 0.0){ diameter++; } if(diameter > maxBlur){ diameter = maxBlur; } // 中心点 int center = (diameter - 1) / 2; // σ的平方 float sita = pow(float(diameter) / 6.0, 2.0); float sum = 0.0; vec4 sumVec4 = vec4(0.0); for(int i = 0; i < maxBlur; i++) if(i<diameter){ for(int j = 0; j < maxBlur; j++) if(j<diameter){ // 遍历周围像素点 float x = float(i-center); float y = float(j-center); // 计算权重 float weight = 0.5 / PI / sita * exp(-(pow(x, 2.0) + pow(y, 2.0)) / sita / 2.0); // 总权重 sum += weight; // 获取像素点 vec4 v = texture2D(sampler, vec2( texCoord.x + x/width, texCoord.y + y/height )); sumVec4 += v * weight; } } // 加总取平均 return vec4(sumVec4.r/sum, sumVec4.g/sum, sumVec4.b/sum, sumVec4.a/sum); }
·
基于这个函数, 实现的效果可以看这里 以及 相应代码 。可以看到,模糊程度是随着模糊长度的加大而增大。
基于一维正态分布的优化实现
通过上面的demo可以发现,模糊直径大于50,会有明显的卡顿,问题在于 texture2D
这个函数在每个像素上被执行了 diameter*diameter
次,假如模糊直径是20,那 texture2D
的被执行次数是400次。对于大图(比如3840 * 2160), texture2D
的执行次数不宜超过100次,否则会有明显的卡顿,在性能较低的电脑上,会直接卡死。在实际的应用中,需要对上述的模糊算法进行优化。
一维正态分布如下图所示:
优化的核心是使用两次一维正态分布(横向和纵向)来替代一次二维正态分布,需要注意的是,第二次的处理需要在第一次处理的基础上执行, texture
的执行次数从原来的 diameter*diameter
次变为 diameter+diameter
次,且不影响模糊的效果。这需要借助webgl中的FrameBuffer来实现。
第一次处理的glsl如下所示
// diameter 模糊直径 // sampler 图片纹理 // width 图片宽度 // height 图片长度 vec4 blur(int diameter,sampler2D sampler,float width,float height){ const float PI = 3.14159265; const int maxBlur = 41; if(mod(float(diameter), 2.0) == 0.0){ diameter++; } if(diameter > maxBlur){ diameter = maxBlur; } int center = (diameter - 1) / 2; float sita = pow(float(diameter) / 6.0, 2.0); float radio = sqrt(0.5 / PI / sita); float sum = 0.0; vec4 sumVec4 = vec4(0.0); for(int i = 0; i < maxBlur; i++) if(i<center + 1){ float weight = radio * exp(-pow(float(i), 2.0) / sita / 2.0); float ii = float(i); if(i == 0){ // 取中心点 vec4 color = texture2D(sampler, texCoord); sumVec4 += color * weight; sum += weight; }else{ // 左边 vec4 left = texture2D(sampler, vec2( texCoord.x - ii/width, texCoord.y)); // 右边 vec4 right = texture2D(sampler, vec2( texCoord.x + ii/width, texCoord.y)); sumVec4 += left * weight; sumVec4 += right * weight; sum += 2.0 * weight; } } return vec4(sumVec4.r/sum, sumVec4.g/sum, sumVec4.b/sum, sumVec4.a/sum); }
第二次处理的glsl如下所示
// diameter 模糊直径 // sampler 图片纹理 // width 图片宽度 // height 图片长度 vec4 blur(int diameter,sampler2D sampler,float width,float height){ const float PI = 3.14159265; const int maxBlur = 41; if(mod(float(diameter), 2.0) == 0.0){ diameter++; } if(diameter > maxBlur){ diameter = maxBlur; } int center = (diameter - 1) / 2; float sita = pow(float(diameter) / 6.0, 2.0); float radio = sqrt(0.5 / PI / sita); float sum = 0.0; vec4 sumVec4 = vec4(0.0); for(int i = 0; i < maxBlur; i++) if(i<center + 1){ float weight = radio * exp(-pow(float(i), 2.0) / sita / 2.0); float ii = float(i); if(i == 0){ // 取中心点 vec4 color = texture2D(sampler, texCoord); sumVec4 += color * weight; sum += weight; }else{ // 取上 vec4 left = texture2D(sampler, vec2( texCoord.x, texCoord.y - ii/height)); // 取下 vec4 right = texture2D(sampler, vec2( texCoord.x, texCoord.y + ii/height)); sumVec4 += left * weight; sumVec4 += right * weight; sum += 2.0 * weight; } } return vec4(sumVec4.r/sum, sumVec4.g/sum, sumVec4.b/sum, sumVec4.a/sum); }
第一次处理需要使用framebuffer应用到一个texture,再在这个texture上应用第二次处理。 具体代码可以看这里 , 实现的效果可以看这里 。可以看到,在优化之后,模糊半径到100以上也不会有卡顿。
后记
在实际的项目中,可以使用 glfx 来封装自定义的算法。它同时自带了很多图像处理的效果,基于framebuffer的链式处理也封装的很好,不用像使用原生那么麻烦。
延伸阅读:
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。