web-dev-qa-db-fra.com

points de tri pour former une ligne continue

J'ai une liste de coordonnées (x, y) qui représentent un squelette de ligne . La liste est obtenue directement à partir d'une image binaire:

import numpy as np    
list=np.where(img_skeleton>0)

Maintenant, les points de la liste sont triés en fonction de leur position dans l'image le long de l'un des axes.

Je voudrais trier la liste de telle sorte que l'ordre représente un chemin lisse le long de la ligne. (Ce n'est actuellement pas le cas où la ligne se courbe en arrière) . Par la suite, je souhaite ajuster une spline à ces points.

Un problème similaire a été décrit et résolu en utilisant arcPy here . Existe-t-il un moyen pratique d’y parvenir en utilisant python, numpy, scipy, openCV (ou une autre bibliothèque?)

ci-dessous est un exemple d'image. il en résulte une liste de 59 (x, y) -coordinates .  enter image description here

lorsque j'envoie la liste à la routine d'ajustement des splines de scipy, je rencontre un problème car les points ne sont pas "ordonnés" sur la ligne:

 enter image description here

21
jlarsch

Je m'excuse pour la longue réponse à l’avance: P (le problème n’est pas ça simple). 

Commençons par reformuler le problème. La recherche d’une ligne qui relie tous les points peut être reformulée comme un problème de chemin le plus court dans un graphe, où (1) les nœuds du graphe sont les points de l’espace, (2) chaque nœud est connecté à ses 2 voisins les plus proches et ( 3) le plus court chemin traverse chacun des nœuds une seule fois. Cette dernière contrainte est très importante (et assez difficile à optimiser). Le problème consiste essentiellement à trouver une permutation de longueur N, où la permutation fait référence à l'ordre de chacun des nœuds (N est le nombre total de nœuds) dans le chemin.

Trouver toutes les permutations possibles et en évaluer le coût est trop coûteux (il existe des permutations N! si je ne me trompe pas, ce qui est trop gros pour poser des problèmes). Ci-dessous, je propose une approche qui trouve les N meilleures permutations (la permutation optimale pour chacun des N points), puis la permutation (parmi celles N) qui minimise l'erreur/coût.

1. Créer un problème aléatoire avec des points non ordonnés

Maintenant, commençons à créer un exemple de problème:

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(x)

plt.plot(x, y)
plt.show()

 enter image description here

Et ici, la version non triée des points [x, y] pour simuler des points aléatoires dans l’espace connectés en ligne:

idx = np.random.permutation(x.size)
x = x[idx]
y = y[idx]

plt.plot(x, y)
plt.show()

 enter image description here

Le problème est alors d’ordonner à ces points de récupérer leur ordre initial afin que la ligne soit tracée correctement.

2. Créer un graphe 2-NN entre les nœuds

Nous pouvons d'abord réorganiser les points dans un tableau [N, 2]:

points = np.c_[x, y]

Ensuite, nous pouvons commencer par créer un graphique du voisin le plus proche pour connecter chacun des nœuds à ses 2 voisins les plus proches:

from sklearn.neighbors import NearestNeighbors

clf = NearestNeighbors(2).fit(points)
G = clf.kneighbors_graph()

G est une matrice éparse N x N, où chaque ligne représente un nœud et les éléments non nuls des colonnes la distance euclidienne à ces points.

On peut alors utiliser networkx pour construire un graphe à partir de cette matrice creuse:

import networkx as nx

T = nx.from_scipy_sparse_matrix(G)

3. Trouver le chemin le plus court depuis la source

Et, ici commence le magique: nous pouvons extraire les chemins en utilisant dfs_preorder_nodes , ce qui créera essentiellement un chemin à travers tous les noeuds (en passant par chacun d'eux exactement une fois) à partir d'un noeud de départ (si non donné, le nœud 0 sera sélectionné).

order = list(nx.dfs_preorder_nodes(T, 0))

xx = x[order]
yy = y[order]

plt.plot(xx, yy)
plt.show()

 enter image description here

Eh bien, ce n’est pas si grave, mais on peut remarquer que la reconstruction n’est pas optimale. En effet, le point 0 dans la liste non ordonnée se trouve au milieu de la ligne, c’est ainsi qu’il va d’abord dans une direction, puis revient et se termine dans l’autre direction.

4. Trouver le chemin avec le plus petit coût de toutes les sources

Donc, afin d'obtenir l'ordre optimal, nous pouvons simplement obtenir le meilleur ordre pour tous les nœuds:

paths = [list(nx.dfs_preorder_nodes(T, i)) for i in range(len(points))]

Maintenant que nous avons le chemin optimal à partir de chacun des nœuds N = 100, nous pouvons les ignorer et trouver celui qui minimise les distances entre les connexions (problème d'optimisation):

mindist = np.inf
minidx = 0

for i in range(len(points)):
    p = paths[i]           # order of nodes
    ordered = points[p]    # ordered nodes
    # find cost of that order by the sum of euclidean distances between points (i) and (i+1)
    cost = (((ordered[:-1] - ordered[1:])**2).sum(1)).sum()
    if cost < mindist:
        mindist = cost
        minidx = i

Les points sont ordonnés pour chacun des chemins optimaux, puis un coût est calculé (en calculant la distance euclidienne entre toutes les paires de points i et i+1). Si le chemin commence au point start ou end, son coût sera le plus faible possible car tous les nœuds seront consécutifs. Par contre, si le chemin commence à un nœud situé au milieu de la ligne, le coût sera très élevé à un moment donné, car il faudra parcourir la fin (ou le début) de la ligne vers la ligne initiale. position pour explorer l'autre direction. Le chemin qui minimise ce coût est le chemin partant d'un point optimal.

opt_order = paths[minidx]

Maintenant, nous pouvons reconstruire l'ordre correctement:

xx = x[opt_order]
yy = y[opt_order]

plt.plot(xx, yy)
plt.show()

 enter image description here

19
Imanol Luengo

Une solution possible consiste à utiliser une approche de voisinage le plus proche, possible à l'aide d'un arbre KDTree. Scikit-learn a une interface agréable. Ceci peut ensuite être utilisé pour construire une représentation graphique en utilisant networkx. Cela ne fonctionnera vraiment que si la ligne à tracer passe par les voisins les plus proches:

from sklearn.neighbors import KDTree
import numpy as np
import networkx as nx

G = nx.Graph()  # A graph to hold the nearest neighbours

X = [(0, 1), (1, 1), (3, 2), (5, 4)]  # Some list of points in 2D
tree = KDTree(X, leaf_size=2, metric='euclidean')  # Create a distance tree

# Now loop over your points and find the two nearest neighbours
# If the first and last points are also the start and end points of the line you can use X[1:-1]
for p in X
    dist, ind = tree.query(p, k=3)
    print ind

    # ind Indexes represent nodes on a graph
    # Two nearest points are at indexes 1 and 2. 
    # Use these to form edges on graph
    # p is the current point in the list
    G.add_node(p)
    n1, l1 = X[ind[0][1]], dist[0][1]  # The next nearest point
    n2, l2 = X[ind[0][2]], dist[0][2]  # The following nearest point  
    G.add_Edge(p, n1)
    G.add_Edge(p, n2)


print G.edges()  # A list of all the connections between points
print nx.shortest_path(G, source=(0,1), target=(5,4))
>>> [(0, 1), (1, 1), (3, 2), (5, 4)]  # A list of ordered points

Mise à jour: si les points de départ et d'arrivée sont inconnus et que vos données sont assez bien séparées, vous pouvez trouver les fins en recherchant des cliques dans le graphique. Les points de départ et d'arrivée formeront une clique. Si le bord le plus long est supprimé de la clique, cela créera une extrémité libre dans le graphique pouvant servir de point de départ et de fin. Par exemple, les points de début et de fin de cette liste apparaissent au milieu:

X = [(0, 1), (0, 0), (2, 1),  (3, 2),  (9, 4), (5, 4)]

 enter image description here

Après avoir construit le graphique, il suffit maintenant de retirer le plus long bord des cliques pour trouver les extrémités libres du graphique:

def find_longest_Edge(l):
    e1 = G[l[0]][l[1]]['weight']
    e2 = G[l[0]][l[2]]['weight']
    e3 = G[l[1]][l[2]]['weight']
    if e2 < e1 > e3:
        return (l[0], l[1])
    Elif e1 < e2 > e3:
        return (l[0], l[2])
    Elif e1 < e3 > e2:
    return (l[1], l[2])

end_cliques = [i for i in list(nx.find_cliques(G)) if len(i) == 3]
Edge_lengths = [find_longest_Edge(i) for i in end_cliques]
G.remove_edges_from(Edge_lengths)
edges = G.edges()

 enter image description here

start_end = [n for n,nbrs in G.adjacency_iter() if len(nbrs.keys()) == 1]
print nx.shortest_path(G, source=start_end[0], target=start_end[1])
>>> [(0, 0), (0, 1), (2, 1), (3, 2), (5, 4), (9, 4)]  # The correct path
6
kezzos

Je travaille sur un problème similaire, mais il a une contrainte importante (un peu comme dans l'exemple donné par l'OP), à savoir que chaque pixel a un ou deux pixels voisins, au sens 8 connectés. Avec cette contrainte, il existe une solution très simple.

def sort_to_form_line(unsorted_list):
    """
    Given a list of neighbouring points which forms a line, but in random order, sort them to the correct order
    IMPORTANT: Each point must be a neighbour (8-point sense) to a least one other point!
    """
    sorted_list = [unsorted_list.pop(0)]

    while len(unsorted_list) > 0:
        i = 0
        while i < len(unsorted_list):
            if are_neighbours(sorted_list[0], unsorted_list[i]):
                #neighbours at front of list
                sorted_list.insert(0, unsorted_list.pop(i))
            Elif are_neighbours(sorted_list[-1], unsorted_list[i]):
                #neighbours at rear of list
                sorted_list.append(unsorted_list.pop(i))
            else:
                i = i+1

    return sorted_list

def are_neighbours(pt1, pt2):
    """
    Check if pt1 and pt2 are neighbours, in the 8-point sense
    pt1 and pt2 has integer coordinates
    """
        return (np.abs(pt1[0]-pt2[0]) < 2) and (np.abs(pt1[1]-pt2[1]) < 2)
0
redraider