ultralytics 8.2.5 New 🌟 Parking Management Solution (#10385)
Co-authored-by: UltralyticsAssistant <web@ultralytics.com> Co-authored-by: Glenn Jocher <glenn.jocher@ultralytics.com>
This commit is contained in:
parent
156b6be8d3
commit
bc9fd45cdf
10 changed files with 451 additions and 81 deletions
|
|
@ -190,9 +190,7 @@ class Heatmap:
|
|||
for box, cls, track_id in zip(self.boxes, self.clss, self.track_ids):
|
||||
# Store class info
|
||||
if self.names[cls] not in self.class_wise_count:
|
||||
if len(self.names[cls]) > 5:
|
||||
self.names[cls] = self.names[cls][:5]
|
||||
self.class_wise_count[self.names[cls]] = {"in": 0, "out": 0}
|
||||
self.class_wise_count[self.names[cls]] = {"IN": 0, "OUT": 0}
|
||||
|
||||
if self.shape == "circle":
|
||||
center = (int((box[0] + box[2]) // 2), int((box[1] + box[3]) // 2))
|
||||
|
|
@ -225,10 +223,10 @@ class Heatmap:
|
|||
|
||||
if (box[0] - prev_position[0]) * (self.counting_region.centroid.x - prev_position[0]) > 0:
|
||||
self.in_counts += 1
|
||||
self.class_wise_count[self.names[cls]]["in"] += 1
|
||||
self.class_wise_count[self.names[cls]]["IN"] += 1
|
||||
else:
|
||||
self.out_counts += 1
|
||||
self.class_wise_count[self.names[cls]]["out"] += 1
|
||||
self.class_wise_count[self.names[cls]]["OUT"] += 1
|
||||
|
||||
# Count objects using line
|
||||
elif len(self.count_reg_pts) == 2:
|
||||
|
|
@ -239,10 +237,10 @@ class Heatmap:
|
|||
|
||||
if (box[0] - prev_position[0]) * (self.counting_region.centroid.x - prev_position[0]) > 0:
|
||||
self.in_counts += 1
|
||||
self.class_wise_count[self.names[cls]]["in"] += 1
|
||||
self.class_wise_count[self.names[cls]]["IN"] += 1
|
||||
else:
|
||||
self.out_counts += 1
|
||||
self.class_wise_count[self.names[cls]]["out"] += 1
|
||||
self.class_wise_count[self.names[cls]]["OUT"] += 1
|
||||
|
||||
else:
|
||||
for box, cls in zip(self.boxes, self.clss):
|
||||
|
|
@ -264,28 +262,21 @@ class Heatmap:
|
|||
heatmap_normalized = cv2.normalize(self.heatmap, None, 0, 255, cv2.NORM_MINMAX)
|
||||
heatmap_colored = cv2.applyColorMap(heatmap_normalized.astype(np.uint8), self.colormap)
|
||||
|
||||
label = "Ultralytics Analytics \t"
|
||||
labels_dict = {}
|
||||
|
||||
for key, value in self.class_wise_count.items():
|
||||
if value["in"] != 0 or value["out"] != 0:
|
||||
if value["IN"] != 0 or value["OUT"] != 0:
|
||||
if not self.view_in_counts and not self.view_out_counts:
|
||||
label = None
|
||||
continue
|
||||
elif not self.view_in_counts:
|
||||
label += f"{str.capitalize(key)}: IN {value['in']} \t"
|
||||
labels_dict[str.capitalize(key)] = f"OUT {value['OUT']}"
|
||||
elif not self.view_out_counts:
|
||||
label += f"{str.capitalize(key)}: OUT {value['out']} \t"
|
||||
labels_dict[str.capitalize(key)] = f"IN {value['IN']}"
|
||||
else:
|
||||
label += f"{str.capitalize(key)}: IN {value['in']} OUT {value['out']} \t"
|
||||
labels_dict[str.capitalize(key)] = f"IN {value['IN']} OUT {value['OUT']}"
|
||||
|
||||
label = label.rstrip()
|
||||
label = label.split("\t")
|
||||
|
||||
if self.count_reg_pts is not None and label is not None:
|
||||
self.annotator.display_counts(
|
||||
counts=label,
|
||||
count_txt_color=self.count_txt_color,
|
||||
count_bg_color=self.count_bg_color,
|
||||
)
|
||||
if labels_dict is not None:
|
||||
self.annotator.display_analytics(self.im0, labels_dict, self.count_txt_color, self.count_bg_color, 10)
|
||||
|
||||
self.im0 = cv2.addWeighted(self.im0, 1 - self.heatmap_alpha, heatmap_colored, self.heatmap_alpha, 0)
|
||||
|
||||
|
|
|
|||
|
|
@ -181,9 +181,7 @@ class ObjectCounter:
|
|||
|
||||
# Store class info
|
||||
if self.names[cls] not in self.class_wise_count:
|
||||
if len(self.names[cls]) > 5:
|
||||
self.names[cls] = self.names[cls][:5]
|
||||
self.class_wise_count[self.names[cls]] = {"in": 0, "out": 0}
|
||||
self.class_wise_count[self.names[cls]] = {"IN": 0, "OUT": 0}
|
||||
|
||||
# Draw Tracks
|
||||
track_line = self.track_history[track_id]
|
||||
|
|
@ -210,10 +208,10 @@ class ObjectCounter:
|
|||
|
||||
if (box[0] - prev_position[0]) * (self.counting_region.centroid.x - prev_position[0]) > 0:
|
||||
self.in_counts += 1
|
||||
self.class_wise_count[self.names[cls]]["in"] += 1
|
||||
self.class_wise_count[self.names[cls]]["IN"] += 1
|
||||
else:
|
||||
self.out_counts += 1
|
||||
self.class_wise_count[self.names[cls]]["out"] += 1
|
||||
self.class_wise_count[self.names[cls]]["OUT"] += 1
|
||||
|
||||
# Count objects using line
|
||||
elif len(self.reg_pts) == 2:
|
||||
|
|
@ -224,33 +222,26 @@ class ObjectCounter:
|
|||
|
||||
if (box[0] - prev_position[0]) * (self.counting_region.centroid.x - prev_position[0]) > 0:
|
||||
self.in_counts += 1
|
||||
self.class_wise_count[self.names[cls]]["in"] += 1
|
||||
self.class_wise_count[self.names[cls]]["IN"] += 1
|
||||
else:
|
||||
self.out_counts += 1
|
||||
self.class_wise_count[self.names[cls]]["out"] += 1
|
||||
self.class_wise_count[self.names[cls]]["OUT"] += 1
|
||||
|
||||
label = "Ultralytics Analytics \t"
|
||||
labels_dict = {}
|
||||
|
||||
for key, value in self.class_wise_count.items():
|
||||
if value["in"] != 0 or value["out"] != 0:
|
||||
if value["IN"] != 0 or value["OUT"] != 0:
|
||||
if not self.view_in_counts and not self.view_out_counts:
|
||||
label = None
|
||||
continue
|
||||
elif not self.view_in_counts:
|
||||
label += f"{str.capitalize(key)}: IN {value['in']} \t"
|
||||
labels_dict[str.capitalize(key)] = f"OUT {value['OUT']}"
|
||||
elif not self.view_out_counts:
|
||||
label += f"{str.capitalize(key)}: OUT {value['out']} \t"
|
||||
labels_dict[str.capitalize(key)] = f"IN {value['IN']}"
|
||||
else:
|
||||
label += f"{str.capitalize(key)}: IN {value['in']} OUT {value['out']} \t"
|
||||
labels_dict[str.capitalize(key)] = f"IN {value['IN']} OUT {value['OUT']}"
|
||||
|
||||
label = label.rstrip()
|
||||
label = label.split("\t")
|
||||
|
||||
if label is not None:
|
||||
self.annotator.display_counts(
|
||||
counts=label,
|
||||
count_txt_color=self.count_txt_color,
|
||||
count_bg_color=self.count_bg_color,
|
||||
)
|
||||
if labels_dict is not None:
|
||||
self.annotator.display_analytics(self.im0, labels_dict, self.count_txt_color, self.count_bg_color, 10)
|
||||
|
||||
def display_frames(self):
|
||||
"""Display frame."""
|
||||
|
|
|
|||
235
ultralytics/solutions/parking_management.py
Normal file
235
ultralytics/solutions/parking_management.py
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
import json
|
||||
from tkinter import filedialog, messagebox
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from PIL import Image, ImageTk
|
||||
|
||||
from ultralytics.utils.checks import check_imshow, check_requirements
|
||||
from ultralytics.utils.plotting import Annotator
|
||||
|
||||
check_requirements("tkinter")
|
||||
import tkinter as tk
|
||||
|
||||
|
||||
class ParkingPtsSelection:
|
||||
def __init__(self, master):
|
||||
# Initialize window and widgets.
|
||||
self.master = master
|
||||
master.title("Ultralytics Parking Zones Points Selector")
|
||||
self.initialize_ui()
|
||||
|
||||
# Initialize properties
|
||||
self.image_path = None
|
||||
self.image = None
|
||||
self.canvas_image = None
|
||||
self.canvas = None
|
||||
self.bounding_boxes = []
|
||||
self.current_box = []
|
||||
self.img_width = 0
|
||||
self.img_height = 0
|
||||
|
||||
# Constants
|
||||
self.canvas_max_width = 1280
|
||||
self.canvas_max_height = 720
|
||||
|
||||
def initialize_ui(self):
|
||||
"""Setup UI components."""
|
||||
# Setup buttons
|
||||
button_frame = tk.Frame(self.master)
|
||||
button_frame.pack(side=tk.TOP)
|
||||
|
||||
tk.Button(button_frame, text="Upload Image", command=self.upload_image).grid(row=0, column=0)
|
||||
tk.Button(button_frame, text="Remove Last BBox", command=self.remove_last_bounding_box).grid(row=0, column=1)
|
||||
tk.Button(button_frame, text="Save", command=self.save_to_json).grid(row=0, column=2)
|
||||
|
||||
# Setup canvas for image display
|
||||
self.canvas = tk.Canvas(self.master, bg="white")
|
||||
self.canvas.pack(side=tk.BOTTOM)
|
||||
self.canvas.bind("<Button-1>", self.on_canvas_click)
|
||||
|
||||
def upload_image(self):
|
||||
"""Upload an image and resize it to fit canvas."""
|
||||
self.image_path = filedialog.askopenfilename(filetypes=[("Image Files", "*.png;*.jpg;*.jpeg")])
|
||||
if not self.image_path:
|
||||
return
|
||||
|
||||
self.image = Image.open(self.image_path)
|
||||
self.img_width, self.img_height = self.image.size
|
||||
|
||||
# Calculate the aspect ratio and resize image
|
||||
aspect_ratio = self.img_width / self.img_height
|
||||
if aspect_ratio > 1:
|
||||
# Landscape orientation
|
||||
canvas_width = min(self.canvas_max_width, self.img_width)
|
||||
canvas_height = int(canvas_width / aspect_ratio)
|
||||
else:
|
||||
# Portrait orientation
|
||||
canvas_height = min(self.canvas_max_height, self.img_height)
|
||||
canvas_width = int(canvas_height * aspect_ratio)
|
||||
|
||||
self.canvas.config(width=canvas_width, height=canvas_height)
|
||||
resized_image = self.image.resize((canvas_width, canvas_height), Image.LANCZOS)
|
||||
self.canvas_image = ImageTk.PhotoImage(resized_image)
|
||||
self.canvas.create_image(0, 0, anchor=tk.NW, image=self.canvas_image)
|
||||
|
||||
# Reset bounding boxes and current box
|
||||
self.bounding_boxes = []
|
||||
self.current_box = []
|
||||
|
||||
def on_canvas_click(self, event):
|
||||
"""Handle mouse clicks on canvas to create points for bounding boxes."""
|
||||
self.current_box.append((event.x, event.y))
|
||||
|
||||
if len(self.current_box) == 4:
|
||||
self.bounding_boxes.append(self.current_box)
|
||||
self.draw_bounding_box(self.current_box)
|
||||
self.current_box = []
|
||||
|
||||
def draw_bounding_box(self, box):
|
||||
"""Draw bounding box on canvas."""
|
||||
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):
|
||||
"""Remove the last drawn bounding box from canvas."""
|
||||
if self.bounding_boxes:
|
||||
self.bounding_boxes.pop() # Remove the last bounding box
|
||||
self.canvas.delete("all") # Clear the canvas
|
||||
self.canvas.create_image(0, 0, anchor=tk.NW, image=self.canvas_image) # Redraw the image
|
||||
|
||||
# Redraw all bounding boxes
|
||||
for box in self.bounding_boxes:
|
||||
self.draw_bounding_box(box)
|
||||
|
||||
messagebox.showinfo("Success", "Last bounding box removed.")
|
||||
else:
|
||||
messagebox.showwarning("Warning", "No bounding boxes to remove.")
|
||||
|
||||
def save_to_json(self):
|
||||
canvas_width, canvas_height = self.canvas.winfo_width(), self.canvas.winfo_height()
|
||||
width_scaling_factor = self.img_width / canvas_width
|
||||
height_scaling_factor = self.img_height / canvas_height
|
||||
bounding_boxes_data = []
|
||||
for box in self.bounding_boxes:
|
||||
print("Bounding Box ", bounding_boxes_data)
|
||||
rescaled_box = []
|
||||
for x, y in box:
|
||||
rescaled_x = int(x * width_scaling_factor)
|
||||
rescaled_y = int(y * height_scaling_factor)
|
||||
rescaled_box.append((rescaled_x, rescaled_y))
|
||||
bounding_boxes_data.append({"points": rescaled_box})
|
||||
with open("bounding_boxes.json", "w") as json_file:
|
||||
json.dump(bounding_boxes_data, json_file, indent=4)
|
||||
|
||||
messagebox.showinfo("Success", "Bounding boxes saved to bounding_boxes.json")
|
||||
|
||||
|
||||
class ParkingManagement:
|
||||
def __init__(
|
||||
self,
|
||||
model_path,
|
||||
txt_color=(0, 0, 0),
|
||||
bg_color=(255, 255, 255),
|
||||
occupied_region_color=(0, 255, 0),
|
||||
available_region_color=(0, 0, 255),
|
||||
margin=10,
|
||||
):
|
||||
# Model path and 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 support imshow
|
||||
self.env_check = check_imshow(warn=True)
|
||||
|
||||
def load_model(self):
|
||||
"""Load the Ultralytics YOLOv8 model for inference and analytics."""
|
||||
from ultralytics import YOLO
|
||||
|
||||
self.model = YOLO(self.model_path)
|
||||
return self.model
|
||||
|
||||
def parking_regions_extraction(self, json_file):
|
||||
"""
|
||||
Extract parking regions from json file.
|
||||
|
||||
Args:
|
||||
json_file (str): file that have all parking slot points
|
||||
"""
|
||||
|
||||
with open(json_file, "r") as json_file:
|
||||
json_data = json.load(json_file)
|
||||
return json_data
|
||||
|
||||
def process_data(self, json_data, im0, boxes, clss):
|
||||
"""
|
||||
Process the model data for parking lot management.
|
||||
|
||||
Args:
|
||||
json_data (str): json data for parking lot management
|
||||
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)
|
||||
total_slots, filled_slots = len(json_data), 0
|
||||
empty_slots = total_slots
|
||||
|
||||
for region in json_data:
|
||||
points = region["points"]
|
||||
points_array = np.array(points, dtype=np.int32).reshape((-1, 1, 2))
|
||||
region_occupied = False
|
||||
|
||||
for box, cls in zip(boxes, clss):
|
||||
x_center = int((box[0] + box[2]) / 2)
|
||||
y_center = int((box[1] + box[3]) / 2)
|
||||
text = f"{self.model.names[int(cls)]}"
|
||||
|
||||
annotator.display_objects_labels(
|
||||
im0, text, self.txt_color, self.bg_color, x_center, y_center, self.margin
|
||||
)
|
||||
dist = cv2.pointPolygonTest(points_array, (x_center, y_center), False)
|
||||
if dist >= 0:
|
||||
region_occupied = True
|
||||
break
|
||||
|
||||
color = self.occupied_region_color if region_occupied else self.available_region_color
|
||||
cv2.polylines(im0, [points_array], isClosed=True, color=color, thickness=2)
|
||||
if region_occupied:
|
||||
filled_slots += 1
|
||||
empty_slots -= 1
|
||||
|
||||
self.labels_dict["Occupancy"] = filled_slots
|
||||
self.labels_dict["Available"] = empty_slots
|
||||
|
||||
annotator.display_analytics(im0, self.labels_dict, self.txt_color, self.bg_color, self.margin)
|
||||
|
||||
def display_frames(self, im0):
|
||||
"""
|
||||
Display frame.
|
||||
|
||||
Args:
|
||||
im0 (ndarray): inference image
|
||||
"""
|
||||
if self.env_check:
|
||||
cv2.namedWindow(self.window_name)
|
||||
cv2.imshow(self.window_name, im0)
|
||||
# Break Window
|
||||
if cv2.waitKey(1) & 0xFF == ord("q"):
|
||||
return
|
||||
Loading…
Add table
Add a link
Reference in a new issue