近似具有多边形的形状

时间:2013-01-05 22:35:54

标签: ruby vector computer-vision vector-graphics edge-detection

从包含透明区域和彩色区域的PNG图像中,我想生成一个N边(N可配置)的多边形,近似于图像的最佳边缘。我希望这个多边形由一系列向量定义。

例如,让我们考虑以下图片:+ link to plus。我可以通过计算每个像素周围的透明像素数来设法检测图像的边缘。我得到以下矩阵:

0000000000000000
0000053335000000
0000030003000000
0000030003000000
0000020002000000
0533210001233500
0300000000000300
0300000000000300
0300000000000300
0533210001233500
0000020002000000
0000030003000000
0000030003000000
0000053335000000
0000000000000000
0000000000000000

我认为,基于这个矩阵,我应该能够获得所有角落的坐标,从而得到向量,但我无法弄清楚如何。在这种情况下,我希望我的程序返回:

[7,2]->[11,2]
[11,2]->[11,6]
[11,6]->[15,6]
... 

你们中有人有建议或链接吗?

最终,我还想要除90和0之外的近似角度,但这确实是第二阶段。

3 个答案:

答案 0 :(得分:2)

我认为您会发现CV工具包中的许多工具对您有用。您最好利用这些资源,而不是推出自己的解决方案。

我认为你有兴趣提取的两个特征是边角。

边缘,就像你想要的那样,可以让你走向形状的轮廓。你现在可能不感兴趣的是Edge Detection技术。这些将您的图像转换为边/空间的二进制图像。相反,您需要查看Hough Transform,它可以为您的图像中的每一行提供终点。如果你正在处理如你所定义的明确,实线,直线,这应该很好。您已将问题标记为Ruby,因此您可以查看OpenCV(OpenCV是用C语言编写的,但要绑定ruby-opencvjavacv个项目。这是Hough Transform documentation for OpenCV。但是,您可能会发现Hough变换不会为您提供连接线。这取决于图像中实际线条的规律性/不规则性。因此,您可能需要手动将线的端点连接到结构中。

角落,对于您提供的图像可能效果很好。标准算法是Harris corner detection。与Hough变换类似,您可以使用此技术返回图像中“最重要”的特征。已知该技术用于给出一致的结果,即使对于相同事物的不同图像也是如此。因此,它经常用于模式识别等。但是,如果您的图像与提供的图像一样简单,您可能能够以这种方式提取所有形状的角。获得图像的形状只需要根据预定义的N边以有意义的方式连接点。

你应该肯定使用这两个功能空间,看看它们是如何工作的,你可以可能同时使用它们以获得更好的效果。

另外,如果您的图片确实是透明的颜色/强度,您可以将图片转换为'binary image'。请注意,这不仅仅是二进制数据。相反,它意味着您只表示两种颜色,一种由0表示,另一种由1表示。这样做会打开一整套适用于灰度和二进制图像的工具。例如,您在上面手动计算的数字矩阵称为distance transformation,可以使用OpenCV等工具轻松高效地完成。

答案 1 :(得分:1)

Hough transform 是一种标准技术,用于在给定一组点的情况下查找线条,多边形和其他形状。它可能正是你在这里寻找的东西。您可以使用Hough变换查找图像中所有可能的线段,然后将附近的线段组合在一起以获得一组近似图像的多边形。

希望这有帮助!

答案 2 :(得分:0)

在如此简单的情况下,您可以执行以下三个步骤:找到形状的质心,根据x轴与当前点和质心形成的线之间的角度对感兴趣的点进行排序,逐步完成排序点。

考虑到这种情况,质心的x坐标是每个感兴趣点的x坐标除以感兴趣点的总数(分别对于质心的y坐标)的总和。要计算角度,使用几乎任何语言的atan2都是一件简单的事情。您的兴趣点是那些以1或5表示的兴趣点,否则它不是一个角落(根据您的输入)。

不要被霍夫解释你的问题,即它不会给出你所追求的分类坐标。这也是一种昂贵的方法。另外,给定你的矩阵,你已经拥有了其他方法无法击败的完美信息(问题当然是重复你所呈现的那么好的结果 - 在那些场合,Hough可能证明是有用的。)

我的Ruby非常糟糕,因此请将以下代码作为问题的指导原则:

include Math

data = ["0000000000000000",
    "0000053335000000",
    "0000030003000000",
    "0000030003000000",
    "0000020002000000",
    "0533210001233500",
    "0300000000000300",
    "0300000000000300",
    "0300000000000300",
    "0533210001233500",
    "0000020002000000",
    "0000030003000000",
    "0000030003000000",
    "0000053335000000",
    "0000000000000000",
    "0000000000000000"]

corner_x = []
corner_y = []
data.each_with_index{|line, i|
    line.split(//).each_with_index{|col, j|
        if col == "1" || col == "5"
            # Cartesian coords.
            corner_x.push(j + 1)
            corner_y.push(data.length - i)
        end
    }
}

centroid_y = corner_y.reduce(:+)/corner_y.length.to_f
centroid_x = corner_x.reduce(:+)/corner_x.length.to_f

corner = []
corner_x.zip(corner_y).each{|c|
    dy = c[1] - centroid_y
    dx = c[0] - centroid_x
    theta = Math.atan2(dy, dx)
    corner.push([theta, c])
}

corner.sort!
corner.each_cons(2) {|c|
    puts "%s->%s" % [c[0][1].inspect, c[1][1].inspect]
}

这导致:

[2, 7]->[6, 7]
[6, 7]->[6, 3]
[6, 3]->[10, 3]
[10, 3]->[10, 7]
[10, 7]->[14, 7]
[14, 7]->[14, 11]
[14, 11]->[10, 11]
[10, 11]->[10, 15]
[10, 15]->[6, 15]
[6, 15]->[6, 11]
[6, 11]->[2, 11]

从最左下角开始,以反时钟顺序排列的是你的顶点(在左下角最左边的(1,1)开始的笛卡尔坐标中)。