Optimize parking management solution (#16288)

Co-authored-by: UltralyticsAssistant <web@ultralytics.com>
Co-authored-by: Ultralytics Assistant <135830346+UltralyticsAssistant@users.noreply.github.com>
This commit is contained in:
Muhammad Rizwan Munawar 2024-09-17 23:27:03 +05:00 committed by GitHub
parent 6f2bb65953
commit 6e3b7dc2f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 92 additions and 133 deletions

View file

@ -74,9 +74,6 @@ Parking management with [Ultralytics YOLOv8](https://github.com/ultralytics/ultr
from ultralytics import solutions from ultralytics import solutions
# Path to json file, that created with above point selection app
polygon_json_path = "bounding_boxes.json"
# Video capture # Video capture
cap = cv2.VideoCapture("Path/to/video/file.mp4") cap = cv2.VideoCapture("Path/to/video/file.mp4")
assert cap.isOpened(), "Error reading video file" assert cap.isOpened(), "Error reading video file"
@ -86,22 +83,16 @@ Parking management with [Ultralytics YOLOv8](https://github.com/ultralytics/ultr
video_writer = cv2.VideoWriter("parking management.avi", cv2.VideoWriter_fourcc(*"mp4v"), fps, (w, h)) video_writer = cv2.VideoWriter("parking management.avi", cv2.VideoWriter_fourcc(*"mp4v"), fps, (w, h))
# Initialize parking management object # Initialize parking management object
management = solutions.ParkingManagement(model_path="yolov8n.pt") parking_manager = solutions.ParkingManagement(
model="yolov8n.pt", # path to model file
json_file="bounding_boxes.json", # path to parking annotations file
)
while cap.isOpened(): while cap.isOpened():
ret, im0 = cap.read() ret, im0 = cap.read()
if not ret: if not ret:
break break
im0 = parking_manager.process_data(im0)
json_data = management.parking_regions_extraction(polygon_json_path)
results = management.model.track(im0, persist=True, show=False)
if results[0].boxes.id is not None:
boxes = results[0].boxes.xyxy.cpu().tolist()
clss = results[0].boxes.cls.cpu().tolist()
management.process_data(json_data, im0, boxes, clss)
management.display_frames(im0)
video_writer.write(im0) video_writer.write(im0)
cap.release() cap.release()
@ -111,14 +102,12 @@ Parking management with [Ultralytics YOLOv8](https://github.com/ultralytics/ultr
### Optional Arguments `ParkingManagement` ### Optional Arguments `ParkingManagement`
| Name | Type | Default | Description | | Name | Type | Default | Description |
| ------------------------ | ------- | ----------------- | -------------------------------------- | | ------------------------ | ------- | ------------- | -------------------------------------------------------------- |
| `model_path` | `str` | `None` | Path to the YOLOv8 model. | | `model` | `str` | `None` | Path to the YOLOv8 model. |
| `txt_color` | `tuple` | `(0, 0, 0)` | RGB color tuple for text. | | `json_file` | `str` | `None` | Path to the JSON file, that have all parking coordinates data. |
| `bg_color` | `tuple` | `(255, 255, 255)` | RGB color tuple for background. | | `occupied_region_color` | `tuple` | `(0, 0, 255)` | RGB color for occupied regions. |
| `occupied_region_color` | `tuple` | `(0, 255, 0)` | RGB color tuple for occupied regions. | | `available_region_color` | `tuple` | `(0, 255, 0)` | RGB color for available regions. |
| `available_region_color` | `tuple` | `(0, 0, 255)` | RGB color tuple for available regions. |
| `margin` | `int` | `10` | Margin for text display. |
### Arguments `model.track` ### Arguments `model.track`

View file

@ -42,10 +42,10 @@ class ParkingPtsSelection:
self.image_path = None self.image_path = None
self.image = None self.image = None
self.canvas_image = None self.canvas_image = None
self.bounding_boxes = [] self.rg_data = [] # region coordinates
self.current_box = [] self.current_box = []
self.img_width = 0 self.imgw = 0 # image width
self.img_height = 0 self.imgh = 0 # image height
# Constants # Constants
self.canvas_max_width = 1280 self.canvas_max_width = 1280
@ -64,17 +64,17 @@ class ParkingPtsSelection:
return return
self.image = Image.open(self.image_path) self.image = Image.open(self.image_path)
self.img_width, self.img_height = self.image.size self.imgw, self.imgh = self.image.size
# Calculate the aspect ratio and resize image # Calculate the aspect ratio and resize image
aspect_ratio = self.img_width / self.img_height aspect_ratio = self.imgw / self.imgh
if aspect_ratio > 1: if aspect_ratio > 1:
# Landscape orientation # Landscape orientation
canvas_width = min(self.canvas_max_width, self.img_width) canvas_width = min(self.canvas_max_width, self.imgw)
canvas_height = int(canvas_width / aspect_ratio) canvas_height = int(canvas_width / aspect_ratio)
else: else:
# Portrait orientation # Portrait orientation
canvas_height = min(self.canvas_max_height, self.img_height) canvas_height = min(self.canvas_max_height, self.imgh)
canvas_width = int(canvas_height * aspect_ratio) canvas_width = int(canvas_height * aspect_ratio)
# Check if canvas is already initialized # Check if canvas is already initialized
@ -90,46 +90,34 @@ class ParkingPtsSelection:
self.canvas.bind("<Button-1>", self.on_canvas_click) self.canvas.bind("<Button-1>", self.on_canvas_click)
# Reset bounding boxes and current box # Reset bounding boxes and current box
self.bounding_boxes = [] self.rg_data = []
self.current_box = [] self.current_box = []
def on_canvas_click(self, event): def on_canvas_click(self, event):
"""Handle mouse clicks on canvas to create points for bounding boxes.""" """Handle mouse clicks on canvas to create points for bounding boxes."""
self.current_box.append((event.x, event.y)) self.current_box.append((event.x, event.y))
x0, y0 = event.x - 3, event.y - 3 self.canvas.create_oval(event.x - 3, event.y - 3, event.x + 3, event.y + 3, fill="red")
x1, y1 = event.x + 3, event.y + 3
self.canvas.create_oval(x0, y0, x1, y1, fill="red")
if len(self.current_box) == 4: if len(self.current_box) == 4:
self.bounding_boxes.append(self.current_box) self.rg_data.append(self.current_box)
self.draw_bounding_box(self.current_box) [
self.canvas.create_line(self.current_box[i], self.current_box[(i + 1) % 4], fill="blue", width=2)
for i in range(4)
]
self.current_box = [] self.current_box = []
def draw_bounding_box(self, box):
"""
Draw bounding box on canvas.
Args:
box (list): Bounding box data
"""
for i in range(4):
x1, y1 = box[i]
x2, y2 = box[(i + 1) % 4]
self.canvas.create_line(x1, y1, x2, y2, fill="blue", width=2)
def remove_last_bounding_box(self): def remove_last_bounding_box(self):
"""Remove the last drawn bounding box from canvas.""" """Remove the last drawn bounding box from canvas."""
from tkinter import messagebox # scope for multi-environment compatibility from tkinter import messagebox # scope for multi-environment compatibility
if self.bounding_boxes: if self.rg_data:
self.bounding_boxes.pop() # Remove the last bounding box self.rg_data.pop() # Remove the last bounding box
self.canvas.delete("all") # Clear the canvas self.canvas.delete("all") # Clear the canvas
self.canvas.create_image(0, 0, anchor=self.tk.NW, image=self.canvas_image) # Redraw the image self.canvas.create_image(0, 0, anchor=self.tk.NW, image=self.canvas_image) # Redraw the image
# Redraw all bounding boxes # Redraw all bounding boxes
for box in self.bounding_boxes: for box in self.rg_data:
self.draw_bounding_box(box) [self.canvas.create_line(box[i], box[(i + 1) % 4], fill="blue", width=2) for i in range(4)]
messagebox.showinfo("Success", "Last bounding box removed.") messagebox.showinfo("Success", "Last bounding box removed.")
else: else:
messagebox.showwarning("Warning", "No bounding boxes to remove.") messagebox.showwarning("Warning", "No bounding boxes to remove.")
@ -138,19 +126,19 @@ class ParkingPtsSelection:
"""Saves rescaled bounding boxes to 'bounding_boxes.json' based on image-to-canvas size ratio.""" """Saves rescaled bounding boxes to 'bounding_boxes.json' based on image-to-canvas size ratio."""
from tkinter import messagebox # scope for multi-environment compatibility from tkinter import messagebox # scope for multi-environment compatibility
canvas_width, canvas_height = self.canvas.winfo_width(), self.canvas.winfo_height() rg_data = [] # regions data
width_scaling_factor = self.img_width / canvas_width for box in self.rg_data:
height_scaling_factor = self.img_height / canvas_height rs_box = [] # rescaled box list
bounding_boxes_data = []
for box in self.bounding_boxes:
rescaled_box = []
for x, y in box: for x, y in box:
rescaled_x = int(x * width_scaling_factor) rs_box.append(
rescaled_y = int(y * height_scaling_factor) (
rescaled_box.append((rescaled_x, rescaled_y)) int(x * self.imgw / self.canvas.winfo_width()), # width scaling
bounding_boxes_data.append({"points": rescaled_box}) int(y * self.imgh / self.canvas.winfo_height()),
)
) # height scaling
rg_data.append({"points": rs_box})
with open("bounding_boxes.json", "w") as f: with open("bounding_boxes.json", "w") as f:
json.dump(bounding_boxes_data, f, indent=4) json.dump(rg_data, f, indent=4)
messagebox.showinfo("Success", "Bounding boxes saved to bounding_boxes.json") messagebox.showinfo("Success", "Bounding boxes saved to bounding_boxes.json")
@ -160,102 +148,85 @@ class ParkingManagement:
def __init__( def __init__(
self, self,
model_path, model, # Ultralytics YOLO model file path
txt_color=(0, 0, 0), json_file, # Parking management annotation file created from Parking Annotator
bg_color=(255, 255, 255), occupied_region_color=(0, 0, 255), # occupied region color
occupied_region_color=(0, 255, 0), available_region_color=(0, 255, 0), # available region color
available_region_color=(0, 0, 255),
margin=10,
): ):
""" """
Initializes the parking management system with a YOLOv8 model and visualization settings. Initializes the parking management system with a YOLOv8 model and visualization settings.
Args: Args:
model_path (str): Path to the YOLOv8 model. model (str): Path to the YOLOv8 model.
txt_color (tuple): RGB color tuple for text. json_file (str): file that have all parking slot points data
bg_color (tuple): RGB color tuple for background.
occupied_region_color (tuple): RGB color tuple for occupied regions. occupied_region_color (tuple): RGB color tuple for occupied regions.
available_region_color (tuple): RGB color tuple for available regions. available_region_color (tuple): RGB color tuple for available regions.
margin (int): Margin for text display.
""" """
# Model path and initialization # Model initialization
self.model_path = model_path
self.model = self.load_model()
# Labels dictionary
self.labels_dict = {"Occupancy": 0, "Available": 0}
# Visualization details
self.margin = margin
self.bg_color = bg_color
self.txt_color = txt_color
self.occupied_region_color = occupied_region_color
self.available_region_color = available_region_color
self.window_name = "Ultralytics YOLOv8 Parking Management System"
# Check if environment supports imshow
self.env_check = check_imshow(warn=True)
def load_model(self):
"""Load the Ultralytics YOLO model for inference and analytics."""
from ultralytics import YOLO from ultralytics import YOLO
return YOLO(self.model_path) self.model = YOLO(model)
@staticmethod # Load JSON data
def parking_regions_extraction(json_file):
"""
Extract parking regions from json file.
Args:
json_file (str): file that have all parking slot points
"""
with open(json_file) as f: with open(json_file) as f:
return json.load(f) self.json_data = json.load(f)
def process_data(self, json_data, im0, boxes, clss): self.pr_info = {"Occupancy": 0, "Available": 0} # dictionary for parking information
self.occ = occupied_region_color
self.arc = available_region_color
self.env_check = check_imshow(warn=True) # check if environment supports imshow
def process_data(self, im0):
""" """
Process the model data for parking lot management. Process the model data for parking lot management.
Args: Args:
json_data (str): json data for parking lot management
im0 (ndarray): inference image im0 (ndarray): inference image
boxes (list): bounding boxes data
clss (list): bounding boxes classes list
Returns:
filled_slots (int): total slots that are filled in parking lot
empty_slots (int): total slots that are available in parking lot
""" """
annotator = Annotator(im0) results = self.model.track(im0, persist=True, show=False) # object tracking
empty_slots, filled_slots = len(json_data), 0
for region in json_data:
points_array = np.array(region["points"], dtype=np.int32).reshape((-1, 1, 2))
region_occupied = False
es, fs = len(self.json_data), 0 # empty slots, filled slots
annotator = Annotator(im0) # init annotator
# extract tracks data
if results[0].boxes.id is None:
self.display_frames(im0)
return im0
boxes = results[0].boxes.xyxy.cpu().tolist()
clss = results[0].boxes.cls.cpu().tolist()
for region in self.json_data:
# Convert points to a NumPy array with the correct dtype and reshape properly
pts_array = np.array(region["points"], dtype=np.int32).reshape((-1, 1, 2))
rg_occupied = False # occupied region initialization
for box, cls in zip(boxes, clss): for box, cls in zip(boxes, clss):
x_center = int((box[0] + box[2]) / 2) xc = int((box[0] + box[2]) / 2)
y_center = int((box[1] + box[3]) / 2) yc = int((box[1] + box[3]) / 2)
text = f"{self.model.names[int(cls)]}"
annotator.display_objects_labels( annotator.display_objects_labels(
im0, text, self.txt_color, self.bg_color, x_center, y_center, self.margin im0, self.model.names[int(cls)], (104, 31, 17), (255, 255, 255), xc, yc, 10
) )
dist = cv2.pointPolygonTest(points_array, (x_center, y_center), False) dist = cv2.pointPolygonTest(pts_array, (xc, yc), False)
if dist >= 0: if dist >= 0:
region_occupied = True rg_occupied = True
break break
if rg_occupied:
fs += 1
es -= 1
color = self.occupied_region_color if region_occupied else self.available_region_color # Plotting regions
cv2.polylines(im0, [points_array], isClosed=True, color=color, thickness=2) color = self.occ if rg_occupied else self.arc
if region_occupied: cv2.polylines(im0, [pts_array], isClosed=True, color=color, thickness=2)
filled_slots += 1
empty_slots -= 1
self.labels_dict["Occupancy"] = filled_slots self.pr_info["Occupancy"] = fs
self.labels_dict["Available"] = empty_slots self.pr_info["Available"] = es
annotator.display_analytics(im0, self.labels_dict, self.txt_color, self.bg_color, self.margin) annotator.display_analytics(im0, self.pr_info, (104, 31, 17), (255, 255, 255), 10)
self.display_frames(im0)
return im0
def display_frames(self, im0): def display_frames(self, im0):
""" """
@ -265,8 +236,7 @@ class ParkingManagement:
im0 (ndarray): inference image im0 (ndarray): inference image
""" """
if self.env_check: if self.env_check:
cv2.namedWindow(self.window_name) cv2.imshow("Ultralytics Parking Manager", im0)
cv2.imshow(self.window_name, im0)
# Break Window # Break Window
if cv2.waitKey(1) & 0xFF == ord("q"): if cv2.waitKey(1) & 0xFF == ord("q"):
return return