前端图片处理 - 高斯模糊

栏目: 编程工具 · 发布时间: 5年前

内容简介:高斯模糊在图片处理中是比较常见的一种处理,其视觉效果就像是经过一个半透明屏幕在观察图像。这篇文章会基于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的链式处理也封装的很好,不用像使用原生那么麻烦。

延伸阅读:


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

不是为了快乐

不是为了快乐

宗萨蒋扬钦哲仁波切 / 姚仁喜 / 深圳报业集团出版社 / 2013-1 / 38.00元

前行修持是一套完整的实修系统,它既是一切佛法修持的根基,又囊括了所有修持的精华,以及心灵之道上所需的一切;既适合入门者打造学佛基本功,也是修行人需要终生修持的心法。书中除了实际的方法指导之外,还不断启发佛法的珍贵与修持的必要,并处处可见对学佛者的鼓舞和纠正,其最终的用心,是让我们踏上不间断的修持之路,真正转化我们僵硬、散乱和困惑的心。 在现代人看来,快乐,理应是最值得追求的目标。我们希望生活......一起来看看 《不是为了快乐》 这本书的介绍吧!

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具