ultralytics 8.2.17__py3-none-any.whl → 8.2.19__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.

Potentially problematic release.


This version of ultralytics might be problematic. Click here for more details.

@@ -9,94 +9,78 @@ from ultralytics.utils.plotting import Annotator, colors
9
9
 
10
10
 
11
11
  class DistanceCalculation:
12
- """A class to calculate distance between two objects in real-time video stream based on their tracks."""
12
+ """A class to calculate distance between two objects in a real-time video stream based on their tracks."""
13
13
 
14
- def __init__(self):
15
- """Initializes the distance calculation class with default values for Visual, Image, track and distance
16
- parameters.
14
+ def __init__(
15
+ self,
16
+ names,
17
+ pixels_per_meter=10,
18
+ view_img=False,
19
+ line_thickness=2,
20
+ line_color=(255, 255, 0),
21
+ centroid_color=(255, 0, 255),
22
+ ):
17
23
  """
24
+ Initializes the DistanceCalculation class with the given parameters.
18
25
 
19
- # Visual & im0 information
26
+ Args:
27
+ names (dict): Dictionary mapping class indices to class names.
28
+ pixels_per_meter (int, optional): Conversion factor from pixels to meters. Defaults to 10.
29
+ view_img (bool, optional): Flag to indicate if the video stream should be displayed. Defaults to False.
30
+ line_thickness (int, optional): Thickness of the lines drawn on the image. Defaults to 2.
31
+ line_color (tuple, optional): Color of the lines drawn on the image (BGR format). Defaults to (255, 255, 0).
32
+ centroid_color (tuple, optional): Color of the centroids drawn (BGR format). Defaults to (255, 0, 255).
33
+ """
34
+ # Visual & image information
20
35
  self.im0 = None
21
36
  self.annotator = None
22
- self.view_img = False
23
- self.line_color = (255, 255, 0)
24
- self.centroid_color = (255, 0, 255)
37
+ self.view_img = view_img
38
+ self.line_color = line_color
39
+ self.centroid_color = centroid_color
25
40
 
26
- # Predict/track information
41
+ # Prediction & tracking information
27
42
  self.clss = None
28
- self.names = None
43
+ self.names = names
29
44
  self.boxes = None
30
- self.line_thickness = 2
45
+ self.line_thickness = line_thickness
31
46
  self.trk_ids = None
32
47
 
33
48
  # Distance calculation information
34
49
  self.centroids = []
35
- self.pixel_per_meter = 10
50
+ self.pixel_per_meter = pixels_per_meter
36
51
 
37
- # Mouse event
52
+ # Mouse event information
38
53
  self.left_mouse_count = 0
39
54
  self.selected_boxes = {}
40
55
 
41
- # Check if environment support imshow
56
+ # Check if environment supports imshow
42
57
  self.env_check = check_imshow(warn=True)
43
58
 
44
- def set_args(
45
- self,
46
- names,
47
- pixels_per_meter=10,
48
- view_img=False,
49
- line_thickness=2,
50
- line_color=(255, 255, 0),
51
- centroid_color=(255, 0, 255),
52
- ):
53
- """
54
- Configures the distance calculation and display parameters.
55
-
56
- Args:
57
- names (dict): object detection classes names
58
- pixels_per_meter (int): Number of pixels in meter
59
- view_img (bool): Flag indicating frame display
60
- line_thickness (int): Line thickness for bounding boxes.
61
- line_color (RGB): color of centroids line
62
- centroid_color (RGB): colors of bbox centroids
63
- """
64
- self.names = names
65
- self.pixel_per_meter = pixels_per_meter
66
- self.view_img = view_img
67
- self.line_thickness = line_thickness
68
- self.line_color = line_color
69
- self.centroid_color = centroid_color
70
-
71
59
  def mouse_event_for_distance(self, event, x, y, flags, param):
72
60
  """
73
- This function is designed to move region with mouse events in a real-time video stream.
61
+ Handles mouse events to select regions in a real-time video stream.
74
62
 
75
63
  Args:
76
- event (int): The type of mouse event (e.g., cv2.EVENT_MOUSEMOVE, cv2.EVENT_LBUTTONDOWN, etc.).
77
- x (int): The x-coordinate of the mouse pointer.
78
- y (int): The y-coordinate of the mouse pointer.
79
- flags (int): Any flags associated with the event (e.g., cv2.EVENT_FLAG_CTRLKEY,
80
- cv2.EVENT_FLAG_SHIFTKEY, etc.).
81
- param (dict): Additional parameters you may want to pass to the function.
64
+ event (int): Type of mouse event (e.g., cv2.EVENT_MOUSEMOVE, cv2.EVENT_LBUTTONDOWN, etc.).
65
+ x (int): X-coordinate of the mouse pointer.
66
+ y (int): Y-coordinate of the mouse pointer.
67
+ flags (int): Flags associated with the event (e.g., cv2.EVENT_FLAG_CTRLKEY, cv2.EVENT_FLAG_SHIFTKEY, etc.).
68
+ param (dict): Additional parameters passed to the function.
82
69
  """
83
- global selected_boxes
84
- global left_mouse_count
85
70
  if event == cv2.EVENT_LBUTTONDOWN:
86
71
  self.left_mouse_count += 1
87
72
  if self.left_mouse_count <= 2:
88
73
  for box, track_id in zip(self.boxes, self.trk_ids):
89
74
  if box[0] < x < box[2] and box[1] < y < box[3] and track_id not in self.selected_boxes:
90
- self.selected_boxes[track_id] = []
91
75
  self.selected_boxes[track_id] = box
92
76
 
93
- if event == cv2.EVENT_RBUTTONDOWN:
77
+ elif event == cv2.EVENT_RBUTTONDOWN:
94
78
  self.selected_boxes = {}
95
79
  self.left_mouse_count = 0
96
80
 
97
81
  def extract_tracks(self, tracks):
98
82
  """
99
- Extracts results from the provided data.
83
+ Extracts tracking results from the provided data.
100
84
 
101
85
  Args:
102
86
  tracks (list): List of tracks obtained from the object tracking process.
@@ -105,55 +89,65 @@ class DistanceCalculation:
105
89
  self.clss = tracks[0].boxes.cls.cpu().tolist()
106
90
  self.trk_ids = tracks[0].boxes.id.int().cpu().tolist()
107
91
 
108
- def calculate_centroid(self, box):
92
+ @staticmethod
93
+ def calculate_centroid(box):
109
94
  """
110
- Calculate the centroid of bounding box.
95
+ Calculates the centroid of a bounding box.
111
96
 
112
97
  Args:
113
- box (list): Bounding box data
98
+ box (list): Bounding box coordinates [x1, y1, x2, y2].
99
+
100
+ Returns:
101
+ (tuple): Centroid coordinates (x, y).
114
102
  """
115
103
  return int((box[0] + box[2]) // 2), int((box[1] + box[3]) // 2)
116
104
 
117
105
  def calculate_distance(self, centroid1, centroid2):
118
106
  """
119
- Calculate distance between two centroids.
107
+ Calculates the distance between two centroids.
120
108
 
121
109
  Args:
122
- centroid1 (point): First bounding box data
123
- centroid2 (point): Second bounding box data
110
+ centroid1 (tuple): Coordinates of the first centroid (x, y).
111
+ centroid2 (tuple): Coordinates of the second centroid (x, y).
112
+
113
+ Returns:
114
+ (tuple): Distance in meters and millimeters.
124
115
  """
125
116
  pixel_distance = math.sqrt((centroid1[0] - centroid2[0]) ** 2 + (centroid1[1] - centroid2[1]) ** 2)
126
- return pixel_distance / self.pixel_per_meter, (pixel_distance / self.pixel_per_meter) * 1000
117
+ distance_m = pixel_distance / self.pixel_per_meter
118
+ distance_mm = distance_m * 1000
119
+ return distance_m, distance_mm
127
120
 
128
121
  def start_process(self, im0, tracks):
129
122
  """
130
- Calculate distance between two bounding boxes based on tracking data.
123
+ Processes the video frame and calculates the distance between two bounding boxes.
131
124
 
132
125
  Args:
133
- im0 (nd array): Image
126
+ im0 (ndarray): The image frame.
134
127
  tracks (list): List of tracks obtained from the object tracking process.
128
+
129
+ Returns:
130
+ (ndarray): The processed image frame.
135
131
  """
136
132
  self.im0 = im0
137
133
  if tracks[0].boxes.id is None:
138
134
  if self.view_img:
139
135
  self.display_frames()
140
- return
141
- self.extract_tracks(tracks)
136
+ return im0
142
137
 
143
- self.annotator = Annotator(self.im0, line_width=2)
138
+ self.extract_tracks(tracks)
139
+ self.annotator = Annotator(self.im0, line_width=self.line_thickness)
144
140
 
145
141
  for box, cls, track_id in zip(self.boxes, self.clss, self.trk_ids):
146
142
  self.annotator.box_label(box, color=colors(int(cls), True), label=self.names[int(cls)])
147
143
 
148
144
  if len(self.selected_boxes) == 2:
149
- for trk_id, _ in self.selected_boxes.items():
145
+ for trk_id in self.selected_boxes.keys():
150
146
  if trk_id == track_id:
151
147
  self.selected_boxes[track_id] = box
152
148
 
153
149
  if len(self.selected_boxes) == 2:
154
- for trk_id, box in self.selected_boxes.items():
155
- centroid = self.calculate_centroid(self.selected_boxes[trk_id])
156
- self.centroids.append(centroid)
150
+ self.centroids = [self.calculate_centroid(self.selected_boxes[trk_id]) for trk_id in self.selected_boxes]
157
151
 
158
152
  distance_m, distance_mm = self.calculate_distance(self.centroids[0], self.centroids[1])
159
153
  self.annotator.plot_distance_and_line(
@@ -168,7 +162,7 @@ class DistanceCalculation:
168
162
  return im0
169
163
 
170
164
  def display_frames(self):
171
- """Display frame."""
165
+ """Displays the current frame with annotations."""
172
166
  cv2.namedWindow("Ultralytics Distance Estimation")
173
167
  cv2.setMouseCallback("Ultralytics Distance Estimation", self.mouse_event_for_distance)
174
168
  cv2.imshow("Ultralytics Distance Estimation", self.im0)
@@ -178,4 +172,5 @@ class DistanceCalculation:
178
172
 
179
173
 
180
174
  if __name__ == "__main__":
181
- DistanceCalculation()
175
+ names = {0: "person", 1: "car"} # example class names
176
+ distance_calculation = DistanceCalculation(names)
@@ -16,28 +16,48 @@ from shapely.geometry import LineString, Point, Polygon
16
16
  class Heatmap:
17
17
  """A class to draw heatmaps in real-time video stream based on their tracks."""
18
18
 
19
- def __init__(self):
19
+ def __init__(
20
+ self,
21
+ classes_names,
22
+ imw=0,
23
+ imh=0,
24
+ colormap=cv2.COLORMAP_JET,
25
+ heatmap_alpha=0.5,
26
+ view_img=False,
27
+ view_in_counts=True,
28
+ view_out_counts=True,
29
+ count_reg_pts=None,
30
+ count_txt_color=(0, 0, 0),
31
+ count_bg_color=(255, 255, 255),
32
+ count_reg_color=(255, 0, 255),
33
+ region_thickness=5,
34
+ line_dist_thresh=15,
35
+ line_thickness=2,
36
+ decay_factor=0.99,
37
+ shape="circle",
38
+ ):
20
39
  """Initializes the heatmap class with default values for Visual, Image, track, count and heatmap parameters."""
21
40
 
22
41
  # Visual information
23
42
  self.annotator = None
24
- self.view_img = False
25
- self.shape = "circle"
43
+ self.view_img = view_img
44
+ self.shape = shape
26
45
 
27
- self.names = None # Classes names
46
+ self.initialized = False
47
+ self.names = classes_names # Classes names
28
48
 
29
49
  # Image information
30
- self.imw = None
31
- self.imh = None
50
+ self.imw = imw
51
+ self.imh = imh
32
52
  self.im0 = None
33
- self.tf = 2
34
- self.view_in_counts = True
35
- self.view_out_counts = True
53
+ self.tf = line_thickness
54
+ self.view_in_counts = view_in_counts
55
+ self.view_out_counts = view_out_counts
36
56
 
37
57
  # Heatmap colormap and heatmap np array
38
- self.colormap = None
58
+ self.colormap = colormap
39
59
  self.heatmap = None
40
- self.heatmap_alpha = 0.5
60
+ self.heatmap_alpha = heatmap_alpha
41
61
 
42
62
  # Predict/track information
43
63
  self.boxes = None
@@ -46,112 +66,48 @@ class Heatmap:
46
66
  self.track_history = defaultdict(list)
47
67
 
48
68
  # Region & Line Information
49
- self.count_reg_pts = None
50
69
  self.counting_region = None
51
- self.line_dist_thresh = 15
52
- self.region_thickness = 5
53
- self.region_color = (255, 0, 255)
70
+ self.line_dist_thresh = line_dist_thresh
71
+ self.region_thickness = region_thickness
72
+ self.region_color = count_reg_color
54
73
 
55
74
  # Object Counting Information
56
75
  self.in_counts = 0
57
76
  self.out_counts = 0
58
77
  self.count_ids = []
59
78
  self.class_wise_count = {}
60
- self.count_txt_color = (0, 0, 0)
61
- self.count_bg_color = (255, 255, 255)
79
+ self.count_txt_color = count_txt_color
80
+ self.count_bg_color = count_bg_color
62
81
  self.cls_txtdisplay_gap = 50
63
82
 
64
83
  # Decay factor
65
- self.decay_factor = 0.99
84
+ self.decay_factor = decay_factor
66
85
 
67
- # Check if environment support imshow
86
+ # Check if environment supports imshow
68
87
  self.env_check = check_imshow(warn=True)
69
88
 
70
- def set_args(
71
- self,
72
- imw,
73
- imh,
74
- classes_names=None,
75
- colormap=cv2.COLORMAP_JET,
76
- heatmap_alpha=0.5,
77
- view_img=False,
78
- view_in_counts=True,
79
- view_out_counts=True,
80
- count_reg_pts=None,
81
- count_txt_color=(0, 0, 0),
82
- count_bg_color=(255, 255, 255),
83
- count_reg_color=(255, 0, 255),
84
- region_thickness=5,
85
- line_dist_thresh=15,
86
- line_thickness=2,
87
- decay_factor=0.99,
88
- shape="circle",
89
- ):
90
- """
91
- Configures the heatmap colormap, width, height and display parameters.
92
-
93
- Args:
94
- colormap (cv2.COLORMAP): The colormap to be set.
95
- imw (int): The width of the frame.
96
- imh (int): The height of the frame.
97
- classes_names (dict): Classes names
98
- line_thickness (int): Line thickness for bounding boxes.
99
- heatmap_alpha (float): alpha value for heatmap display
100
- view_img (bool): Flag indicating frame display
101
- view_in_counts (bool): Flag to control whether to display the incounts on video stream.
102
- view_out_counts (bool): Flag to control whether to display the outcounts on video stream.
103
- count_reg_pts (list): Object counting region points
104
- count_txt_color (RGB color): count text color value
105
- count_bg_color (RGB color): count highlighter line color
106
- count_reg_color (RGB color): Color of object counting region
107
- region_thickness (int): Object counting Region thickness
108
- line_dist_thresh (int): Euclidean Distance threshold for line counter
109
- decay_factor (float): value for removing heatmap area after object passed
110
- shape (str): Heatmap shape, rect or circle shape supported
111
- """
112
- self.tf = line_thickness
113
- self.names = classes_names
114
- self.imw = imw
115
- self.imh = imh
116
- self.heatmap_alpha = heatmap_alpha
117
- self.view_img = view_img
118
- self.view_in_counts = view_in_counts
119
- self.view_out_counts = view_out_counts
120
- self.colormap = colormap
121
-
122
89
  # Region and line selection
123
- if count_reg_pts is not None:
124
- if len(count_reg_pts) == 2:
90
+ self.count_reg_pts = count_reg_pts
91
+ print(self.count_reg_pts)
92
+ if self.count_reg_pts is not None:
93
+ if len(self.count_reg_pts) == 2:
125
94
  print("Line Counter Initiated.")
126
- self.count_reg_pts = count_reg_pts
127
95
  self.counting_region = LineString(self.count_reg_pts)
128
- elif len(count_reg_pts) >= 3:
96
+ elif len(self.count_reg_pts) >= 3:
129
97
  print("Polygon Counter Initiated.")
130
- self.count_reg_pts = count_reg_pts
131
98
  self.counting_region = Polygon(self.count_reg_pts)
132
99
  else:
133
100
  print("Invalid Region points provided, region_points must be 2 for lines or >= 3 for polygons.")
134
101
  print("Using Line Counter Now")
135
102
  self.counting_region = LineString(self.count_reg_pts)
136
103
 
137
- # Heatmap new frame
138
- self.heatmap = np.zeros((int(self.imh), int(self.imw)), dtype=np.float32)
139
-
140
- self.count_txt_color = count_txt_color
141
- self.count_bg_color = count_bg_color
142
- self.region_color = count_reg_color
143
- self.region_thickness = region_thickness
144
- self.decay_factor = decay_factor
145
- self.line_dist_thresh = line_dist_thresh
146
- self.shape = shape
147
-
148
- # shape of heatmap, if not selected
104
+ # Shape of heatmap, if not selected
149
105
  if self.shape not in {"circle", "rect"}:
150
106
  print("Unknown shape value provided, 'circle' & 'rect' supported")
151
107
  print("Using Circular shape now")
152
108
  self.shape = "circle"
153
109
 
154
- def extract_results(self, tracks):
110
+ def extract_results(self, tracks, _intialized=False):
155
111
  """
156
112
  Extracts results from the provided data.
157
113
 
@@ -171,18 +127,20 @@ class Heatmap:
171
127
  tracks (list): List of tracks obtained from the object tracking process.
172
128
  """
173
129
  self.im0 = im0
174
- if tracks[0].boxes.id is None:
175
- self.heatmap = np.zeros((int(self.imh), int(self.imw)), dtype=np.float32)
176
- if self.view_img and self.env_check:
177
- self.display_frames()
178
- return im0
130
+
131
+ # Initialize heatmap only once
132
+ if not self.initialized:
133
+ self.heatmap = np.zeros((int(self.im0.shape[0]), int(self.im0.shape[1])), dtype=np.float32)
134
+ self.initialized = True
135
+
179
136
  self.heatmap *= self.decay_factor # decay factor
137
+
180
138
  self.extract_results(tracks)
181
139
  self.annotator = Annotator(self.im0, self.tf, None)
182
140
 
183
- if self.count_reg_pts is not None:
141
+ if self.track_ids is not None:
184
142
  # Draw counting region
185
- if self.view_in_counts or self.view_out_counts:
143
+ if self.count_reg_pts is not None:
186
144
  self.annotator.draw_region(
187
145
  reg_pts=self.count_reg_pts, color=self.region_color, thickness=self.region_thickness
188
146
  )
@@ -214,25 +172,12 @@ class Heatmap:
214
172
 
215
173
  prev_position = self.track_history[track_id][-2] if len(self.track_history[track_id]) > 1 else None
216
174
 
217
- # Count objects in any polygon
218
- if len(self.count_reg_pts) >= 3:
219
- is_inside = self.counting_region.contains(Point(track_line[-1]))
220
-
221
- if prev_position is not None and is_inside and track_id not in self.count_ids:
222
- self.count_ids.append(track_id)
223
-
224
- if (box[0] - prev_position[0]) * (self.counting_region.centroid.x - prev_position[0]) > 0:
225
- self.in_counts += 1
226
- self.class_wise_count[self.names[cls]]["IN"] += 1
227
- else:
228
- self.out_counts += 1
229
- self.class_wise_count[self.names[cls]]["OUT"] += 1
230
-
231
- # Count objects using line
232
- elif len(self.count_reg_pts) == 2:
233
- if prev_position is not None and track_id not in self.count_ids:
234
- distance = Point(track_line[-1]).distance(self.counting_region)
235
- if distance < self.line_dist_thresh and track_id not in self.count_ids:
175
+ if self.count_reg_pts is not None:
176
+ # Count objects in any polygon
177
+ if len(self.count_reg_pts) >= 3:
178
+ is_inside = self.counting_region.contains(Point(track_line[-1]))
179
+
180
+ if prev_position is not None and is_inside and track_id not in self.count_ids:
236
181
  self.count_ids.append(track_id)
237
182
 
238
183
  if (box[0] - prev_position[0]) * (self.counting_region.centroid.x - prev_position[0]) > 0:
@@ -242,6 +187,22 @@ class Heatmap:
242
187
  self.out_counts += 1
243
188
  self.class_wise_count[self.names[cls]]["OUT"] += 1
244
189
 
190
+ # Count objects using line
191
+ elif len(self.count_reg_pts) == 2:
192
+ if prev_position is not None and track_id not in self.count_ids:
193
+ distance = Point(track_line[-1]).distance(self.counting_region)
194
+ if distance < self.line_dist_thresh and track_id not in self.count_ids:
195
+ self.count_ids.append(track_id)
196
+
197
+ if (box[0] - prev_position[0]) * (
198
+ self.counting_region.centroid.x - prev_position[0]
199
+ ) > 0:
200
+ self.in_counts += 1
201
+ self.class_wise_count[self.names[cls]]["IN"] += 1
202
+ else:
203
+ self.out_counts += 1
204
+ self.class_wise_count[self.names[cls]]["OUT"] += 1
205
+
245
206
  else:
246
207
  for box, cls in zip(self.boxes, self.clss):
247
208
  if self.shape == "circle":
@@ -258,26 +219,26 @@ class Heatmap:
258
219
  else:
259
220
  self.heatmap[int(box[1]) : int(box[3]), int(box[0]) : int(box[2])] += 2
260
221
 
222
+ if self.count_reg_pts is not None:
223
+ labels_dict = {}
224
+
225
+ for key, value in self.class_wise_count.items():
226
+ if value["IN"] != 0 or value["OUT"] != 0:
227
+ if not self.view_in_counts and not self.view_out_counts:
228
+ continue
229
+ elif not self.view_in_counts:
230
+ labels_dict[str.capitalize(key)] = f"OUT {value['OUT']}"
231
+ elif not self.view_out_counts:
232
+ labels_dict[str.capitalize(key)] = f"IN {value['IN']}"
233
+ else:
234
+ labels_dict[str.capitalize(key)] = f"IN {value['IN']} OUT {value['OUT']}"
235
+
236
+ if labels_dict is not None:
237
+ self.annotator.display_analytics(self.im0, labels_dict, self.count_txt_color, self.count_bg_color, 10)
238
+
261
239
  # Normalize, apply colormap to heatmap and combine with original image
262
240
  heatmap_normalized = cv2.normalize(self.heatmap, None, 0, 255, cv2.NORM_MINMAX)
263
241
  heatmap_colored = cv2.applyColorMap(heatmap_normalized.astype(np.uint8), self.colormap)
264
-
265
- labels_dict = {}
266
-
267
- for key, value in self.class_wise_count.items():
268
- if value["IN"] != 0 or value["OUT"] != 0:
269
- if not self.view_in_counts and not self.view_out_counts:
270
- continue
271
- elif not self.view_in_counts:
272
- labels_dict[str.capitalize(key)] = f"OUT {value['OUT']}"
273
- elif not self.view_out_counts:
274
- labels_dict[str.capitalize(key)] = f"IN {value['IN']}"
275
- else:
276
- labels_dict[str.capitalize(key)] = f"IN {value['IN']} OUT {value['OUT']}"
277
-
278
- if labels_dict is not None:
279
- self.annotator.display_analytics(self.im0, labels_dict, self.count_txt_color, self.count_bg_color, 10)
280
-
281
242
  self.im0 = cv2.addWeighted(self.im0, 1 - self.heatmap_alpha, heatmap_colored, self.heatmap_alpha, 0)
282
243
 
283
244
  if self.env_check and self.view_img:
@@ -294,4 +255,5 @@ class Heatmap:
294
255
 
295
256
 
296
257
  if __name__ == "__main__":
297
- Heatmap()
258
+ classes_names = {0: "person", 1: "car"} # example class names
259
+ heatmap = Heatmap(classes_names)