给定一组点,所围成的边界

14

我目前使用的算法有一些问题,想让它生成一个边界。

以下是当前行为的示例:

Current behavior

以下是所需行为的MSPaint示例:

Wanted behavior

C#中凸包的当前代码:https://hastebin.com/dudejesuja.cs

所以我的问题如下:

1)这个可能吗?

R:是的

2)这甚至叫凸包吗?(我不这么认为)

R:不,它被称为边界,链接:https://www.mathworks.com/help/matlab/ref/boundary.html

3)这是否比传统凸包更耗费性能?

R:据我的研究,它应该具有相同的性能

4)类似的伪代码示例或者算法实现?

R:尚未回答或我还没有找到解决方案


2
  1. 实际上这不是凸包。凸包应该没有凸性,即没有凹陷的部分。它只是外部的四个点。
  2. 我不明白你实际上想要什么。为什么只选择中间的那两个点?为什么不选择其他点?在你的实际用例中,这可能更有意义,但就连接这些点而言,在这个例子中你的选择似乎完全是任意的。为什么使用6个点而不像左边那样使用5个点?为什么不使用4个点,就像真正的凸包一样?
- alkasm
1
为什么你的图形不是像这样在右边:https://imgur.com/a/Ot9W9d9?当然,你可以继续将这个想法应用到其他点上。在这个过程中,你是否真的从除了点以外的任何信息开始,或者这些点是你唯一拥有的信息?只是想确保这不是一个 XY 问题。如果我是你,我会尝试在每个顶点之间建立连接(构建完全图)。然后开始删除你不想要的外部线条,并查看是否遵循你可以使用的规则。 - alkasm
@AlexanderReynolds 是的,我再次道歉,我还是新手,虽然在我的脑海中很有意义,但我无法用语言表达出来,我正在努力变得更好,希望这不是一个 XY 问题,现在的示例可能比第一个要好得多,感谢您的帮助。 - Mário Gabriel
2
看起来你可能想要一个轮廓,但是在离散的点上定义它会很困难。然而,生成这些点的规则可能有助于生成轮廓。这些点在图像中来自哪里?再次强调——我知道这不是你想要的——但是你的标题和问题(最小凸包)确实是你在第一张图片中得到的内容,所以你想要的不是凸包。 - alkasm
3
寻找“alpha shapes”。 - MBo
显示剩余8条评论
6个回答

30

这里有一些Python代码,可以计算alpha-shape(凹多边形)并仅保留外边界。这可能就是Matlab的boundary函数内部执行的操作。

from scipy.spatial import Delaunay
import numpy as np


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 an edge 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

如果您使用以下测试代码运行它,您将得到这个图形,看起来就像您所需要的:enter image description here
from matplotlib.pyplot import *

# Constructing the input point data
np.random.seed(0)
x = 3.0 * np.random.rand(2000)
y = 2.0 * np.random.rand(2000) - 1.0
inside = ((x ** 2 + y ** 2 > 1.0) & ((x - 3) ** 2 + y ** 2 > 1.0)
points = np.vstack([x[inside], y[inside]]).T

# Computing the alpha shape
edges = alpha_shape(points, alpha=0.25, only_outer=True)

# Plotting the output
figure()
axis('equal')
plot(points[:, 0], points[:, 1], '.')
for i, j in edges:
    plot(points[[i, j], 0], points[[i, j], 1])
show()

编辑:根据评论的要求,这里提供一些代码,用于将输出的边集拼接成连续边序列。

def find_edges_with(i, edge_set):
    i_first = [j for (x,j) in edge_set if x==i]
    i_second = [j for (j,x) in edge_set if x==i]
    return i_first,i_second

def stitch_boundaries(edges):
    edge_set = edges.copy()
    boundary_lst = []
    while len(edge_set) > 0:
        boundary = []
        edge0 = edge_set.pop()
        boundary.append(edge0)
        last_edge = edge0
        while len(edge_set) > 0:
            i,j = last_edge
            j_first, j_second = find_edges_with(j, edge_set)
            if j_first:
                edge_set.remove((j, j_first[0]))
                edge_with_j = (j, j_first[0])
                boundary.append(edge_with_j)
                last_edge = edge_with_j
            elif j_second:
                edge_set.remove((j_second[0], j))
                edge_with_j = (j, j_second[0])  # flip edge rep
                boundary.append(edge_with_j)
                last_edge = edge_with_j

            if edge0[0] == last_edge[1]:
                break

        boundary_lst.append(boundary)
    return boundary_lst

您可以遍历边界列表并将每个边的第一个索引对应的点添加到一起,从而得到一个边界多边形。


很高兴能得到一些代码作为答案。请注意,随着“edges”的增加,这将变得越来越慢。 - acraig5075
2
我编辑了答案,添加了将边集拼接到边界多边形的代码。一般的想法是将连续的边附加到链的末尾,直到它闭合多边形。希望这可以帮助到您。 - Iddo Hanniel
1
我相信您可能使用了一个太小的alpha值。您的点相对较远,因此我尝试使用alpha=1000和alpha=2500的值,并获得了预期的结果。 - Iddo Hanniel
嗨Iddo - 謝謝你!玩alpha給了我希望的結果。 - PetGriffin
1
算法返回一个列表,其中每个列表表示一个边界。因此,如果有多个 blob(并且假设 alpha 值足够小,以便它们不会合并),则该列表将包含多个边界。 - Iddo Hanniel
显示剩余8条评论

2
我会使用不同的方法来解决这个问题。由于我们正在处理一个二维点集,因此计算点区域的边界矩形是很直接的。然后,我会通过水平和竖直线将该矩形划分为“单元格”,并对于每个单元格,只需计算位于其范围内的像素数量。由于每个单元格只能有4个相邻单元格(相邻的单元格通过单元格边缘相连),因此边界单元格将是至少具有一个空相邻单元格或具有位于边界矩形边界处的单元格边缘的单元格。然后可以沿边界单元格边缘构建边界。边界看起来像“楼梯”,但选择更小的单元格大小将改善结果。事实上,单元格大小应该经过实验确定;它不能太小,否则区域内可能出现空单元格。可以使用点之间的平均距离作为单元格大小的下限。

1
你所描述的是一种叫做四叉树的数据结构。 - user1196549

1
正如大多数前期专家所指出的那样,这可能不是凸包而是凹包,换句话说是 Alpha Shape。Iddo 提供了一份清晰的 Python 代码来获取这个形状。但是,如果您正在处理大量点云,则也可以直接利用一些现有的软件包实现这一点,可能速度更快,计算内存较少。
[1] Alpha Shape 工具箱:一个用于生成 n 维 alpha 形状的工具箱。

https://plotly.com/python/v3/alpha-shapes/

Plotly:它可以生成Mesh3d对象,根据键值可以是该集合的凸包,Delaunay三角剖分或alpha集。

https://plotly.com/python/v3/alpha-shapes/


1

0
一个想法是使用点云创建三角形网格,可能通过 Delanuay 三角剖分实现。

enter image description here

填充这些三角形的颜色,然后运行级别集或主动轮廓分割,找到其颜色不同于外部“背景”颜色的形状的外部边界。

https://xphilipp.developpez.com/contribuez/SnakeAnimation.gif

上面的动画没有完全展示,但许多类似的算法可以进行配置。

注意:三角剖分算法必须进行调整,以免仅创建凸包 - 例如从 Delaunay 结果中删除具有太大角度和边的三角形。初步代码可能如下:

from scipy.spatial import Delaunay

points = np.array([[13.43, 12.89], [14.44, 13.86], [13.67, 15.87], [13.39, 14.95],\
[12.66, 13.86], [10.93, 14.24], [11.69, 15.16], [13.06, 16.24], [11.29, 16.35],\
[10.28, 17.33], [10.12, 15.49], [9.03, 13.76], [10.12, 14.08], [9.07, 15.87], \
[9.6, 16.68], [7.18, 16.19], [7.62, 14.95], [8.39, 16.79], [8.59, 14.51], \
[8.1, 13.43], [6.57, 11.59], [7.66, 11.97], [6.94, 13.86], [6.53, 14.84], \
[5.48, 12.84], [6.57, 12.56], [5.6, 11.27], [6.29, 10.08], [7.46, 10.45], \
[7.78, 7.21], [7.34, 8.72], [6.53, 8.29], [5.85, 8.83], [5.56, 10.24], [5.32, 7.8], \
[5.08, 9.86], [6.01, 5.75], [6.41, 7.48], [8.19, 5.69], [8.23, 4.72], [6.85, 6.34], \
[7.02, 4.07], [9.4, 3.2], [9.31, 4.99], [7.86, 3.15], [10.73, 2.82], [10.32, 4.88], \
[9.72, 1.58], [11.85, 5.15], [12.46, 3.47], [12.18, 1.58], [11.49, 3.69], \
[13.1, 4.99], [13.63, 2.61]])

tri = Delaunay(points,furthest_site=False)
res = []
for t in tri.simplices:
  A,B,C = points[t[0]],points[t[1]],points[t[2]]
  e1 = B-A; e2 = C-A
  num = np.dot(e1, e2)
  n1 = np.linalg.norm(e1); n2 = np.linalg.norm(e2)
  denom =  n1 * n2
  d1 = np.rad2deg(np.arccos(num/denom))
  e1 = C-B; e2 = A-B
  num = np.dot(e1, e2)
  denom = np.linalg.norm(e1) * np.linalg.norm(e2)
  d2 = np.rad2deg(np.arccos(num/denom))
  d3 = 180-d1-d2
  res.append([n1,n2,d1,d2,d3])

res = np.array(res)
m = res[:,[0,1]].mean()*res[:,[0,1]].std()

mask = np.any(res[:,[2,3,4]] > 110) & (res[:,0] < m) & (res[:,1] < m )

plt.triplot(points[:,0], points[:,1], tri.simplices[mask])

enter image description here

然后填充颜色并分段。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接