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.
Files changed (31) hide show
  1. tests/test_solutions.py +34 -45
  2. ultralytics/__init__.py +1 -1
  3. ultralytics/cfg/__init__.py +46 -39
  4. ultralytics/data/augment.py +2 -2
  5. ultralytics/solutions/__init__.py +14 -6
  6. ultralytics/solutions/ai_gym.py +39 -28
  7. ultralytics/solutions/analytics.py +22 -18
  8. ultralytics/solutions/distance_calculation.py +25 -25
  9. ultralytics/solutions/heatmap.py +40 -38
  10. ultralytics/solutions/instance_segmentation.py +69 -0
  11. ultralytics/solutions/object_blurrer.py +89 -0
  12. ultralytics/solutions/object_counter.py +35 -33
  13. ultralytics/solutions/object_cropper.py +84 -0
  14. ultralytics/solutions/parking_management.py +21 -9
  15. ultralytics/solutions/queue_management.py +20 -39
  16. ultralytics/solutions/region_counter.py +54 -51
  17. ultralytics/solutions/security_alarm.py +40 -30
  18. ultralytics/solutions/solutions.py +594 -16
  19. ultralytics/solutions/speed_estimation.py +34 -31
  20. ultralytics/solutions/streamlit_inference.py +34 -28
  21. ultralytics/solutions/trackzone.py +29 -18
  22. ultralytics/solutions/vision_eye.py +69 -0
  23. ultralytics/trackers/utils/kalman_filter.py +23 -23
  24. ultralytics/utils/instance.py +3 -3
  25. ultralytics/utils/plotting.py +0 -414
  26. {ultralytics-8.3.87.dist-info → ultralytics-8.3.88.dist-info}/METADATA +1 -1
  27. {ultralytics-8.3.87.dist-info → ultralytics-8.3.88.dist-info}/RECORD +31 -27
  28. {ultralytics-8.3.87.dist-info → ultralytics-8.3.88.dist-info}/LICENSE +0 -0
  29. {ultralytics-8.3.87.dist-info → ultralytics-8.3.88.dist-info}/WHEEL +0 -0
  30. {ultralytics-8.3.87.dist-info → ultralytics-8.3.88.dist-info}/entry_points.txt +0 -0
  31. {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 Annotator, colors
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
- calculate: Processes video frames and calculates the distance between selected objects.
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
- >>> processed_frame = distance_calc.calculate(frame)
36
- >>> cv2.imshow("Distance Calculation", processed_frame)
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 (Dict): Additional parameters passed to the function.
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 calculate(self, im0):
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
- (numpy.ndarray): The processed image frame with annotations and distance calculations.
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
- >>> processed_frame = dc.calculate(frame)
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
- self.annotator.box_label(box, color=colors(int(cls), True), label=self.names[int(cls)])
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
- # Store user selected boxes in centroids list
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 pixels distance
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
- self.annotator.plot_distance_and_line(pixels_distance, self.centroids)
118
-
119
- self.centroids = []
116
+ annotator.plot_distance_and_line(pixels_distance, self.centroids)
120
117
 
121
- self.display_output(im0) # display output with base class function
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
- return im0 # return output image for more usage
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))
@@ -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.utils.plotting import Annotator
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 (Annotator): Object for drawing annotations on the image.
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
- generate_heatmap: Generates and applies the heatmap effect to each frame.
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.generate_heatmap(frame)
31
+ >>> processed_frame = heatmap.process(frame)
32
32
  """
33
33
 
34
34
  def __init__(self, **kwargs):
35
- """Initializes the Heatmap class for real-time video stream heatmap generation based on object tracks."""
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 # bool variable for heatmap initialization
39
- if self.region is not None: # check if user provided the region coordinates
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
- # store colormap
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 generate_heatmap(self, im0):
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
- (np.ndarray): Processed image with heatmap overlay and object counts (if region is specified).
82
-
83
- Examples:
84
- >>> heatmap = Heatmap()
85
- >>> im0 = cv2.imread("image.jpg")
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
- self.initialized = True # Initialize heatmap only once
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
- # Draw bounding box and counting region
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) # store classwise counts in dict
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
- # Store tracking previous position and perform object counting
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(im0) # Display the counts on the frame
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
- im0 = cv2.addWeighted(
117
- im0,
118
- 0.5,
119
- cv2.applyColorMap(
120
- cv2.normalize(self.heatmap, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8), self.colormap
121
- ),
122
- 0.5,
123
- 0,
124
- )
125
-
126
- self.display_output(im0) # display output with base class function
127
- return im0 # return output image for more usage
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 Annotator, colors
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
- count: Processes input data (frames or object tracks) and updates counts.
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
- >>> processed_frame = counter.count(frame)
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 # Bool variable for region initialization
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 values in the current frame.
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
- >>> track_id = 1
64
- >>> prev_position = (120, 220)
65
- >>> cls = 0
66
- >>> counter.count_objects(current_centroid, track_id, prev_position, cls)
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, im0):
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
- im0 (numpy.ndarray): The input image or frame to display counts on.
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(im0, labels_dict, (104, 31, 17), (255, 255, 255), 10)
147
+ self.annotator.display_analytics(plot_im, labels_dict, (104, 31, 17), (255, 255, 255), 10)
152
148
 
153
- def count(self, im0):
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
- (numpy.ndarray): The processed image with annotations and count information.
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
- >>> processed_frame = counter.count(frame)
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) # store classwise counts in dict
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
- # store previous position of track for object counting
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.display_counts(im0) # Display the counts on the frame
201
- self.display_output(im0) # display output with base class function
202
-
203
- return im0 # return output image for more usage
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
+ )