非单连通二值图像的周长

Perimeter of non simply connected binary images

本文关键字:周长 二值图像 单连通      更新时间:2023-10-16

我正在尝试计算二进制图像中一个区域的周长。当一个区域是单连通的,即它没有"孔",一切都很简单:我只是检查每个像素是否属于该区域,并且至少有一个不属于该区域的邻居……我有一个变量来计算满足这个条件的像素数。

对于有孔的区域,我用了另一种方法。我从边界中的一个像素开始,如果它本身是一个边界像素,就"跳转"到它的邻居(增加计数器)。当我回到初始像素时,这个过程结束了,还有一些奇怪的地方。像这样:

int iPosCol = iStartCol, int iPosRow = iStartRow;
do 
{
//check neighbors, pick point on the perimeter
//condition: value == label, pixel at the border.
check8Neighbors(iPosCol, iPosRow);
updatePixPosition(iPosCol, iPosRow);
}
while ( iPosC != iStartC || iPosR != iStartR );

问题是,如果区域中的孔靠近边界(1像素距离),该方法将不起作用。

是否存在计算非单连通区域周长的标准方法,或者我是否以错误的方式处理问题?

正如JCooper所指出的那样,连接组件又名区域标记又名轮廓检测是一种寻找连接像素区域的算法,通常在已二值化的图像中,使所有像素都是黑色或白色。

连接组件标签的维基百科条目包含"单次通过"算法的伪代码(http://en.wikipedia.org/wiki/Connected-component_labeling)。

http://en.wikipedia.org/wiki/Connected-component_labeling

另一个单遍算法可以在Chang和Chen的论文"A Component-Labeling algorithm Using Contour Tracing Technique"中找到。这篇论文还包括一个边缘跟踪算法的描述,如果你愿意,你可以用它来找到轮廓。http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.58.3213

那篇论文很好地描述了边缘跟踪,但我将在这里描述基本思想。

假设一个图形的外轮廓由像素a到f表示,背景像素由"-"表示:

- - - - -
- a b - -
- - - c -
- - - d -
- f e - -
- - - - -

如果我们从上到下,沿着每一行从左到右扫描图像,那么遇到的第一个像素是像素a。为了从像素a移动到像素b,然后从b移动到c,我们使用相对于当前像素定义的8个方向来跟踪每次移动的方向,p:

6 7 8
5 p 1
4 3 2

从背景"-"到像素"a"的移动方向为1。虽然我们知道"b"在哪里,但软件不知道,所以我们顺时针检查"a"周围的所有方向,以找到沿轮廓的下一个像素。我们不需要检查方向5(左),因为我们刚刚从背景像素到"a"的左边。我们要做的是顺时针检查6、7、8、1、2等方向,寻找下一个轮廓像素。在相对于"a"在方向6,7,8上只找到背景像素后,我们也沿着方向1找到了"b"。"

如果我们看从c到d的转变,我们向3方向移动。为了找到下一个轮廓像素"e",我们检查方向8,1,2,3,4,并通过向4方向移动来找到轮廓像素"e"。

一般规则是,如果我们的最后一次移动是在方向d,我们检查下一步移动的第一个方向是方向d - 3。如果最后一次移动是在方向5(向左移动),那么我们在方向2开始下一次顺时针搜索。

在代码中,我们通常使用方向0 - 7,显然你会使用模运算或类似的数学,但我希望这个想法是清楚的。Chang和Chen的论文相当好地描述了基本的轮廓跟踪算法,并且还提到了必要的检查,如果算法需要重新跟踪某些像素。


边缘跟踪算法可能足以满足您的需求,但出于各种原因,您可能也想找到像素的连接区域。

对于连接组件算法,要记住的一件事是你想要考虑像素的"邻居"。你可以只看"4个邻居":

-  n  -
n  p  n
-  n  -

,其中"p"为中心像素,"n"为四个相邻像素,"-"为非相邻像素。您还可以考虑"8个邻居",它们只是围绕给定像素的所有像素:

n  n  n
n  p  n
n  n  n

通常,在检查前景对象的连通性时,4邻居是更好的选择。如果选择8邻居技术,则可以将如下所示的棋盘模式视为单个对象:

p - p - p - p - p
- p - p - p - p - 
p - p - p - p - p
- p - p - p - p - 
p - p - p - p - p
- p - p - p - p - 

假设你有一个像下面这样的斑点,前景像素标记为"p",背景像素标记为"-":

- - - - - - - - - -
- - - - p p p - - -
- - p p p - p p - -
- - - p p - p p - -
- - - - p p p - - -
- p p p p p p - - -
- - - - - - - - - -

如果你只考虑外轮廓的像素,你会发现计算周长可能有点棘手。对于下面的像素1,2,3,4和5,您可以使用像素1 - 5计算周长,从像素1到2,然后从像素2到3,依次移动。通常情况下,最好只使用沿对角线的像素1、3和5来计算这段的周长。对于底部的单行像素,必须注意算法不会对这些像素进行两次计数。

- - - - - - - - - -
- - - - p p p - - -
- - 1 2 p - p p - -
- - - 3 4 - p p - -
- - - - 5 p p - - -
- p p p p p p - - -
- - - - - - - - - -

对于相对较大的连接区域,没有"半岛"突出,只有一个像素宽,计算周长相对简单。对于非常小的物体,很难计算出"真正的"周长,部分原因是我们有有限数量的离散方形像素,代表一个现实世界的物体,其轮廓可能是光滑的,有点弯曲。对象的图像表示为chunky。

如果你有一个从边缘跟踪算法中找到的有序像素列表,那么你可以通过检查轮廓像素列表中两个连续像素的X变化和Y变化来计算周长。通过计算沿轮廓像素的像素到像素距离的总和来计算周长。

对于像素N和像素N + 1:如果X相同或Y相同,则从N到N + 1的方向为左、右、上或下,距离为1。

如果像素N和N + 1的X和Y都不相同,则从一个像素移动到下一个像素的方向与水平成45度角,像素中心之间的距离为根号2。

无论您创建什么算法,都要考虑针对简单图形检查其准确性:正方形、矩形、圆形等。圆特别有助于检查周长计算,因为图像中圆(特别是小圆)的轮廓将具有锯齿状而不是光滑的边缘。

- - - - - - - - - -
- - - p p p p - - -
- - p p p p p p - -
- - p p p p p p - -
- - p p p p p p - -
- - - p p p p - - -
- - - - - - - - - -

有一些技术可以在灰度和彩色图像中找到形状并计算周长,这些技术不依赖于二值化来使图像只有黑白,但这些技术更棘手。对于许多应用程序,简单的标准技术将起作用:

  1. 选择阈值对图像进行二值化。
  2. 在二值化图像上运行连通分量/区域标记算法
  3. 使用连通区域的轮廓像素计算周长

许多大学使用的图像处理教科书中有许多关于图像处理的问题的答案。如果你要深入研究图像处理,你应该至少有一本像这样的教科书;它会节省你在网上寻找答案的时间。

数字图像处理(第三版)作者:Gonzalez and Woods

书网站:http://www.imageprocessingplace.com/

你应该可以在网上找到国际版,大约35美元。

如果你最终要写很多代码来执行几何计算,另一本方便的参考书是计算机图形学几何工具施耐德和埃伯利。

http://www.amazon.com/Geometric-Computer-Graphics-Morgan-Kaufmann/dp/1558605940

价格昂贵,但有时你可以在多站点搜索引擎上找到便宜的二手副本,比如

http://www.addall.com

本书的更正、理论pdf和代码可以在这里找到:http://www.geometrictools.com/

我的建议是:让我们假设你想找到一个黑色区域的边界(为了简单)。首先在图像的所有侧面添加一个额外的白色列和一个额外的白色行。这样做是为了简化极端情况,我将尝试解释它在哪里有帮助。

接下来,从您所在区域的任何像素进行广度优先搜索。图中的边被定义为连接相邻的黑色单元格。通过这个BFS,你将找到你的区域中的所有像素。现在选择最底部(您可以在线性中找到它),如果有许多最底部,只需选择其中的任何一个。选择它下面的像素——这个像素肯定是白色的,因为:我们选择了我们区域中最底部的像素,如果像素是黑色的,BFS就会访问它。另外,由于我们添加了额外的行和列,在最底部的像素下面有一个像素。

现在执行另一个BFS,这次通过白色相邻像素(我们在这里添加了额外的行和列)。通过这种方法,我们可以找到一个白色区域围绕着我们感兴趣的黑色区域。现在,所有来自原始黑色区域的像素与新发现的白色区域中的像素相邻的都是边界的一部分,并且只有它们是它的一部分。计算这些像素,就得到了周长

这个解决方案很复杂,因为我们不想把洞的边界算作周长的一部分——如果这个条件不存在,我们可以只计算初始黑色区域中与任何白色像素或图像边界相邻的所有像素(这里我们不需要添加行和列)。

希望这个答案对你有帮助。

也许最简单的方法是运行一个连接组件算法,然后填补漏洞。