【问题标题】:Efficiently implementing erode/dilate有效实施腐蚀/扩张
【发布时间】:2014-02-18 12:58:50
【问题描述】:

因此,通常使用四个 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

【问题讨论】:

  • 这不是一个完整的解决方案,只是一个想法:我认为该操作是可分解的,即您可以通过执行 3x1 扩张和 1x3 扩张来获得 3x3 扩张一行,这要快得多。一个 1x9 的膨胀可以分解成两个 1x3 的膨胀。 (我知道这适用于高斯模糊,但不确定它是否适用于腐蚀/膨胀。)

标签: algorithm math computer-vision filtering convolution


【解决方案1】:

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

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

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

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

您经常想用同一个内核重复膨胀,所以时间再次乘以所需的膨胀次数。

我有三个加快扩张速度的基本思路:

  1. KxL 内核的扩展等于 Kx1 内核的扩展,然后是 1xL 内核的扩展。在 O(KNM) 和 O(LNM)

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

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


现在详细说明第 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));
    }
}

【讨论】:

    【解决方案2】:

    我能想到的唯一方法是缓冲最大像素值和找到它们的行,这样当最大值不再低于它时,您只需对内核大小的行/列进行完整迭代.
    在下面的类似 C 的伪代码中,我假设了有符号整数、源和目标的二维行主数组以及 [±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];
      }
    }
    

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

    【讨论】:

    • 我尝试了这种方法,但未能创建适当的输出。我可能对这个伪代码理解有误。您能否描述以下变量:nx、ny、sy、sx、dx、dy
    • @ckain: nx 和 ny 是图像的宽度和高度(我假设过滤器应用于整个图像)。 sx 和 sy 是它的迭代器,dx 和 dy 是矩形内核的水平和垂直偏移量。
    • @ckain:我应该补充一点,我还没有实际测试过伪代码。我建议的想法是跟踪最大值及其位置,以便仅在绝对必须更新时更新它。
    • @ckain:好的,所以我已经运行了一些测试,我看到了我预期的结果。你是怎么失败的?我可能误解了这个问题:-/
    【解决方案3】:

    提高复杂性的一种理论方法是为 KxK 像素保持 BST,删除先前的 Kx1 像素并添加下一个 Kx1 像素。此操作的成本为 2K log K,并且将重复 NxN 次。总体而言,计算时间将从 NxNxKxK 变为 NxNxKxlog K

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

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

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

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

        【讨论】:

          猜你喜欢
          • 2017-10-26
          • 2023-02-02
          • 2020-03-19
          • 2010-12-01
          • 1970-01-01
          • 2014-07-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多