用凹域域对一组点进行三角测量

Dan*_*ton 7 algorithm delaunay triangulation convex-hull

建立

给定凸包内的一些节点集,假设该域包含一个或多个凹面区域:

具有凹陷区域的点分布

其中蓝点是点,黑线表示域.假设点被保持为points长度的2D数组n,其中n是点对的数量.

然后让我们使用类似scipy.spatial的Delaunay方法对点进行三角测量:

域内的三角测量

如您所见,人们可能会遇到穿过域的三角形的创建.

删除任何跨越域外的三角形的好算法方法是什么?理想但不一定,单面边缘仍然保留域形状(即,没有去除三角形的主要间隙).


由于我的问题似乎继续得到大量的活动,我想跟进我目前正在使用的应用程序.

假设您已定义边界,则可以使用光线投射算法来确定多边形是否在域内.

去做这个:

  1. 取每个多边形的质心为C_i = (x_i,y_i).
  2. 然后,想象一条线L = [C_i,(+inf,y_i)]:也就是说,一条横跨东部超过域末端的线.
  3. 对于边界s_i中的每个边界段S,检查交叉点L.如果是,请向内部计数器添加+1 intersection_count; 否则,什么都不添加.
  4. 后之间的所有交叉点的数量Ls_i for i=1..N计算:

    if intersection_count % 2 == 0:
        return True # triangle outside convex hull
    else:
        return False # triangle inside convex hull
    
    Run Code Online (Sandbox Code Playgroud)

如果没有明确定义边界,我发现将形状"映射"到布尔数组并使用邻居跟踪算法来定义它是有帮助的.请注意,此方法假设一个实体域,您需要对其中包含"漏洞"的域使用更复杂的算法.

Idd*_*iel 5

这是一些可以执行您想要的操作的 Python 代码。

首先,构建 alpha 形状(参见我之前的回答):

def alpha_shape(points, alpha, only_outer=True):
    """
    Compute the alpha shape (concave hull) of a set of points.
    :param points: np.array of shape (n,2) points.
    :param alpha: alpha value.
    :param only_outer: boolean value to specify if we keep only the outer border or also inner edges.
    :return: set of (i,j) pairs representing edges of the alpha-shape. (i,j) are the indices in the points array.
    """
    assert points.shape[0] > 3, "Need at least four points"

    def add_edge(edges, i, j):
        """
        Add a line between the i-th and j-th points,
        if not in the list already
        """
        if (i, j) in edges or (j, i) in edges:
            # already added
            assert (j, i) in edges, "Can't go twice over same directed edge right?"
            if only_outer:
                # if both neighboring triangles are in shape, it's not a boundary edge
                edges.remove((j, i))
            return
        edges.add((i, j))

    tri = Delaunay(points)
    edges = set()
    # Loop over triangles:
    # ia, ib, ic = indices of corner points of the triangle
    for ia, ib, ic in tri.vertices:
        pa = points[ia]
        pb = points[ib]
        pc = points[ic]
        # Computing radius of triangle circumcircle
        # www.mathalino.com/reviewer/derivation-of-formulas/derivation-of-formula-for-radius-of-circumcircle
        a = np.sqrt((pa[0] - pb[0]) ** 2 + (pa[1] - pb[1]) ** 2)
        b = np.sqrt((pb[0] - pc[0]) ** 2 + (pb[1] - pc[1]) ** 2)
        c = np.sqrt((pc[0] - pa[0]) ** 2 + (pc[1] - pa[1]) ** 2)
        s = (a + b + c) / 2.0
        area = np.sqrt(s * (s - a) * (s - b) * (s - c))
        circum_r = a * b * c / (4.0 * area)
        if circum_r < alpha:
            add_edge(edges, ia, ib)
            add_edge(edges, ib, ic)
            add_edge(edges, ic, ia)
    return edges
Run Code Online (Sandbox Code Playgroud)

要计算 alpha 形状的外边界的边缘,请使用以下示例调用:

edges = alpha_shape(points, alpha=alpha_value, only_outer=True)
Run Code Online (Sandbox Code Playgroud)

现在,在计算出edges的 alpha 形状的外边界后points,以下函数将确定一个点是否(x,y)在外边界内。

def is_inside(x, y, points, edges, eps=1.0e-10):
    intersection_counter = 0
    for i, j in edges:
        assert abs((points[i,1]-y)*(points[j,1]-y)) > eps, 'Need to handle these end cases separately'
        y_in_edge_domain = ((points[i,1]-y)*(points[j,1]-y) < 0)
        if y_in_edge_domain:
            upper_ind, lower_ind = (i,j) if (points[i,1]-y) > 0 else (j,i)
            upper_x = points[upper_ind, 0] 
            upper_y = points[upper_ind, 1]
            lower_x = points[lower_ind, 0] 
            lower_y = points[lower_ind, 1]

            # is_left_turn predicate is evaluated with: sign(cross_product(upper-lower, p-lower))
            cross_prod = (upper_x - lower_x)*(y-lower_y) - (upper_y - lower_y)*(x-lower_x)
            assert abs(cross_prod) > eps, 'Need to handle these end cases separately'
            point_is_left_of_segment = (cross_prod > 0.0)
            if point_is_left_of_segment:
                intersection_counter = intersection_counter + 1
    return (intersection_counter % 2) != 0
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

在上图所示的输入中(取自我之前的回答),调用is_inside(1.5, 0.0, points, edges)将返回True,而is_inside(1.5, 3.0, points, edges)将返回False

请注意,is_inside上面的函数不处理退化情况。我添加了两个断言来检测此类情况(您可以定义适合您的应用程序的任何 epsilon 值)。在许多应用程序中,这已经足够了,但如果不是,并且您遇到这些最终情况,则需要单独处理它们。例如,请参阅此处关于实现几何算法时的鲁棒性和精度问题。