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

@@ -1,259 +1,93 @@
1
1
  # Ultralytics YOLO 🚀, AGPL-3.0 license
2
2
 
3
- from collections import defaultdict
4
-
5
3
  import cv2
6
4
  import numpy as np
7
5
 
8
- from ultralytics.utils.checks import check_imshow, check_requirements
6
+ from ultralytics.solutions.object_counter import ObjectCounter # Import object counter class
9
7
  from ultralytics.utils.plotting import Annotator
10
8
 
11
- check_requirements("shapely>=2.0.0")
12
-
13
- from shapely.geometry import LineString, Point, Polygon
14
-
15
9
 
16
- class Heatmap:
10
+ class Heatmap(ObjectCounter):
17
11
  """A class to draw heatmaps in real-time video stream based on their tracks."""
18
12
 
19
- def __init__(
20
- self,
21
- 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
- ):
39
- """Initializes the heatmap class with default values for Visual, Image, track, count and heatmap parameters."""
40
- # Visual information
41
- self.annotator = None
42
- self.view_img = view_img
43
- self.shape = shape
44
-
45
- self.initialized = False
46
- self.names = names # Classes names
47
-
48
- # Image information
49
- self.imw = imw
50
- self.imh = imh
51
- self.im0 = None
52
- self.tf = line_thickness
53
- self.view_in_counts = view_in_counts
54
- self.view_out_counts = view_out_counts
55
-
56
- # Heatmap colormap and heatmap np array
57
- self.colormap = colormap
58
- self.heatmap = None
59
- self.heatmap_alpha = heatmap_alpha
60
-
61
- # Predict/track information
62
- self.boxes = []
63
- self.track_ids = []
64
- self.clss = []
65
- self.track_history = defaultdict(list)
66
-
67
- # Region & Line Information
68
- self.counting_region = None
69
- self.line_dist_thresh = line_dist_thresh
70
- self.region_thickness = region_thickness
71
- self.region_color = count_reg_color
72
-
73
- # Object Counting Information
74
- self.in_counts = 0
75
- self.out_counts = 0
76
- self.count_ids = []
77
- self.class_wise_count = {}
78
- self.count_txt_color = count_txt_color
79
- self.count_bg_color = count_bg_color
80
- self.cls_txtdisplay_gap = 50
81
-
82
- # Decay factor
83
- self.decay_factor = decay_factor
13
+ def __init__(self, **kwargs):
14
+ """Initializes function for heatmap class with default values."""
15
+ super().__init__(**kwargs)
84
16
 
85
- # Check if environment supports imshow
86
- self.env_check = check_imshow(warn=True)
17
+ self.initialized = False # bool variable for heatmap initialization
18
+ if self.region is not None: # check if user provided the region coordinates
19
+ self.initialize_region()
87
20
 
88
- # Region and line selection
89
- self.count_reg_pts = count_reg_pts
90
- print(self.count_reg_pts)
91
- if self.count_reg_pts is not None:
92
- if len(self.count_reg_pts) == 2:
93
- print("Line Counter Initiated.")
94
- self.counting_region = LineString(self.count_reg_pts)
95
- elif len(self.count_reg_pts) >= 3:
96
- print("Polygon Counter Initiated.")
97
- self.counting_region = Polygon(self.count_reg_pts)
98
- else:
99
- print("Invalid Region points provided, region_points must be 2 for lines or >= 3 for polygons.")
100
- print("Using Line Counter Now")
101
- self.counting_region = LineString(self.count_reg_pts)
21
+ # store colormap
22
+ self.colormap = cv2.COLORMAP_PARULA if self.CFG["colormap"] is None else self.CFG["colormap"]
102
23
 
103
- # Shape of heatmap, if not selected
104
- if self.shape not in {"circle", "rect"}:
105
- print("Unknown shape value provided, 'circle' & 'rect' supported")
106
- print("Using Circular shape now")
107
- self.shape = "circle"
108
-
109
- def extract_results(self, tracks):
24
+ def heatmap_effect(self, box):
110
25
  """
111
- Extracts results from the provided data.
26
+ Efficient calculation of heatmap area and effect location for applying colormap.
112
27
 
113
28
  Args:
114
- tracks (list): List of tracks obtained from the object tracking process.
115
- """
116
- if tracks[0].boxes.id is not None:
117
- self.boxes = tracks[0].boxes.xyxy.cpu()
118
- self.clss = tracks[0].boxes.cls.tolist()
119
- self.track_ids = tracks[0].boxes.id.int().tolist()
120
-
121
- def generate_heatmap(self, im0, tracks):
29
+ box (list): Bounding Box coordinates data [x0, y0, x1, y1]
122
30
  """
123
- Generate heatmap based on tracking data.
31
+ x0, y0, x1, y1 = map(int, box)
32
+ radius_squared = (min(x1 - x0, y1 - y0) // 2) ** 2
124
33
 
125
- Args:
126
- im0 (nd array): Image
127
- tracks (list): List of tracks obtained from the object tracking process.
128
- """
129
- self.im0 = im0
34
+ # Create a meshgrid with region of interest (ROI) for vectorized distance calculations
35
+ xv, yv = np.meshgrid(np.arange(x0, x1), np.arange(y0, y1))
130
36
 
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
37
+ # Calculate squared distances from the center
38
+ dist_squared = (xv - ((x0 + x1) // 2)) ** 2 + (yv - ((y0 + y1) // 2)) ** 2
135
39
 
136
- self.heatmap *= self.decay_factor # decay factor
40
+ # Create a mask of points within the radius
41
+ within_radius = dist_squared <= radius_squared
137
42
 
138
- self.extract_results(tracks)
139
- self.annotator = Annotator(self.im0, self.tf, None)
43
+ # Update only the values within the bounding box in a single vectorized operation
44
+ self.heatmap[y0:y1, x0:x1][within_radius] += 2
140
45
 
141
- if self.track_ids:
142
- # Draw counting region
143
- if self.count_reg_pts is not None:
144
- self.annotator.draw_region(
145
- reg_pts=self.count_reg_pts, color=self.region_color, thickness=self.region_thickness
146
- )
147
-
148
- for box, cls, track_id in zip(self.boxes, self.clss, self.track_ids):
149
- # Store class info
150
- if self.names[cls] not in self.class_wise_count:
151
- self.class_wise_count[self.names[cls]] = {"IN": 0, "OUT": 0}
152
-
153
- if self.shape == "circle":
154
- center = (int((box[0] + box[2]) // 2), int((box[1] + box[3]) // 2))
155
- radius = min(int(box[2]) - int(box[0]), int(box[3]) - int(box[1])) // 2
46
+ def generate_heatmap(self, im0):
47
+ """
48
+ Generate heatmap for each frame using Ultralytics.
156
49
 
157
- y, x = np.ogrid[0 : self.heatmap.shape[0], 0 : self.heatmap.shape[1]]
158
- mask = (x - center[0]) ** 2 + (y - center[1]) ** 2 <= radius**2
50
+ Args:
51
+ im0 (ndarray): Input image array for processing
52
+ Returns:
53
+ im0 (ndarray): Processed image for further usage
54
+ """
55
+ self.heatmap = np.zeros_like(im0, dtype=np.float32) * 0.99 if not self.initialized else self.heatmap
56
+ self.initialized = True # Initialize heatmap only once
159
57
 
160
- self.heatmap[int(box[1]) : int(box[3]), int(box[0]) : int(box[2])] += (
161
- 2 * mask[int(box[1]) : int(box[3]), int(box[0]) : int(box[2])]
162
- )
58
+ self.annotator = Annotator(im0, line_width=self.line_width) # Initialize annotator
59
+ self.extract_tracks(im0) # Extract tracks
163
60
 
164
- else:
165
- self.heatmap[int(box[1]) : int(box[3]), int(box[0]) : int(box[2])] += 2
61
+ # Iterate over bounding boxes, track ids and classes index
62
+ for box, track_id, cls in zip(self.boxes, self.track_ids, self.clss):
63
+ # Draw bounding box and counting region
64
+ self.heatmap_effect(box)
166
65
 
167
- # Store tracking hist
168
- track_line = self.track_history[track_id]
169
- track_line.append((float((box[0] + box[2]) / 2), float((box[1] + box[3]) / 2)))
170
- if len(track_line) > 30:
171
- track_line.pop(0)
66
+ if self.region is not None:
67
+ self.annotator.draw_region(reg_pts=self.region, color=(104, 0, 123), thickness=self.line_width * 2)
68
+ self.store_tracking_history(track_id, box) # Store track history
69
+ self.store_classwise_counts(cls) # store classwise counts in dict
172
70
 
71
+ # Store tracking previous position and perform object counting
173
72
  prev_position = self.track_history[track_id][-2] if len(self.track_history[track_id]) > 1 else None
73
+ self.count_objects(self.track_line, box, track_id, prev_position, cls) # Perform object counting
174
74
 
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:
181
- self.count_ids.append(track_id)
182
-
183
- if (box[0] - prev_position[0]) * (self.counting_region.centroid.x - prev_position[0]) > 0:
184
- self.in_counts += 1
185
- self.class_wise_count[self.names[cls]]["IN"] += 1
186
- else:
187
- self.out_counts += 1
188
- self.class_wise_count[self.names[cls]]["OUT"] += 1
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
-
206
- else:
207
- for box, cls in zip(self.boxes, self.clss):
208
- if self.shape == "circle":
209
- center = (int((box[0] + box[2]) // 2), int((box[1] + box[3]) // 2))
210
- radius = min(int(box[2]) - int(box[0]), int(box[3]) - int(box[1])) // 2
211
-
212
- y, x = np.ogrid[0 : self.heatmap.shape[0], 0 : self.heatmap.shape[1]]
213
- mask = (x - center[0]) ** 2 + (y - center[1]) ** 2 <= radius**2
214
-
215
- self.heatmap[int(box[1]) : int(box[3]), int(box[0]) : int(box[2])] += (
216
- 2 * mask[int(box[1]) : int(box[3]), int(box[0]) : int(box[2])]
217
- )
218
-
219
- else:
220
- self.heatmap[int(box[1]) : int(box[3]), int(box[0]) : int(box[2])] += 2
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)
75
+ self.display_counts(im0) if self.region is not None else None # Display the counts on the frame
238
76
 
239
77
  # Normalize, apply colormap to heatmap and combine with original image
240
- heatmap_normalized = cv2.normalize(self.heatmap, None, 0, 255, cv2.NORM_MINMAX)
241
- heatmap_colored = cv2.applyColorMap(heatmap_normalized.astype(np.uint8), self.colormap)
242
- self.im0 = cv2.addWeighted(self.im0, 1 - self.heatmap_alpha, heatmap_colored, self.heatmap_alpha, 0)
243
-
244
- if self.env_check and self.view_img:
245
- self.display_frames()
246
-
247
- return self.im0
248
-
249
- def display_frames(self):
250
- """Display frame."""
251
- cv2.imshow("Ultralytics Heatmap", self.im0)
252
-
253
- if cv2.waitKey(1) & 0xFF == ord("q"):
254
- return
255
-
256
-
257
- if __name__ == "__main__":
258
- classes_names = {0: "person", 1: "car"} # example class names
259
- heatmap = Heatmap(classes_names)
78
+ im0 = (
79
+ im0
80
+ if self.track_data.id is None
81
+ else cv2.addWeighted(
82
+ im0,
83
+ 0.5,
84
+ cv2.applyColorMap(
85
+ cv2.normalize(self.heatmap, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8), self.colormap
86
+ ),
87
+ 0.5,
88
+ 0,
89
+ )
90
+ )
91
+
92
+ self.display_output(im0) # display output with base class function
93
+ return im0 # return output image for more usage