【问题标题】:Calculating length of objects in binary image - algorithm计算二值图像中对象的长度 - 算法
【发布时间】:2011-02-28 19:21:40
【问题描述】:

我需要计算二进制图像中对象的长度(对象内部像素之间的最大距离)。因为它是一个二值图像,所以我们可以认为它是一个二维数组,值为 0(白色)和 1(黑色)。我需要的是一个聪明的(最好是简单的)算法来执行这个操作。请记住,图像中有很多对象。

要澄清的图像:

示例输入图像:

【问题讨论】:

  • 整洁的问题 - 看起来像米饭?种子?
  • 解决方案需要多快?您还需要精确的解决方案吗?
  • @Mark B. - 确实是大米,@shuttle87 - 不一定要 100% 准确。
  • @Mark:MATLAB 的默认图像之一,rice.pngmathworks.co.jp/access/helpdesk/help/toolbox/images/rice.gif
  • @Jacek:你在使用 MATLAB 吗?

标签: algorithm arrays image image-processing


【解决方案1】:

如果对象的边界是凸的并且没有三个顶点在一条线上(即在不改变多边形的情况下不能删除任何顶点),我认为问题很简单:然后你可以随机选择两个点并使用简单的梯度下降类型搜索找到最长的线:

Start with random vertices A, B
See if the line A' - B is longer than A - B where A' is the point left of A; if so, replace A with A'
See if the line A' - B is longer than A - B where A' is the point right of A; if so, replace A with A'
Do the same for B
repeat until convergence

所以我建议为每个种子 blob 找到凸包,移除所有“多余”顶点(以确保收敛)并运行上述算法。

构造一个凸包是一个 O(n log n) 操作 IIRC,其中 n 是边界像素的数量。对于像这样的小物体应该非常有效。 编辑:我只记得需要使用凸包算法的 O(n log n) 来对点进行排序。如果边界点是连通分量分析的结果,则它们已经排序。所以整个算法应该在 O(n) 时间内运行,其中 n 是边界点的数量。 (不过,工作量很大,因为您可能必须编写自己的凸包算法或修改一个以跳过排序。)

添加:评论回复

如果您不需要 100% 的准确度,您可以简单地为每个斑点拟合一个椭圆并计算长轴的长度:这可以从 central moments 计算出来(IIRC,如果最大特征值,它只是平方根协方差矩阵),所以它是一个 O(n) 操作,并且可以在图像的单次扫描中有效地计算。它还有一个额外的优势,即它考虑了一个斑点的所有像素,而不仅仅是两个极值点,即它受噪声的影响要小得多。

【讨论】:

  • 啊,是的,计算连接组件,如果形状有些复杂,每个连接组件的凸包将为您提供一个很好的近似值。
  • @gmatt:这不是一个近似值。我很确定他正在寻找的极值点总是在凸包上。使用凸包不会添加任何新点,它只会删除无论如何都不能成为解决方案的点。
  • 实际上,我认为简单的边缘检测可能会更好地获取轮廓,而不是在每个单独的斑点/形状上完全凸包。另外我不认为梯度下降是最好的方法,太消耗资源了。
  • @gmatt:这种“梯度下降”算法具有 N 距离计算的最坏情况性能,其中 N 是边界点的数量。我怀疑你可以变得更快。凸包是瓶颈,但需要它来确保收敛。如果您发现任何改进算法的方法,请发布!
【解决方案2】:

找出与区域具有相同归一化第二中心矩的椭圆的长轴长度。在 MATLAB 中,您可以使用 regionprops

【讨论】:

    【解决方案3】:

    一种非常粗略的蛮力方法是首先识别所有边缘像素(对象中与非黑色像素相邻的任何黑色像素)并计算所有可能的边缘像素对之间的距离。这些距离中最长的一个将为您提供对象的长度。

    如果对象的形状始终与样本中的形状相似,则可以通过仅评估对象中具有最高和最低 x 和 y 值的像素来加快速度。

    【讨论】:

      【解决方案4】:

      我建议尝试“反向”距离变换。在数学形态学的神奇世界中(抱歉无法抗拒头韵),距离变换为您提供每个像素到其最近边界像素的最近距离。在您的情况下,您对到边界像素的最远距离感兴趣,因此我巧妙地应用了“反向”前缀。

      您可以找到有关距离变换herehere 的信息。我相信matlab按照here实现距离变换。这会让我相信你可以在octave 中找到距离变换的开源实现。此外,如果opencv 实现了它,我一点也不感到惊讶。

      我没有考虑太多,但对我来说很直观,您应该能够反转距离变换并在与原始距离变换大致相同的时间内计算它。

      【讨论】:

        【解决方案5】:

        我认为您可以考虑使用breadth first search 算法。

        基本思想是循环遍历图像中的每一行和每一列,如果您还没有访问过节点(节点是带有彩色像素的行和列),那么您将首先运行广度搜索。您将尽可能访问每个节点,并跟踪对象的最大和最小点。

        这是一些 C++ 示例代码(未经测试):

        #include <vector>
        #include <queue>
        #include <cmath>
        using namespace std;
        
        // used to transition from given row, col to each of the
        // 8 different directions
        int dr[] = { -1, 0, 1, -1, 1, -1, 0, 1 };
        int dc[] = { -1, -1, -1, 0, 0, 1, 1, 1 };
        
        // WHITE or COLORED cells
        const int WHITE = 0;
        const int COLORED = 1;
        
        // number of rows and columns
        int nrows = 2000;
        int ncols = 2000;
        
        // assume G is the image
        int G[2000][2000];
        
        // the "visited array"
        bool vis[2000][2000];
        
        // get distance between 2 points
        inline double getdist(double x1, double y1, double x2, double y2) {
          double d1 = x1 - x2;
          double d2 = y1 - y2;
          return sqrt(d1*d1+d2*d2);
        }
        
        // this function performs the breadth first search
        double bfs(int startRow, int startCol) {
          queue< int > q;
        
          q.push(startRow);
          q.push(startCol);
        
          vector< pair< int, int > > points;
        
          while(!q.empty()) {
            int r = q.front();
            q.pop();
            int c = q.front();
            q.pop();
        
            // already visited?
            if (vis[r][c])
              continue;
        
        
            points.push_back(make_pair(r,c));     
        
            vis[r][c] = true;
        
            // try all eight directions
            for(int i = 0; i < 8; ++i) {
              int nr = r + dr[i];
              int nc = c + dc[i];
        
              if (nr < 0 || nr >= nrows || nc < 0 || nc >= ncols)
                continue; // out of bounds
        
              // push next node on queue  
              q.push(nr);
              q.push(nc);
        
            }    
          }
        
          // the distance is maximum difference between any 2 points encountered in the BFS
          double diff = 0;
          for(int i = 0; i < (int)points.size(); ++i) {
            for(int j = i+1; j < (int)points.size(); ++j) {
              diff = max(diff,getdist(points[i].first,points[i].second,points[j].first,points[j].second));
            }
          }
          return diff;
        }
        
        int main() {
        
          vector< double > lengths;
        
          memset(vis,false,sizeof vis);  
          for(int r = 0; r < nrows; ++r) {
            for(int c = 0; c < ncols; ++c) {
              if (G[r][c] == WHITE)
                continue; // we don't care about cells without objects
              if (vis[r][c])
                continue; // we've already processed this object
        
              // find the length of this object
              double len = bfs(r,c);
              lengths.push_back(len);
            }
          }
        
          return 0;
        }
        

        【讨论】:

        • 如果你只想找到边界上的两个点,那么触摸种子的每个像素听起来效率很低。
        • 包含的列表几乎比代码本身还要长!你在哪里使用复数?!
        • @gmatt - 抱歉,这是来自我的编程竞赛模板,我一直有将它们留在其中的习惯,但我同意这个论坛可能不是一个好主意。感谢您指出。
        • @nikie - 有时简单是最好的。如果图像不是太大,在上面提供的 OP 示例中不是太大,那么我认为上述算法可能足够有效。如果我们说的是十亿像素,那么我同意 BFS 可能不会削减它。
        • 再想一想,我认为这甚至行不通:如果不知道相应的“最大”点,您就无法判断一个点是否是“最小”点。我试图理解 C++ 代码,但我很确定这也行不通:你认为 max&lt;pair&lt;int,int&gt; &gt; 在做什么?
        猜你喜欢
        • 1970-01-01
        • 2013-04-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-23
        • 2013-06-15
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多