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é.
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')
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:
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.
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)
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 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.
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.
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.')