所以我一直在尝试编写连接组件标记算法,但它没有给我所需的结果。现在我有一个带有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;
}
}
}
}
答案 0 :(得分: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个对象。
除此之外:您可能想稍微清理一下代码。您不使用eqSet
和disjointSet
,因此请从代码中删除它们。两个数组objectArray
和parents
用于相同的事情;将它们合并为一个。您还应该重构将八个相邻像素发现到一个函数中(对每个潜在邻居调用一次)以使事情更容易阅读并避免上面的索引错误。