ultralytics 8.3.87__py3-none-any.whl → 8.3.88__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 +34 -45
- ultralytics/__init__.py +1 -1
- ultralytics/cfg/__init__.py +46 -39
- ultralytics/data/augment.py +2 -2
- ultralytics/solutions/__init__.py +14 -6
- ultralytics/solutions/ai_gym.py +39 -28
- ultralytics/solutions/analytics.py +22 -18
- ultralytics/solutions/distance_calculation.py +25 -25
- ultralytics/solutions/heatmap.py +40 -38
- ultralytics/solutions/instance_segmentation.py +69 -0
- ultralytics/solutions/object_blurrer.py +89 -0
- ultralytics/solutions/object_counter.py +35 -33
- ultralytics/solutions/object_cropper.py +84 -0
- ultralytics/solutions/parking_management.py +21 -9
- ultralytics/solutions/queue_management.py +20 -39
- ultralytics/solutions/region_counter.py +54 -51
- ultralytics/solutions/security_alarm.py +40 -30
- ultralytics/solutions/solutions.py +594 -16
- ultralytics/solutions/speed_estimation.py +34 -31
- ultralytics/solutions/streamlit_inference.py +34 -28
- ultralytics/solutions/trackzone.py +29 -18
- ultralytics/solutions/vision_eye.py +69 -0
- ultralytics/trackers/utils/kalman_filter.py +23 -23
- ultralytics/utils/instance.py +3 -3
- ultralytics/utils/plotting.py +0 -414
- {ultralytics-8.3.87.dist-info → ultralytics-8.3.88.dist-info}/METADATA +1 -1
- {ultralytics-8.3.87.dist-info → ultralytics-8.3.88.dist-info}/RECORD +31 -27
- {ultralytics-8.3.87.dist-info → ultralytics-8.3.88.dist-info}/LICENSE +0 -0
- {ultralytics-8.3.87.dist-info → ultralytics-8.3.88.dist-info}/WHEEL +0 -0
- {ultralytics-8.3.87.dist-info → ultralytics-8.3.88.dist-info}/entry_points.txt +0 -0
- {ultralytics-8.3.87.dist-info → ultralytics-8.3.88.dist-info}/top_level.txt +0 -0
@@ -4,8 +4,8 @@ import math
|
|
4
4
|
|
5
5
|
import cv2
|
6
6
|
|
7
|
-
from ultralytics.solutions.solutions import BaseSolution
|
8
|
-
from ultralytics.utils.plotting import
|
7
|
+
from ultralytics.solutions.solutions import BaseSolution, SolutionAnnotator, SolutionResults
|
8
|
+
from ultralytics.utils.plotting import colors
|
9
9
|
|
10
10
|
|
11
11
|
class DistanceCalculation(BaseSolution):
|
@@ -18,22 +18,17 @@ class DistanceCalculation(BaseSolution):
|
|
18
18
|
Attributes:
|
19
19
|
left_mouse_count (int): Counter for left mouse button clicks.
|
20
20
|
selected_boxes (Dict[int, List[float]]): Dictionary to store selected bounding boxes and their track IDs.
|
21
|
-
annotator (Annotator): An instance of the Annotator class for drawing on the image.
|
22
|
-
boxes (List[List[float]]): List of bounding boxes for detected objects.
|
23
|
-
track_ids (List[int]): List of track IDs for detected objects.
|
24
|
-
clss (List[int]): List of class indices for detected objects.
|
25
|
-
names (List[str]): List of class names that the model can detect.
|
26
21
|
centroids (List[List[int]]): List to store centroids of selected bounding boxes.
|
27
22
|
|
28
23
|
Methods:
|
29
24
|
mouse_event_for_distance: Handles mouse events for selecting objects in the video stream.
|
30
|
-
|
25
|
+
process: Processes video frames and calculates the distance between selected objects.
|
31
26
|
|
32
27
|
Examples:
|
33
28
|
>>> distance_calc = DistanceCalculation()
|
34
29
|
>>> frame = cv2.imread("frame.jpg")
|
35
|
-
>>>
|
36
|
-
>>> cv2.imshow("Distance Calculation",
|
30
|
+
>>> results = distance_calc.process(frame)
|
31
|
+
>>> cv2.imshow("Distance Calculation", results.plot_im)
|
37
32
|
>>> cv2.waitKey(0)
|
38
33
|
"""
|
39
34
|
|
@@ -44,8 +39,7 @@ class DistanceCalculation(BaseSolution):
|
|
44
39
|
# Mouse event information
|
45
40
|
self.left_mouse_count = 0
|
46
41
|
self.selected_boxes = {}
|
47
|
-
|
48
|
-
self.centroids = [] # Initialize empty list to store centroids
|
42
|
+
self.centroids = [] # Store centroids of selected objects
|
49
43
|
|
50
44
|
def mouse_event_for_distance(self, event, x, y, flags, param):
|
51
45
|
"""
|
@@ -56,7 +50,7 @@ class DistanceCalculation(BaseSolution):
|
|
56
50
|
x (int): X-coordinate of the mouse pointer.
|
57
51
|
y (int): Y-coordinate of the mouse pointer.
|
58
52
|
flags (int): Flags associated with the event (e.g., cv2.EVENT_FLAG_CTRLKEY, cv2.EVENT_FLAG_SHIFTKEY).
|
59
|
-
param (
|
53
|
+
param (Any): Additional parameters passed to the function.
|
60
54
|
|
61
55
|
Examples:
|
62
56
|
>>> # Assuming 'dc' is an instance of DistanceCalculation
|
@@ -73,7 +67,7 @@ class DistanceCalculation(BaseSolution):
|
|
73
67
|
self.selected_boxes = {}
|
74
68
|
self.left_mouse_count = 0
|
75
69
|
|
76
|
-
def
|
70
|
+
def process(self, im0):
|
77
71
|
"""
|
78
72
|
Processes a video frame and calculates the distance between two selected bounding boxes.
|
79
73
|
|
@@ -84,41 +78,47 @@ class DistanceCalculation(BaseSolution):
|
|
84
78
|
im0 (numpy.ndarray): The input image frame to process.
|
85
79
|
|
86
80
|
Returns:
|
87
|
-
(
|
81
|
+
(SolutionResults): Contains processed image `plot_im`, `total_tracks` (int) representing the total number
|
82
|
+
of tracked objects, and `pixels_distance` (float) representing the distance between selected objects
|
83
|
+
in pixels.
|
88
84
|
|
89
85
|
Examples:
|
90
86
|
>>> import numpy as np
|
91
87
|
>>> from ultralytics.solutions import DistanceCalculation
|
92
88
|
>>> dc = DistanceCalculation()
|
93
89
|
>>> frame = np.random.randint(0, 255, (480, 640, 3), dtype=np.uint8)
|
94
|
-
>>>
|
90
|
+
>>> results = dc.process(frame)
|
91
|
+
>>> print(f"Distance: {results.pixels_distance:.2f} pixels")
|
95
92
|
"""
|
96
|
-
self.annotator = Annotator(im0, line_width=self.line_width) # Initialize annotator
|
97
93
|
self.extract_tracks(im0) # Extract tracks
|
94
|
+
annotator = SolutionAnnotator(im0, line_width=self.line_width) # Initialize annotator
|
98
95
|
|
96
|
+
pixels_distance = 0
|
99
97
|
# Iterate over bounding boxes, track ids and classes index
|
100
98
|
for box, track_id, cls in zip(self.boxes, self.track_ids, self.clss):
|
101
|
-
|
99
|
+
annotator.box_label(box, color=colors(int(cls), True), label=self.names[int(cls)])
|
102
100
|
|
101
|
+
# Update selected boxes if they're being tracked
|
103
102
|
if len(self.selected_boxes) == 2:
|
104
103
|
for trk_id in self.selected_boxes.keys():
|
105
104
|
if trk_id == track_id:
|
106
105
|
self.selected_boxes[track_id] = box
|
107
106
|
|
108
107
|
if len(self.selected_boxes) == 2:
|
109
|
-
#
|
108
|
+
# Calculate centroids of selected boxes
|
110
109
|
self.centroids.extend(
|
111
110
|
[[int((box[0] + box[2]) // 2), int((box[1] + box[3]) // 2)] for box in self.selected_boxes.values()]
|
112
111
|
)
|
113
|
-
# Calculate
|
112
|
+
# Calculate Euclidean distance between centroids
|
114
113
|
pixels_distance = math.sqrt(
|
115
114
|
(self.centroids[0][0] - self.centroids[1][0]) ** 2 + (self.centroids[0][1] - self.centroids[1][1]) ** 2
|
116
115
|
)
|
117
|
-
|
118
|
-
|
119
|
-
self.centroids = []
|
116
|
+
annotator.plot_distance_and_line(pixels_distance, self.centroids)
|
120
117
|
|
121
|
-
self.
|
118
|
+
self.centroids = [] # Reset centroids for next frame
|
119
|
+
plot_im = annotator.result()
|
120
|
+
self.display_output(plot_im) # Display output with base class function
|
122
121
|
cv2.setMouseCallback("Ultralytics Solutions", self.mouse_event_for_distance)
|
123
122
|
|
124
|
-
|
123
|
+
# Return SolutionResults with processed image and calculated metrics
|
124
|
+
return SolutionResults(plot_im=plot_im, pixels_distance=pixels_distance, total_tracks=len(self.track_ids))
|
ultralytics/solutions/heatmap.py
CHANGED
@@ -4,7 +4,7 @@ import cv2
|
|
4
4
|
import numpy as np
|
5
5
|
|
6
6
|
from ultralytics.solutions.object_counter import ObjectCounter
|
7
|
-
from ultralytics.
|
7
|
+
from ultralytics.solutions.solutions import SolutionAnnotator, SolutionResults
|
8
8
|
|
9
9
|
|
10
10
|
class Heatmap(ObjectCounter):
|
@@ -18,28 +18,33 @@ class Heatmap(ObjectCounter):
|
|
18
18
|
initialized (bool): Flag indicating whether the heatmap has been initialized.
|
19
19
|
colormap (int): OpenCV colormap used for heatmap visualization.
|
20
20
|
heatmap (np.ndarray): Array storing the cumulative heatmap data.
|
21
|
-
annotator (
|
21
|
+
annotator (SolutionAnnotator): Object for drawing annotations on the image.
|
22
22
|
|
23
23
|
Methods:
|
24
24
|
heatmap_effect: Calculates and updates the heatmap effect for a given bounding box.
|
25
|
-
|
25
|
+
process: Generates and applies the heatmap effect to each frame.
|
26
26
|
|
27
27
|
Examples:
|
28
28
|
>>> from ultralytics.solutions import Heatmap
|
29
29
|
>>> heatmap = Heatmap(model="yolo11n.pt", colormap=cv2.COLORMAP_JET)
|
30
30
|
>>> frame = cv2.imread("frame.jpg")
|
31
|
-
>>> processed_frame = heatmap.
|
31
|
+
>>> processed_frame = heatmap.process(frame)
|
32
32
|
"""
|
33
33
|
|
34
34
|
def __init__(self, **kwargs):
|
35
|
-
"""
|
35
|
+
"""
|
36
|
+
Initializes the Heatmap class for real-time video stream heatmap generation based on object tracks.
|
37
|
+
|
38
|
+
Args:
|
39
|
+
**kwargs (Any): Keyword arguments passed to the parent ObjectCounter class.
|
40
|
+
"""
|
36
41
|
super().__init__(**kwargs)
|
37
42
|
|
38
|
-
self.initialized = False #
|
39
|
-
if self.region is not None: #
|
43
|
+
self.initialized = False # Flag for heatmap initialization
|
44
|
+
if self.region is not None: # Check if user provided the region coordinates
|
40
45
|
self.initialize_region()
|
41
46
|
|
42
|
-
#
|
47
|
+
# Store colormap
|
43
48
|
self.colormap = cv2.COLORMAP_PARULA if self.CFG["colormap"] is None else self.CFG["colormap"]
|
44
49
|
self.heatmap = None
|
45
50
|
|
@@ -49,11 +54,6 @@ class Heatmap(ObjectCounter):
|
|
49
54
|
|
50
55
|
Args:
|
51
56
|
box (List[float]): Bounding box coordinates [x0, y0, x1, y1].
|
52
|
-
|
53
|
-
Examples:
|
54
|
-
>>> heatmap = Heatmap()
|
55
|
-
>>> box = [100, 100, 200, 200]
|
56
|
-
>>> heatmap.heatmap_effect(box)
|
57
57
|
"""
|
58
58
|
x0, y0, x1, y1 = map(int, box)
|
59
59
|
radius_squared = (min(x1 - x0, y1 - y0) // 2) ** 2
|
@@ -70,7 +70,7 @@ class Heatmap(ObjectCounter):
|
|
70
70
|
# Update only the values within the bounding box in a single vectorized operation
|
71
71
|
self.heatmap[y0:y1, x0:x1][within_radius] += 2
|
72
72
|
|
73
|
-
def
|
73
|
+
def process(self, im0):
|
74
74
|
"""
|
75
75
|
Generate heatmap for each frame using Ultralytics.
|
76
76
|
|
@@ -78,50 +78,52 @@ class Heatmap(ObjectCounter):
|
|
78
78
|
im0 (np.ndarray): Input image array for processing.
|
79
79
|
|
80
80
|
Returns:
|
81
|
-
(
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
>>> result = heatmap.generate_heatmap(im0)
|
81
|
+
(SolutionResults): Contains processed image `plot_im`,
|
82
|
+
'in_count' (int, count of objects entering the region),
|
83
|
+
'out_count' (int, count of objects exiting the region),
|
84
|
+
'classwise_count' (dict, per-class object count), and
|
85
|
+
'total_tracks' (int, total number of tracked objects).
|
87
86
|
"""
|
88
87
|
if not self.initialized:
|
89
88
|
self.heatmap = np.zeros_like(im0, dtype=np.float32) * 0.99
|
90
|
-
|
89
|
+
self.initialized = True # Initialize heatmap only once
|
91
90
|
|
92
|
-
self.annotator = Annotator(im0, line_width=self.line_width) # Initialize annotator
|
93
91
|
self.extract_tracks(im0) # Extract tracks
|
92
|
+
self.annotator = SolutionAnnotator(im0, line_width=self.line_width) # Initialize annotator
|
94
93
|
|
95
94
|
# Iterate over bounding boxes, track ids and classes index
|
96
95
|
for box, track_id, cls in zip(self.boxes, self.track_ids, self.clss):
|
97
|
-
#
|
96
|
+
# Apply heatmap effect for the bounding box
|
98
97
|
self.heatmap_effect(box)
|
99
98
|
|
100
99
|
if self.region is not None:
|
101
100
|
self.annotator.draw_region(reg_pts=self.region, color=(104, 0, 123), thickness=self.line_width * 2)
|
102
101
|
self.store_tracking_history(track_id, box) # Store track history
|
103
|
-
self.store_classwise_counts(cls) #
|
102
|
+
self.store_classwise_counts(cls) # Store classwise counts in dict
|
104
103
|
current_centroid = ((box[0] + box[2]) / 2, (box[1] + box[3]) / 2)
|
105
|
-
#
|
104
|
+
# Get previous position if available
|
106
105
|
prev_position = None
|
107
106
|
if len(self.track_history[track_id]) > 1:
|
108
107
|
prev_position = self.track_history[track_id][-2]
|
109
108
|
self.count_objects(current_centroid, track_id, prev_position, cls) # Perform object counting
|
110
109
|
|
110
|
+
plot_im = self.annotator.result()
|
111
111
|
if self.region is not None:
|
112
|
-
self.display_counts(
|
112
|
+
self.display_counts(plot_im) # Display the counts on the frame
|
113
113
|
|
114
114
|
# Normalize, apply colormap to heatmap and combine with original image
|
115
115
|
if self.track_data.id is not None:
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
116
|
+
normalized_heatmap = cv2.normalize(self.heatmap, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
|
117
|
+
colored_heatmap = cv2.applyColorMap(normalized_heatmap, self.colormap)
|
118
|
+
plot_im = cv2.addWeighted(plot_im, 0.5, colored_heatmap, 0.5, 0)
|
119
|
+
|
120
|
+
self.display_output(plot_im) # Display output with base class function
|
121
|
+
|
122
|
+
# Return SolutionResults
|
123
|
+
return SolutionResults(
|
124
|
+
plot_im=plot_im,
|
125
|
+
in_count=self.in_count,
|
126
|
+
out_count=self.out_count,
|
127
|
+
classwise_count=self.classwise_counts,
|
128
|
+
total_tracks=len(self.track_ids),
|
129
|
+
)
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# Ultralytics YOLO 🚀, AGPL-3.0 license
|
2
|
+
|
3
|
+
from ultralytics.solutions.solutions import BaseSolution, SolutionAnnotator, SolutionResults
|
4
|
+
from ultralytics.utils.plotting import colors
|
5
|
+
|
6
|
+
|
7
|
+
class InstanceSegmentation(BaseSolution):
|
8
|
+
"""
|
9
|
+
A class to manage instance segmentation in images or video streams.
|
10
|
+
|
11
|
+
This class extends the BaseSolution class and provides functionality for performing instance segmentation, including
|
12
|
+
drawing segmented masks with bounding boxes and labels.
|
13
|
+
|
14
|
+
Attributes:
|
15
|
+
model (str): The segmentation model to use for inference.
|
16
|
+
|
17
|
+
Methods:
|
18
|
+
process: Processes the input image to perform instance segmentation and annotate results.
|
19
|
+
|
20
|
+
Examples:
|
21
|
+
>>> segmenter = InstanceSegmentation()
|
22
|
+
>>> frame = cv2.imread("frame.jpg")
|
23
|
+
>>> results = segmenter.segment(frame)
|
24
|
+
>>> print(f"Total segmented instances: {results['total_tracks']}")
|
25
|
+
"""
|
26
|
+
|
27
|
+
def __init__(self, **kwargs):
|
28
|
+
"""
|
29
|
+
Initializes the InstanceSegmentation class for detecting and annotating segmented instances.
|
30
|
+
|
31
|
+
Args:
|
32
|
+
**kwargs (Any): Keyword arguments passed to the BaseSolution parent class.
|
33
|
+
model (str): Model name or path, defaults to "yolo11n-seg.pt".
|
34
|
+
"""
|
35
|
+
kwargs["model"] = kwargs.get("model", "yolo11n-seg.pt")
|
36
|
+
super().__init__(**kwargs)
|
37
|
+
|
38
|
+
def process(self, im0):
|
39
|
+
"""
|
40
|
+
Performs instance segmentation on the input image and annotates the results.
|
41
|
+
|
42
|
+
Args:
|
43
|
+
im0 (numpy.ndarray): The input image for segmentation.
|
44
|
+
|
45
|
+
Returns:
|
46
|
+
(SolutionResults): Object containing the annotated image and total number of tracked instances.
|
47
|
+
|
48
|
+
Examples:
|
49
|
+
>>> segmenter = InstanceSegmentation()
|
50
|
+
>>> frame = cv2.imread("image.jpg")
|
51
|
+
>>> summary = segmenter.segment(frame)
|
52
|
+
>>> print(summary)
|
53
|
+
"""
|
54
|
+
self.extract_tracks(im0) # Extract tracks (bounding boxes, classes, and masks)
|
55
|
+
annotator = SolutionAnnotator(im0, self.line_width)
|
56
|
+
|
57
|
+
# Iterate over detected classes, track IDs, and segmentation masks
|
58
|
+
if self.masks is None:
|
59
|
+
self.LOGGER.warning("⚠️ No masks detected! Ensure you're using a supported Ultralytics segmentation model.")
|
60
|
+
else:
|
61
|
+
for cls, t_id, mask in zip(self.clss, self.track_ids, self.masks):
|
62
|
+
# Annotate the image with segmentation mask, mask color, and label
|
63
|
+
annotator.segmentation_mask(mask=mask, mask_color=colors(t_id, True), label=self.names[cls])
|
64
|
+
|
65
|
+
plot_im = annotator.result()
|
66
|
+
self.display_output(plot_im) # Display the annotated output using the base class function
|
67
|
+
|
68
|
+
# Return SolutionResults
|
69
|
+
return SolutionResults(plot_im=plot_im, total_tracks=len(self.track_ids))
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
2
|
+
|
3
|
+
|
4
|
+
import cv2
|
5
|
+
|
6
|
+
from ultralytics.solutions.solutions import BaseSolution, SolutionAnnotator, SolutionResults
|
7
|
+
from ultralytics.utils import LOGGER
|
8
|
+
from ultralytics.utils.plotting import colors
|
9
|
+
|
10
|
+
|
11
|
+
class ObjectBlurrer(BaseSolution):
|
12
|
+
"""
|
13
|
+
A class to manage the blurring of detected objects in a real-time video stream.
|
14
|
+
|
15
|
+
This class extends the BaseSolution class and provides functionality for blurring objects based on detected bounding
|
16
|
+
boxes. The blurred areas are updated directly in the input image, allowing for privacy preservation or other effects.
|
17
|
+
|
18
|
+
Attributes:
|
19
|
+
blur_ratio (int): The intensity of the blur effect applied to detected objects (higher values create more blur).
|
20
|
+
iou (float): Intersection over Union threshold for object detection.
|
21
|
+
conf (float): Confidence threshold for object detection.
|
22
|
+
|
23
|
+
Methods:
|
24
|
+
process: Applies a blurring effect to detected objects in the input image.
|
25
|
+
extract_tracks: Extracts tracking information from detected objects.
|
26
|
+
display_output: Displays the processed output image.
|
27
|
+
|
28
|
+
Examples:
|
29
|
+
>>> blurrer = ObjectBlurrer()
|
30
|
+
>>> frame = cv2.imread("frame.jpg")
|
31
|
+
>>> processed_results = blurrer.process(frame)
|
32
|
+
>>> print(f"Total blurred objects: {processed_results.total_tracks}")
|
33
|
+
"""
|
34
|
+
|
35
|
+
def __init__(self, **kwargs):
|
36
|
+
"""
|
37
|
+
Initializes the ObjectBlurrer class for applying a blur effect to objects detected in video streams or images.
|
38
|
+
|
39
|
+
Args:
|
40
|
+
**kwargs (Any): Keyword arguments passed to the parent class and for configuration.
|
41
|
+
blur_ratio (float): Intensity of the blur effect (0.1-1.0, default=0.5).
|
42
|
+
"""
|
43
|
+
super().__init__(**kwargs)
|
44
|
+
blur_ratio = kwargs.get("blur_ratio", 0.5)
|
45
|
+
if blur_ratio < 0.1:
|
46
|
+
LOGGER.warning("⚠️ blur ratio cannot be less than 0.1, updating it to default value 0.5")
|
47
|
+
blur_ratio = 0.5
|
48
|
+
self.blur_ratio = int(blur_ratio * 100)
|
49
|
+
|
50
|
+
def process(self, im0):
|
51
|
+
"""
|
52
|
+
Applies a blurring effect to detected objects in the input image.
|
53
|
+
|
54
|
+
This method extracts tracking information, applies blur to regions corresponding to detected objects,
|
55
|
+
and annotates the image with bounding boxes.
|
56
|
+
|
57
|
+
Args:
|
58
|
+
im0 (numpy.ndarray): The input image containing detected objects.
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
(SolutionResults): Object containing the processed image and number of tracked objects.
|
62
|
+
- plot_im (numpy.ndarray): The annotated output image with blurred objects.
|
63
|
+
- total_tracks (int): The total number of tracked objects in the frame.
|
64
|
+
|
65
|
+
Examples:
|
66
|
+
>>> blurrer = ObjectBlurrer()
|
67
|
+
>>> frame = cv2.imread("image.jpg")
|
68
|
+
>>> results = blurrer.process(frame)
|
69
|
+
>>> print(f"Blurred {results.total_tracks} objects")
|
70
|
+
"""
|
71
|
+
self.extract_tracks(im0) # Extract tracks
|
72
|
+
annotator = SolutionAnnotator(im0, self.line_width)
|
73
|
+
|
74
|
+
# Iterate over bounding boxes and classes
|
75
|
+
for box, cls in zip(self.boxes, self.clss):
|
76
|
+
# Crop and blur the detected object
|
77
|
+
blur_obj = cv2.blur(
|
78
|
+
im0[int(box[1]) : int(box[3]), int(box[0]) : int(box[2])],
|
79
|
+
(self.blur_ratio, self.blur_ratio),
|
80
|
+
)
|
81
|
+
# Update the blurred area in the original image
|
82
|
+
im0[int(box[1]) : int(box[3]), int(box[0]) : int(box[2])] = blur_obj
|
83
|
+
annotator.box_label(box, label=self.names[cls], color=colors(cls, True)) # Annotate bounding box
|
84
|
+
|
85
|
+
plot_im = annotator.result()
|
86
|
+
self.display_output(plot_im) # Display the output using the base class function
|
87
|
+
|
88
|
+
# Return a SolutionResults
|
89
|
+
return SolutionResults(plot_im=plot_im, total_tracks=len(self.track_ids))
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
2
2
|
|
3
|
-
from ultralytics.solutions.solutions import BaseSolution
|
4
|
-
from ultralytics.utils.plotting import
|
3
|
+
from ultralytics.solutions.solutions import BaseSolution, SolutionAnnotator, SolutionResults
|
4
|
+
from ultralytics.utils.plotting import colors
|
5
5
|
|
6
6
|
|
7
7
|
class ObjectCounter(BaseSolution):
|
@@ -24,12 +24,12 @@ class ObjectCounter(BaseSolution):
|
|
24
24
|
count_objects: Counts objects within a polygonal or linear region.
|
25
25
|
store_classwise_counts: Initializes class-wise counts if not already present.
|
26
26
|
display_counts: Displays object counts on the frame.
|
27
|
-
|
27
|
+
process: Processes input data (frames or object tracks) and updates counts.
|
28
28
|
|
29
29
|
Examples:
|
30
30
|
>>> counter = ObjectCounter()
|
31
31
|
>>> frame = cv2.imread("frame.jpg")
|
32
|
-
>>>
|
32
|
+
>>> results = counter.process(frame)
|
33
33
|
>>> print(f"Inward count: {counter.in_count}, Outward count: {counter.out_count}")
|
34
34
|
"""
|
35
35
|
|
@@ -41,7 +41,7 @@ class ObjectCounter(BaseSolution):
|
|
41
41
|
self.out_count = 0 # Counter for objects moving outward
|
42
42
|
self.counted_ids = [] # List of IDs of objects that have been counted
|
43
43
|
self.classwise_counts = {} # Dictionary for counts, categorized by object class
|
44
|
-
self.region_initialized = False #
|
44
|
+
self.region_initialized = False # Flag indicating whether the region has been initialized
|
45
45
|
|
46
46
|
self.show_in = self.CFG["show_in"]
|
47
47
|
self.show_out = self.CFG["show_out"]
|
@@ -51,7 +51,7 @@ class ObjectCounter(BaseSolution):
|
|
51
51
|
Counts objects within a polygonal or linear region based on their tracks.
|
52
52
|
|
53
53
|
Args:
|
54
|
-
current_centroid (Tuple[float, float]): Current centroid
|
54
|
+
current_centroid (Tuple[float, float]): Current centroid coordinates (x, y) in the current frame.
|
55
55
|
track_id (int): Unique identifier for the tracked object.
|
56
56
|
prev_position (Tuple[float, float]): Last frame position coordinates (x, y) of the track.
|
57
57
|
cls (int): Class index for classwise count updates.
|
@@ -60,10 +60,10 @@ class ObjectCounter(BaseSolution):
|
|
60
60
|
>>> counter = ObjectCounter()
|
61
61
|
>>> track_line = {1: [100, 200], 2: [110, 210], 3: [120, 220]}
|
62
62
|
>>> box = [130, 230, 150, 250]
|
63
|
-
>>>
|
64
|
-
>>>
|
65
|
-
>>>
|
66
|
-
>>> counter.count_objects(
|
63
|
+
>>> track_id_num = 1
|
64
|
+
>>> previous_position = (120, 220)
|
65
|
+
>>> class_to_count = 0 # In COCO model, class 0 = person
|
66
|
+
>>> counter.count_objects((140, 240), track_id_num, previous_position, class_to_count)
|
67
67
|
"""
|
68
68
|
if prev_position is None or track_id in self.counted_ids:
|
69
69
|
return
|
@@ -101,10 +101,10 @@ class ObjectCounter(BaseSolution):
|
|
101
101
|
and current_centroid[0] > prev_position[0]
|
102
102
|
or region_width >= region_height
|
103
103
|
and current_centroid[1] > prev_position[1]
|
104
|
-
): # Moving right
|
104
|
+
): # Moving right or downward
|
105
105
|
self.in_count += 1
|
106
106
|
self.classwise_counts[self.names[cls]]["IN"] += 1
|
107
|
-
else: # Moving left
|
107
|
+
else: # Moving left or upward
|
108
108
|
self.out_count += 1
|
109
109
|
self.classwise_counts[self.names[cls]]["OUT"] += 1
|
110
110
|
self.counted_ids.append(track_id)
|
@@ -116,9 +116,6 @@ class ObjectCounter(BaseSolution):
|
|
116
116
|
Args:
|
117
117
|
cls (int): Class index for classwise count updates.
|
118
118
|
|
119
|
-
This method ensures that the 'classwise_counts' dictionary contains an entry for the specified class,
|
120
|
-
initializing 'IN' and 'OUT' counts to zero if the class is not already present.
|
121
|
-
|
122
119
|
Examples:
|
123
120
|
>>> counter = ObjectCounter()
|
124
121
|
>>> counter.store_classwise_counts(0) # Initialize counts for class index 0
|
@@ -128,12 +125,12 @@ class ObjectCounter(BaseSolution):
|
|
128
125
|
if self.names[cls] not in self.classwise_counts:
|
129
126
|
self.classwise_counts[self.names[cls]] = {"IN": 0, "OUT": 0}
|
130
127
|
|
131
|
-
def display_counts(self,
|
128
|
+
def display_counts(self, plot_im):
|
132
129
|
"""
|
133
130
|
Displays object counts on the input image or frame.
|
134
131
|
|
135
132
|
Args:
|
136
|
-
|
133
|
+
plot_im (numpy.ndarray): The image or frame to display counts on.
|
137
134
|
|
138
135
|
Examples:
|
139
136
|
>>> counter = ObjectCounter()
|
@@ -146,11 +143,10 @@ class ObjectCounter(BaseSolution):
|
|
146
143
|
for key, value in self.classwise_counts.items()
|
147
144
|
if value["IN"] != 0 or value["OUT"] != 0
|
148
145
|
}
|
149
|
-
|
150
146
|
if labels_dict:
|
151
|
-
self.annotator.display_analytics(
|
147
|
+
self.annotator.display_analytics(plot_im, labels_dict, (104, 31, 17), (255, 255, 255), 10)
|
152
148
|
|
153
|
-
def
|
149
|
+
def process(self, im0):
|
154
150
|
"""
|
155
151
|
Processes input data (frames or object tracks) and updates object counts.
|
156
152
|
|
@@ -161,19 +157,21 @@ class ObjectCounter(BaseSolution):
|
|
161
157
|
im0 (numpy.ndarray): The input image or frame to be processed.
|
162
158
|
|
163
159
|
Returns:
|
164
|
-
(
|
160
|
+
(SolutionResults): Contains processed image `im0`, 'in_count' (int, count of objects entering the region),
|
161
|
+
'out_count' (int, count of objects exiting the region), 'classwise_count' (Dict, per-class object count),
|
162
|
+
and 'total_tracks' (int, total number of tracked objects).
|
165
163
|
|
166
164
|
Examples:
|
167
165
|
>>> counter = ObjectCounter()
|
168
166
|
>>> frame = cv2.imread("path/to/image.jpg")
|
169
|
-
>>>
|
167
|
+
>>> results = counter.process(frame)
|
170
168
|
"""
|
171
169
|
if not self.region_initialized:
|
172
170
|
self.initialize_region()
|
173
171
|
self.region_initialized = True
|
174
172
|
|
175
|
-
self.annotator = Annotator(im0, line_width=self.line_width) # Initialize annotator
|
176
173
|
self.extract_tracks(im0) # Extract tracks
|
174
|
+
self.annotator = SolutionAnnotator(im0, line_width=self.line_width) # Initialize annotator
|
177
175
|
|
178
176
|
self.annotator.draw_region(
|
179
177
|
reg_pts=self.region, color=(104, 0, 123), thickness=self.line_width * 2
|
@@ -184,20 +182,24 @@ class ObjectCounter(BaseSolution):
|
|
184
182
|
# Draw bounding box and counting region
|
185
183
|
self.annotator.box_label(box, label=self.names[cls], color=colors(cls, True))
|
186
184
|
self.store_tracking_history(track_id, box) # Store track history
|
187
|
-
self.store_classwise_counts(cls) #
|
185
|
+
self.store_classwise_counts(cls) # Store classwise counts in dict
|
188
186
|
|
189
|
-
# Draw tracks of objects
|
190
|
-
self.annotator.draw_centroid_and_tracks(
|
191
|
-
self.track_line, color=colors(int(cls), True), track_thickness=self.line_width
|
192
|
-
)
|
193
187
|
current_centroid = ((box[0] + box[2]) / 2, (box[1] + box[3]) / 2)
|
194
|
-
#
|
188
|
+
# Store previous position of track for object counting
|
195
189
|
prev_position = None
|
196
190
|
if len(self.track_history[track_id]) > 1:
|
197
191
|
prev_position = self.track_history[track_id][-2]
|
198
192
|
self.count_objects(current_centroid, track_id, prev_position, cls) # Perform object counting
|
199
193
|
|
200
|
-
self.
|
201
|
-
self.
|
202
|
-
|
203
|
-
|
194
|
+
plot_im = self.annotator.result()
|
195
|
+
self.display_counts(plot_im) # Display the counts on the frame
|
196
|
+
self.display_output(plot_im) # Display output with base class function
|
197
|
+
|
198
|
+
# Return SolutionResults
|
199
|
+
return SolutionResults(
|
200
|
+
plot_im=plot_im,
|
201
|
+
in_count=self.in_count,
|
202
|
+
out_count=self.out_count,
|
203
|
+
classwise_count=self.classwise_counts,
|
204
|
+
total_tracks=len(self.track_ids),
|
205
|
+
)
|