ultralytics 8.3.14__py3-none-any.whl → 8.3.16__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tests/test_solutions.py +20 -16
- ultralytics/__init__.py +1 -1
- ultralytics/cfg/solutions/default.yaml +1 -0
- ultralytics/data/split_dota.py +3 -3
- ultralytics/engine/exporter.py +1 -1
- ultralytics/nn/autobackend.py +7 -3
- ultralytics/solutions/ai_gym.py +43 -9
- ultralytics/solutions/analytics.py +65 -12
- ultralytics/solutions/distance_calculation.py +50 -10
- ultralytics/solutions/heatmap.py +50 -14
- ultralytics/solutions/object_counter.py +80 -24
- ultralytics/solutions/parking_management.py +161 -166
- ultralytics/solutions/queue_management.py +56 -11
- ultralytics/solutions/solutions.py +75 -20
- ultralytics/solutions/speed_estimation.py +41 -7
- ultralytics/solutions/streamlit_inference.py +2 -3
- ultralytics/utils/torch_utils.py +1 -1
- {ultralytics-8.3.14.dist-info → ultralytics-8.3.16.dist-info}/METADATA +11 -11
- {ultralytics-8.3.14.dist-info → ultralytics-8.3.16.dist-info}/RECORD +23 -23
- {ultralytics-8.3.14.dist-info → ultralytics-8.3.16.dist-info}/LICENSE +0 -0
- {ultralytics-8.3.14.dist-info → ultralytics-8.3.16.dist-info}/WHEEL +0 -0
- {ultralytics-8.3.14.dist-info → ultralytics-8.3.16.dist-info}/entry_points.txt +0 -0
- {ultralytics-8.3.14.dist-info → ultralytics-8.3.16.dist-info}/top_level.txt +0 -0
@@ -1,18 +1,40 @@
|
|
1
1
|
# Ultralytics YOLO 🚀, AGPL-3.0 license
|
2
2
|
|
3
|
-
from
|
4
|
-
|
5
|
-
from ultralytics.solutions.solutions import BaseSolution # Import a parent class
|
3
|
+
from ultralytics.solutions.solutions import BaseSolution
|
6
4
|
from ultralytics.utils.plotting import Annotator, colors
|
7
5
|
|
8
6
|
|
9
7
|
class ObjectCounter(BaseSolution):
|
10
|
-
"""
|
8
|
+
"""
|
9
|
+
A class to manage the counting of objects in a real-time video stream based on their tracks.
|
10
|
+
|
11
|
+
This class extends the BaseSolution class and provides functionality for counting objects moving in and out of a
|
12
|
+
specified region in a video stream. It supports both polygonal and linear regions for counting.
|
13
|
+
|
14
|
+
Attributes:
|
15
|
+
in_count (int): Counter for objects moving inward.
|
16
|
+
out_count (int): Counter for objects moving outward.
|
17
|
+
counted_ids (List[int]): List of IDs of objects that have been counted.
|
18
|
+
classwise_counts (Dict[str, Dict[str, int]]): Dictionary for counts, categorized by object class.
|
19
|
+
region_initialized (bool): Flag indicating whether the counting region has been initialized.
|
20
|
+
show_in (bool): Flag to control display of inward count.
|
21
|
+
show_out (bool): Flag to control display of outward count.
|
22
|
+
|
23
|
+
Methods:
|
24
|
+
count_objects: Counts objects within a polygonal or linear region.
|
25
|
+
store_classwise_counts: Initializes class-wise counts if not already present.
|
26
|
+
display_counts: Displays object counts on the frame.
|
27
|
+
count: Processes input data (frames or object tracks) and updates counts.
|
28
|
+
|
29
|
+
Examples:
|
30
|
+
>>> counter = ObjectCounter()
|
31
|
+
>>> frame = cv2.imread("frame.jpg")
|
32
|
+
>>> processed_frame = counter.count(frame)
|
33
|
+
>>> print(f"Inward count: {counter.in_count}, Outward count: {counter.out_count}")
|
34
|
+
"""
|
11
35
|
|
12
36
|
def __init__(self, **kwargs):
|
13
|
-
"""
|
14
|
-
objects.
|
15
|
-
"""
|
37
|
+
"""Initializes the ObjectCounter class for real-time object counting in video streams."""
|
16
38
|
super().__init__(**kwargs)
|
17
39
|
|
18
40
|
self.in_count = 0 # Counter for objects moving inward
|
@@ -26,14 +48,23 @@ class ObjectCounter(BaseSolution):
|
|
26
48
|
|
27
49
|
def count_objects(self, track_line, box, track_id, prev_position, cls):
|
28
50
|
"""
|
29
|
-
|
51
|
+
Counts objects within a polygonal or linear region based on their tracks.
|
30
52
|
|
31
53
|
Args:
|
32
|
-
track_line (
|
33
|
-
box (
|
34
|
-
track_id (int):
|
35
|
-
prev_position (
|
36
|
-
cls (int): Class index for classwise count updates
|
54
|
+
track_line (Dict): Last 30 frame track record for the object.
|
55
|
+
box (List[float]): Bounding box coordinates [x1, y1, x2, y2] for the specific track in the current frame.
|
56
|
+
track_id (int): Unique identifier for the tracked object.
|
57
|
+
prev_position (Tuple[float, float]): Last frame position coordinates (x, y) of the track.
|
58
|
+
cls (int): Class index for classwise count updates.
|
59
|
+
|
60
|
+
Examples:
|
61
|
+
>>> counter = ObjectCounter()
|
62
|
+
>>> track_line = {1: [100, 200], 2: [110, 210], 3: [120, 220]}
|
63
|
+
>>> box = [130, 230, 150, 250]
|
64
|
+
>>> track_id = 1
|
65
|
+
>>> prev_position = (120, 220)
|
66
|
+
>>> cls = 0
|
67
|
+
>>> counter.count_objects(track_line, box, track_id, prev_position, cls)
|
37
68
|
"""
|
38
69
|
if prev_position is None or track_id in self.counted_ids:
|
39
70
|
return
|
@@ -42,7 +73,7 @@ class ObjectCounter(BaseSolution):
|
|
42
73
|
dx = (box[0] - prev_position[0]) * (centroid.x - prev_position[0])
|
43
74
|
dy = (box[1] - prev_position[1]) * (centroid.y - prev_position[1])
|
44
75
|
|
45
|
-
if len(self.region) >= 3 and self.r_s.contains(Point(track_line[-1])):
|
76
|
+
if len(self.region) >= 3 and self.r_s.contains(self.Point(track_line[-1])):
|
46
77
|
self.counted_ids.append(track_id)
|
47
78
|
# For polygon region
|
48
79
|
if dx > 0:
|
@@ -52,7 +83,7 @@ class ObjectCounter(BaseSolution):
|
|
52
83
|
self.out_count += 1
|
53
84
|
self.classwise_counts[self.names[cls]]["OUT"] += 1
|
54
85
|
|
55
|
-
elif len(self.region) < 3 and LineString([prev_position, box[:2]]).intersects(self.
|
86
|
+
elif len(self.region) < 3 and self.LineString([prev_position, box[:2]]).intersects(self.r_s):
|
56
87
|
self.counted_ids.append(track_id)
|
57
88
|
# For linear region
|
58
89
|
if dx > 0 and dy > 0:
|
@@ -64,20 +95,34 @@ class ObjectCounter(BaseSolution):
|
|
64
95
|
|
65
96
|
def store_classwise_counts(self, cls):
|
66
97
|
"""
|
67
|
-
Initialize class-wise counts if not already present.
|
98
|
+
Initialize class-wise counts for a specific object class if not already present.
|
68
99
|
|
69
100
|
Args:
|
70
|
-
cls (int): Class index for classwise count updates
|
101
|
+
cls (int): Class index for classwise count updates.
|
102
|
+
|
103
|
+
This method ensures that the 'classwise_counts' dictionary contains an entry for the specified class,
|
104
|
+
initializing 'IN' and 'OUT' counts to zero if the class is not already present.
|
105
|
+
|
106
|
+
Examples:
|
107
|
+
>>> counter = ObjectCounter()
|
108
|
+
>>> counter.store_classwise_counts(0) # Initialize counts for class index 0
|
109
|
+
>>> print(counter.classwise_counts)
|
110
|
+
{'person': {'IN': 0, 'OUT': 0}}
|
71
111
|
"""
|
72
112
|
if self.names[cls] not in self.classwise_counts:
|
73
113
|
self.classwise_counts[self.names[cls]] = {"IN": 0, "OUT": 0}
|
74
114
|
|
75
115
|
def display_counts(self, im0):
|
76
116
|
"""
|
77
|
-
|
117
|
+
Displays object counts on the input image or frame.
|
78
118
|
|
79
119
|
Args:
|
80
|
-
im0 (ndarray): The input image or frame
|
120
|
+
im0 (numpy.ndarray): The input image or frame to display counts on.
|
121
|
+
|
122
|
+
Examples:
|
123
|
+
>>> counter = ObjectCounter()
|
124
|
+
>>> frame = cv2.imread("image.jpg")
|
125
|
+
>>> counter.display_counts(frame)
|
81
126
|
"""
|
82
127
|
labels_dict = {
|
83
128
|
str.capitalize(key): f"{'IN ' + str(value['IN']) if self.show_in else ''} "
|
@@ -91,12 +136,21 @@ class ObjectCounter(BaseSolution):
|
|
91
136
|
|
92
137
|
def count(self, im0):
|
93
138
|
"""
|
94
|
-
Processes input data (frames or object tracks) and updates counts.
|
139
|
+
Processes input data (frames or object tracks) and updates object counts.
|
140
|
+
|
141
|
+
This method initializes the counting region, extracts tracks, draws bounding boxes and regions, updates
|
142
|
+
object counts, and displays the results on the input image.
|
95
143
|
|
96
144
|
Args:
|
97
|
-
im0 (ndarray): The input image
|
98
|
-
|
99
|
-
|
145
|
+
im0 (numpy.ndarray): The input image or frame to be processed.
|
146
|
+
|
147
|
+
Returns:
|
148
|
+
(numpy.ndarray): The processed image with annotations and count information.
|
149
|
+
|
150
|
+
Examples:
|
151
|
+
>>> counter = ObjectCounter()
|
152
|
+
>>> frame = cv2.imread("path/to/image.jpg")
|
153
|
+
>>> processed_frame = counter.count(frame)
|
100
154
|
"""
|
101
155
|
if not self.region_initialized:
|
102
156
|
self.initialize_region()
|
@@ -122,7 +176,9 @@ class ObjectCounter(BaseSolution):
|
|
122
176
|
)
|
123
177
|
|
124
178
|
# store previous position of track for object counting
|
125
|
-
prev_position =
|
179
|
+
prev_position = None
|
180
|
+
if len(self.track_history[track_id]) > 1:
|
181
|
+
prev_position = self.track_history[track_id][-2]
|
126
182
|
self.count_objects(self.track_line, box, track_id, prev_position, cls) # Perform object counting
|
127
183
|
|
128
184
|
self.display_counts(im0) # Display the counts on the frame
|
@@ -5,237 +5,232 @@ import json
|
|
5
5
|
import cv2
|
6
6
|
import numpy as np
|
7
7
|
|
8
|
-
from ultralytics.
|
8
|
+
from ultralytics.solutions.solutions import LOGGER, BaseSolution, check_requirements
|
9
9
|
from ultralytics.utils.plotting import Annotator
|
10
10
|
|
11
11
|
|
12
12
|
class ParkingPtsSelection:
|
13
|
-
"""
|
13
|
+
"""
|
14
|
+
A class for selecting and managing parking zone points on images using a Tkinter-based UI.
|
15
|
+
|
16
|
+
This class provides functionality to upload an image, select points to define parking zones, and save the
|
17
|
+
selected points to a JSON file. It uses Tkinter for the graphical user interface.
|
18
|
+
|
19
|
+
Attributes:
|
20
|
+
tk (module): The Tkinter module for GUI operations.
|
21
|
+
filedialog (module): Tkinter's filedialog module for file selection operations.
|
22
|
+
messagebox (module): Tkinter's messagebox module for displaying message boxes.
|
23
|
+
master (tk.Tk): The main Tkinter window.
|
24
|
+
canvas (tk.Canvas): The canvas widget for displaying the image and drawing bounding boxes.
|
25
|
+
image (PIL.Image.Image): The uploaded image.
|
26
|
+
canvas_image (ImageTk.PhotoImage): The image displayed on the canvas.
|
27
|
+
rg_data (List[List[Tuple[int, int]]]): List of bounding boxes, each defined by 4 points.
|
28
|
+
current_box (List[Tuple[int, int]]): Temporary storage for the points of the current bounding box.
|
29
|
+
imgw (int): Original width of the uploaded image.
|
30
|
+
imgh (int): Original height of the uploaded image.
|
31
|
+
canvas_max_width (int): Maximum width of the canvas.
|
32
|
+
canvas_max_height (int): Maximum height of the canvas.
|
33
|
+
|
34
|
+
Methods:
|
35
|
+
setup_ui: Sets up the Tkinter UI components.
|
36
|
+
initialize_properties: Initializes the necessary properties.
|
37
|
+
upload_image: Uploads an image, resizes it to fit the canvas, and displays it.
|
38
|
+
on_canvas_click: Handles mouse clicks to add points for bounding boxes.
|
39
|
+
draw_box: Draws a bounding box on the canvas.
|
40
|
+
remove_last_bounding_box: Removes the last bounding box and redraws the canvas.
|
41
|
+
redraw_canvas: Redraws the canvas with the image and all bounding boxes.
|
42
|
+
save_to_json: Saves the bounding boxes to a JSON file.
|
43
|
+
|
44
|
+
Examples:
|
45
|
+
>>> parking_selector = ParkingPtsSelection()
|
46
|
+
>>> # Use the GUI to upload an image, select parking zones, and save the data
|
47
|
+
"""
|
14
48
|
|
15
49
|
def __init__(self):
|
16
|
-
"""Initializes the UI for
|
50
|
+
"""Initializes the ParkingPtsSelection class, setting up UI and properties for parking zone point selection."""
|
17
51
|
check_requirements("tkinter")
|
52
|
+
import tkinter as tk
|
53
|
+
from tkinter import filedialog, messagebox
|
18
54
|
|
19
|
-
|
55
|
+
self.tk, self.filedialog, self.messagebox = tk, filedialog, messagebox
|
56
|
+
self.setup_ui()
|
57
|
+
self.initialize_properties()
|
58
|
+
self.master.mainloop()
|
20
59
|
|
21
|
-
|
22
|
-
|
60
|
+
def setup_ui(self):
|
61
|
+
"""Sets up the Tkinter UI components for the parking zone points selection interface."""
|
62
|
+
self.master = self.tk.Tk()
|
23
63
|
self.master.title("Ultralytics Parking Zones Points Selector")
|
24
|
-
|
25
|
-
# Disable window resizing
|
26
64
|
self.master.resizable(False, False)
|
27
65
|
|
28
|
-
#
|
66
|
+
# Canvas for image display
|
29
67
|
self.canvas = self.tk.Canvas(self.master, bg="white")
|
68
|
+
self.canvas.pack(side=self.tk.BOTTOM)
|
30
69
|
|
31
|
-
#
|
70
|
+
# Button frame with buttons
|
32
71
|
button_frame = self.tk.Frame(self.master)
|
33
72
|
button_frame.pack(side=self.tk.TOP)
|
34
73
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
# Initialize properties
|
42
|
-
self.image_path = None
|
43
|
-
self.image = None
|
44
|
-
self.canvas_image = None
|
45
|
-
self.rg_data = [] # region coordinates
|
46
|
-
self.current_box = []
|
47
|
-
self.imgw = 0 # image width
|
48
|
-
self.imgh = 0 # image height
|
74
|
+
for text, cmd in [
|
75
|
+
("Upload Image", self.upload_image),
|
76
|
+
("Remove Last BBox", self.remove_last_bounding_box),
|
77
|
+
("Save", self.save_to_json),
|
78
|
+
]:
|
79
|
+
self.tk.Button(button_frame, text=text, command=cmd).pack(side=self.tk.LEFT)
|
49
80
|
|
50
|
-
|
51
|
-
|
52
|
-
self.
|
53
|
-
|
54
|
-
self.
|
81
|
+
def initialize_properties(self):
|
82
|
+
"""Initialize properties for image, canvas, bounding boxes, and dimensions."""
|
83
|
+
self.image = self.canvas_image = None
|
84
|
+
self.rg_data, self.current_box = [], []
|
85
|
+
self.imgw = self.imgh = 0
|
86
|
+
self.canvas_max_width, self.canvas_max_height = 1280, 720
|
55
87
|
|
56
88
|
def upload_image(self):
|
57
|
-
"""
|
58
|
-
from tkinter import filedialog
|
59
|
-
|
89
|
+
"""Uploads and displays an image on the canvas, resizing it to fit within specified dimensions."""
|
60
90
|
from PIL import Image, ImageTk # scope because ImageTk requires tkinter package
|
61
91
|
|
62
|
-
self.
|
63
|
-
if not self.
|
92
|
+
self.image = Image.open(self.filedialog.askopenfilename(filetypes=[("Image Files", "*.png;*.jpg;*.jpeg")]))
|
93
|
+
if not self.image:
|
64
94
|
return
|
65
95
|
|
66
|
-
self.image = Image.open(self.image_path)
|
67
96
|
self.imgw, self.imgh = self.image.size
|
68
|
-
|
69
|
-
# Calculate the aspect ratio and resize image
|
70
97
|
aspect_ratio = self.imgw / self.imgh
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
canvas_height = min(self.canvas_max_height, self.imgh)
|
78
|
-
canvas_width = int(canvas_height * aspect_ratio)
|
79
|
-
|
80
|
-
# Check if canvas is already initialized
|
81
|
-
if self.canvas:
|
82
|
-
self.canvas.destroy() # Destroy previous canvas
|
83
|
-
|
84
|
-
self.canvas = self.tk.Canvas(self.master, bg="white", width=canvas_width, height=canvas_height)
|
85
|
-
resized_image = self.image.resize((canvas_width, canvas_height), Image.LANCZOS)
|
86
|
-
self.canvas_image = ImageTk.PhotoImage(resized_image)
|
87
|
-
self.canvas.create_image(0, 0, anchor=self.tk.NW, image=self.canvas_image)
|
98
|
+
canvas_width = (
|
99
|
+
min(self.canvas_max_width, self.imgw) if aspect_ratio > 1 else int(self.canvas_max_height * aspect_ratio)
|
100
|
+
)
|
101
|
+
canvas_height = (
|
102
|
+
min(self.canvas_max_height, self.imgh) if aspect_ratio <= 1 else int(canvas_width / aspect_ratio)
|
103
|
+
)
|
88
104
|
|
89
|
-
self.canvas.
|
105
|
+
self.canvas.config(width=canvas_width, height=canvas_height)
|
106
|
+
self.canvas_image = ImageTk.PhotoImage(self.image.resize((canvas_width, canvas_height), Image.LANCZOS))
|
107
|
+
self.canvas.create_image(0, 0, anchor=self.tk.NW, image=self.canvas_image)
|
90
108
|
self.canvas.bind("<Button-1>", self.on_canvas_click)
|
91
109
|
|
92
|
-
|
93
|
-
self.rg_data = []
|
94
|
-
self.current_box = []
|
110
|
+
self.rg_data.clear(), self.current_box.clear()
|
95
111
|
|
96
112
|
def on_canvas_click(self, event):
|
97
|
-
"""
|
113
|
+
"""Handles mouse clicks to add points for bounding boxes on the canvas."""
|
98
114
|
self.current_box.append((event.x, event.y))
|
99
115
|
self.canvas.create_oval(event.x - 3, event.y - 3, event.x + 3, event.y + 3, fill="red")
|
100
|
-
|
101
116
|
if len(self.current_box) == 4:
|
102
|
-
self.rg_data.append(self.current_box)
|
103
|
-
|
104
|
-
|
105
|
-
for i in range(4)
|
106
|
-
]
|
107
|
-
self.current_box = []
|
117
|
+
self.rg_data.append(self.current_box.copy())
|
118
|
+
self.draw_box(self.current_box)
|
119
|
+
self.current_box.clear()
|
108
120
|
|
109
|
-
def
|
110
|
-
"""
|
111
|
-
|
121
|
+
def draw_box(self, box):
|
122
|
+
"""Draws a bounding box on the canvas using the provided coordinates."""
|
123
|
+
for i in range(4):
|
124
|
+
self.canvas.create_line(box[i], box[(i + 1) % 4], fill="blue", width=2)
|
112
125
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
self.
|
126
|
+
def remove_last_bounding_box(self):
|
127
|
+
"""Removes the last bounding box from the list and redraws the canvas."""
|
128
|
+
if not self.rg_data:
|
129
|
+
self.messagebox.showwarning("Warning", "No bounding boxes to remove.")
|
130
|
+
return
|
131
|
+
self.rg_data.pop()
|
132
|
+
self.redraw_canvas()
|
117
133
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
134
|
+
def redraw_canvas(self):
|
135
|
+
"""Redraws the canvas with the image and all bounding boxes."""
|
136
|
+
self.canvas.delete("all")
|
137
|
+
self.canvas.create_image(0, 0, anchor=self.tk.NW, image=self.canvas_image)
|
138
|
+
for box in self.rg_data:
|
139
|
+
self.draw_box(box)
|
124
140
|
|
125
141
|
def save_to_json(self):
|
126
|
-
"""Saves
|
127
|
-
|
128
|
-
|
129
|
-
rg_data = [] # regions data
|
130
|
-
for box in self.rg_data:
|
131
|
-
rs_box = [
|
132
|
-
(
|
133
|
-
int(x * self.imgw / self.canvas.winfo_width()), # width scaling
|
134
|
-
int(y * self.imgh / self.canvas.winfo_height()), # height scaling
|
135
|
-
)
|
136
|
-
for x, y in box
|
137
|
-
]
|
138
|
-
rg_data.append({"points": rs_box})
|
142
|
+
"""Saves the selected parking zone points to a JSON file with scaled coordinates."""
|
143
|
+
scale_w, scale_h = self.imgw / self.canvas.winfo_width(), self.imgh / self.canvas.winfo_height()
|
144
|
+
data = [{"points": [(int(x * scale_w), int(y * scale_h)) for x, y in box]} for box in self.rg_data]
|
139
145
|
with open("bounding_boxes.json", "w") as f:
|
140
|
-
json.dump(
|
146
|
+
json.dump(data, f, indent=4)
|
147
|
+
self.messagebox.showinfo("Success", "Bounding boxes saved to bounding_boxes.json")
|
141
148
|
|
142
|
-
messagebox.showinfo("Success", "Bounding boxes saved to bounding_boxes.json")
|
143
149
|
|
150
|
+
class ParkingManagement(BaseSolution):
|
151
|
+
"""
|
152
|
+
Manages parking occupancy and availability using YOLO model for real-time monitoring and visualization.
|
144
153
|
|
145
|
-
class
|
146
|
-
|
154
|
+
This class extends BaseSolution to provide functionality for parking lot management, including detection of
|
155
|
+
occupied spaces, visualization of parking regions, and display of occupancy statistics.
|
147
156
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
"""
|
156
|
-
Initializes the parking management system with a YOLOv8 model and visualization settings.
|
157
|
+
Attributes:
|
158
|
+
json_file (str): Path to the JSON file containing parking region details.
|
159
|
+
json (List[Dict]): Loaded JSON data containing parking region information.
|
160
|
+
pr_info (Dict[str, int]): Dictionary storing parking information (Occupancy and Available spaces).
|
161
|
+
arc (Tuple[int, int, int]): RGB color tuple for available region visualization.
|
162
|
+
occ (Tuple[int, int, int]): RGB color tuple for occupied region visualization.
|
163
|
+
dc (Tuple[int, int, int]): RGB color tuple for centroid visualization of detected objects.
|
157
164
|
|
158
|
-
|
159
|
-
|
160
|
-
json_file (str): file that have all parking slot points data
|
161
|
-
occupied_region_color (tuple): RGB color tuple for occupied regions.
|
162
|
-
available_region_color (tuple): RGB color tuple for available regions.
|
163
|
-
"""
|
164
|
-
# Model initialization
|
165
|
-
from ultralytics import YOLO
|
165
|
+
Methods:
|
166
|
+
process_data: Processes model data for parking lot management and visualization.
|
166
167
|
|
167
|
-
|
168
|
+
Examples:
|
169
|
+
>>> from ultralytics.solutions import ParkingManagement
|
170
|
+
>>> parking_manager = ParkingManagement(model="yolov8n.pt", json_file="parking_regions.json")
|
171
|
+
>>> results = parking_manager(source="parking_lot_video.mp4")
|
172
|
+
>>> print(f"Occupied spaces: {parking_manager.pr_info['Occupancy']}")
|
173
|
+
>>> print(f"Available spaces: {parking_manager.pr_info['Available']}")
|
174
|
+
"""
|
168
175
|
|
169
|
-
|
170
|
-
with
|
171
|
-
|
176
|
+
def __init__(self, **kwargs):
|
177
|
+
"""Initializes the parking management system with a YOLO model and visualization settings."""
|
178
|
+
super().__init__(**kwargs)
|
172
179
|
|
173
|
-
self.
|
180
|
+
self.json_file = self.CFG["json_file"] # Load JSON data
|
181
|
+
if self.json_file is None:
|
182
|
+
LOGGER.warning("❌ json_file argument missing. Parking region details required.")
|
183
|
+
raise ValueError("❌ Json file path can not be empty")
|
174
184
|
|
175
|
-
self.
|
176
|
-
|
185
|
+
with open(self.json_file) as f:
|
186
|
+
self.json = json.load(f)
|
177
187
|
|
178
|
-
self.
|
188
|
+
self.pr_info = {"Occupancy": 0, "Available": 0} # dictionary for parking information
|
179
189
|
|
180
|
-
|
181
|
-
|
182
|
-
|
190
|
+
self.arc = (0, 0, 255) # available region color
|
191
|
+
self.occ = (0, 255, 0) # occupied region color
|
192
|
+
self.dc = (255, 0, 189) # centroid color for each box
|
183
193
|
|
184
|
-
|
185
|
-
im0 (ndarray): inference image
|
194
|
+
def process_data(self, im0):
|
186
195
|
"""
|
187
|
-
|
196
|
+
Processes the model data for parking lot management.
|
188
197
|
|
189
|
-
|
190
|
-
|
198
|
+
This function analyzes the input image, extracts tracks, and determines the occupancy status of parking
|
199
|
+
regions defined in the JSON file. It annotates the image with occupied and available parking spots,
|
200
|
+
and updates the parking information.
|
191
201
|
|
192
|
-
|
193
|
-
|
194
|
-
self.display_frames(im0)
|
195
|
-
return im0
|
202
|
+
Args:
|
203
|
+
im0 (np.ndarray): The input inference image.
|
196
204
|
|
197
|
-
|
198
|
-
|
205
|
+
Examples:
|
206
|
+
>>> parking_manager = ParkingManagement(json_file="parking_regions.json")
|
207
|
+
>>> image = cv2.imread("parking_lot.jpg")
|
208
|
+
>>> parking_manager.process_data(image)
|
209
|
+
"""
|
210
|
+
self.extract_tracks(im0) # extract tracks from im0
|
211
|
+
es, fs = len(self.json), 0 # empty slots, filled slots
|
212
|
+
annotator = Annotator(im0, self.line_width) # init annotator
|
199
213
|
|
200
|
-
for region in self.
|
214
|
+
for region in self.json:
|
201
215
|
# Convert points to a NumPy array with the correct dtype and reshape properly
|
202
216
|
pts_array = np.array(region["points"], dtype=np.int32).reshape((-1, 1, 2))
|
203
217
|
rg_occupied = False # occupied region initialization
|
204
|
-
for box, cls in zip(boxes, clss):
|
205
|
-
xc = int((box[0] + box[2]) / 2)
|
206
|
-
yc = int((box[1] + box[3]) / 2)
|
207
|
-
annotator.display_objects_labels(
|
208
|
-
im0, self.model.names[int(cls)], (104, 31, 17), (255, 255, 255), xc, yc, 10
|
209
|
-
)
|
218
|
+
for box, cls in zip(self.boxes, self.clss):
|
219
|
+
xc, yc = int((box[0] + box[2]) / 2), int((box[1] + box[3]) / 2)
|
210
220
|
dist = cv2.pointPolygonTest(pts_array, (xc, yc), False)
|
211
221
|
if dist >= 0:
|
222
|
+
# cv2.circle(im0, (xc, yc), radius=self.line_width * 4, color=self.dc, thickness=-1)
|
223
|
+
annotator.display_objects_labels(
|
224
|
+
im0, self.model.names[int(cls)], (104, 31, 17), (255, 255, 255), xc, yc, 10
|
225
|
+
)
|
212
226
|
rg_occupied = True
|
213
227
|
break
|
214
|
-
if rg_occupied
|
215
|
-
fs += 1
|
216
|
-
es -= 1
|
217
|
-
|
228
|
+
fs, es = (fs + 1, es - 1) if rg_occupied else (fs, es)
|
218
229
|
# Plotting regions
|
219
|
-
|
220
|
-
cv2.polylines(im0, [pts_array], isClosed=True, color=color, thickness=2)
|
230
|
+
cv2.polylines(im0, [pts_array], isClosed=True, color=self.occ if rg_occupied else self.arc, thickness=2)
|
221
231
|
|
222
|
-
self.pr_info["Occupancy"] = fs
|
223
|
-
self.pr_info["Available"] = es
|
232
|
+
self.pr_info["Occupancy"], self.pr_info["Available"] = fs, es
|
224
233
|
|
225
234
|
annotator.display_analytics(im0, self.pr_info, (104, 31, 17), (255, 255, 255), 10)
|
226
|
-
|
227
|
-
|
228
|
-
return im0
|
229
|
-
|
230
|
-
def display_frames(self, im0):
|
231
|
-
"""
|
232
|
-
Display frame.
|
233
|
-
|
234
|
-
Args:
|
235
|
-
im0 (ndarray): inference image
|
236
|
-
"""
|
237
|
-
if self.env_check:
|
238
|
-
cv2.imshow("Ultralytics Parking Manager", im0)
|
239
|
-
# Break Window
|
240
|
-
if cv2.waitKey(1) & 0xFF == ord("q"):
|
241
|
-
return
|
235
|
+
self.display_output(im0) # display output with base class function
|
236
|
+
return im0 # return output image for more usage
|