首页 > 开发 > HTML > 正文

js高斯模糊算法问题

2017-09-09 13:56:19  来源: 网友分享
<!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的源码,又看不太懂
希望大家指教一下

解决方案

高斯模糊有两种方案做:

  • 直接用二维公式进行二重循环,复杂度为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;}