506 lines
19 KiB
Python
506 lines
19 KiB
Python
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()
|