我正在尝试理解super fast blur algorithm背后的算法。下面是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);
}
如果我的猜测错了,请纠正我:
以下循环有什么作用?它与预先计算内核表有关吗?怎么样的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);
我没有看过第二遍,因为这几乎是我的头脑。任何澄清都会受到赞赏,对垂直传递循环的任何评论都会有所帮助。
答案 0 :(得分:17)
自从我写了一篇,我想我可以解释得最好: - )
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是滑动窗口内的累积像素总和。你看到的是右侧的新像素被添加到总和中,窗口中最左边的像素被从总和中移除。
答案 1 :(得分:4)
此框模糊算法在this paper from 2001中列出。
它基本上做的是模糊图像两次;首先是水平方向,然后是垂直方向。最终结果与计算图像卷积的方式相同(2r+1
到x-r
以及x+r
到每个点y-r
。)
在每个步骤中,模糊像素值仅仅是该范围内所有像素的平均值。这可以通过在每个点保持运行总计来快速计算。将范围向右(向下)移动一个像素时,将减去左(顶)端的像素,并在右(底)端添加像素。您仍然必须将这些运行总计除以y+r
,但可以通过为2r+1
预先计算n/(2r+1)
的定点值并将其存储在(0≤n<256)
(与一个8位小数部分。)
每次扫描开始时的短求和循环就是计算运行总计的初始值。
为了避免访问超出范围的像素而对dv[]
和max()
进行了一些操作,这就是它的全部内容。
答案 2 :(得分:0)
您会从渐变表中注意到模糊将从外部向内构建,因此它将首先模糊边缘然后模糊中心。为了从中心向边缘模糊,只需获取 mul_table 中的所有值并从中减去255:这会反转位图 - 您可以想象渐变映射中像素的亮度相等到那里使用的模糊半径 - 白色像素大模糊,黑色像素小模糊。
快速反转的方法:
使用Sublime Text和Microsoft Excel可以轻松地反转值...
Sublime Text:
使用垂直排列的逗号将所有值放入列中,然后通过单击并使用鼠标滚动拖动,您可以向下选择并按Enter键将单个数字放在一行上。现在再次单击并拖动鼠标滚轮并插入&#34; - 255&#34;在每个值之后,和#34; =&#34;在每个值之前(同时单击并拖动以选择所有逗号并删除它们)。现在选择所有行并复制。
Excel的最终格式应为: = (原始mul_table值) - 255 ...即 = 512 - 255 强>
Excel:在Sublime中复制格式化的值后,粘贴到Excel中最左上角的单元格,Excel将评估&#34; = 512-255&#34;为您而立即创造新的倒置值。复制所有单元格并粘贴回您的js文件并重新插入逗号。
你的CompoundBlur现在会从中心向边缘模糊..