了解超快速模糊算法

Sun*_*Sun 9 java algorithm image

我试图理解超快速模糊算法背后的算法.下面是java的端口,它与android一起作为测试.看起来这个版本做了一些我不太了解的优化,也没有任何评论.

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);
}
Run Code Online (Sandbox Code Playgroud)

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

以下循环有什么作用?它与预先计算内核表有关吗?怎么样的div,是内核表大小?我想我想问的是,什么是dv []应该存储?

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

查看水平传递:下面的循环看起来总结了单独的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;
        }
Run Code Online (Sandbox Code Playgroud)

我们只根据半径和当前像素位置选择最左边的像素和最右边的像素吗?

 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]];
Run Code Online (Sandbox Code Playgroud)

接下来让我最为困惑的是:我是否正确地说,正确的左右像素之间的区别,并添加了我们拥有的运行RGB总数?

  rsum+=((p1 & 0xff0000)-(p2 & 0xff0000))>>16;
  gsum+=((p1 & 0x00ff00)-(p2 & 0x00ff00))>>8;
  bsum+= (p1 & 0x0000ff)-(p2 & 0x0000ff);
Run Code Online (Sandbox Code Playgroud)

我没有看过第二遍,因为这几乎是我的头脑.任何澄清将不胜感激,任何关于垂直通道循环的评论也会有所帮助,谢谢.

Qua*_*ndo 18

自从我写了那个,我想我可以解释得最好:-)

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

此行预先计算查找表,以查找可能发生的所有可能的平均值.这是为了避免在内环中进行昂贵的划分.在某些系统上直接进行除法而不是进行数组查找现在实际上可能更快,但是当我写它时,查找是更快的方式.

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;
        }
Run Code Online (Sandbox Code Playgroud)

该算法快速的原因是它使用滑动窗口,因此减少了所需的像素查找次数.窗口从左边缘向右滑动(在第二遍中从上到下),只在右侧添加一个像素,从左侧移除一个像素.上面的代码通过使用最左边的像素预填充窗口来初始化窗口,具体取决于内核大小.

 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]]; 
Run Code Online (Sandbox Code Playgroud)

这是添加新像素但同时处理边框条件的部分(当窗口尝试读取或删除位图外的像素时).

 rsum+=((p1 & 0xff0000)-(p2 & 0xff0000))>>16;
  gsum+=((p1 & 0x00ff00)-(p2 & 0x00ff00))>>8;
  bsum+= (p1 & 0x0000ff)-(p2 & 0x0000ff);
Run Code Online (Sandbox Code Playgroud)

rsum,gsum和bsum是滑动窗口内的累积像素总和.你看到的是右侧的新像素被添加到总和中,窗口中最左边的像素被从总和中移除.


squ*_*age 5

从2001年开始本文概述了这种盒子模糊算法。

它的基本作用是使图像模糊两次。首先在水平方向上,然后在垂直方向上。最终结果与您计算出的图像的卷积具有一个正方形2r+1像素(即在每个点上从x-rx+r以及从y-ry+r)相同。

在每个步骤中,模糊像素值只是该范围内所有像素的平均值。可以通过在每个点上保持总计来快速计算。当您将范围向右(向下)移动一个像素时,您要减去左(顶部)端的像素,然后在右(底部)端添加像素。您仍然必须将这些运行总计除以2r+1,但是可以通过预先计算n/(2r+1)for 的定点值(0?n<256)并将其存储在中dv[](使用8位小数部分)来加快运算速度。

每次扫描开始时的短暂求和循环就在那里,用于计算运行总计的初始值。

并且以位杂耍与max()min()为了避免访问超出范围像素,这是对所有有它。