理解超快速模糊算法

9

我正在尝试理解超快速模糊算法背后的算法。以下是适用于Android测试的Java端口。看起来这个版本做了一些我不太理解的优化,也没有任何注释。

void fastblur(Bitmap img, int radius){

    if (radius<1){
        return;
    }
    int w= img.getWidth();
    int h=img.getHeight();
    int wm=w-1;
    int hm=h-1;
    int wh=w*h;
    int div=radius+radius+1;
    int r[]=new int[wh];
    int g[]=new int[wh];
    int b[]=new int[wh];
    int rsum,gsum,bsum,x,y,i,p,p1,p2,yp,yi,yw;
    int vmin[] = new int[Math.max(w,h)];
    int vmax[] = new int[Math.max(w,h)];
    int[] pix= new  int[w*h];

    img.getPixels(pix, 0, w, 0,0,w, h);

    int dv[]=new int[256*div];
    for (i=0;i<256*div;i++){
        dv[i]=(i/div);
    }

    yw=yi=0;

    for (y=0;y<h;y++){
        rsum=gsum=bsum=0;
        for(i=-radius;i<=radius;i++){
            p=pix[yi+Math.min(wm,Math.max(i,0))];
            rsum+=(p & 0xff0000)>>16;
            gsum+=(p & 0x00ff00)>>8;
            bsum+= p & 0x0000ff;
        }
        for (x=0;x<w;x++){

            r[yi]=dv[rsum];
            g[yi]=dv[gsum];
            b[yi]=dv[bsum];

            if(y==0){
                vmin[x]=Math.min(x+radius+1,wm);
                vmax[x]=Math.max(x-radius,0);
            }
            p1=pix[yw+vmin[x]];
            p2=pix[yw+vmax[x]];

            rsum+=((p1 & 0xff0000)-(p2 & 0xff0000))>>16;
            gsum+=((p1 & 0x00ff00)-(p2 & 0x00ff00))>>8;
            bsum+= (p1 & 0x0000ff)-(p2 & 0x0000ff);
            yi++;
        }
        yw+=w;
    }

    for (x=0;x<w;x++){
        rsum=gsum=bsum=0;
        yp=-radius*w;
        for(i=-radius;i<=radius;i++){
            yi=Math.max(0,yp)+x;
            rsum+=r[yi];
            gsum+=g[yi];
            bsum+=b[yi];
            yp+=w;
        }
        yi=x;
        for (y=0;y<h;y++){
            pix[yi]=0xff000000 | (dv[rsum]<<16) | (dv[gsum]<<8) | dv[bsum];
            if(x==0){
                vmin[y]=Math.min(y+radius+1,hm)*w;
                vmax[y]=Math.max(y-radius,0)*w;
            }
            p1=x+vmin[y];
            p2=x+vmax[y];

            rsum+=r[p1]-r[p2];
            gsum+=g[p1]-g[p2];
            bsum+=b[p1]-b[p2];

            yi+=w;
        }
    }

    img.setPixels(pix,0, w,0,0,w,h);
}

如果我猜错了,请纠正我:

下面的循环做了什么?它是否与预计算内核表有关?div是内核表的大小吗?我想我想问的是,dv[]应该存储什么?

int dv[]=new int[256*div];
for (i=0;i<256*div;i++){
    dv[i]=(i/div);
}

观察水平通道:

下面的循环看起来像是在累加单独的RGB值,但实际上仅在每行的起始像素处进行此操作,因为只有当我们处理完所有像素并达到宽度时,yi 才会增加一次。这是因为我们在下一个循环中处理像素时会将RGB总和相加吗?

        for(i=-radius;i<=radius;i++){
            int ind = yi+Math.min(wm,Math.max(i,0));
            p=pix[ind];
            rsum+=(p & 0xff0000)>>16;
            gsum+=(p & 0x00ff00)>>8;
            bsum+= p & 0x0000ff;
        }

我们是否只根据半径和当前像素位置选择最左边的像素和最右边的像素?
 if(y==0){
   vmin[x]=Math.min(x+radius+1,wm);
   vmax[x]=Math.max(x-radius,0);
  } 

  p1=pix[yw+vmin[x]];
  p2=pix[yw+vmax[x]];

下面是最令我困惑的部分: 我是否正确地说,我们正在获取右像素和左像素之间的差异,并将其添加到我们已有的RGB总数中?
  rsum+=((p1 & 0xff0000)-(p2 & 0xff0000))>>16;
  gsum+=((p1 & 0x00ff00)-(p2 & 0x00ff00))>>8;
  bsum+= (p1 & 0x0000ff)-(p2 & 0x0000ff);

我还没有看到第二遍,因为这对我来说太深奥了。如果能有任何澄清,那将不胜感激。并且对于垂直通过程中的循环,如果有任何评论也会很有帮助,谢谢。


1
我认为同样的算法在这里有更好地描述:http://blog.ivank.net/fastest-gaussian-blur.html - Ivan Kuckir
3个回答

23

既然我写了这个,我想我可以最好地解释它 :-)

 int dv[]=new int[256*div]; 
 for (i=0;i<256*div;i++){
     dv[i]=(i/div); 
}

这一行代码会预先计算出可能出现的所有均值的查找表。这样可以避免在内部循环中进行昂贵的除法操作。在某些系统上,直接进行除法运算而不是进行数组查找可能实际上更快,但是当我编写它时,查找表的方式是更快的。

for(i=-radius;i<=radius;i++){
            int ind = yi+Math.min(wm,Math.max(i,0));
            p=pix[ind];
            rsum+=(p & 0xff0000)>>16;
            gsum+=(p & 0x00ff00)>>8;
            bsum+= p & 0x0000ff;
        }

该算法之所以快速,是因为它使用了滑动窗口,从而减少了所需的像素查找次数。窗口从左边缘向右滑动(第二遍从上到下),只在右侧添加一个像素并从左侧移除一个像素。上面的代码通过根据内核大小预先填充窗口的最左边缘像素来初始化窗口。

 if(y==0){
   vmin[x]=Math.min(x+radius+1,wm);
   vmax[x]=Math.max(x-radius,0);
  } 

  p1=pix[yw+vmin[x]];
  p2=pix[yw+vmax[x]]; 

这部分是添加一个新像素并同时处理边界条件的代码(当窗口尝试读取或删除位图之外的像素时)。

 rsum+=((p1 & 0xff0000)-(p2 & 0xff0000))>>16;
  gsum+=((p1 & 0x00ff00)-(p2 & 0x00ff00))>>8;
  bsum+= (p1 & 0x0000ff)-(p2 & 0x0000ff);

rsum、gsum和bsum是滑动窗口内像素的累计和。您看到的是将右侧的新像素添加到总和中,并将窗口最左侧的像素从总和中移除。


我刚有机会认真阅读了一下,之前没有接受,抱歉,但这确实很有帮助。 - Sun
嗨,Quasimondo,我尝试了你的fastBlur算法,它很棒,但是在Genymotion模拟器上会崩溃,特定设备上会出现问题,不知道为什么。崩溃异常是java.lang.ArrayIndexOutOfBoundsException: length=65536; index=65889。你有任何想法吗?谢谢。 - David_Wang
在这些模拟器上,最大允许的数组大小似乎是65536,因此当您传入比该大小更大的图像时,StackBlur会耗尽内存。唯一的解决方法是分块进行模糊处理,尽管这些块也需要填充以避免在边缘处产生伪影。 - Quasimondo

6
此盒状模糊算法在2001年的这篇论文中概述
它的基本原理是将图像先水平方向模糊化,再垂直方向模糊化。最终结果相当于用一个边长为2r+1个像素的正方形框对图像进行卷积运算(即在每个点处从x-rx+r和从y-ry+r)。
在每一步中,模糊的像素值就是该范围内所有像素值的平均值。可以通过在每个点上保持一个累计总数来快速计算。当您将范围向右(或向下)移动一个像素时,需要减去左(或上)端的像素并加上右(或下)端的像素。仍需将这些累计总数除以2r+1,但可以通过预先计算n/(2r+1)的定点值(对于(0≤n<256),具有8位小数部分),并将其存储在dv []中来加速此过程。
每次扫描开始时的简短求和循环仅用于计算运行总数的初始值。
通过使用max()min()进行一些操作来避免访问超出范围的像素,这就是全部。

0

使用CompoundBlur时的提示

从渐变表中,您会注意到模糊效果将从外向内构建,因此它将首先模糊边缘,然后模糊中心。为了从中心向边缘模糊,只需取 mul_table 中的所有值,并从中减去255:这会反转位图-您可以想象在渐变地图中像素的亮度相当于使用的模糊半径-白色像素大模糊,黑色像素小模糊。

快速反转方法:

使用Sublime Text和Microsoft Excel您可以轻松地反转值...

Sublime Text:

通过将所有值垂直排列带有逗号的列,然后通过鼠标滚轮点击和拖动选择向下并按Enter将单个数字放在单个行上。现在再次使用鼠标滚轮点击并拖动,并在每个值之后插入“- 255”,在每个值之前插入“=”(还要单击并拖动以选择所有逗号并将其删除)。现在选择所有行并复制即可。

Excel 的最终格式应为:=(原始 mul_table 值)- 255 ... 即 = 512 - 255

Excel:在 Sublime 中复制格式化值后,将其粘贴到 Excel 中最左上角的单元格中,Excel 将为您评估“=512-255”,并立即创建新的反转值。复制所有单元格并将其粘贴回 js 文件中,并重新插入逗号。

现在,您的 CompoundBlur 将从中心向边缘模糊。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接