web-dev-qa-db-ja.com

点を並べ替えて連続線を形成する

線の骨格を表す(x、y)座標のリストがあります。リストはバイナリイメージから直接取得されます。

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

これで、リスト内のポイントは、軸の1つに沿った画像内の位置に従ってソートされます。

順序が線に沿った滑らかなパスを表すようにリストを並べ替えたいのですが。 (これは現在、線が曲がっている場合には当てはまりません)。続いて、これらの点にスプラインをフィットさせます。

同様の問題が説明され、arcPy here を使用して解決されました。 python、numpy、scipy、openCV(または別のライブラリ)を使用してこれを実現する便利な方法はありますか?

以下は画像の例です。 59(x、y)座標のリストになります。 enter image description here

リストをscipyのスプラインフィッティングルーチンに送信すると、ポイントがライン上で「順序付け」されていないため、問題が発生しています。

enter image description here

25
jlarsch

長い回答を前もってお詫びします:P(問題はそれ単純ではありません)。

問題の言い換えから始めましょう。すべてのポイントを結ぶ線を見つけることは、グラフの最短経路問題として再定式化できます。ここで、(1)グラフノードは空間内のポイントであり、(2)各ノードはその2つの最近傍に接続され、( 3)最短パスは各ノードを通過します1回だけ。この最後の制約は非常に重要です(最適化するのは非常に困難です)。基本的に、問題は、長さ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]

次に、最近傍グラフを作成して、各ノードを2つの最近傍に接続することから始めます。

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.ソースからの最短経路を見つける

そして、ここでmagicを開始します: dfs_preorder_nodes を使用してパスを抽出できます。これにより、すべてのノードを通るパスが作成されます(各ノードを通過します)正確に1回)開始ノードを指定(指定しない場合、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が行の中央にあるためです。つまり、最初に1つの方向に進み、次に戻ってもう1つの方向に終了します。

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

ポイントは最適なパスごとに順序付けされ、次にコストが計算されます(ポイントのすべてのペアii+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

23
Imanol Luengo

可能な解決策の1つは、KDTreeを使用して可能な最近傍アプローチを使用することです。 Scikit-learnには、Niceインターフェイスがあります。これは、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

グラフを作成した後、クリークから最も長いエッジを削除して、グラフの自由端を見つけます。

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

私は同様の問題に取り組んでいますが、8連結の意味で、各ピクセルに1つまたは2つの隣接するピクセルがあるという重要な制約があります(OPによって与えられる例のように)。この制約により、非常に簡単な解決策があります。

def sort_to_form_line(unsorted_list):
    """
    Given a list of neighboring points which forms a line, but in random order, 
    sort them to the correct order.
    IMPORTANT: Each point must be a neighbor (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