Python学习计划:Dijkstra,OpenCV和UI算法(第1部分)

迷宫是人类常见的难题,但它们是一个有趣的编程问题,我们可以使用最短路径方法(例如Dijkstra算法)来解决。



记住Dijkstra的算法



Dijkstra的算法是最流行的图论算法之一。它用于查找有向图上节点之间的最短路径。我们从源节点和节点之间的已知边长开始。



首先,我们将从源到所有节点的距离值分配。节点s的值为0,因为它是源。其余的以∞值开始。



图片



我们感兴趣的节点是具有最低值(以灰色显示)s的原始节点。首先,我们将每个相邻顶点``削弱''到我们感兴趣的节点,将其值更新为当前值或感兴趣节点的值加上连接边的长度的最小值...



图片



节点s现在完成(黑色),并且其邻居a和b具有其新值。感兴趣的新节点是b,因此我们重复“弱化” b的邻居并最终确定b的最短路径值的过程。



图片



经过每个节点之后,我们最终将获得一个图表,显示从源到每个节点的最短路径长度。



图片



图片



图片



运行Dijkstra算法后的最终图表。每个节点上的数字表示距源节点的最短距离。



概念化迷宫图像



图片



我们可以将图像想象为像素矩阵。每个像素(为简单起见)的值为RGB 0,0,0(黑色)或255,255,255(白色)。我们的目标是创建一条最短的路径,该路径从白色开始,并且不会越过黑色边界。为了表示该目标,我们可以将每个像素视为一个节点,并根据RGB值的差异绘制边缘长度在相邻像素之间的边缘。我们将使用欧几里德平方距离公式,并添加0.1以确保没有路径长度0-(对Dijkstra算法的要求):



图片



该公式使迷宫边界上的相交距离过大。如我们所见,从源头到目的地的最短路径显然将绕过障碍物,而不是通过障碍物。



图片



实作



我们可以使用OpenCV(一个流行的Python计算机视觉库)提取像素值并显示迷宫图像。我们还通过在迷宫中添加点来定义起点和终点的坐标



import cv2
import matplotlib.pyplot as plt
import numpy as np
img = cv2.imread('maze.png') # read an image from a file using
cv2.circle(img,(5,220), 3, (255,0,0), -1) # add a circle at (5, 220)
cv2.circle(img, (25,5), 3, (0,0,255), -1) # add a circle at (5,5)
plt.figure(figsize=(7,7))
plt.imshow(img) # show the image
plt.show()


图片


我们正在创建一个Vertex类,它将帮助我们跟踪坐标。我们还希望跟踪父节点,以便一旦找到距离值就可以重建整个路径。



class Vertex:
    def __init__(self,x_coord,y_coord):
        self.x=x_coord
        self.y=y_coord
        self.d=float('inf') #current distance from source node
        self.parent_x=None
        self.parent_y=None
        self.processed=False
        self.index_in_queue=None


我们需要创建一个表示图像中像素的二维排列的顶点矩阵。这将是Dijkstra算法的基础。我们还维护一个低优先级队列,以跟踪未处理的节点。



def find_shortest_path(img,src,dst):
    pq=[] #min-heap priority queue
    imagerows,imagecols=img.shape[0],img.shape[1]
    matrix = np.full((imagerows, imagecols), None) 
    #access matrix elements by matrix[row][col]
    #fill matrix with vertices
    for r in range(imagerows):
        for c in range(imagecols):
            matrix[r][c]=Vertex(c,r)
            matrix[r][c].index_in_queue=len(pq)
            pq.append(matrix[r][c])
    #set source distance value to 0
    matrix[source_y][source_x].d=0
    #maintain min-heap invariant (minimum d Vertex at list index 0)
    pq = bubble_up(pq, matrix[source_y][source_x].index_in_queue)


我们需要一些辅助函数来帮助我们找到顶点之间的边以及边的长度:



#Implement euclidean squared distance formula
def get_distance(img,u,v):
    return 0.1 + (float(img[v][0])-float(img[u][0]))**2+(float(img[v][1])-float(img[u][1]))**2+(float(img[v][2])-float(img[u][2]))**2
#Return neighbor directly above, below, right, and left
def get_neighbors(mat,r,c):
    shape=mat.shape
    neighbors=[]
    #ensure neighbors are within image boundaries
    if r > 0 and not mat[r-1][c].processed:
         neighbors.append(mat[r-1][c])
    if r < shape[0] - 1 and not mat[r+1][c].processed:
            neighbors.append(mat[r+1][c])
    if c > 0 and not mat[r][c-1].processed:
        neighbors.append(mat[r][c-1])
    if c < shape[1] - 1 and not mat[r][c+1].processed:
            neighbors.append(mat[r][c+1])
    return neighbors


现在我们可以实现Dijkstra的算法并将距离值(d)分配给迷宫图像中所有像素的顶点:



while len(pq) > 0:
    u=pq[0] #smallest-value unprocessed node
    #remove node of interest from the queue
    pq[0]=pq[-1] 
    pq[0].index_in_queue=0
    pq.pop()
    pq=bubble_down(pq,0) #min-heap function, see source code 
    
    u.processed=True
    neighbors = get_neighbors(matrix,u.y,u.x)
    for v in neighbors:
        dist=get_distance(img,(u.y,u.x),(v.y,v.x))
        if u.d + dist < v.d:
            v.d = u.d+dist
            v.parent_x=u.x #keep track of the shortest path
            v.parent_y=u.y
            idx=v.index_in_queue
            pq=bubble_down(pq,idx) 
            pq=bubble_up(pq,idx)


现在我们可以调用最短路径函数并在迷宫中绘制解决方案:



img = cv2.imread('maze.png') # read an image from a file using opencv (cv2) library
p = find_shortest_path(img, (25,5), (5,220))
drawPath(img,p)
plt.figure(figsize=(7,7))
plt.imshow(img) # show the image on the screen 
plt.show()


图片



图片



让我们尝试来自Internet的其他迷宫。



图片



图片



图片



图片



完整的源代码可以在GitHub上找到



继续:一个Python培训项目:带有40行代码的界面(第2部分)



图片



了解更多有关如何通过接受SkillFactory付费在线课程来从零开始或获得技能和薪资水平晋升的方法:











All Articles