it-swarm.com.ru

сортировка точек для формирования непрерывной линии

У меня есть список (x, y) -координат, которые представляют линейный скелет . Список получен непосредственно из двоичного изображения:

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

Теперь точки в списке отсортированы в соответствии с их положением на изображении вдоль одной из осей.

Я хотел бы отсортировать список таким образом, чтобы порядок представлял собой плавный путь вдоль линии. (В настоящее время это не тот случай, когда линия изгибается назад) . Впоследствии я хочу подогнать сплайн к этим точкам.

Аналогичная проблема была описана и решена с помощью arcPy здесь . Есть ли удобный способ добиться этого, используя python, numpy, scipy, openCV (или другую библиотеку?)

ниже приведен пример изображения. в результате получается список из 59 (x, y) -координат .  enter image description here

когда я отправляю список в процедуру подгонки сплайнов Сципи, я сталкиваюсь с проблемой, потому что точки не «упорядочены» в строке:

 enter image description here

21
jlarsch

Я заранее прошу прощения за длинный ответ: P (проблема не в том, что это просто). 

Давайте начнем с переписывания проблемы. Нахождение линии, соединяющей все точки, можно переформулировать как задачу кратчайшего пути в графе, где (1) узлы графа являются точками в пространстве, (2) каждый узел связан со своими 2 ближайшими соседями и ( 3) кратчайший путь проходит через каждый из узлов только один раз. Последнее ограничение очень важно (и довольно сложно оптимизировать). По сути, проблема состоит в том, чтобы найти перестановку длины N, где перестановка относится к порядку каждого из узлов (N - общее количество узлов) в пути.

Поиск всех возможных перестановок и оценка их стоимости слишком дороги (если я не ошибаюсь, есть перестановки N!, что слишком много для проблем). Ниже я предлагаю подход, который находит N лучших перестановок (оптимальная перестановка для каждой из точек N), а затем нахожу перестановку (из этих N), которая минимизирует ошибку/стоимость.

1. Создать случайную задачу с неупорядоченными точками

Теперь давайте приступим к созданию примера задачи:

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

И вот, несортированная версия точек [x, y] для имитации случайных точек в пространстве соединена линией:

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

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

 enter image description here

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

2. Создать 2-NN граф между узлами

Сначала мы можем переставить точки в массиве [N, 2]:

points = np.c_[x, y]

Затем мы можем начать с создания графа ближайших соседей, чтобы соединить каждый из узлов с двумя ближайшими соседями:

from sklearn.neighbors import NearestNeighbors

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

G - это разреженная матрица N x N, где каждая строка представляет узел, а ненулевые элементы столбцов - евклидово расстояние до этих точек.

Затем мы можем использовать networkx для построения графа из этой разреженной матрицы:

import networkx as nx

T = nx.from_scipy_sparse_matrix(G)

3. Найти кратчайший путь от источника

И здесь начинается магия: мы можем извлечь пути, используя dfs_preorder_nodes , который по существу создаст путь через все узлы (проходя через каждый из них ровно один раз) для данного начального узла (если не задано, 0 узел будет выбран).

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

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

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

 enter image description here

Ну, это не так уж плохо, но мы можем заметить, что реконструкция не является оптимальной. Это происходит потому, что точка 0 в неупорядоченном списке лежит в середине строки, то есть сначала она идет в одном направлении, а затем возвращается и заканчивается в другом направлении.

4. Найти путь с наименьшей стоимостью из всех источников

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

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

Теперь, когда у нас есть оптимальный путь, начинающийся с каждого из узлов N = 100, мы можем отбросить их и найти тот, который минимизирует расстояния между соединениями (проблема оптимизации):

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

Точки упорядочиваются для каждого из оптимальных путей, а затем вычисляется стоимость (путем вычисления евклидова расстояния между всеми парами точек i и i+1). Если путь начинается с точки start или end, он будет иметь наименьшую стоимость, поскольку все узлы будут последовательными. С другой стороны, если путь начинается в узле, который находится в середине линии, в какой-то момент стоимость будет очень высокой, так как потребуется пройти от конца (или начала) линии до начальной положение, чтобы исследовать другое направление. Путь, который минимизирует эту стоимость, - это путь, начинающийся в оптимальной точке.

opt_order = paths[minidx]

Теперь мы можем правильно восстановить порядок:

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

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

 enter image description here

19
Imanol Luengo

Одним из возможных решений является использование подхода ближайших соседей, возможного с использованием KDTree. Scikit-learn имеет приятный интерфейс. Затем его можно использовать для построения представления графа с использованием networkx. Это будет действительно работать только в том случае, если рисуемая линия должна проходить через ближайших соседей:

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

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

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

 enter image description here

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

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

Я работаю над аналогичной проблемой, но у нее есть важное ограничение (очень похожее на пример, приведенный OP), заключающееся в том, что каждый пиксель имеет один или два соседних пикселя в 8-связном смысле. С этим ограничением есть очень простое решение.

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