graduation-project/navigate_gui.py
weixin_46229132 3d358609bb 导航界面
2025-05-06 09:43:13 +08:00

506 lines
19 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import heapq
import os
import random
import tkinter as tk
from collections import deque
from tkinter import messagebox
import cv2
import numpy as np
# 停车场布局数据
left_side = list(range(119, 132))
right_side = list(range(99, 87, -1))
# 将右侧车位编号转换为三位数格式例如099, 098, ...
right_side = [str(number).zfill(3) for number in right_side]
entry_left = [117, 115, 113]
entry_right = [108, 106, 104, 102, 101, 100]
p_row1 = [118, 116, 114, 112, 111, 110, None, 109, 107, 105, 103]
middle_bottom_row1 = [292, 290, 288, 286, 284, 282, 280, 278, 276, 274, 272]
middle_top_row2 = [293, 291, 289, 287, 285, 283, 281, 279, 277, 275, 273, 271]
middle_bottom_row2 = list(range(259, 271))
middle_top_row3 = [None] * 10 + [00] + [None]
middle_bottom_row3 = [258, 257, 256, None, None,
None, None, None, None, 255, 254, None]
# 样式参数
car_space_width = 40
car_space_height = 80
road_width = 40
margin = 0
canvas_width = 12 * car_space_width + road_width * \
2 + 2 * car_space_height + 11 * margin
canvas_height = 4 * road_width + 7 * car_space_height + 3 * margin
middle_area_offset_y = 100 # 中间区域整体下移
# 车位状态对应颜色
status_color = {
"free": "lightgreen",
"occupied": "tomato",
}
default_color = "white"
selected_color = "cyan" # 搜索高亮颜色
# 设置 YOLO 检测结果路径
detection_folder = r"D:\car2\parking_folders"
# A*算法的启发式函数
def heuristic(a, b):
return abs(a[0] - b[0]) + abs(a[1] - b[1]) # 曼哈顿距离
class ParkingLot(tk.Tk):
def __init__(self):
super().__init__()
self.title("停车场平面图 🚗")
self.geometry(f"{canvas_width}x{canvas_height}")
self.canvas = tk.Canvas(self, width=canvas_width,
height=canvas_height, bg="#CCCCCC")
self.canvas.pack()
# 搜索部分
self.search_entry = tk.Entry(self)
self.search_entry.pack(pady=5)
self.search_button = tk.Button(
self, text="搜索车位", command=self.search_spot)
self.search_button.pack(pady=5)
self.spots = {} # 存储车位信息
self.path = [] # 存储路径
self.parkpot = {}
self.grid = self.get_grid()
self.obstacles = [] # 存储障碍物坐标
self.get_parkpot()
self.current_path = []
self.draw_parking_lot()
def get_grid(self):
row = int(canvas_height / 40)
column = int(canvas_width / 40)
grid = np.zeros((row, column), np.int64)
for i in range(4):
for j in range(14):
grid[2 + 5 * i][2 + j] = 1
for i in range(2):
grid[3:, 2 + i * 13] = 1
grid[:2, 8] = 1
return grid
def get_parkpot(self):
"""获取车位入口坐标"""
for idx, name in enumerate(left_side):
self.parkpot[name] = (3 + idx, 2, "left")
for idx, name in enumerate(right_side):
self.parkpot[name] = (3 + idx, 15, "right")
for idx, name in enumerate(entry_left):
self.parkpot[name] = (2, 4 + idx, "up")
for idx, name in enumerate(entry_right):
self.parkpot[name] = (2, 10 + idx, "up")
for idx, name in enumerate(p_row1):
self.parkpot[name] = (2, 4 + idx, "down")
for idx, name in enumerate(middle_bottom_row1):
self.parkpot[name] = (7, 4 + idx, "up")
for idx, name in enumerate(middle_top_row2):
self.parkpot[name] = (7, 3 + idx, "down")
for idx, name in enumerate(middle_bottom_row2):
self.parkpot[name] = (12, 3 + idx, "up")
for idx, name in enumerate(middle_top_row3):
self.parkpot[name] = (12, 3 + idx, "down")
for idx, name in enumerate(middle_bottom_row3):
self.parkpot[name] = (17, 3 + idx, "up")
def draw_parking_lot(self):
# 入口灰色路
inx_start = 5 * car_space_width + road_width + car_space_height + 5 * margin
self.canvas.create_rectangle(
inx_start, 0, inx_start + road_width, car_space_height, fill="gray", outline="gray"
)
self.canvas.create_text(
inx_start + road_width / 2, 20, text="入口", fill="red", font=("Arial", 16))
self.canvas.create_rectangle(
car_space_height,
car_space_height,
canvas_width - car_space_height,
car_space_height + road_width,
fill="gray",
outline="gray",
)
self.canvas.create_rectangle(
car_space_height,
car_space_height + road_width,
car_space_height + road_width,
canvas_height,
fill="gray",
outline="gray",
)
self.canvas.create_rectangle(
canvas_width - car_space_height - road_width,
car_space_height + road_width,
canvas_width - car_space_height,
canvas_height,
fill="gray",
outline="gray",
)
self.canvas.create_rectangle(
car_space_height,
3 * car_space_height + margin + road_width,
canvas_width - car_space_height,
3 * car_space_height + margin + road_width + road_width,
fill="gray",
outline="gray",
)
self.canvas.create_rectangle(
car_space_height,
5 * car_space_height + 2 * margin + 2 * road_width,
canvas_width - car_space_height,
5 * car_space_height + 2 * margin + 2 * road_width + road_width,
fill="gray",
outline="gray",
)
self.canvas.create_rectangle(
car_space_height,
canvas_height - road_width,
canvas_width - car_space_height,
canvas_height,
fill="gray",
outline="gray",
)
self.canvas.create_line(inx_start + road_width / 2, 30,
inx_start + road_width / 2, 100, fill="blue", width=5)
# 入口两边车位
for idx, number in enumerate(entry_left):
x = car_space_height + road_width + car_space_width + \
idx * (car_space_width + margin) + margin
y = 0
self.draw_parking_spot(x, y, number, direction="down")
for idx, number in enumerate(entry_right):
x = inx_start + road_width + car_space_width + \
idx * (car_space_width + margin) + margin * 2
y = 0
self.draw_parking_spot(x, y, number, direction="down")
# 左右两列
for idx, number in enumerate(left_side):
x = 0
y = car_space_height + road_width + \
idx * (car_space_width + margin)
self.draw_parking_spot(x, y, number, direction="right")
for idx, number in enumerate(right_side):
x = canvas_width - car_space_height
y = road_width + car_space_height + \
idx * (car_space_width + margin)
self.draw_parking_spot(x, y, number, direction="left")
# 中间区域
start_y = car_space_height + road_width
start_x = car_space_height + road_width + margin + car_space_width
self.draw_middle_row(start_x, start_y, p_row1, up=True)
self.draw_middle_row(
start_x,
start_y + car_space_height + margin,
middle_bottom_row1,
up=False,
)
start_y += road_width + margin + 2 * car_space_height
start_x -= car_space_width + margin
self.draw_middle_row(start_x, start_y, middle_top_row2, up=True)
self.draw_middle_row(
start_x, start_y + car_space_height + margin, middle_bottom_row2, up=False)
start_y += road_width + margin + 2 * car_space_height
self.draw_middle_row(start_x, start_y, middle_top_row3, up=True)
self.draw_middle_row(
start_x, start_y + car_space_height + margin, middle_bottom_row3, up=False)
def draw_middle_row(self, start_x, start_y, row_data, up=True):
"""
绘制中间行的停车位。
:param start_y: 行起始的y坐标
:param row_data: 行数据,包含停车位编号的列表
:param up: 布尔值指示停车位的头部方向默认为True向上
"""
current_x = start_x
for number in row_data:
# 绘制停车位矩形,去掉边框
rect = self.canvas.create_rectangle(
current_x,
start_y,
current_x + car_space_width,
start_y + car_space_height,
fill=default_color,
# outline="#CCCCCC", # 修改:将边框颜色设置为与背景相同的颜色
)
if number is not None:
# 初始随机给状态
status = random.choice(["free", "occupied"])
# 存储停车位信息
self.spots[rect] = {"number": number, "status": status}
# 绘制指向停车位的箭头
self.draw_arrow(current_x, start_y, "up" if up else "down")
# 绘制停车位编号
self.canvas.create_text(
current_x + car_space_width / 2,
start_y + car_space_height / 2,
text=str(number),
font=("Arial", 8),
)
# 绑定点击事件以切换停车位状态
self.canvas.tag_bind(rect, "<Button-1>", self.toggle_spot)
# 按状态上色
fill_color = status_color.get(status, default_color)
self.canvas.itemconfig(rect, fill=fill_color)
# 添加障碍物坐标
self.obstacles.append((current_x, start_y))
# 更新当前x坐标以绘制下一个停车位
current_x += car_space_width + margin
def draw_arrow(self, x, y, direction):
if direction == "up":
points = [
x + car_space_width / 2,
y + 10,
x + 10,
y + 30,
x + car_space_width - 10,
y + 30,
]
elif direction == "down":
points = [
x + car_space_width / 2,
y + car_space_height - 10,
x + 10,
y + car_space_height - 30,
x + car_space_width - 10,
y + car_space_height - 30,
]
elif direction == "right":
points = [
x + car_space_height - 5,
y + car_space_width / 2,
x + car_space_height - 20,
y + 5,
x + car_space_height - 20,
y + car_space_width - 5,
]
else:
points = [
x + 5,
y + car_space_width / 2,
x + 20,
y + 5,
x + 20,
y + car_space_width - 5,
]
self.canvas.create_polygon(points, fill="black")
def draw_parking_spot(self, x, y, number, direction="up"):
if direction not in ["left", "right"]:
rect = self.canvas.create_rectangle(
x, y, x + car_space_width, y + car_space_height, fill=default_color, outline="black"
)
elif direction == "left":
rect = self.canvas.create_rectangle(
x, y, x + car_space_height, y + car_space_width, fill=default_color, outline="black"
)
else:
rect = self.canvas.create_rectangle(
x, y, x + car_space_height, y + car_space_width, fill=default_color, outline="black"
)
# 获取车位编号对应的YOLO检测结果图像路径
detection_image_path = os.path.join(
detection_folder, f"{number}.jpg") # 假设图像以车位编号命名
# 检查文件是否存在
if os.path.exists(detection_image_path):
# 读取 YOLO 检测结果图像(假设是红绿图像,红色表示有车,绿色表示空位)
detection_image = cv2.imread(detection_image_path)
# 检查图片是否为空
if detection_image is not None:
# 假设检测图像中左上角是车位的检测状态,我们可以检查该区域的颜色
# 获取车位区域的颜色
region_color = detection_image[10, 10] # 假设检测图像左上角有车位状态信息
# 判断颜色,红色表示有车,绿色表示空位
if np.array_equal(region_color, [0, 0, 255]): # 红色
status = "occupied"
elif np.array_equal(region_color, [0, 255, 0]): # 绿色
status = "free"
else:
status = "free" # 默认状态为免费
else:
status = "free" # 默认状态为免费,如果读取失败
else:
status = "free" # 默认状态为免费,如果没有检测结果
self.spots[rect] = {"number": number, "status": status}
# 绘制箭头
self.draw_arrow(x, y, direction)
# 绘制车位编号
if direction not in ["left", "right"]:
self.canvas.create_text(
x + car_space_width / 2,
y + car_space_height / 2,
text=str(number),
font=("Arial", 8),
)
else:
self.canvas.create_text(
x + car_space_height / 2,
y + car_space_width / 2,
text=str(number),
font=("Arial", 8),
)
# 绑定点击事件
self.canvas.tag_bind(rect, "<Button-1>", self.toggle_spot)
# 按状态设置颜色
fill_color = status_color.get(status, default_color)
self.canvas.itemconfig(rect, fill=fill_color)
def find_path(self, matrix):
# 定义起点和终点
start = (0, 8)
end = None
for i in range(len(matrix)):
for j in range(len(matrix[0])):
if matrix[i][j] == 2:
end = (i, j)
break
if end:
break
# 定义方向数组,表示上下左右四个方向
directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
# 初始化队列,存储当前坐标和路径
queue = deque()
queue.append((start, [start]))
# 初始化访问标记数组
visited = [[False for _ in range(len(matrix[0]))]
for _ in range(len(matrix))]
visited[start[0]][start[1]] = True
# 开始 BFS
while queue:
(x, y), path = queue.popleft()
# 如果当前点是终点,返回路径
if (x, y) == end:
return path # 返回找到的路径
# 检查四个方向
for dx, dy in directions:
nx, ny = x + dx, y + dy
# 判断新坐标是否在矩阵范围内,并且是路径或目的地且未被访问过
if 0 <= nx < len(matrix) and 0 <= ny < len(matrix[0]):
if matrix[nx][ny] in [1, 2] and not visited[nx][ny]:
visited[nx][ny] = True
queue.append(((nx, ny), path + [(nx, ny)]))
return [] # 如果没有找到路径,返回空列表
def toggle_spot(self, event):
clicked = event.widget.find_withtag("current")[0]
spot = self.spots.get(clicked)
if spot["status"] != "free":
messagebox.showinfo("提示", "该车位已满,请选择其他车位!")
else:
messagebox.showinfo(
"车位信息",
f"车位编号: {spot['number']}\n状态: {spot['status']}",
)
coords = self.canvas.coords(clicked)
X1, Y1, X2, Y2 = coords
# X1 = X1*40+20
# Y1 = Y1*40+20
# X2=X2*40+20
# Y2=Y2*40+20
number = spot["number"]
parkspot = self.parkpot[number]
# 清除之前的路径
self.clear_previous_path()
matrix = self.grid.copy()
matrix[parkspot[0]][parkspot[1]] = 2
path = self.find_path(matrix)
direction = parkspot[2]
if path:
# 转换为像素坐标
path_coords = [(x * 40 + 20, y * 40 + 20) for (x, y) in path]
if direction == 'left':
path_coords.append((X2, (Y1+Y2)/2))
elif direction == 'right':
path_coords.append((X1, (Y1+Y2)/2))
elif direction == 'up':
path_coords.append(((X1+X2)/2, Y2))
else:
path_coords.append(((X1+X2)/2, Y1))
# 保存路径图形项ID
self.current_path = []
print(path_coords)
# 绘制路径并记录图形项ID
for i in range(len(path_coords) - 1):
y1, x1 = path_coords[i]
y2, x2 = path_coords[i + 1]
if i == len(path_coords) - 2:
line_id = self.canvas.create_line(
x1, y1, y2, x2, fill="blue", width=5, arrow=tk.LAST)
else:
line_id = self.canvas.create_line(
x1, y1, x2, y2, fill="blue", width=5)
self.current_path.append(line_id)
else:
messagebox.showinfo("提示", "未找到到达该车位的路径!")
def clear_previous_path(self):
"""清除之前绘制的路径"""
for item_id in self.current_path:
self.canvas.delete(item_id)
self.current_path.clear()
def search_spot(self):
query = self.search_entry.get()
found = False
for rect, spot in self.spots.items():
if str(spot["number"]) == query:
self.canvas.itemconfig(rect, fill=selected_color)
self.find_path(rect)
found = True
else:
# 恢复成状态颜色
self.canvas.itemconfig(rect, fill=status_color.get(
spot["status"], default_color))
if not found:
messagebox.showwarning("提示", "未找到该车位编号!")
def is_obstacle(self, coords):
# 判断当前位置是否为车位(障碍物)
for x, y in self.obstacles:
if x <= coords[0] <= x + car_space_width and y <= coords[1] <= y + car_space_height:
return True
return False
if __name__ == "__main__":
app = ParkingLot()
app.mainloop()