Python 将点排序以形成一条连续线

Python 将点排序以形成一条连续线,python,opencv,numpy,image-processing,scipy,Python,Opencv,Numpy,Image Processing,Scipy,我有一个代表线骨架的(x,y)坐标列表。 该列表直接从二值图像中获得: import numpy as np list=np.where(img_skeleton>0) 现在,列表中的点将根据它们在图像中的位置沿其中一个轴进行排序 我想对列表进行排序,使其顺序表示沿着直线的平滑路径。(当前不是直线向后弯曲的情况)。 随后,我想将样条曲线拟合到这些点 使用arcPy描述并解决了一个类似的问题。使用python、numpy、scipy、openCV(或其他库)是否有一种方便的方法来实

我有一个代表线骨架的(x,y)坐标列表。 该列表直接从二值图像中获得:

import numpy as np    
list=np.where(img_skeleton>0)
现在,列表中的点将根据它们在图像中的位置沿其中一个轴进行排序

我想对列表进行排序,使其顺序表示沿着直线的平滑路径。(当前不是直线向后弯曲的情况)。 随后,我想将样条曲线拟合到这些点

使用arcPy描述并解决了一个类似的问题。使用python、numpy、scipy、openCV(或其他库)是否有一种方便的方法来实现这一点

下面是一个示例图像。它会产生一个59(x,y)坐标的列表。

当我将列表发送到scipy的样条曲线拟合例程时,我遇到了一个问题,因为点在直线上没有“排序”:


一种可能的解决方案是使用最近邻方法,可以使用KDTree。Scikit learn有一个很好的界面。然后可以使用networkx构建图形表示。只有当要绘制的线穿过最近的邻居时,这才真正起作用:

来自sklearn.KDTree
将numpy作为np导入
将networkx导入为nx
G=nx.Graph()#保存最近邻居的图
X=[(0,1)、(1,1)、(3,2)、(5,4)]#2D中的一些点列表
tree=KDTree(X,leaf_size=2,metric='euclidean')#创建距离树
#现在,把你的点循环一下,找到两个最近的邻居
#如果第一个点和最后一个点也是直线的起点和终点,则可以使用X[1:-1]
对于X中的p
dist,ind=tree.query(p,k=3)
印刷工业
#索引表示图上的节点
#最近的两个点位于索引1和2处。
#使用这些在图形上形成边
#p是列表中的当前点
G.add_节点(p)
n1,l1=X[ind[0][1],距离[0][1]#下一个最近的点
n2,l2=X[ind[0][2]],距离[0][2]#以下最近点
G.增加边缘(p,n1)
G.添加边缘(p,n2)
打印G.edges()#点之间所有连接的列表
打印nx.最短路径(G,源=(0,1),目标=(5,4))
>>>[(0,1)、(1,1)、(3,2)、(5,4)]#有序点列表
更新:如果起点和终点未知,并且您的数据被合理地分开,您可以通过在图中查找派系来找到终点。起点和终点将形成一个集团。如果从团中删除最长的边,它将在图形中创建一个可用作起点和终点的自由端。例如,此列表中的起点和终点显示在中间:

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

在构建图形之后,现在需要从团中移除最长边以找到图形的自由端:

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

我很抱歉提前给出了这么长的答案: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()

这里,点的未排序版本
[x,y]
用于模拟空间中连接在一条直线上的随机点:

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

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

然后,问题是对这些点进行排序以恢复其原始顺序,以便正确绘制直线

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
是一个稀疏的
nxn
矩阵,其中每行表示一个节点,列的非零元素表示到这些点的欧氏距离

然后,我们可以使用
networkx
从这个稀疏矩阵构造一个图:

import networkx as nx

T = nx.from_scipy_sparse_matrix(G)
3.从源查找最短路径 然后,魔术开始了:我们可以使用提取路径,这将在给定起始节点(如果不给定,将选择0节点)的情况下,创建一条通过所有节点(每个节点只经过一次)的路径

嗯,还不算太糟,但我们可以注意到重建不是最优的。这是因为无序列表中的点<代码> 0代码/代码>在中间行,也就是说,它首先在一个方向上,然后返回,然后在另一个方向上完成。 4.从所有来源中找到成本最小的路径 因此,为了获得最佳顺序,我们只需获得所有节点的最佳顺序:

paths = [list(nx.dfs_preorder_nodes(T, i)) for i in range(len(points))]
现在我们有了从每个
N=100
节点开始的最佳路径,我们可以丢弃它们并找到一个使连接之间的距离最小化的路径(优化问题):

现在,我们可以正确地重建顺序:

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

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

我正在研究一个类似的问题,但它有一个重要的约束条件(很像OP给出的示例),即每个像素都有一个或两个相邻像素,在8连通的意义上。有了这个约束,就有了一个非常简单的so
paths = [list(nx.dfs_preorder_nodes(T, i)) for i in range(len(points))]
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
opt_order = paths[minidx]
xx = x[opt_order]
yy = y[opt_order]

plt.plot(xx, yy)
plt.show()
import numpy as np
from scipy.signal import savgol_filter
from sklearn.decomposition import PCA

def XYclean(x,y): 

    xy = np.concatenate((x.reshape(-1,1), y.reshape(-1,1)), axis=1)     

    # make PCA object
    pca = PCA(2)
    # fit on data
    pca.fit(xy)
    
    #transform into pca space   
    xypca = pca.transform(xy) 
    newx = xypca[:,0]
    newy = xypca[:,1]

    #sort
    indexSort = np.argsort(x)
    newx = newx[indexSort]
    newy = newy[indexSort]

    #add some more points (optional)
    f = interpolate.interp1d(newx, newy, kind='linear')        
    newX=np.linspace(np.min(newx), np.max(newx), 100)
    newY = f(newX)            

    #smooth with a filter (optional)
    window = 43
    newY = savgol_filter(newY, window, 2)

    #return back to old coordinates
    xyclean = pca.inverse_transform(np.concatenate((newX.reshape(-1,1), newY.reshape(-1,1)), axis=1) )
    xc=xyclean[:,0]
    yc = xyclean[:,1]

    return xc, yc