js高斯模糊算法问题

<!DOCTYPE html>
<html lang="en">
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
  <title>高斯模糊</title>
 </head>
 <body>
    <div id="wrap" class="wrap">
        <img src="img/12338563.jpg" alt="" id="imageSource" height="500" width="800"/>
        <canvas id="myCanvas"></canvas>
    </div>
 <script type="text/javascript">
    //-------------------------------做一个基于canvas的【图片上传,剪切】的插件
    //---------------------------------测试需在服务器环境下
        window.onload = function() { 
            var canvas = document.getElementById("myCanvas"); 
            var image = document.getElementById("imageSource"); 
            // re-size the canvas deminsion 
            console.log(image.width)
            canvas.width = image.width; 
            canvas.height = image.height; 
            // get 2D render object 
            var context = canvas.getContext("2d"); 
            context.drawImage(image, 0, 0,canvas.width,canvas.height); 
            var canvasData = context.getImageData(0, 0, canvas.width, canvas.height); //获取图片信息


            //对于一个数组来说,它的每一个元素并不拥有坐标,因此需要写一个方法,也就是告诉【x,y,radius】,就会返还一个数组,当然首先要确定的是【width,height】因为只有这样才能够给数组一个坐标
            //对于canvas来说,确定【width,height】就确定了【canvasData.data】的长度为【width*height*4】
            //但是坐标范围用width,height来固定

            //-------------------------------------------------高斯模糊开始------------------------------------------------------
            var radius = 20 ; //定义模糊半径
            var quan = getQuan(radius);
            var quanSub = ArrSub(quan);
            quan = jiaQuan(quan,quanSub); //获取权值

            var canvasDataCtrl = canvasData ;
            var width = canvas.width ,
                height = canvas.height ;
            console.log(new Date().getSeconds()) //高斯模糊开始时间
            for (var i=0;i<width ;i++ )
            {
                for (var j=0;j<height ;j++ )
                {
                    var index= (width*4)*j+i*4;
                    canvasDataCtrl.data[index] = getValue(i,j,0,quan); //r
                    canvasDataCtrl.data[index+1] = getValue(i,j,1,quan); //g
                    canvasDataCtrl.data[index+2] = getValue(i,j,2,quan); //b
                    canvasDataCtrl.data[index+3] = getValue(i,j,3,quan); //a
                }
            }
            context.putImageData(canvasDataCtrl, 0, 0);

            console.log(new Date().getSeconds()) //高斯模糊结束时间
            //console.log(value)
            //在chrome上运行时间大概有30s,运行这么长时间,肯定是算法问题,网上的StackBlur运行时间就很短
            //在计算量上,如果图片是像素是500×800,那这张图片所包含的rgba信息就有160,0000 之多
            //再加上要对每个像素的rgba值进行高斯转换,计算量就相当之大了,怎么解决呢
            console.log('模糊完成')


            function gaosi(x,y,a){ //根据高斯二维公式,获取点的高斯值
                var e = Math.E ;
                var pi = Math.PI ;
                return 1/(2*pi*a*a)*Math.pow(e,-((x*x+y*y)/(2*a*a)))
            }
            //假设radius=x,那么会获得一个(2*x+1)×(2*x+1)的数组矩阵
            //数组第一个元素的坐标是【-x,x】

            function getQuan(radius){ //获取每个点的高斯值,返回数组
                var quan = []
                for (var i=-radius;i<=radius ;i++ )
                {
                    for (var j=radius;j>=-radius ;j-- )
                    {
                        quan.push(gaosi(i,j,20));
                    }
                }
                return quan ;
            }
            function ArrSub(arr){ //返回高斯数组所有元素的值的和
                var sub = 0 ;
                for (var i=0,len=quan.length;i<len ;i++ )
                {
                    sub += quan[i] ;
                }
                return sub ;
            }
            function jiaQuan(arr,quanSub){ //获取权的数组
                for (var i=0,len=arr.length;i<len ;i++ )
                {
                    arr[i] = arr[i]/quanSub;
                }
                return arr ;
            }
            function getValue(x,y,index,quan){ //根据坐标以及rgba[index]来算出高斯模糊的最终值
                var imgdata = getInfoArr(x,y,index)
                var value = 0 ;
                for (var i=0,len=imgdata.length;i<len ;i++ )
                {
                    value += imgdata[i]*quan[i] ;
                }
                return Math.round(value) ;//返回整数
            }

            function getInfo(x,y,index){ //返回点的【rgba】值
                if (x<0)
                {
                    x=-x
                }else if (x>width-1)
                {
                    x=2*(width-1)-x
                }
                if (y<0)
                {
                    y=-y
                }else if (y>height-1)
                {
                    y=2*(height-1)-y
                }
                var i = (width*4)*y+x*4+index;
                return canvasData.data[i]
            }
            function getInfoArr(x,y,index){ //根据坐标和radius【模糊半径】,返回要和权数组相乘的数组
                var arr = [];
                for (var i=x-radius;i<=x+radius ;i++ )
                {
                    for (var j=y+radius;j>=y-radius ;j-- )
                    {
                        arr.push(getInfo(i,j,index))
                    }
                }
                return arr ;
            }
        }; 

 </script>
 </body>
</html>

最近看了阮一峰老师关于实现高斯模糊效果的博客,自己就用js和canvas写了这么一个效果
无奈执行时间太长,看stackblur的源码,又看不太懂
希望大家指教一下

阅读 9.3k
评论 2015-03-20 提问
    3 个回答

    高斯模糊有两种方案做:

    • 直接用二维公式进行二重循环,复杂度为O(xy(2r)^2)
    • 用一维公式分别对x、y循环,复杂度为O(2xy(2r))

    测试结果:

    • 用二重循环:(500*800,20) 4566ms
    • 分别循环:(500*800,20) 237ms

    可以发现刚好差20倍左右,也就是radius模糊半径的值

    结果图:

    图片描述
    图片描述
    图片描述

    实现代码如下:

    代码较长,建议移步到我的博客看代码

    html:

    html<!DOCTYPE html>
    <html>
    <head lang="en">
        <meta charset="UTF-8">
        <title>test</title>
        <script src="GaussianBlur.js"></script>
    </head>
    <body>
        <img src="images/test3.jpg" alt="img source" id="imgSource">
        <canvas id="canvas"></canvas>
    </body>
    </html>
    

    javascript:

    • gaussBlur : 二重循环
    • gaussBlur1 : 分别循环
    javascript/**
     * Created by zhaofengmiao on 15/3/22.
     */
    window.onload = function(){
        var img = document.getElementById("imgSource"),
            canvas = document.getElementById('canvas'),
            width = img.width,
            height = img.height;
    
        // console.log(width);
    
        canvas.width = width;
        canvas.height = height;
    
        var context = canvas.getContext("2d");
        context.drawImage(img, 0, 0);
    
        var canvasData = context.getImageData(0, 0, canvas.width, canvas.height);
    
        //console.log(canvasData);
    
        // 开始
        var startTime = +new Date();
    
        var tempData = gaussBlur(canvasData, 20);
    
    
        context.putImageData(tempData,0,0);
    
        var endTime = +new Date();
        console.log(" 一共经历时间:" + (endTime - startTime) + "ms");
    }
    
    /**
     * 此函数为二重循环
     */
    function gaussBlur(imgData, radius, sigma) {
        var pixes = imgData.data,
            width = imgData.width,
            height = imgData.height;
    
        radius = radius || 5;
        sigma = sigma || radius / 3;
    
        var gaussEdge = radius * 2 + 1;    // 高斯矩阵的边长
    
        var gaussMatrix = [],
            gaussSum = 0,
            a = 1 / (2 * sigma * sigma * Math.PI),
            b = -a * Math.PI;
    
        for (var i=-radius; i<=radius; i++) {
            for (var j=-radius; j<=radius; j++) {
                var gxy = a * Math.exp((i * i + j * j) * b);
                gaussMatrix.push(gxy);
                gaussSum += gxy;    // 得到高斯矩阵的和,用来归一化
            }
        }
        var gaussNum = (radius + 1) * (radius + 1);
        for (var i=0; i<gaussNum; i++) {
            gaussMatrix[i] = gaussMatrix[i] / gaussSum;    // 除gaussSum是归一化
        }
    
        //console.log(gaussMatrix);
    
        // 循环计算整个图像每个像素高斯处理之后的值
        for (var x=0; x<width;x++) {
            for (var y=0; y<height; y++) {
                var r = 0,
                    g = 0,
                    b = 0;
    
                //console.log(1);
    
                // 计算每个点的高斯处理之后的值
                for (var i=-radius; i<=radius; i++) {
                    // 处理边缘
                    var m = handleEdge(i, x, width);
                    for (var j=-radius; j<=radius; j++) {
                        // 处理边缘
                        var mm = handleEdge(j, y, height);
    
                        var currentPixId = (mm * width + m) * 4;
    
                        var jj = j + radius;
                        var ii = i + radius;
                        r += pixes[currentPixId] * gaussMatrix[jj * gaussEdge + ii];
                        g += pixes[currentPixId + 1] * gaussMatrix[jj * gaussEdge + ii];
                        b += pixes[currentPixId + 2] * gaussMatrix[jj * gaussEdge + ii];
    
                    }
                }
                var pixId = (y * width + x) * 4;
    
                pixes[pixId] = ~~r;
                pixes[pixId + 1] = ~~g;
                pixes[pixId + 2] = ~~b;
            }
        }
        imgData.data = pixes;
        return imgData;
    }
    
    function handleEdge(i, x, w) {
        var  m = x + i;
        if (m < 0) {
            m = -m;
        } else if (m >= w) {
            m = w + i - x;
        }
        return m;
    }
    
    /**
     * 此函数为分别循环
     */
    function gaussBlur1(imgData,radius, sigma) {
        var pixes = imgData.data;
        var width = imgData.width;
        var height = imgData.height;
        var gaussMatrix = [],
            gaussSum = 0,
            x, y,
            r, g, b, a,
            i, j, k, len;
    
    
        radius = Math.floor(radius) || 3;
        sigma = sigma || radius / 3;
    
        a = 1 / (Math.sqrt(2 * Math.PI) * sigma);
        b = -1 / (2 * sigma * sigma);
        //生成高斯矩阵
        for (i = 0, x = -radius; x <= radius; x++, i++){
            g = a * Math.exp(b * x * x);
            gaussMatrix[i] = g;
            gaussSum += g;
    
        }
        //归一化, 保证高斯矩阵的值在[0,1]之间
        for (i = 0, len = gaussMatrix.length; i < len; i++) {
            gaussMatrix[i] /= gaussSum;
        }
        //x 方向一维高斯运算
        for (y = 0; y < height; y++) {
            for (x = 0; x < width; x++) {
                r = g = b = a = 0;
                gaussSum = 0;
                for(j = -radius; j <= radius; j++){
                    k = x + j;
                    if(k >= 0 && k < width){//确保 k 没超出 x 的范围
                        //r,g,b,a 四个一组
                        i = (y * width + k) * 4;
                        r += pixes[i] * gaussMatrix[j + radius];
                        g += pixes[i + 1] * gaussMatrix[j + radius];
                        b += pixes[i + 2] * gaussMatrix[j + radius];
                        // a += pixes[i + 3] * gaussMatrix[j];
                        gaussSum += gaussMatrix[j + radius];
                    }
                }
                i = (y * width + x) * 4;
                // 除以 gaussSum 是为了消除处于边缘的像素, 高斯运算不足的问题
                // console.log(gaussSum)
                pixes[i] = r / gaussSum;
                pixes[i + 1] = g / gaussSum;
                pixes[i + 2] = b / gaussSum;
                // pixes[i + 3] = a ;
            }
        }
        //y 方向一维高斯运算
        for (x = 0; x < width; x++) {
            for (y = 0; y < height; y++) {
                r = g = b = a = 0;
                gaussSum = 0;
                for(j = -radius; j <= radius; j++){
                    k = y + j;
                    if(k >= 0 && k < height){//确保 k 没超出 y 的范围
                        i = (k * width + x) * 4;
                        r += pixes[i] * gaussMatrix[j + radius];
                        g += pixes[i + 1] * gaussMatrix[j + radius];
                        b += pixes[i + 2] * gaussMatrix[j + radius];
                        // a += pixes[i + 3] * gaussMatrix[j];
                        gaussSum += gaussMatrix[j + radius];
                    }
                }
                i = (y * width + x) * 4;
                pixes[i] = r / gaussSum;
                pixes[i + 1] = g / gaussSum;
                pixes[i + 2] = b / gaussSum;
                // pixes[i] = r ;
                // pixes[i + 1] = g ;
                // pixes[i + 2] = b ;
                // pixes[i + 3] = a ;
            }
        }
        //end
        imgData.data = pixes;
        return imgData;
    }
    
    评论 赞赏 2015-03-22
      sunway
      • 4
      • 新人请关照
      评论 赞赏 2015-03-20
        mearo
        • 1
        • 新人请关照

        你好,我最近也正在纠结高斯模糊这个问题,之前一直在用stackblur,但是貌似对透明的处理不是很理想,如果启用模糊alpha通道,就会把透明处理成白色,如果不模糊,就会是黑色,然后圆角图标模糊出来的边缘就很不好看。最近也在思考是否自己写一个方法实现。
        大致看了你的方法,可否考虑把宽度和高度分开模糊,而不是双重的循环。望交流。

        评论 赞赏 2015-03-22
          撰写回答

          登录后参与交流、获取后续更新提醒