如何找到能够适合凹多边形内部的最大圆?
只要能够实时处理具有约50个顶点的多边形,暴力算法就可以。
如何找到能够适合凹多边形内部的最大圆?
只要能够实时处理具有约50个顶点的多边形,暴力算法就可以。
为什么?因为圆的边沿上每个点到该中心点的距离都相等。根据定义,最大圆将具有最大半径,并且将至少在两点处接触多边形,因此如果找到距离多边形最远的内部点,则已经找到了圆心。
这个问题在地理学中出现,并通过迭代解决以任意精度。它被称为难以到达的极点问题。请参见《难以到达的极点:计算地球上最遥远地方的算法》。
基本算法如下:
需要注意的是,如何测试一个点是否在多边形内或外:解决这个问题的最简单方法是向右投射一条射线。如果它跨过奇数条边,则在多边形内。如果是偶数,则在多边形外。
此外,就测试到任何边缘的距离而言,您需要考虑两种情况:
(2)很容易。到边缘的距离是到两个顶点距离的最小值。对于(1),相交于从测试点开始以90度角度量的边缘上的最近点将是最接近的点。请参见点到射线或线段的距离。
摘要:理论上,这可以在O(n)时间内完成。实践中,您可以在O(nlogn)时间内完成。
广义沃罗诺伊图。
如果您将多边形的顶点和边视为一组站点,并将内部逐格分割成“最近邻单元格”,则会得到所谓的(广义)沃罗诺伊图。沃罗诺伊图由节点和连接它们的边组成。节点的间隙是其与定义多边形面的距离。
(这里的多边形甚至有洞; 原理仍然有效.)
现在的关键观察是最大内切圆的中心接触三个多边形面(顶点或边),而且没有其他面更靠近。因此,中心必须位于具有最大间隙的 Voronoi 节点上。
在上面的例子中,标记最大内切圆中心的节点接触多边形的两条边和一个顶点。
另外,中轴线是从凸角顶点发出的沃罗诺伊边被删除的沃罗诺伊图。因此,最大内切圆的中心也位于中轴线上。
来源:我的一篇博客文章,其中涉及最大内切圆的概括。您可以在那里找到有关沃罗诺伊图及其与最大内切圆的关系的更多信息。
算法和实现。
您可以计算沃罗诺伊图。Fortune提出了一个最坏情况为O(n log n)的算法,用于点和线段,A sweepline algorithm for Voronoi diagrams,SoCG'86。Held发布了软件包Vroni,预期时间复杂度为O(n log n),它实际上还可以计算最大内切圆。而且boost中似乎也有实现。
对于简单多边形(即没有洞的多边形),Chin等人提出了一种时间最优算法,其运行时间为O(n),Finding the Medial Axis of a Simple Polygon in Linear Time,1999年。
暴力法。
然而,由于您表示使用暴力算法没有问题:那么试试所有三个站点(顶点和边缘)的三元组。对于每个三元组,您找到候选沃罗诺伊节点,即等距离的三个站点,并检查是否有任何其他站点会与候选最大内切圆相交。如果有相交,则将候选项取消。在所有三元组中找到您可以找到的最大值。
请查看我的硕士论文第3章,了解有关计算三个站点等距位置的更多细节。
一个O(n log(n))算法:
我基于cv2实现了一段Python代码,用于获取掩模/多边形/轮廓内的最大/最大内切圆。它支持非凸/中空形状。
import cv2
import numpy as np
def get_test_mask():
# Create an image
r = 100
mask = np.zeros((4 * r, 4 * r), dtype=np.uint8)
# Create a sequence of points to make a contour
vert = [None] * 6
vert[0] = (3 * r // 2, int(1.34 * r))
vert[1] = (1 * r, 2 * r)
vert[2] = (3 * r // 2, int(2.866 * r))
vert[3] = (5 * r // 2, int(2.866 * r))
vert[4] = (3 * r, 2 * r)
vert[5] = (5 * r // 2, int(1.34 * r))
# Draw it in mask
for i in range(6):
cv2.line(mask, vert[i], vert[(i + 1) % 6], (255), 63)
return mask
mask = get_test_mask()
"""
Get the maximum/largest inscribed circle inside mask/polygon/contours.
Support non-convex/hollow shape
"""
dist_map = cv2.distanceTransform(mask, cv2.DIST_L2, cv2.DIST_MASK_PRECISE)
_, radius, _, center = cv2.minMaxLoc(dist_map)
result = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
cv2.circle(result, tuple(center), int(radius), (0, 0, 255), 2, cv2.LINE_8, 0)
# minEnclosingCircle directly by cv2
contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[-2:]
center2, radius2 = cv2.minEnclosingCircle(np.concatenate(contours, 0))
cv2.circle(result, (int(center2[0]), int(center2[1])), int(radius2), (0, 255, 0,), 2)
cv2.imshow("mask", mask)
cv2.imshow("result", result)
cv2.waitKey(0)
我使用直线骨架算法将图像放置在多边形内,分为三个步骤:
请访问https://smartdiagram.com/simple-infographics-3d-charts-2/以尝试。
一个O(n log X)的算法,其中X取决于您想要的精度。
二分查找最大半径R的圆:
在每次迭代中,对于给定的半径r,将每条边E“向内”推动R,以获得E'。对于每个边缘E',将半平面H定义为所有点“在”多边形内部的集合(使用E'作为边界)。现在,在O(n)时间内计算所有这些半平面E'的交集。如果交集非空,则如果使用交集中的任何点作为中心绘制半径为r的圆,则它将位于给定的多边形内部。