有什么有效的方法可以确定某个点是否位于点云的凸包中?
- 2025-02-27 09:07:00
- admin 原创
- 25
问题描述:
我在 numpy 中有一个坐标点云。对于大量的点,我想找出这些点是否位于点云的凸包中。
我尝试了 pyhull,但我不知道如何检查某个点是否在ConvexHull
:
hull = ConvexHull(np.array([(1, 2), (3, 4), (3, 6)]))
for s in hull.simplices:
s.in_simplex(np.array([2, 3]))
引发 LinAlgError:数组必须是正方形。
解决方案 1:
这是一个只需要 scipy 的简单解决方案:
def in_hull(p, hull):
"""
Test if points in `p` are in `hull`
`p` should be a `NxK` coordinates of `N` points in `K` dimensions
`hull` is either a scipy.spatial.Delaunay object or the `MxK` array of the
coordinates of `M` points in `K`dimensions for which Delaunay triangulation
will be computed
"""
from scipy.spatial import Delaunay
if not isinstance(hull,Delaunay):
hull = Delaunay(hull)
return hull.find_simplex(p)>=0
它返回一个布尔数组,其中的True
值表示位于给定凸包中的点。它可以像这样使用:
tested = np.random.rand(20,3)
cloud = np.random.rand(50,3)
print in_hull(tested,cloud)
如果您安装了 matplotlib,您还可以使用以下函数调用第一个函数并绘制结果。仅适用于二维数据,由Nx2
数组给出:
def plot_in_hull(p, hull):
"""
plot relative to `in_hull` for 2d data
"""
import matplotlib.pyplot as plt
from matplotlib.collections import PolyCollection, LineCollection
from scipy.spatial import Delaunay
if not isinstance(hull,Delaunay):
hull = Delaunay(hull)
# plot triangulation
poly = PolyCollection(hull.points[hull.vertices], facecolors='w', edgecolors='b')
plt.clf()
plt.title('in hull')
plt.gca().add_collection(poly)
plt.plot(hull.points[:,0], hull.points[:,1], 'o', hold=1)
# plot the convex hull
edges = set()
edge_points = []
def add_edge(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
return
edges.add( (i, j) )
edge_points.append(hull.points[ [i, j] ])
for ia, ib in hull.convex_hull:
add_edge(ia, ib)
lines = LineCollection(edge_points, color='g')
plt.gca().add_collection(lines)
plt.show()
# plot tested points `p` - black are inside hull, red outside
inside = in_hull(p,hull)
plt.plot(p[ inside,0],p[ inside,1],'.k')
plt.plot(p[-inside,0],p[-inside,1],'.r')
解决方案 2:
我不会使用凸包算法,因为你不需要计算凸包,你只想检查你的点是否可以表示为一组点的凸组合,这些点的子集定义了一个凸包。此外,寻找凸包的计算成本很高,尤其是在高维空间中。
事实上,仅仅找出一个点是否可以表示为另一组点的凸组合这一问题就可以表述为线性规划问题。
import numpy as np
from scipy.optimize import linprog
def in_hull(points, x):
n_points = len(points)
n_dim = len(x)
c = np.zeros(n_points)
A = np.r_[points.T,np.ones((1,n_points))]
b = np.r_[x, np.ones(1)]
lp = linprog(c, A_eq=A, b_eq=b)
return lp.success
n_points = 10000
n_dim = 10
Z = np.random.rand(n_points,n_dim)
x = np.random.rand(n_dim)
print(in_hull(Z, x))
例如,我解决了 10 维中 10000 个点的问题。执行时间在毫秒范围内。不想知道使用 QHull 需要多长时间。
解决方案 3:
你好,我不确定如何使用你的程序库来实现这一点。但有一个简单的算法可以用文字描述来实现这一点:
创建一个绝对位于船体外部的点。称之为 Y
生成一条连接你所讨论的点(X)和新点 Y 的线段。
环绕凸包的所有边缘段。检查每个边段是否与 XY 相交。
如果计算出的交点数为偶数(包括 0),则 X 位于船体外部。否则,X 位于船体内部。
如果发生这种情况,XY 会穿过船体上的一个顶点,或直接与船体的某个边缘重叠,请稍微移动 Y。
上述方法也适用于凹壳。您可以在下图中看到(绿点是您要确定的 X 点。黄色标记交叉点。
解决方案 4:
首先,获取点云的凸包。
然后按逆时针顺序循环遍历凸包的所有边。对于每条边,检查目标点是否位于该边的“左侧”。执行此操作时,将边视为围绕凸包逆时针指向的向量。如果目标点位于所有向量的“左侧”,则它包含在多边形内;否则,它位于多边形之外。
这个 Stack Overflow 上的另一个主题包含一个解决方案,用于查找某个点位于一条线的哪“侧”:
确定某个点位于一条线的哪一侧
这种方法的运行时间复杂度(一旦您已经有凸包)为O(n),其中 n 是凸包具有的边数。
请注意,这仅适用于凸多边形。但您正在处理凸包,因此它应该适合您的需求。
看起来你已经有办法获取点云的凸包了。但是如果你发现你必须自己实现,维基百科上有一个很好的凸包算法列表:
凸包算法
解决方案 5:
使用equations
以下属性ConvexHull
:
def point_in_hull(point, hull, tolerance=1e-12):
return all(
(np.dot(eq[:-1], point) + eq[-1] <= tolerance)
for eq in hull.equations)
换句话说,当且仅当对于每个方程(描述面),该点与法向量 ( eq[:-1]
) 加上偏移量 ( eq[-1]
) 之间的点积小于或等于零时,该点才位于凸包中。tolerance = 1e-12
由于数值精度问题,您可能希望将其与较小的正常数进行比较,而不是与零进行比较(否则,您可能会发现凸包的顶点不在凸包中)。
示范:
import matplotlib.pyplot as plt
import numpy as np
from scipy.spatial import ConvexHull
points = np.array([(1, 2), (3, 4), (3, 6), (2, 4.5), (2.5, 5)])
hull = ConvexHull(points)
np.random.seed(1)
random_points = np.random.uniform(0, 6, (100, 2))
for simplex in hull.simplices:
plt.plot(points[simplex, 0], points[simplex, 1])
plt.scatter(*points.T, alpha=.5, color='k', s=200, marker='v')
for p in random_points:
point_is_in_hull = point_in_hull(p, hull)
marker = 'x' if point_is_in_hull else 'd'
color = 'g' if point_is_in_hull else 'm'
plt.scatter(p[0], p[1], marker=marker, color=color)
解决方案 6:
只是为了完整性,这里有一个穷人的解决方案:
import pylab
import numpy
from scipy.spatial import ConvexHull
def is_p_inside_points_hull(points, p):
global hull, new_points # Remove this line! Just for plotting!
hull = ConvexHull(points)
new_points = numpy.append(points, p, axis=0)
new_hull = ConvexHull(new_points)
if list(hull.vertices) == list(new_hull.vertices):
return True
else:
return False
# Test:
points = numpy.random.rand(10, 2) # 30 random points in 2-D
# Note: the number of points must be greater than the dimention.
p = numpy.random.rand(1, 2) # 1 random point in 2-D
print is_p_inside_points_hull(points, p)
# Plot:
pylab.plot(points[:,0], points[:,1], 'o')
for simplex in hull.simplices:
pylab.plot(points[simplex,0], points[simplex,1], 'k-')
pylab.plot(p[:,0], p[:,1], '^r')
pylab.show()
P
这个想法很简单:如果添加一个p
位于凸包“内部”的点,则一组点的凸包顶点不会改变;[P1, P2, ..., Pn]
和的凸包顶点[P1, P2, ..., Pn, p]
相同。但如果p
位于“外部”,则顶点必须改变。这适用于 n 维,但您必须计算ConvexHull
两次。
两个二维示例图:
错误的:
真的:
解决方案 7:
看起来您正在使用二维点云,因此我想指导您进行凸多边形的点内多边形测试的包含测试。
Scipy 的凸包算法允许在 2 维或更多维中查找凸包,这比 2D 点云所需的更复杂。因此,我建议使用不同的算法,例如这个。这是因为凸包的点在多边形测试中真正需要的是按顺时针顺序排列的凸包点列表,以及多边形内的点。
该方法的时间性能如下:
构建凸包的时间为 O(N log N)
预处理中的 O(h) 用于计算(并存储)内部点的楔角
每个多边形内点的查询耗时 O(log h)。
其中N是点云中的点数,h是点云凸包中的点数。
解决方案 8:
在@Charlie Brummitt 工作的基础上,我实现了一个更高效的版本,可以同时检查多个点是否位于凸包中,并用更快的线性代数替换任何循环。
import numpy as np
from scipy.spatial.qhull import _Qhull
def in_hull(points, queries):
hull = _Qhull(b"i", points,
options=b"",
furthest_site=False,
incremental=False,
interior_point=None)
equations = hull.get_simplex_facet_array()[2].T
return np.all(queries @ equations[:-1] < - equations[-1], axis=1)
# ============== Demonstration ================
points = np.random.rand(8, 2)
queries = np.random.rand(3, 2)
print(in_hull(points, queries))
_Qhull
请注意,为了提高效率,我使用了较低级别的类。
解决方案 9:
为了利用这个答案,一次检查 numpy 数组中的所有点,这对我有用:
import matplotlib.pyplot as plt
import numpy as np
from scipy.spatial import ConvexHull
points = np.array([(1, 2), (3, 4), (3, 6), (2, 4.5), (2.5, 5)])
hull = ConvexHull(points)
np.random.seed(1)
random_points = np.random.uniform(0, 6, (100, 2))
# get array of boolean values indicating in hull if True
in_hull = np.all(np.add(np.dot(random_points, hull.equations[:,:-1].T),
hull.equations[:,-1]) <= tolerance, axis=1)
random_points_in_hull = random_points[in_hull]
解决方案 10:
对于那些感兴趣的人,我制作了@charlie-brummit答案的矢量化版本:
def points_in_hull(p, hull, tol=1e-12):
return np.all(hull.equations[:,:-1] @ p.T + np.repeat(hull.equations[:,-1][None,:], len(p), axis=0).T <= tol, 0)
现在是p
一个[N,2]
数组。它比推荐的解决方案 (@Sildoreth答案) 快约 6 倍,比原始解决方案快约 10 倍。
改编的没有 for 循环的演示:(从下面粘贴以避免在线程中搜索)
import matplotlib.pyplot as plt
import numpy as np
from scipy.spatial import ConvexHull
points = np.array([(1, 2), (3, 4), (3, 6), (2, 4.5), (2.5, 5)])
hull = ConvexHull(points)
np.random.seed(1)
random_points = np.random.uniform(0, 6, (100, 2))
for simplex in hull.simplices:
plt.plot(points[simplex, 0], points[simplex, 1])
plt.scatter(*points.T, alpha=.5, color='k', s=200, marker='v')
in_hull = points_in_hull(random_points, hull)
plt.scatter(random_points[in_hull,0], random_points[in_hull,1], marker='x', color='g')
plt.scatter(random_points[~in_hull,0], random_points[~in_hull,1], marker='d', color='m')
解决方案 11:
如果你想继续使用 scipy,你必须使用凸包(你这样做了)
>>> from scipy.spatial import ConvexHull
>>> points = np.random.rand(30, 2) # 30 random points in 2-D
>>> hull = ConvexHull(points)
然后构建船体上的点列表。以下是文档中绘制船体的代码
>>> import matplotlib.pyplot as plt
>>> plt.plot(points[:,0], points[:,1], 'o')
>>> for simplex in hull.simplices:
>>> plt.plot(points[simplex,0], points[simplex,1], 'k-')
因此,我建议计算船体上的点列表
pts_hull = [(points[simplex,0], points[simplex,1])
for simplex in hull.simplices]
(虽然我没有尝试)
您还可以使用自己的代码来计算船体,并返回 x、y 点。
如果您想知道原始数据集中的某个点是否位于船体上,那么您就完成了。
如果你想知道任何一点是在船体内部还是外部,你必须做更多的工作。你需要做的可能是
对于连接船体两个单纯形的所有边:确定你的点是在上面还是在下面
如果点位于所有线之下,或位于所有线之上,则它位于船体之外
为了加速,一旦一个点位于一条线之上和另一条线之下,它就在船体内部。
解决方案 12:
矢量化,带有绘图功能(基于此处的其他精彩答案:1、2):
import matplotlib.pyplot as plt
import numpy as np
from scipy.spatial import ConvexHull
def points_in_hull(points: np.ndarray, hull: ConvexHull, tolerance: float = 1e-12):
return np.all(np.add(points @ hull.equations[:, :-1].T, hull.equations[:, -1]) <= tolerance, axis=1)
points = np.array([(1, 2), (3, 4), (3, 6), (2, 4.5), (2.5, 5)])
hull = ConvexHull(points)
np.random.seed(1)
random_points = np.random.uniform(0, 6, (1000, 2))
in_hull = points_in_hull(random_points, hull)
random_points_in_hull = random_points[in_hull]
# plot
fig, ax = plt.subplots(figsize=(20, 10))
plt.scatter(random_points[:, 0], random_points[:, 1], color='blue')
plt.scatter(points[:, 0], points[:, 1], color='green')
plt.scatter(random_points_in_hull[:, 0], random_points_in_hull[:, 1], color='red')
for simplex in hull.simplices:
plt.plot(points[simplex, 0], points[simplex, 1], 'k-')
plt.show()
解决方案 13:
根据这篇文章,这是我针对具有 4 条边的凸区域提出的快速解决方案(您可以轻松将其扩展到更多边)
def same_sign(arr): return np.all(arr > 0) if arr[0] > 0 else np.all(arr < 0)
def inside_quad(pts, pt):
a = pts - pt
d = np.zeros((4,2))
d[0,:] = pts[1,:]-pts[0,:]
d[1,:] = pts[2,:]-pts[1,:]
d[2,:] = pts[3,:]-pts[2,:]
d[3,:] = pts[0,:]-pts[3,:]
res = np.cross(a,d)
return same_sign(res), res
points = np.array([(1, 2), (3, 4), (3, 6), (2.5, 5)])
np.random.seed(1)
random_points = np.random.uniform(0, 6, (1000, 2))
print wlk1.inside_quad(points, random_points[0])
res = np.array([inside_quad(points, p)[0] for p in random_points])
print res[:4]
plt.plot(random_points[:,0], random_points[:,1], 'b.')
plt.plot(random_points[res][:,0], random_points[res][:,1], 'r.')
解决方案 14:
scipy.spatial
对于那些可能感兴趣的人,我编写了自己的实现,它完全不依赖于(尽管如果需要,它用于计算切比雪夫中心),它既更快,又提供了额外的功能。然而,它只支持二维空间。该模块在Githubscipy.optimize
上公开可用。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 项目管理必备:盘点2024年13款好用的项目管理软件
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)