@@ -86,7 +86,12 @@ def detect_objects(self, frame):
86
86
for result in results :
87
87
for box in result .boxes :
88
88
x1 , y1 , x2 , y2 = box .xyxy [0 ]
89
- bboxes .append ((int (x1 ), int (y1 ), int (x2 - x1 ), int (y2 - y1 )))
89
+ w = int (x2 - x1 )
90
+ h = int (y2 - y1 )
91
+ if w > 0 and h > 0 :
92
+ bboxes .append ((int (x1 ), int (y1 ), w , h ))
93
+ else :
94
+ logging .warning (f"Detected invalid bounding box with zero area: { (x1 , y1 , w , h )} " )
90
95
logging .debug (f"Detected { len (bboxes )} bounding boxes." )
91
96
return bboxes
92
97
except Exception as e :
@@ -114,6 +119,12 @@ def create_tracker(self, image, bbox, algorithm):
114
119
Returns:
115
120
dict or None: A dictionary containing tracker info or None if initialization fails.
116
121
"""
122
+ # Validate the bounding box
123
+ x , y , w , h = bbox
124
+ if w <= 0 or h <= 0 :
125
+ logging .error (f"Invalid bounding box with zero area: { bbox } . Cannot initialize tracker." )
126
+ return None
127
+
117
128
tracker = None
118
129
logging .debug (f"Initializing tracker '{ algorithm } ' with bbox: { bbox } " )
119
130
@@ -200,9 +211,11 @@ def update_single_tracker(tracker_info):
200
211
elif algorithm == 'Nano' and area >= upper_threshold :
201
212
logging .info (f"Switching tracker { idx } from Nano to KCF due to size increase." )
202
213
self .switch_tracker (tracker_info , scaled_image , 'KCF' )
214
+
203
215
except Exception as e :
204
216
logging .error (f"Exception in tracker { idx } ({ algorithm } ): { e } " )
205
217
tracker_info ['ok' ] = False
218
+ self .trackers .remove (tracker_info )
206
219
207
220
for idx , tracker_info in enumerate (self .trackers ):
208
221
tracker_info ['index' ] = idx
@@ -228,8 +241,10 @@ def switch_tracker(self, tracker_info, scaled_image, new_algorithm):
228
241
})
229
242
logging .info (f"Tracker { idx } switched to { new_algorithm } ." )
230
243
else :
231
- logging .error (f"Failed to switch tracker { idx } to { new_algorithm } ." )
244
+ logging .error (f"Failed to switch tracker { idx } to { new_algorithm } due to invalid bbox. Removing tracker." )
245
+ # Remove the tracker since it cannot be initialized
232
246
tracker_info ['ok' ] = False
247
+ self .trackers .remove (tracker_info )
233
248
234
249
def reset_trackers (self ):
235
250
"""Reset all trackers."""
@@ -290,6 +305,51 @@ def draw_fps(frame, prev_time, prev_fps):
290
305
return prev_time , fps
291
306
292
307
308
+ def is_bbox_touches_frame (bbox , frame_width , frame_height ):
309
+ """
310
+ Check if any part of the bounding box is touches the frame boundaries.
311
+
312
+ Args:
313
+ bbox (list or tuple): Bounding box in the format [x, y, w, h].
314
+ frame_width (int): Width of the frame.
315
+ frame_height (int): Height of the frame.
316
+
317
+ Returns:
318
+ bool: True if any part of the bounding box is touches the frame, False otherwise.
319
+ """
320
+ x , y , w , h = bbox
321
+
322
+ # Check if bounding box is within frame boundaries
323
+ if x < 0 or y < 0 or x + w > frame_width or y + h > frame_height :
324
+ return True # Bounding box touches or crosses the frame boundary
325
+ else :
326
+ return False # Bounding box is completely inside the frame
327
+
328
+
329
+ def display_tracker_info (debug_image , index , algorithm , elapsed_time_ms , score , color_list ):
330
+ """
331
+ Display tracker information on the debug image.
332
+
333
+ Args:
334
+ debug_image (numpy.ndarray): Image on which to draw.
335
+ index (int): Index of the tracker.
336
+ algorithm (str): Name of the tracking algorithm.
337
+ elapsed_time_ms (float): Elapsed time in milliseconds.
338
+ score (str or float): Tracking score.
339
+ color_list (list): List of colors for drawing.
340
+ """
341
+ if score != '-' :
342
+ text = f"{ algorithm } : { elapsed_time_ms :.1f} ms Score:{ score :.2f} "
343
+ else :
344
+ text = f"{ algorithm } : { elapsed_time_ms :.1f} ms"
345
+ cv .putText (debug_image , text ,
346
+ (10 , int (25 * (index + 1 ))),
347
+ cv .FONT_HERSHEY_SIMPLEX ,
348
+ 0.7 ,
349
+ color_list [index % len (color_list )],
350
+ 2 , cv .LINE_AA )
351
+
352
+
293
353
def process_tracking_results (trackers , debug_image , tracker_manager , color_list , scale_factor ):
294
354
"""
295
355
Process tracking results, draw bounding boxes, and handle tracking failures.
@@ -301,6 +361,10 @@ def process_tracking_results(trackers, debug_image, tracker_manager, color_list,
301
361
color_list (list): List of colors for drawing.
302
362
scale_factor (float): The scaling factor used for resizing images.
303
363
"""
364
+ frame_height , frame_width = debug_image .shape [:2 ] # Get frame dimensions
365
+
366
+ trackers_to_reset = [] # List to keep track of trackers that need to be reset
367
+
304
368
for tracker_info in trackers :
305
369
index = tracker_info ['index' ]
306
370
ok = tracker_info ['ok' ]
@@ -318,10 +382,17 @@ def process_tracking_results(trackers, debug_image, tracker_manager, color_list,
318
382
h = int (h / scale_factor )
319
383
new_bbox = [x , y , w , h ]
320
384
385
+ # Use the separate function to check boundary crossing
386
+ if is_bbox_touches_frame (new_bbox , frame_width , frame_height ):
387
+ logging .info (f"Tracker { index } ({ algorithm } ) bounding box crossed frame boundary. Resetting tracker." )
388
+ trackers_to_reset .append (index )
389
+ continue # Skip drawing and processing this tracker
390
+
321
391
# Draw bounding box
322
392
cv .rectangle (debug_image ,
323
- (new_bbox [0 ], new_bbox [1 ]),
324
- (new_bbox [0 ] + new_bbox [2 ], new_bbox [1 ] + new_bbox [3 ]),
393
+ (max (new_bbox [0 ], 0 ), max (new_bbox [1 ], 0 )),
394
+ (min (new_bbox [0 ] + new_bbox [2 ], frame_width - 1 ),
395
+ min (new_bbox [1 ] + new_bbox [3 ], frame_height - 1 )),
325
396
color_list [index % len (color_list )],
326
397
thickness = 2 )
327
398
logging .debug (f"Tracker { index } ({ algorithm } ) updated successfully with bbox: { new_bbox } " )
@@ -331,18 +402,15 @@ def process_tracking_results(trackers, debug_image, tracker_manager, color_list,
331
402
tracker_manager .reset_trackers ()
332
403
break
333
404
334
- # Display tracker info
405
+ # Display tracker info using the new function
335
406
elapsed_time_ms = elapsed_time * 1000
336
- if score != '-' :
337
- text = f"{ algorithm } : { elapsed_time_ms :.1f} ms Score:{ score :.2f} "
338
- else :
339
- text = f"{ algorithm } : { elapsed_time_ms :.1f} ms"
340
- cv .putText (debug_image , text ,
341
- (10 , int (25 * (index + 1 ))),
342
- cv .FONT_HERSHEY_SIMPLEX ,
343
- 0.7 ,
344
- color_list [index % len (color_list )],
345
- 2 , cv .LINE_AA )
407
+ display_tracker_info (debug_image , index , algorithm , elapsed_time_ms , score , color_list )
408
+
409
+ # Reset trackers that are marked for reset
410
+ if trackers_to_reset :
411
+ for idx in sorted (trackers_to_reset , reverse = True ):
412
+ logging .info (f"Removing tracker { idx } due to boundary crossing." )
413
+ del tracker_manager .trackers [idx ]
346
414
347
415
348
416
def main ():
0 commit comments