有效地实施侵蚀/扩张

时间:2014-02-18 12:58:51

标签: algorithm math computer-vision filtering convolution

通过使用四个for循环来实现通常且非常低效的最小/最大过滤器。

for( index1 < dy ) { // y loop
    for( index2 < dx ) { // x loop
        for( index3 < StructuringElement.dy() ) { // kernel y
            for( index4 < StructuringElement.dx() ) { // kernel x
                pixel = src(index3+index4);
                val = (pixel > val) ? pixel : val; // max
            }
        }
        dst(index2, index1) = val;
    }
}

然而,这种方法效率很低,因为它再次检查先前检查的值。所以我想知道在下一次迭代中使用先前检查过的值来实现这个的方法是什么?

可以对结构元素大小/原点进行任何假设。

更新:我特别希望了解这种或某种实施方式的任何见解:http://dl.acm.org/citation.cfm?id=2114689

5 个答案:

答案 0 :(得分:8)

我一直关注这个问题,希望有人会写出一个充实的答案,因为我正在思考同样的问题。

到目前为止,这是我自己的尝试;我没有对此进行测试,但我认为你可以通过任何结构元素重复扩张和侵蚀,只需访问每个像素两次:

假设:假设结构元素/内核是KxL矩形,图像是NxM矩形。假设K和L是奇数。

您概述的基本方法有四个for循环,需要O(K*L*N*M)个时间才能完成。

通常你想用相同的内核反复扩张,所以时间再乘以所需的扩张数量。

我有三个加速扩张的基本想法:

  1. KxL内核的扩张等于通过Kx1内核扩张,然后通过1xL内核扩张。您可以在O(K N M)和O(L N M)

  2. 中仅使用三个for循环进行这两种扩张
  3. 然而,您可以更快地对Kx1内核进行扩张:您只需要访问每个像素一次。为此,您需要一个特定的数据结构,如下所述。这允许您在O(N * M)中进行单次扩张,而不管内核大小

  4. Kx1内核的重复扩张等于较大内核的单次扩张。如果使用Kx1内核扩展P次,则等于使用((K-1)*P + 1) x 1内核的单次扩张。 因此,您可以在O(N * M)时间内一次性使用任何内核大小进行重复扩张。


  5. 现在对步骤2进行详细说明 您需要具有以下属性的队列:

    • 以恒定时间将元素推送到队列的后面。
    • 以恒定时间从队列前面弹出一个元素。
    • 以恒定时间查询队列中当前最小或最大的元素。

    此stackoverflow答案中描述了如何构建此类队列:Implement a queue in which push_rear(), pop_front() and get_min() are all constant time operations。 不幸的是没有多少伪代码,但基本的想法似乎是合理的。

    使用这样的队列,您可以一次性计算Kx1扩张:

    Assert(StructuringElement.dy()==1);
    int kernel_half = (StructuringElement.dx()-1) /2;
    
    for( y < dy ) { // y loop
    
        for( x <= kernel_half ) { // initialize the queue 
            queue.Push(src(x, y));
        }
    
        for( x < dx ) { // x loop
    
            // get the current maximum of all values in the queue
             dst(x, y) = queue.GetMaximum();
    
            // remove the first pixel from the queue
            if (x > kernel_half)
                queue.Pop();
    
            // add the next pixel to the queue
            if (x < dx - kernel_half)
                queue.Push(src(x + kernel_half, y));
        }
    }
    

答案 1 :(得分:1)

提高复杂度的理论方法是保持KxK像素的BST,删除预先存在的Kx1像素并向其添加下一个Kx1像素。此操作的成本为2K log K,并且将重复NxN次。总的来说,计算时间将从NxNxKxK

变为NxNxKxlog K.

答案 2 :(得分:1)

我能想到的唯一方法是缓冲最大像素值和它们所在的行,这样你只需要在内核大小的行/列上进行完整的迭代,而最大值不再在它下面。
在下面的类C伪代码中,我假设有符号整数,源和目标的2d行主阵列和[±dx,±dy]上的矩形内核。

//initialise the maxima and their row positions
for(x=0; x < nx; ++x)
{
  row[x] = -1;
  buf[x] = 0;
}

for(sy=0; sy < ny; ++sy)
{
  //update the maxima and their row positions
  for(x=0; x < nx; ++x)
  {
    if(row[x] < max(sy-dy, 0))
    {
      //maximum out of scope, search column
      row[x] = max(sy-dy, 0);
      buf[x] = src[row[x]][x];
      for(y=row[x]+1; y <= min(sy+dy, ny-1); ++y)
      {
        if(src[y][x]>=buf[x])
        {
          row[x] = y;
          buf[x] = src[y][x];
        }
      }
    }
    else
    {
      //maximum in scope, check latest value
      y = min(sy+dy, ny-1);
      if(src[y][x] >= buf[x])
      {
        row[x] = y;
        buf[x] = src[y][x];
      }
    }
  }

  //initialise maximum column position
  col = -1;

  for(sx=0; sx < nx; ++sx)
  {
    //update maximum column position
    if(col<max(sx-dx, 0))
    {
      //maximum out of scope, search buffer
      col = max(sx-dx, 0);
      for(x=col+1; x <= min(sx+dx, nx-1); ++x)
      {
        if(buf[x] >= buf[col]) col = x;
      }
    }
    else
    {
      //maximum in scope, check latest value
      x = min(sx+dx, nx-1);
      if(buf[x] >= buf[col]) col = x;
    }

    //assign maximum to destination
    dest[sy][sx] = buf[col];
  }
}

当源从左上角的最大值平滑到右下角的最小值时,会发生最坏情况的性能,在每一步都强制执行完整的行或列扫描(尽管它仍然比原始的嵌套循环更有效) 。
我希望平均案例性能要好得多,因为包含增加值(行和列)的区域将在需要扫描之前更新最大值。
也就是说,没有实际测试它我会建议你运行一些基准而不是相信我的直觉!

答案 3 :(得分:1)

相同类型的优化可用作“非最大抑制”算法 http://www.vision.ee.ethz.ch/publications/papers/proceedings/eth_biwi_00446.pdf

答案 4 :(得分:0)

在1D中,在O(N)中使用形态小波变换:

https://gist.github.com/matovitch/11206318

你可以在2D中获得O(N * M)。 HugoRune解决方案更简单,可能更快(尽管这可能会得到改进)。