非凸多边形内的最大圆

时间:2010-11-25 17:15:29

标签: algorithm polygon computational-geometry geometry

如何找到可以放入凹多边形内的最大圆?

只要能够实时处理具有~50个顶点的多边形,就可以使用强力算法。

7 个答案:

答案 0 :(得分:40)

解决这个问题的关键是首先进行观察:适合任意多边形的最大圆的中心是:

  1. 在多边形内;和
  2. 距离多边形边缘任意一点最远。
  3. 为什么呢?因为圆的边缘上的每个点都与该中心等距。根据定义,最大的圆将具有最大的半径,并且将在至少两个点上触摸多边形,因此如果您发现距离多边形最远的点,则您找到了圆的中心。

    此问题出现在地理位置,并以迭代方式解决为任意精度。它被称为无法接近的极点问题。请参阅Poles of Inaccessibility: A Calculation Algorithm for the Remotest Places on Earth

    基本算法的工作原理如下:

    1. 将R定义为直线区域,从(x min ,y min )到(x max ,y max );
    2. 将R分为任意数量的点。本文使用21作为启发式(意思是将高度和宽度除以20);
    3. 剪裁多边形外的任何点;
    4. 对于剩余部分,找到距边缘上任何一点最远的点;
    5. 从那时起定义一个具有较小间隔和边界的新R,并从步骤2重复以获得任意精度答案。本文将R减少了2的平方根。
    6. 一个注意事项,如何测试一个点是否在多边形内部:问题的这一部分的最简单的解决方案是将光线投射到该点的右侧。如果它穿过奇数个边,则它在多边形内。如果它是偶数,它就在外面。

      此外,至于测试到任何边缘的距离,有两种情况需要考虑:

      1. 该点垂直于该边缘上的一个点(在两个顶点的边界内);或
      2. 不是。
      3. (2)很容易。到边缘的距离是到两个顶点的距离的最小值。对于(1),该边缘上的最近点将是从您正在测试的点开始以90度角与边缘相交的点。请参阅Distance of a Point to a Ray or Segment

答案 1 :(得分:13)

O(n log(n))算法:

  1. 构建P中边缘的Voronoi Diagram。例如,可以使用Fortunes algorithm来完成。
  2. 对于V内的Voronoi节点(等距三边或更多边的点);
  3. 找到P中边缘距离最大的节点。此节点是最大内切圆的中心。

答案 2 :(得分:13)

如果有人正在寻找实际的实现,我设计了一个更快的算法,可以在给定的精度下解决这个问题并使其成为一个JavaScript库。它类似于@cletus描述的迭代网格算法,但它保证获得全局最优,并且在实践中也快20-40倍。

检查出来:https://github.com/mapbox/polylabel

答案 3 :(得分:6)

总结:理论上,这可以在O(n)时间内完成。实际上,你可以在O(n log n)时间内完成。

广义Voronoi图。

如果您将多边形的顶点和边缘视为一组网站并将内部细分为“最近邻居单元格”,那么您将获得所谓的(通用)Voronoi图。 Voronoi图由连接它们的节点和边组成。节点的间隙是指定义多边形面的距离。

Voronoi diagram of a polygon
(这里多边形甚至有孔;原理仍然有效。)

现在的关键观察是最大内切圆的中心接触多边形的三个面(顶点或边),并且没有其他面可以更接近。因此,中心必须位于Voronoi节点上,即具有最大间隙的节点。

在上面的示例中,标记最大内切圆的中心的节点接触多边形的两个边和顶点。

顺便说一下,内侧轴是Voronoi图,其中去除了从反射顶点发出的Voronoi边缘。因此,最大内切圆的中心也位于中轴上。

来源:我的blog article,用于处理某些点上最大内切圆的概括。在那里你可以找到更多关于Voronoi图及其与最大内切圆的关系。

算法&实现

您实际上可以计算Voronoi图。 Fortune给出了点和段的最坏情况O(n log n)算法,用于Voronoi图的扫描线算法,SoCG'86。 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章,了解有关计算三个站点的等距基因座的更多详细信息。

答案 4 :(得分:1)

O(n log X)算法,其中X取决于您想要的精度。

二进制搜索圆的最大半径R:

在每次迭代中,对于给定的半径r,将每个边E向内推“R”,得到E'。对于每个边E',将半平面H定义为多边形“内部”的所有点的集合(使用E'作为边界)。现在,计算所有这些半平面E'的交集,这可以在O(n)时间内完成。如果交点非空,则如果使用交点中的任意点作为中心绘制半径为r的圆,则它将位于给定多边形内。

答案 5 :(得分:0)

我使用“直形骨骼”通过三个步骤将图像放置在多边形内:

  1. 使用Straight Skeleton算法(图1)查找直线骨骼
  2. 基于直线骨架,找到最大的圆(图2)
  3. 在该圆圈内绘制图像(图3)

在以下位置尝试:https://smart-diagram.com/diagram-designer/?template=365

Find the straight skeleton using the Straight Skeleton algorithm Base on the straight skeleton, find the largest circle Draw the image inside that circle

答案 6 :(得分:0)

我实现了一个基于cv2的python代码,以获取mask / polygon / contours中最大/最大的内接圆。它支持非凸/空心形状。

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)

enter image description here
红色圆圈是最大内切圆

来源:https://gist.github.com/DIYer22/f82dc329b27c2766b21bec4a563703cc