【问题标题】:Having trouble with connected component labeling algorithm连接组件标记算法有问题
【发布时间】:2014-02-16 23:07:39
【问题描述】:

所以我一直在尝试编写一个连通分量标记算法,但它并没有给我想要的结果。现在我有一张带有 3 朵玫瑰(不重叠)的图像,我想用自己的灰度值标记每朵玫瑰。在我应用标记算法之前,我使用一个阈值来去除背景,只保留玫瑰。玫瑰的灰度值为 1(全白),背景的灰度值为 0(黑色)。这是它的样子:

完成此操作后,我应用了标记算法。它应该根据给玫瑰的标签给玫瑰三个不同的灰度值。但相反,该算法在前两朵玫瑰上创建了这种奇怪的渐变图案,而最后一朵似乎是单个灰度值。这是一张图片:

该算法可能看起来很复杂,但实际上非常简单。我首先在列上递归,然后在行上递归,对于每个非背景像素,我检查是否有任何相邻像素已被标记(意味着它们的 objectArray 值不为零)。如果是这样,我将它们添加到邻居列表中。然后我继续检查这个列表是否不为空,如果是,我通过递增对象值并将其值分配给当前像素的标签值来唯一地标记当前像素,并且我还将当前像素的父值设置为这个唯一的标签。如果不为空,则确定邻居列表中的最小标签值,将所有邻居的父值设置为该标签值,并将当前像素的标签和父值设置为该标签值。我对每个像素重复此操作,直到整个图像都被标记。

完成后,我再次递归像素值,这次将每个像素的标签值设置为其父值。然后我继续根据其标签值为像素分配一个新的灰度值。

我不明白为什么算法没有正确标记玫瑰。有人可以帮帮我吗?算法如下:

public void label()
{
    int objects = 1;
    int[,] objectArray = new int[colors.GetLength(1), colors.GetLength(0)];
    DisjointSets disjointSet = new DisjointSets();
    int[,] parents = new int[colors.GetLength(1), colors.GetLength(0)];
    List<List<int>> eqSet = new List<List<int>>();
    for (int i = 0; i < colors.GetLength(1); i++) for (int j = 0; j < colors.GetLength(0); j++)
    {
        if (this[i, j].Gray == 1)
        {
            List<Label> neighbors = new List<Label>();
            if (i > 0)
            {
                if (this[i - 1, j].Gray == 1)
                {
                    if (objectArray[i - 1, j] != 0)
                    {
                        neighbors.Add(new Label(i - 1, j, 0));
                    }
                }
                if (j > 0)
                {
                    if (this[i - 1, j - 1].Gray == 1)
                    {
                        if (objectArray[i - 1, j - 1] != 0)
                        {
                            neighbors.Add(new Label(i - 1, j - 1, 0));
                        }
                    }
                }
                if (j < colors.GetLength(0))
                {
                    if (this[i - 1, j + 1].Gray == 1)
                    {
                        if (objectArray[i - 1, j] != 0)
                        {
                            neighbors.Add(new Label(i - 1, j, 0));
                        }
                    }
                }
            }
            if (j > 0)
            {
                if (this[i, j - 1].Gray == 1)
                {
                    if (objectArray[i, j - 1] != 0)
                    {
                        neighbors.Add(new Label(i, j - 1, 0));
                    }
                }
                if (i < colors.GetLength(1))
                {
                    if (this[i + 1, j - 1].Gray == 1)
                    {
                        if (objectArray[i + 1, j - 1] != 0)
                        {
                            neighbors.Add(new Label(i + 1, j - 1, 0));
                        }
                    }
                }
            }
            if (i < colors.GetLength(1))
            {
                if (this[i + 1, j].Gray == 1)
                {
                    if (objectArray[i + 1, j] != 0)
                    {
                        neighbors.Add(new Label(i + 1, j, 0));
                    }
                }
                if (this[i + 1, j + 1].Gray == 1)
                {
                    if (objectArray[i + 1, j + 1] != 0)
                    {
                        neighbors.Add(new Label(i + 1, j + 1, 0));
                    }
                }
            }
            if (j < colors.GetLength(0))
            {
                if (this[i, j + 1].Gray == 1)
                {
                    if (objectArray[i, j + 1] != 0)
                    {
                        neighbors.Add(new Label(i, j + 1, 0));
                    }
                }
            }

            if (neighbors.Count == 0)
            {
                objects++;
                objectArray[i, j] = objects;
                parents[i, j] = objects;
            }
            if (neighbors.Count > 0)
            {
                int smallestLabel = 10000;
                foreach (Label x in neighbors)
                    if (objectArray[x.X, x.Y] < smallestLabel)
                        smallestLabel = objectArray[x.X, x.Y];

                foreach (Label x in neighbors)
                    parents[x.X, x.Y] = smallestLabel;

                objectArray[i, j] = smallestLabel;
                parents[i, j] = smallestLabel;
            }
        }
    }
    for (int i = 0; i < colors.GetLength(1); i++) for (int j = 0; j < colors.GetLength(0); j++)
    {
        if (this[i, j].Gray == 1)
        {
            if (objectArray[i, j] != 0)
            {
                objectArray[i, j] = parents[i, j];
                ColorWrap c = this[i, j];
                c.X = (float)objectArray[i, j] / objects;
                c.Y = (float)objectArray[i, j] / objects;
                c.Z = (float)objectArray[i, j] / objects;
                this[i, j] = c;
            }
        }
    }
}

【问题讨论】:

  • 简单地为构成对象的每个像素设置一个 int 值,我用它来为这些对象赋予特定的灰度值。算法应该检查图像的连接像素,并给它找到的每个“对象”(连接像素的“组”,应该是每一朵玫瑰)一个标签值(例如,构成左上角玫瑰的像素将获得标签值 1,左下角的玫瑰像素将分别获得标签值 2,组成右玫瑰的像素将分别获得标签值 3)。我使用这些标签值给这些像素一个新的灰度值 (c.X = (float)objectArray[i, j] / objects; etc)
  • @user1683526 这是二值图像还是灰度图像?
  • 这是一个彩色图像,在我对其进行其余操作之前将其转换为灰度。我可能应该更清楚这一点。

标签: c# algorithm components


【解决方案1】:

检查第三个邻居时出现索引错误:

                if (this[i - 1, j + 1].Gray == 1)
                {
                    if (objectArray[i - 1, j] != 0)
                    {
                        neighbors.Add(new Label(i - 1, j, 0));
                    }
                }

这三个位置都应该是“j + 1”。

不过,这并不能解决您的问题。当您的算法遇到对角线边缘时,您的算法会出现问题,这些对角线边缘是西北方向的黑色和东南方向的白色。

您从左到右按列扫描图像,从上到下按行扫描图像。您检查了八个潜在的邻居,但实际上,您只能将像素添加到您已经通过的邻居列表中,即您左侧列中的三个像素和当前位置上方的像素。其他四个相邻像素的父(或对象索引)为 0。

现在考虑这样的边:

          #######...
          ######....
          #####.....
          ####......
          ###O......
          ###.......
          ##x.......
          #xx.......

('#' 是黑色,'.' 是未分配的白色,'x' 是已分配父级的白色,'O' 标记您当前的位置。)

您只会找到黑色或未分配的相邻瓷砖。您的邻居列表将为空,这意味着您的算法创建了一个新对象,尽管它在逻辑上应该属于东南部尚未发现的对象。

(您尝试通过为所有相邻像素分配新组的值来回溯,但这只能修复一个像素。它还可以创建空组,即没有对应像素的对象。)

无论如何,我认为您的方法太复杂了。它也没有考虑到组件主体上方和左侧的角落。无需创建额外的数据结构,如果您使用灰度图像,您可以在图片本身中进行标记。将图像转换为纯黑白后,将所有像素传递一次。如果一个像素是白色的,从那里用对应于下一个对象的正灰度值填充图片并增加对象的数量。然后再传一遍所有像素,根据物体id和物体个数调整灰度值。

警告:当我说你可以做图片本身的所有事情时,你将标签限制为 254 个对象。

旁白:您可能需要稍微清理一下您的代码。您不使用eqSetdisjointSet,因此请从您的代码中删除它们。两个数组objectArrayparents 用于同一事物;将它们合并为一个。您还应该将查找八个相邻像素的方法重构为一个函数(每个潜在邻居调用一次),以使事情更易于阅读并避免上述索引错误。

【讨论】:

  • 非常感谢。我会研究洪水填充算法,我没想到。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-26
  • 1970-01-01
  • 2010-11-06
  • 2018-05-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多