2013-05-25 3 views
24

У меня есть облако точек координат в 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, вы должны оконтуривающим (вы сделали это)

>>> 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 точки.

Если вы хотите узнать, находится ли точка с вашего исходного набора данных на корпусе, значит, все готово.

Я хочу знать, есть ли какая-либо точка внутри корпуса или снаружи, вам нужно немного поработать больше. То, что вы должны сделать может быть

  • для всех ребер, соединяющих две симплексов вашего корпуса: решить, будет ли выше или ниже

  • если точка находится ниже всех линий, или выше всех линий вашей точки, он находится за пределами корпуса

Как скорость вверх, как только точка была выше одной линии и ниже друг друга, он находится внутри корпуса.

+0

Я хочу узнать, если произвольная точка находится в выпуклой оболочке точечного облака или вне ее. :) – AME

+0

так вы удовлетворены ответом? – octoback

+1

Ваш ответ для внутри или снаружи корпуса неверен в том, что выше и ниже не является достаточным испытанием. Например, если точка находится за пределами корпуса, но, скажем, на полпути по 45-градусной диагонали, тогда ваш тест не удастся. Вместо этого суммируйте углы между контрольной точкой и всеми точками выпуклой оболочки: если она внутри углов будет суммироваться до 2pi, а если она снаружи, они будут суммироваться до 0 (или у меня могут быть некоторые детали этого неправильного, но вот основная идея). – tom10

3

Похоже, вы используете двумерное облако точек, поэтому я хотел бы направить вас на inclusion test для точечного полигона тестирования выпуклых многоугольников.

Алгоритм выпуклого корпуса Scipy позволяет находить выпуклые оболочки в 2 или более измерениях, которые сложнее, чем это необходимо для двумерного точечного облака. Поэтому я рекомендую использовать другой алгоритм, например this one. Это потому, что, поскольку вам действительно нужно для точечного полигона тестирования выпуклой оболочки, это список выпуклых точек корпуса по часовой стрелке и точка, находящаяся внутри многоугольника.

Производительность время этого подхода заключается в том, как следует:

  • O (N журнал N) для построения выпуклой оболочки
  • О (Н) в предварительной обработке, чтобы вычислить (и магазина) углы клина из внутренняя точка
  • O (log h) для запроса на точку в полигоне.

Где N - количество точек в облаке точек, а h - количество точек в выпуклой оболочке точечных облаков.

42

Вот простое решение, которое требует только 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') 
+2

этот ответ работает в n измерениях! – joshua

13

первых, получить выпуклую оболочку для вашего облака точек.

Затем проведите по всем краям выпуклой оболочки по часовой стрелке. Для каждого из ребер проверьте, лежит ли ваша целевая точка на «левом» краю. При этом обработайте края как векторы, указывающие против часовой стрелки вокруг выпуклой оболочки. Если целевой точкой является «левый» всех векторов, то он содержится в многоугольнике; иначе он лежит вне многоугольника.

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

+0

Если кто-то уже вычислил выпуклую оболочку точек, то этот подход является самым простым. – CODError

20

Привет, Я не уверен, как использовать вашу библиотеку программ для достижения этой цели. Но есть простой алгоритм достижения этого, описанный словами:

  1. создать точку, которая определенно находится за пределами вашего корпуса. Назовите его Y
  2. производят отрезок линии, соединяющий вашу точку (X) с новой точкой Y.
  3. обведите все крайние сегменты вашего выпуклого корпуса. проверьте каждый из них, если сегмент пересекается с XY.
  4. Если число пересечений, которое вы подсчитали, равно (включая 0), X находится вне корпуса. В противном случае X находится внутри корпуса.
  5. Если это так, XY проходит через одну из ваших вершин на корпусе или непосредственно накладывается на один из краев корпуса, немного перемещайте Y.
  6. вышеупомянутые работы для вогнутого корпуса. Вы можете видеть на иллюстрации ниже (Зеленая точка - это точка Х, которую вы пытаетесь определить.Желтый обозначает точки пересечения. illustration
+3

+1 Хороший подход. Для выпуклой оболочки, вероятно, легче найти точку, определенную внутри корпуса (среднее значение всех вершин корпуса), а затем следовать вашему методу с отмененными условиями успеха. – Jaime

+0

Хотя это немного странно, есть пара случаев, когда это не удастся: 1) Если вы выберете точку, которая является коллинеарной с парой вершин на корпусе, а контрольная точка также будет коллинеарной с этими вершинами, то вы технически получите бесконечное количество пересечений. 2) если ваша контрольная точка и X и внешняя точка Y коллинеарны с вершиной на пересечении нечетного числа граней (3d-случай), вы ошибочно заключаете, что контрольная точка находится фактически внутри корпуса ... на по крайней мере, вам может потребоваться проверить случай 2. Например обеспечить неколониарность XYV – wmsmith

4

Просто для Комплектности, вот бедный угодник решение:

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 дважды.

Два примера участка в 2-D:

Ложные:

New point (red) falls outside the convex hull

Правда:

New point (red) falls inside the convex hull

+0

Я копаю! Но я скажу это: КУРС РАЗМЕРНОСТИ. Более 8 измерений и ядра разбиваются. –

3

Используйте атрибут ConvexHullequations:

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) 

output of demonstration

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

4

Я бы не использовал алгоритм с выпуклым корпусом, потому что вам не нужно вычислять выпуклую оболочку, вы просто хотите проверить, может ли ваша точка выражаться как выпуклая комбинация множества точек, из которых подмножество определяет выпуклую оболочку. Более того, обнаружение выпуклой оболочки является дорогостоящим, особенно в более высоких измерениях.

На самом деле простая задача выяснить, может ли точка быть выражена в виде выпуклой комбинации другого набора точек, можно сформулировать как задачу линейного программирования.

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_[Z.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)) 

Для примера я решил проблему для 10000 точек в 10 измерениях. Время выполнения находится в диапазоне ms. Не хотел бы знать, сколько времени это займет с QHull.

Смежные вопросы