parking_management.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. # Ultralytics YOLO 🚀, AGPL-3.0 license
  2. import json
  3. from tkinter import filedialog, messagebox
  4. import cv2
  5. import numpy as np
  6. from PIL import Image, ImageTk
  7. from ultralytics.utils.checks import check_imshow, check_requirements
  8. from ultralytics.utils.plotting import Annotator
  9. class ParkingPtsSelection:
  10. def __init__(self):
  11. """Initializes the UI for selecting parking zone points in a tkinter window."""
  12. check_requirements("tkinter")
  13. import tkinter as tk
  14. self.tk = tk
  15. self.master = tk.Tk()
  16. self.master.title("Ultralytics Parking Zones Points Selector")
  17. # Disable window resizing
  18. self.master.resizable(False, False)
  19. # Setup canvas for image display
  20. self.canvas = self.tk.Canvas(self.master, bg="white")
  21. # Setup buttons
  22. button_frame = self.tk.Frame(self.master)
  23. button_frame.pack(side=self.tk.TOP)
  24. self.tk.Button(button_frame, text="Upload Image", command=self.upload_image).grid(row=0, column=0)
  25. self.tk.Button(button_frame, text="Remove Last BBox", command=self.remove_last_bounding_box).grid(
  26. row=0, column=1
  27. )
  28. self.tk.Button(button_frame, text="Save", command=self.save_to_json).grid(row=0, column=2)
  29. # Initialize properties
  30. self.image_path = None
  31. self.image = None
  32. self.canvas_image = None
  33. self.bounding_boxes = []
  34. self.current_box = []
  35. self.img_width = 0
  36. self.img_height = 0
  37. # Constants
  38. self.canvas_max_width = 1280
  39. self.canvas_max_height = 720
  40. self.master.mainloop()
  41. def upload_image(self):
  42. """Upload an image and resize it to fit canvas."""
  43. self.image_path = filedialog.askopenfilename(filetypes=[("Image Files", "*.png;*.jpg;*.jpeg")])
  44. if not self.image_path:
  45. return
  46. self.image = Image.open(self.image_path)
  47. self.img_width, self.img_height = self.image.size
  48. # Calculate the aspect ratio and resize image
  49. aspect_ratio = self.img_width / self.img_height
  50. if aspect_ratio > 1:
  51. # Landscape orientation
  52. canvas_width = min(self.canvas_max_width, self.img_width)
  53. canvas_height = int(canvas_width / aspect_ratio)
  54. else:
  55. # Portrait orientation
  56. canvas_height = min(self.canvas_max_height, self.img_height)
  57. canvas_width = int(canvas_height * aspect_ratio)
  58. # Check if canvas is already initialized
  59. if self.canvas:
  60. self.canvas.destroy() # Destroy previous canvas
  61. self.canvas = self.tk.Canvas(self.master, bg="white", width=canvas_width, height=canvas_height)
  62. resized_image = self.image.resize((canvas_width, canvas_height), Image.LANCZOS)
  63. self.canvas_image = ImageTk.PhotoImage(resized_image)
  64. self.canvas.create_image(0, 0, anchor=self.tk.NW, image=self.canvas_image)
  65. self.canvas.pack(side=self.tk.BOTTOM)
  66. self.canvas.bind("<Button-1>", self.on_canvas_click)
  67. # Reset bounding boxes and current box
  68. self.bounding_boxes = []
  69. self.current_box = []
  70. def on_canvas_click(self, event):
  71. """Handle mouse clicks on canvas to create points for bounding boxes."""
  72. self.current_box.append((event.x, event.y))
  73. x0, y0 = event.x - 3, event.y - 3
  74. x1, y1 = event.x + 3, event.y + 3
  75. self.canvas.create_oval(x0, y0, x1, y1, fill="red")
  76. if len(self.current_box) == 4:
  77. self.bounding_boxes.append(self.current_box)
  78. self.draw_bounding_box(self.current_box)
  79. self.current_box = []
  80. def draw_bounding_box(self, box):
  81. """
  82. Draw bounding box on canvas.
  83. Args:
  84. box (list): Bounding box data
  85. """
  86. for i in range(4):
  87. x1, y1 = box[i]
  88. x2, y2 = box[(i + 1) % 4]
  89. self.canvas.create_line(x1, y1, x2, y2, fill="blue", width=2)
  90. def remove_last_bounding_box(self):
  91. """Remove the last drawn bounding box from canvas."""
  92. if self.bounding_boxes:
  93. self.bounding_boxes.pop() # Remove the last bounding box
  94. self.canvas.delete("all") # Clear the canvas
  95. self.canvas.create_image(0, 0, anchor=self.tk.NW, image=self.canvas_image) # Redraw the image
  96. # Redraw all bounding boxes
  97. for box in self.bounding_boxes:
  98. self.draw_bounding_box(box)
  99. messagebox.showinfo("Success", "Last bounding box removed.")
  100. else:
  101. messagebox.showwarning("Warning", "No bounding boxes to remove.")
  102. def save_to_json(self):
  103. """Saves rescaled bounding boxes to 'bounding_boxes.json' based on image-to-canvas size ratio."""
  104. canvas_width, canvas_height = self.canvas.winfo_width(), self.canvas.winfo_height()
  105. width_scaling_factor = self.img_width / canvas_width
  106. height_scaling_factor = self.img_height / canvas_height
  107. bounding_boxes_data = []
  108. for box in self.bounding_boxes:
  109. rescaled_box = []
  110. for x, y in box:
  111. rescaled_x = int(x * width_scaling_factor)
  112. rescaled_y = int(y * height_scaling_factor)
  113. rescaled_box.append((rescaled_x, rescaled_y))
  114. bounding_boxes_data.append({"points": rescaled_box})
  115. with open("bounding_boxes.json", "w") as json_file:
  116. json.dump(bounding_boxes_data, json_file, indent=4)
  117. messagebox.showinfo("Success", "Bounding boxes saved to bounding_boxes.json")
  118. class ParkingManagement:
  119. def __init__(
  120. self,
  121. model_path,
  122. txt_color=(0, 0, 0),
  123. bg_color=(255, 255, 255),
  124. occupied_region_color=(0, 255, 0),
  125. available_region_color=(0, 0, 255),
  126. margin=10,
  127. ):
  128. """
  129. Initializes the parking management system with a YOLOv8 model and visualization settings.
  130. Args:
  131. model_path (str): Path to the YOLOv8 model.
  132. txt_color (tuple): RGB color tuple for text.
  133. bg_color (tuple): RGB color tuple for background.
  134. occupied_region_color (tuple): RGB color tuple for occupied regions.
  135. available_region_color (tuple): RGB color tuple for available regions.
  136. margin (int): Margin for text display.
  137. """
  138. # Model path and initialization
  139. self.model_path = model_path
  140. self.model = self.load_model()
  141. # Labels dictionary
  142. self.labels_dict = {"Occupancy": 0, "Available": 0}
  143. # Visualization details
  144. self.margin = margin
  145. self.bg_color = bg_color
  146. self.txt_color = txt_color
  147. self.occupied_region_color = occupied_region_color
  148. self.available_region_color = available_region_color
  149. self.window_name = "Ultralytics YOLOv8 Parking Management System"
  150. # Check if environment supports imshow
  151. self.env_check = check_imshow(warn=True)
  152. def load_model(self):
  153. """Load the Ultralytics YOLOv8 model for inference and analytics."""
  154. from ultralytics import YOLO
  155. self.model = YOLO(self.model_path)
  156. return self.model
  157. @staticmethod
  158. def parking_regions_extraction(json_file):
  159. """
  160. Extract parking regions from json file.
  161. Args:
  162. json_file (str): file that have all parking slot points
  163. """
  164. with open(json_file, "r") as json_file:
  165. return json.load(json_file)
  166. def process_data(self, json_data, im0, boxes, clss):
  167. """
  168. Process the model data for parking lot management.
  169. Args:
  170. json_data (str): json data for parking lot management
  171. im0 (ndarray): inference image
  172. boxes (list): bounding boxes data
  173. clss (list): bounding boxes classes list
  174. Returns:
  175. filled_slots (int): total slots that are filled in parking lot
  176. empty_slots (int): total slots that are available in parking lot
  177. """
  178. annotator = Annotator(im0)
  179. total_slots, filled_slots = len(json_data), 0
  180. empty_slots = total_slots
  181. for region in json_data:
  182. points = region["points"]
  183. points_array = np.array(points, dtype=np.int32).reshape((-1, 1, 2))
  184. region_occupied = False
  185. for box, cls in zip(boxes, clss):
  186. x_center = int((box[0] + box[2]) / 2)
  187. y_center = int((box[1] + box[3]) / 2)
  188. text = f"{self.model.names[int(cls)]}"
  189. annotator.display_objects_labels(
  190. im0, text, self.txt_color, self.bg_color, x_center, y_center, self.margin
  191. )
  192. dist = cv2.pointPolygonTest(points_array, (x_center, y_center), False)
  193. if dist >= 0:
  194. region_occupied = True
  195. break
  196. color = self.occupied_region_color if region_occupied else self.available_region_color
  197. cv2.polylines(im0, [points_array], isClosed=True, color=color, thickness=2)
  198. if region_occupied:
  199. filled_slots += 1
  200. empty_slots -= 1
  201. self.labels_dict["Occupancy"] = filled_slots
  202. self.labels_dict["Available"] = empty_slots
  203. annotator.display_analytics(im0, self.labels_dict, self.txt_color, self.bg_color, self.margin)
  204. def display_frames(self, im0):
  205. """
  206. Display frame.
  207. Args:
  208. im0 (ndarray): inference image
  209. """
  210. if self.env_check:
  211. cv2.namedWindow(self.window_name)
  212. cv2.imshow(self.window_name, im0)
  213. # Break Window
  214. if cv2.waitKey(1) & 0xFF == ord("q"):
  215. return