File size: 9,290 Bytes
f6228f9 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# Ultralytics YOLO 🚀, AGPL-3.0 license
import json
import cv2
import numpy as np
from ultralytics.utils.checks import check_imshow, check_requirements
from ultralytics.utils.plotting import Annotator
class ParkingPtsSelection:
"""Class for selecting and managing parking zone points on images using a Tkinter-based UI."""
def __init__(self):
"""Initializes the UI for selecting parking zone points in a tkinter window."""
check_requirements("tkinter")
import tkinter as tk # scope for multi-environment compatibility
self.tk = tk
self.master = tk.Tk()
self.master.title("Ultralytics Parking Zones Points Selector")
# Disable window resizing
self.master.resizable(False, False)
# Setup canvas for image display
self.canvas = self.tk.Canvas(self.master, bg="white")
# Setup buttons
button_frame = self.tk.Frame(self.master)
button_frame.pack(side=self.tk.TOP)
self.tk.Button(button_frame, text="Upload Image", command=self.upload_image).grid(row=0, column=0)
self.tk.Button(button_frame, text="Remove Last BBox", command=self.remove_last_bounding_box).grid(
row=0, column=1
)
self.tk.Button(button_frame, text="Save", command=self.save_to_json).grid(row=0, column=2)
# Initialize properties
self.image_path = None
self.image = None
self.canvas_image = None
self.rg_data = [] # region coordinates
self.current_box = []
self.imgw = 0 # image width
self.imgh = 0 # image height
# Constants
self.canvas_max_width = 1280
self.canvas_max_height = 720
self.master.mainloop()
def upload_image(self):
"""Upload an image and resize it to fit canvas."""
from tkinter import filedialog
from PIL import Image, ImageTk # scope because ImageTk requires tkinter package
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.imgw, self.imgh = self.image.size
# Calculate the aspect ratio and resize image
aspect_ratio = self.imgw / self.imgh
if aspect_ratio > 1:
# Landscape orientation
canvas_width = min(self.canvas_max_width, self.imgw)
canvas_height = int(canvas_width / aspect_ratio)
else:
# Portrait orientation
canvas_height = min(self.canvas_max_height, self.imgh)
canvas_width = int(canvas_height * aspect_ratio)
# Check if canvas is already initialized
if self.canvas:
self.canvas.destroy() # Destroy previous canvas
self.canvas = self.tk.Canvas(self.master, bg="white", 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=self.tk.NW, image=self.canvas_image)
self.canvas.pack(side=self.tk.BOTTOM)
self.canvas.bind("<Button-1>", self.on_canvas_click)
# Reset bounding boxes and current box
self.rg_data = []
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))
self.canvas.create_oval(event.x - 3, event.y - 3, event.x + 3, event.y + 3, fill="red")
if len(self.current_box) == 4:
self.rg_data.append(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 = []
def remove_last_bounding_box(self):
"""Remove the last drawn bounding box from canvas."""
from tkinter import messagebox # scope for multi-environment compatibility
if self.rg_data:
self.rg_data.pop() # Remove the last bounding box
self.canvas.delete("all") # Clear the canvas
self.canvas.create_image(0, 0, anchor=self.tk.NW, image=self.canvas_image) # Redraw the image
# Redraw all bounding boxes
for box in self.rg_data:
[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.")
else:
messagebox.showwarning("Warning", "No bounding boxes to remove.")
def save_to_json(self):
"""Saves rescaled bounding boxes to 'bounding_boxes.json' based on image-to-canvas size ratio."""
from tkinter import messagebox # scope for multi-environment compatibility
rg_data = [] # regions data
for box in self.rg_data:
rs_box = [
(
int(x * self.imgw / self.canvas.winfo_width()), # width scaling
int(y * self.imgh / self.canvas.winfo_height()), # height scaling
)
for x, y in box
]
rg_data.append({"points": rs_box})
with open("bounding_boxes.json", "w") as f:
json.dump(rg_data, f, indent=4)
messagebox.showinfo("Success", "Bounding boxes saved to bounding_boxes.json")
class ParkingManagement:
"""Manages parking occupancy and availability using YOLOv8 for real-time monitoring and visualization."""
def __init__(
self,
model, # Ultralytics YOLO model file path
json_file, # Parking management annotation file created from Parking Annotator
occupied_region_color=(0, 0, 255), # occupied region color
available_region_color=(0, 255, 0), # available region color
):
"""
Initializes the parking management system with a YOLOv8 model and visualization settings.
Args:
model (str): Path to the YOLOv8 model.
json_file (str): file that have all parking slot points data
occupied_region_color (tuple): RGB color tuple for occupied regions.
available_region_color (tuple): RGB color tuple for available regions.
"""
# Model initialization
from ultralytics import YOLO
self.model = YOLO(model)
# Load JSON data
with open(json_file) as f:
self.json_data = json.load(f)
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.
Args:
im0 (ndarray): inference image
"""
results = self.model.track(im0, persist=True, show=False) # object tracking
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):
xc = int((box[0] + box[2]) / 2)
yc = int((box[1] + box[3]) / 2)
annotator.display_objects_labels(
im0, self.model.names[int(cls)], (104, 31, 17), (255, 255, 255), xc, yc, 10
)
dist = cv2.pointPolygonTest(pts_array, (xc, yc), False)
if dist >= 0:
rg_occupied = True
break
if rg_occupied:
fs += 1
es -= 1
# Plotting regions
color = self.occ if rg_occupied else self.arc
cv2.polylines(im0, [pts_array], isClosed=True, color=color, thickness=2)
self.pr_info["Occupancy"] = fs
self.pr_info["Available"] = es
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):
"""
Display frame.
Args:
im0 (ndarray): inference image
"""
if self.env_check:
cv2.imshow("Ultralytics Parking Manager", im0)
# Break Window
if cv2.waitKey(1) & 0xFF == ord("q"):
return
|