【问题标题】:Understanding super fast blur algorithm了解超快速模糊算法
【发布时间】:2014-02-20 12:52:37
【问题描述】:

我试图了解super fast blur algorithm 背后的算法。下面是与 android 一起工作的 java 端口作为测试。貌似这个版本做了一些我不太明白的优化,也没有任何cmets。

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);

我还没有看第二遍,因为这几乎超出了我的想象。任何澄清将不胜感激,任何关于垂直传球循环的评论也会有所帮助,谢谢。

【问题讨论】:

标签: java image algorithm


【解决方案1】:

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

 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 是滑动窗口内像素的累加和。您看到的是右侧的新像素被添加到总和中,而窗口中最左侧的像素被从总和中移除。

【讨论】:

  • 我刚刚有机会真正阅读了一些东西,所以很抱歉没有早点接受,但这真的很有帮助。
  • 嗨,Quasimondo,我试过你的 fastBlur 算法,效果很好,但它在 Genymotion 模拟器上崩溃,使用特定设备,不知道为什么。崩溃异常是 java.lang.ArrayIndexOutOfBoundsException: length=65536; index=65889 你有什么想法吗?谢谢
  • 在我看来,这些模拟器上允许的最大数组大小为 65536,因此当您传入大于该大小的图像时,StackBlur 会耗尽内存。解决这个问题的唯一方法是在图块中进行模糊处理,尽管还需要对图块进行填充以避免边缘出现伪影。
【解决方案2】:

this paper from 2001 中概述了此框模糊算法。

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

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

每次扫描开始时的简短求和循环仅用于计算运行总计的初始值。

加上 max()min() 的一些杂耍以避免访问超出范围的像素,这就是它的全部内容。

【讨论】:

    【解决方案3】:

    使用CompoundBlur时的提示

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

    快速反相方法:

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

    崇高的文字:

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

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

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

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

    【讨论】:

      猜你喜欢
      • 2022-10-06
      • 2015-06-22
      • 1970-01-01
      • 2021-11-09
      • 2013-02-05
      • 1970-01-01
      • 2015-10-16
      • 2021-12-03
      • 1970-01-01
      相关资源
      最近更新 更多