web-dev-qa-db-fra.com

Quel est un moyen efficace de savoir si un point se trouve dans la coque convexe d'un nuage de points?

J'ai un nuage de points de coordonnées en numpy. Pour un nombre élevé de points, je veux savoir si les points se trouvent dans la coque convexe du nuage de points.

J'ai essayé le pyhull mais je n'arrive pas à savoir comment vérifier si un point est dans le ConvexHull:

hull = ConvexHull(np.array([(1, 2), (3, 4), (3, 6)]))
for s in hull.simplices:
    s.in_simplex(np.array([2, 3]))

lève LinAlgError: le tableau doit être carré.

31
AME

Voici une solution simple qui ne nécessite que 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

Il renvoie un tableau booléen où les valeurs True indiquent les points qui se trouvent dans la coque convexe donnée. Il peut être utilisé comme ceci:

tested = np.random.Rand(20,3)
cloud  = np.random.Rand(50,3)

print in_hull(tested,cloud)

Si matplotlib est installé, vous pouvez également utiliser la fonction suivante qui appelle la première et trace les résultats. Pour les données 2D uniquement, donné par Nx2 tableaux:

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')
61
Juh_

Salut, je ne sais pas comment utiliser votre bibliothèque de programmes pour y parvenir. Mais il existe un algorithme simple pour y parvenir décrit en mots:

  1. créer un point qui est définitivement en dehors de votre coque. Appelez ça Y
  2. produire un segment de ligne reliant votre point en question (X) au nouveau point Y.
  3. boucle autour de tous les segments de bord de votre coque convexe. vérifiez pour chacun d'eux si le segment intersecte avec XY.
  4. Si le nombre d'intersections que vous avez compté est pair (y compris 0), X est en dehors de la coque. Sinon, X est à l'intérieur de la coque.
  5. si tel est le cas, XY passe par l'un de vos sommets sur la coque, ou chevauche directement l'un des bords de votre coque, déplacez légèrement Y.
  6. ce qui précède fonctionne également pour la coque concave. Vous pouvez voir dans l'illustration ci-dessous (le point vert est le point X que vous essayez de déterminer. Le jaune marque les points d'intersection. illustration
23
firemana

Je n'utiliserais pas d'algorithme de coque convexe, car vous n'avez pas besoin de calculer la coque convexe, vous voulez juste vérifier si votre point peut être exprimé comme une combinaison convexe de l'ensemble de points dont un sous-ensemble définit une coque convexe. De plus, trouver la coque convexe est coûteux en calcul, en particulier dans les dimensions supérieures.

En fait, le simple problème de savoir si un point peut être exprimé comme une combinaison convexe d'un autre ensemble de points peut être formulé comme un problème de programmation linéaire.

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

Pour l'exemple, j'ai résolu le problème pour 10000 points en 10 dimensions. Le temps d'exécution est de l'ordre de ms. Je ne voudrais pas savoir combien de temps cela prendrait avec QHull.

16
Nils

Utilisez l'attribut equations de 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)

En d'autres termes, un point est dans la coque si et seulement si pour chaque équation (décrivant les facettes) le produit scalaire entre le point et le vecteur normal (eq[:-1]) plus le décalage (eq[-1]) est inférieur ou égal à zéro. Vous voudrez peut-être comparer à une petite constante positive tolerance = 1e-12 plutôt qu'à zéro en raison de problèmes de précision numérique (sinon, vous pouvez constater qu'un sommet de la coque convexe n'est pas dans la coque convexe).

Manifestation:

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

5
Charlie Brummitt

Il semble que vous utilisiez un nuage de points 2D, donc je voudrais vous diriger vers le test d'inclusion pour le test point par polygone des polygones convexes.

L'algorithme de coque convexe de Scipy permet de trouver des coques convexes en 2 dimensions ou plus, ce qui est plus compliqué que pour un nuage de points 2D. Par conséquent, je recommande d'utiliser un algorithme différent, tel que celui-ci . En effet, comme vous avez vraiment besoin d'un test de point dans le polygone d'une coque convexe, la liste des points de coque convexes dans le sens des aiguilles d'une montre, et un point qui se trouve à l'intérieur du polygone.

Les performances temporelles de cette approche sont les suivantes:

  • O (N log N) pour construire la coque convexe
  • O (h) en prétraitement pour calculer (et stocker) les angles de coin à partir du point intérieur
  • O (log h) par requête de point dans le polygone.

Où N est le nombre de points dans le nuage de points et h est le nombre de points dans la coque convexe des nuages ​​de points.

4
Nuclearman

Si vous voulez continuer avec scipy, vous devez coque convexe (vous l'avez fait)

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

puis construisez la liste des points sur la coque. Voici le code de doc pour tracer la coque

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

Donc, à partir de là, je proposerais de calculer la liste des points sur la coque

pts_hull = [(points[simplex,0], points[simplex,1]) 
                            for simplex in hull.simplices] 

(même si je n'ai pas essayé)

Et vous pouvez également venir avec votre propre code pour calculer la coque, renvoyant les points x, y.

Si vous voulez savoir si un point de votre jeu de données d'origine se trouve sur la coque, alors vous avez terminé.

Je veux savoir si un point quelconque est à l'intérieur ou à l'extérieur de la coque, vous devez faire un peu plus de travail. Ce que vous devrez faire pourrait être

  • pour tous les bords joignant deux simplices de votre coque: décidez si votre point est au dessus ou en dessous

  • si le point est en dessous de toutes les lignes, ou au-dessus de toutes les lignes, il est en dehors de la coque

Comme accélération, dès qu'un point se trouve au-dessus d'une ligne et en dessous, il se trouve à l'intérieur de la coque.

1
kiriloff

Basé sur this post, voici ma solution rapide et sale pour des régions convexes à 4 côtés (vous pouvez facilement l'étendre à plus)

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

0
BBDynSys