您所提到的第一个线程中的方法可以使用alpha-shape(有时称为凹壳)概念来采用凹形情况,这也是您所提到的第二个参考答案建议的方法。
Alpha-shape是Delaunay三角剖分的三角形子集,其中每个三角形都满足外接半径条件。以下代码是从
我的以前的回答修改而来,用于计算alpha-shape中Delaunay三角形的集合。一旦计算出Delaunay三角形和alpha-shape掩码,就可以采用您所引用的快速方法来应用于alpha-shape,我将在下面解释。
def circ_radius(p0,p1,p2):
"""
Vectorized computation of triangle circumscribing radii.
See for example https://www.cuemath.com/jee/circumcircle-formulae-trigonometry/
"""
a = p1-p0
b = p2-p0
norm_a = np.linalg.norm(a, axis=1)
norm_b = np.linalg.norm(b, axis=1)
norm_a_b = np.linalg.norm(a-b, axis=1)
cross_a_b = np.cross(a,b)
return (norm_a*norm_b*norm_a_b) / np.abs(2.0*cross_a_b)
def alpha_shape_delaunay_mask(points, alpha):
"""
Compute the alpha shape (concave hull) of a set of points and return the Delaunay triangulation and a boolean
mask for any triangle in the triangulation whether it belongs to the alpha shape.
:param points: np.array of shape (n,2) points.
:param alpha: alpha value.
:return: Delaunay triangulation dt and boolean array is_in_shape, so that dt.simplices[is_in_alpha] contains
only the triangles that belong to the alpha shape.
"""
assert points.shape[0] > 3, "Need at least four points"
dt = Delaunay(points)
p0 = points[dt.simplices[:,0],:]
p1 = points[dt.simplices[:,1],:]
p2 = points[dt.simplices[:,2],:]
rads = circ_radius(p0, p1, p2)
is_in_shape = (rads < alpha)
return dt, is_in_shape
你可以根据第一篇参考文献中的方法进行调整,不仅可以检查点是否在Delaunay三角形中(如果是,则在凸包内),还可以检查它是否在alpha-shape三角形中。下面的函数可以实现这个功能:
def in_alpha_shape(p, dt, is_in_alpha):
simplex_ids = dt.find_simplex(p)
res = np.full(p.shape[0], False)
res[simplex_ids >= 0] = is_in_alpha[simplex_ids[simplex_ids >= 0]]
return res
这个方法非常快,因为它依赖于Delaunay find_simplex()
函数的高效搜索实现。
使用以下代码运行它(使用alpha=2
)在您发布的示例数据点上,将得到以下图中的结果,我认为这不是您想要的...
points = np.vstack([x, y]).T
alpha = 2.
dt, is_in_alpha = alpha_shape_delaunay_mask(points, alpha)
p1 = np.stack((xx.ravel(),yy.ravel())).T
cond = in_alpha_shape(p1, dt, is_in_alpha)
p2 = p1[cond,:]
plt.figure()
plt.scatter(x, y)
plt.scatter(p2[:,0],p2[:,1], s=10)
以上结果的原因是,由于输入点之间存在较大间隔,因此数据的alpha-shape不会遵循多边形从您的点。增加alpha参数也无济于事,因为它会在其他地方切割凹角。如果您添加更密集的采样点,则此alpha-shape方法非常适合您的任务。如果不是,则下面我提出另一种解决方案。
由于原始多边形不适用于alpha-shape方法,因此您需要实现一个函数,该函数返回一个给定多边形内的点是否存在。以下函数基于累积内/外角度实现这样的算法(请参见这里进行解释)。
def points_in_polygon(pts, polygon):
"""
Returns if the points are inside the given polygon,
Implemented with angle accumulation.
see:
https://en.wikipedia.org/wiki/Point_in_polygon#Winding_number_algorithm
:param np.ndarray pts: 2d points
:param np.ndarray polygon: 2d polygon
:return: Returns if the points are inside the given polygon, array[i] == True means pts[i] is inside the polygon.
"""
polygon = np.vstack((polygon, polygon[0, :]))
sum_angles = np.zeros([len(pts), ])
for i in range(len(polygon) - 1):
v1 = polygon[i, :] - pts
norm_v1 = np.linalg.norm(v1, axis=1, keepdims=True)
norm_v1[norm_v1 == 0.0] = 1.0
v1 = v1 / norm_v1
v2 = polygon[i + 1, :] - pts
norm_v2 = np.linalg.norm(v2, axis=1, keepdims=True)
norm_v2[norm_v2 == 0.0] = 1.0
v2 = v2 / norm_v2
dot_prods = np.sum(v1 * v2, axis=1)
cross_prods = np.cross(v1, v2)
angs = np.arccos(np.clip(dot_prods, -1, 1))
angs = np.sign(cross_prods) * angs
sum_angles += angs
sum_degrees = np.rad2deg(sum_angles)
return abs(sum_degrees) > 90.0
使用下面的代码调用它将得到以下图形,我相信这就是您要找的。
points = np.vstack([x, y]).T
p1 = np.vstack([xx.ravel(), yy.ravel()]).T
cond = points_in_polygon(p1, points)
p2 = p1[cond,:]
plt.figure()
plt.scatter(x, y)
plt.plot(x, y)
plt.scatter(p2[:,0],p2[:,1], s=10)
(n-1)!
个,最大面积? - Bob