运行长度编码数字形状的轮廓

5

数字形状是二进制图像中一组相连的像素(一个 blob)。

它可以通过运行长度编码来紧凑表示,即将像素分组成水平线段,并存储起始端点坐标和长度。通常,RLC 表示按栅格顺序存储运行,即从左到右逐行存储。

对于光滑形状,存储要求从 O(N²) 降至 O(N)。

形状的轮廓是一串封闭的像素链,当其内部被填充(通过 flood filling 算法)时,恢复形状。它也是 O(N) 表示。当形状可用位图表示时,可以通过 contouring 算法获得轮廓。

我正在寻找一种算法,可以直接计算给定其 RLC 表示的形状的轮廓,而不需要在中间绘制它。该算法预计以运行次数为线性时间运行。

enter image description here

你遇到解决方案了吗?

1
请看一下你的轮廓,它似乎有误。在左下角附近有一个像素完全被蓝色和绿色像素包围,但仍然是轮廓的一部分。所有其他轮廓像素似乎定义为被4个邻居包围的“非轮廓”像素,但这个像素突出了。 - schnaader
1
您期望输出的格式是什么?是一个大纲点列表吗?如果是,它们需要按特定顺序排序吗(例如顺时针绕组)? - Jerry Federspiel
1
@schnaader:我已经修复了这个问题。这个图只是为了说明而存在的。 - user1196549
@JerryFederspiel:没错,顺时针绕。 - user1196549
3个回答

1

如果一个像素点填充了,但相邻的像素点未填充,则该像素点为边界像素。给定每行填充像素的RLE编码,我们可以对三个相邻的行进行操作,计算出边界像素的RLE版本,然后解码它。

基本上,我们有一个扫描线算法。使用三行如下:

   ***********   ****
************************
  ****        ******

我们从RLE中获取事件点(^):

   ***********   ****
************************
  ****        ******
^ ^^  ^       ^  ^  ^^  ^

首先要做的是将具有上下空像素的中间填充像素指定为边界。(如果需要指导,对于区间列表的差集算法非常相似。)
   ***********   ****
BBB***BBBBBBBBBBB***BBBB
  ****        ******

然后,对于已填充但未知是否为边界的区间,检查左端点是否向左有空格,右端点是否向右有空格。如果是(分别),它们就是边界。


抱歉,我没有表述清楚,轮廓应该按顺时针顺序生成为链。挑战的一部分是重新排序发现的轮廓像素。 - user1196549

0

提示:

如其他答案所述,发射轮廓像素列表可以实现为扫描线过程,在此期间检查运行端点的3x3邻域。

此过程将以混乱的方式发出像素,作为需要存储和重新排序的直接和反向弧序列。

另一种选择是基于实现标准Moore邻域算法的想法,该算法具有按所需顺序枚举轮廓像素的优点。

此过程需要知道当前像素周围的8个邻域配置,并且想法是在移动到另一个像素时更新此邻域:我们维护包含当前像素的运行的索引以及上下行中两个面对的运行的索引。

在移动到另一个像素时,我们需要更新这三个索引,这将涉及对排序运行列表进行短顺序搜索。这可以看作是对像素的伪随机访问机制,考虑到连续访问是强烈局部的并且可以被某种方式缓存。


更新:

在我使用的游程编码表示中,只有黑色行被编码为三元组(X, Y, L)。这些行按从上到下的行顺序排序,然后按每行从左到右的顺序排列。

为了方便起见,我们可以切换到“线性寻址”方案,就好像所有图像行都已经连接在一起,每个像素由单个数字Z = X + Y.Nx(其中Nx是图像宽度)指定。

因此,我们有一个黑色行的列表,白色行隐含在两个相邻的黑色行之间。

在处理过程中,我们可以始终记住立即在当前像素之前或之上开始的行的索引(R[I].Z <= Z < R[I+1].Z)。我们可以通过检查是否在行内或在行和下一行之间来确定像素的颜色(Z < R[I].Z + R[I].L)。

如果我们向左移动一个位置,Z减少1,我们可能需要选择前一个行(I--)。

如果我们向上移动一个位置,Z 会减少 Nx ,我们可能需要回溯若干次(I-- 直到 R[I].Z <= Z 再次成立)。

enter image description here

该图片展示了当前像素及其四个相邻像素,以及黑色行的“影响区域”。我们可以类似地处理所有八个位移方向。

正如我们所看到的,每次移动最多需要一行中运行的数量的操作,被认为是一个小值。利用这个概念,我们可以以合理的代价遍历RLC表示,而无需重建整个位图。

由于Moore邻域算法的时间复杂度与轮廓长度成线性关系,基于这种线性运行寻址的实现也将花费线性时间(对于每行有限数量的运行)。


0
注意:本答案假设“非轮廓”意味着“被4个邻居包围”,因此结果与您的示例略有不同(1像素绿色而不是蓝色)。 所有轮廓像素都是指不是所有4个“邻居像素”(像素左、右、上、下)都设置的像素。
从上到下解码RLC时,您可以使用以下伪代码算法获取轮廓像素:
 For the first line
   All decoded pixels are outline pixels
 For the subsequent lines
   Leftmost and rightmost pixels of each RLC run are outline pixels
   All other pixels are outline pixels if:
     The pixel above isn't set (case A)
     The pixel below isn't set (case B)

情况A和B意味着您需要查看当前像素上方/下方的像素,因此算法实际上应该是一种流水线式/向前查看一行,因为在解码下一行之前,将无法检测到情况B。

编辑:为了随后按顺时针顺序对像素进行排序,您可以使用外轮廓是对角连接的单像素宽线的事实。选择顶部行中的一个像素,您将有两个可能的下一个像素,跟随右侧、下方或右侧和下方的像素。之后,只需跟随您尚未访问的邻居像素,直到没有邻居像素为止。例如:

  /----- First pixel you pick, A and B are neighbour candidates, A is the "correct" one
  v
  xAxxx           
 B     x        
 x    x      xxx
 x     xxxxxx   x
  xx           x
    xxxxxxxxxxx

  s0123             Result after following the neighbours (s = start, e = end),
 e     4            numbers from 0-9 show order of traversal
 1    5      234
 0     678901   5
  98           6
    76543210987

抱歉,我没有表述清楚,轮廓应该按顺时针顺序产生。挑战的一部分是重新排列被发现的像素以形成轮廓。 - user1196549
@YvesDaoust:添加了如何进行顺时针排序的说明。 - schnaader
1
顺时针绕不意味着角度增加,而是指当您沿顺时针方向转动形状时,遵循像素链的自然顺序。由于凹面,角度可能不会单调增长。 - user1196549
@YvesDaoust:你说得对。我想我找到了更好的排序方法,请看我的编辑。 - schnaader
这在概念上是正确的。可以证明轮廓像素恰好有两个8连通邻居,因此可以进行链式跟踪。但是当您在一个像素处时,如何知道这两个邻居是哪些?查找整个列表将太昂贵。 - user1196549

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接