找到一个点位于点云的凸包中的有效方法是什么?

时间:2013-05-25 14:41:50

标签: python numpy convex-hull

我在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:数组必须是方形的。

11 个答案:

答案 0 :(得分:58)

这是一个简单的解决方案,只需要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')

答案 1 :(得分:21)

您好我不确定如何使用您的程序库来实现这一目标。但是有一个简单的算法来实现这一点:

  1. 创造一个绝对在船体外面的点。叫它Y
  2. 生成一个线段,将您的问题点(X)连接到新点Y。
  3. 环绕凸壳的所有边缘段。如果段与XY相交,则检查它们中的每一个。
  4. 如果您计算的交叉点数是偶数(包括0),则X在船体外。否则X在船体内。
  5. 如果发生这种情况,XY会穿过船体上的一个顶点,或者直接与船体的一个边缘重叠,稍微移动一下。
  6. 上述凹壳船的工作原理。您可以在下图中看到(绿点是您要确定的X点。黄色标记交叉点。 illustration

答案 2 :(得分:17)

首先,获得点云的凸包。

然后以逆时针顺序环绕凸包的所有边缘。对于每条边,检查您的目标点是否位于该边的“左侧”。执行此操作时,将边缘视为逆向指向凸包的向量。如果目标点是所有向量的“左”,则它由多边形包含;否则,它位于多边形之外。

Loop and Check whether point is always to the "left"

这个其他Stack Overflow主题包括一个解决方案,可以找到一个点所在的“侧面”: Determine Which Side of a Line a Point Lies

<小时/> 这种方法的运行时复杂性(一旦你已经有了凸包)就是 O(n),其中n是凸包的边数。

请注意,这仅适用于凸多边形。但是你正在处理一个凸壳,所以它应该适合你的需要。

看起来你已经有办法为你的点云获得凸包。但是如果你发现你必须实现自己的,那么维基百科有一个很好的凸包算法列表: Convex Hull Algorithms

答案 3 :(得分:16)

我不会使用凸包算法,因为您不需要计算凸包,您只需要检查您的点是否可以表示为子集定义凸的点集的凸组合船体。此外,找到凸包在计算上是昂贵的,特别是在更高的维度上。

事实上,找出一个点是否可以表示为另一组点的凸组合的单纯问题可以表述为线性规划问题。

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点的问题。执行时间在ms范围内。不想知道QHull需要多长时间。

答案 4 :(得分:5)

仅仅为了完整,这是一个穷人的解决方案:

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()

这个想法很简单:如果你添加一个落在&#34;内部的点P,那么一组点p的凸包顶点就不会改变。船体; [P1, P2, ..., Pn][P1, P2, ..., Pn, p]的凸包顶点是相同的。但如果p落在&#34;外面&#34;,那么顶点必须改变。 这适用于n维,但您必须计算ConvexHull两次。

2-D中的两个示例图:

题:

New point (red) falls outside the convex hull

真:

New point (red) falls inside the convex hull

答案 5 :(得分:5)

使用equations的{​​{1}}属性:

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

output of demonstration

答案 6 :(得分:4)

看起来您正在使用2D点云,所以我想引导您到inclusion test进行凸多边形的多边形点测试。

Scipy的凸包算法允许在2维或更多维中找到凸包,这比2D点云需要更复杂。因此,我建议使用其他算法,例如this one。这是因为您真正需要凸包的多边形点测试是顺时针顺序的凸包点列表,以及多边形内部的点。

此方法的时间表现如下:

  • O(N log N)构造凸包
  • O(h)在预处理中从内部点计算(并存储)楔角
  • 每个多边形点查询的O(log h)。

其中N是点云中的点数,h是点云凸壳中的点数。

答案 7 :(得分:1)

如果你想保持scipy,你需要凸壳(你这样做)

>>> from scipy.spatial import ConvexHull
>>> points = np.random.rand(30, 2)   # 30 random points in 2-D
>>> hull = ConvexHull(points)

然后在船体上建立点列表。以下是doc绘制船体的代码

>>> 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点。

如果您想知道原始数据集中的某个点是否在船体上,那么您就完成了。

我想要的是知道船体内部或外部是否有任何一点,你必须做更多的工作。你需要做的是

  • 连接船体两个单体的所有边缘:决定您的点是在上面还是下面

  • 如果点位于所有线下方,或者位于所有线上方,则位于船体外

作为一个加速点,一旦一个点高于一条线并且低于另一条线,它就在船体内。

答案 8 :(得分:1)

在@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 :(得分:0)

基于this帖子,这是我对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.')

enter image description here

答案 10 :(得分:0)

要搭载 this answer,一次检查 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]