非凸多边形内的最大圆
Posted
技术标签:
【中文标题】非凸多边形内的最大圆【英文标题】:Largest circle inside a non-convex polygon 【发布时间】:2011-05-15 20:02:26 【问题描述】:如何找到可以容纳在凹多边形内的最大圆?
蛮力算法是可以的,只要它可以实时处理具有约 50 个顶点的多边形。
【问题讨论】:
请注意,“实时”并不代表速度。实时意味着可以精确预测获得结果的时间(在预定义的范围内) 大概这些不是正多边形? @JonB 正确,这应该适用于凹多边形。 抱歉,今天的阅读理解有困难。 凸多边形看这里:***.com/questions/3953623/… 【参考方案1】:我使用 Straight Skeletons 通过三个步骤将图像放置在多边形内:
-
使用直骨架算法查找直骨架(图 1)
在直骨架的基础上,找到最大的圆(图2)
在该圆圈内绘制图像(图 3)
试试看:https://smartdiagram.com/simple-infographics-3d-charts-2/
【讨论】:
【参考方案2】:我实现了一段基于 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://gist.github.com/DIYer22/f82dc329b27c2766b21bec4a563703cc
【讨论】:
谢谢,这对我有帮助!【参考方案3】:解决这个问题的关键是首先进行观察:适合任意多边形内的最大圆的中心是以下点:
-
在多边形内部;和
距多边形边缘上的任何点最远。
为什么?因为圆边缘上的每个点都与该中心等距。根据定义,最大的圆将具有最大的半径,并且将在至少两个点上与多边形接触,因此,如果您找到离多边形最远的点,您就找到了圆的中心。
这个问题出现在地理学中,并以任意精度迭代求解。它被称为不可访问的极点问题。见Poles of Inaccessibility: A Calculation Algorithm for the Remotest Places on Earth。
基本算法是这样工作的:
-
将 R 定义为从 (xmin,ymin) 到 (xmax,ymax 的直线区域子>);
将 R 分成任意数量的点。论文使用 21 作为启发式(意思是将高和宽除以 20);
剪裁多边形外的任何点;
对于余数,找到距离边缘上任意点最远的点;
从那时起,定义一个具有更小间隔和界限的新 R,并从第 2 步开始重复,以获得任意精度的答案。该论文将 R 减少了平方根 2 的因子。
注意,如何测试一个点是否在多边形内:这部分问题的最简单解决方案是在该点的右侧投射一条射线。如果它穿过奇数条边,则它在多边形内。如果是偶数,则在外面。
此外,就测试到任何边缘的距离而言,您需要考虑两种情况:
-
该点垂直于该边上的一个点(在两个顶点的边界内);或
不是。
(2) 很简单。到边的距离是到两个顶点的距离中的最小值。对于 (1),该边缘上的最近点将是从您正在测试的点开始以 90 度角与边缘相交的点。见Distance of a Point to a Ray or Segment。
【讨论】:
似乎是一种实现起来相当简单的算法,这正是我正在寻找的。根据文章,不能保证找到的解决方案是绝对最大值(对于我的特殊情况,这可能不是问题)。 我认为可以修改此算法以确定绝对最大值。这个想法是为每个矩形计算两个值:到多边形边缘的最大距离的下限(矩形的 4 个顶点的最大距离)和上限(通过添加 0.5*sqrt(rect_size_x^2 + rect_size_y^2)。然后,运行细分搜索,将所有未处理的候选矩形保留在优先级队列中(按上限降序排列),并丢弃所有上限低于目前发现的最大下限的矩形。 链接坏了...另一个参考:arxiv.org/pdf/1212.3193.pdf 很好的答案!这种解释使我能够在短短几分钟内用代码实现解决方案。 是否有正确性证明或质量评估?如果点选得不好,这显然会遇到局部最小值。【参考方案4】:总结:理论上,这可以在 O(n) 时间内完成。实际上,您可以在 O(n log n) 时间内完成。
广义 Voronoi 图。
如果您将多边形的顶点和边视为一组站点并将内部细分为“最近邻单元”,那么您将得到所谓的(广义)Voronoi 图。 Voronoi 图由节点和连接它们的边组成。节点的clearance 是到其定义多边形面的距离。
(这里的多边形甚至有洞;原理仍然有效。)
现在的关键观察是最大内切圆的中心接触多边形的三个面(顶点或边),没有其他面可以更接近。所以中心必须位于Voronoi节点上,即间隙最大的节点。
在上面的示例中,标记最大内切圆中心的节点接触多边形的两条边和一个顶点。
顺便说一下,中轴是 Voronoi 图,其中移除了从反射顶点发出的那些 Voronoi 边。因此,最大内接圆的圆心也位于中轴上。
来源:我的一个blog article,它在某个点处理最大内切圆的概括。在那里您可以找到有关 Voronoi 图及其与最大内切圆的关系的更多信息。
算法和实现。
您实际上可以计算 Voronoi 图。 Fortune, Voronoi 图的扫描线算法, SoCG'86 给出了用于点和线段的最坏情况 O(n log n) 算法。 Held 发布了具有预期 O(n log n) 时间复杂度的软件包Vroni,它实际上也计算了最大内切圆。 boost 中似乎也有实现。
对于简单的多边形(即没有孔),在 O(n) 时间内运行的时间最优算法归功于 Chin 等人,Finding the Medial Axis of a Simple Polygon in Linear Time,1999。
蛮力。
但是,正如您所说,您可以使用蛮力算法:简单地尝试所有三元组站点(顶点和边)怎么样。对于每个三元组,您找到候选 Voronoi 节点,即到三个站点的等距基因座,并检查是否有任何其他站点与候选最大内切圆相交。如果有交叉路口,您解雇候选人。从所有三胞胎中找出你能找到的最好的。
请参阅我的Master thesis 中的第 3 章,了解有关计算三个站点的等距基因座的更多详细信息。
【讨论】:
【参考方案5】:如果有人正在寻找实际的实现,我设计了一个更快的算法,可以在给定的精度下解决这个问题,并将它变成一个 javascript 库。它类似于@cletus 描述的迭代网格算法,但它保证获得全局最优,并且在实践中也快 20-40 倍。
查看:https://github.com/mapbox/polylabel
【讨论】:
这在 Java 中可用吗? 我在 C# 中需要这个,所以移植了它:gist.github.com/dfaivre/acfef42cdbf411555956e9eba65dd30d 相关:***.com/questions/1203135/… 这个答案对我很有帮助!我在 Dart 中需要这个,所以我移植了它:pub.dev/packages/polylabel【参考方案6】:O(n log X) 算法,其中 X 取决于您想要的精度。
二分查找圆的最大半径R:
在每次迭代中,对于给定的半径 r,将每个边 E“向内”推 R,得到 E'。对于每个边 E',将半平面 H 定义为多边形“内部”所有点的集合(使用 E' 作为边界)。现在,计算所有这些半平面 E' 的交集,这可以在 O(n) 时间内完成。如果交点不为空,那么如果你以交点中的任意一点为中心画一个半径为 r 的圆,它将在给定的多边形内。
【讨论】:
似乎需要多边形的凸度。对于有孔或无孔的非凸多边形,我可以立即构建示例,其中任何此类半平面集的所有交点都是空的,因为可能有两条“背靠背”的边。【参考方案7】:一个 O(n log(n)) 算法:
-
在 P 中构造边的Voronoi Diagram。这可以通过例如Fortunes algorithm 来完成。
对于 P 内的 Voronoi 节点(与三个或更多边等距的点);
在 P 中找到到边距离最大的节点。该节点是最大内切圆的中心。
【讨论】:
您想要的是 边 的 Voronoi 图,而不是顶点。例如,参见valis.cs.uiuc.edu/~sariel/research/CG/applets/medial_axis/…。边 Voronoi 图有曲线段,顶点 Voronoi 图只有直线。您想要的另一个名称是“中轴”。这是一个讨论差异的网站:groups.csail.mit.edu/graphics/classes/6.838/S98/meetings/m25/…以上是关于非凸多边形内的最大圆的主要内容,如果未能解决你的问题,请参考以下文章
HDU - 3644:A Chocolate Manufacturer's Problem(模拟退火, 求多边形内最大圆半径)