我有一个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错误:数组必须是方阵。
我有一个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错误:数组必须是方阵。
这里有一个简单的解决方案,仅需使用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,你也可以使用下面这个函数,它会调用第一个函数并绘制结果。对于仅包含2D数据的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')
我不会使用凸包算法,因为你不需要计算凸包,你只需要检查你的点是否可以表示为定义凸包的一部分点的凸组合。此外,在高维空间中找到凸包是计算上非常昂贵的。
实际上,仅仅找出一个点是否可以表示为另一组点的凸组合的问题就可以被转化为线性规划问题。
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需要多长时间。
Z
是点云。如果有的话,描述 x
的 Z
中点的权重存储在 lp
的属性中。 - Nils你好,我不确定如何使用你的程序库来实现这个功能。但是下面用文字描述了一个简单的算法:
首先,获取您点云的凸包。
然后按逆时针顺序循环遍历所有凸包边缘。对于每个边缘,请检查您的目标点是否位于该边缘的“左侧”。在执行此操作时,将边缘视为指向凸包周围按逆时针方向旋转的向量。如果目标点位于所有向量的“左侧”,则它被多边形包含;否则,它位于多边形外部。
这个Stack Overflow主题还提供了一种解决方法,可以找到点在线的哪一侧: 确定点位于线的哪一侧
请注意,这仅适用于凸多边形。但是您正在处理凸包,因此它应该满足您的需求。
看起来您已经有一种方法可以获取您点云的凸包。但是,如果您发现必须自己实现凸包,维基百科在这里列出了一个很好的凸包算法列表: 凸包算法
使用ConvexHull
的equations
属性:
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)
为了完整起见,这里提供一个简单的解决方案:
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
。在@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))
无法从'scipy.spatial.qhull'导入名称'_Qhull'
。 - zxdawnscipy
版本非常新。 _Qhull
不再被公开。所以你别无选择,只能使用旧版本的 scipy 或者稍微慢一些的 python 接口。 - milembar参考此答案,若想同时检查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]
对于那些感兴趣的人,我制作了一个向量化版本的 @charlie-brummit answer:
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 answer)快约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')
cloud
是一个N行K列的数组,其中包含了K维空间中的N个点。使用ConvexHull(cloud).vertices
(来自scipy.spatial)可以得到凸包上的点的索引,也就是“外部点”。 - Juh_hull = Delaunay(hull)
行出现了“TypeError:float()参数必须是字符串或数字”的错误。有什么想法吗? - Gulzar