Zhen Ye Claude Opus 4.6 commited on
Commit
a70bcf5
·
1 Parent(s): 0d3be57

Add diagnostic logging to pinpoint tracker dropping detections

Browse files

4 targeted log points across inference.py and utils/tracker.py to
diagnose why first-frame preview shows bounding boxes but the
processed video has none — the tracker is the prime suspect.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

inference.py CHANGED
@@ -947,6 +947,7 @@ def run_inference(
947
  depth_stats.update(dep_res.depth_map)
948
 
949
  # --- POST PROCESSING ---
 
950
  for i, (idx, frame, dep_res) in enumerate(zip(indices, frames, depth_results)):
951
  # 1. Detections — use cached for frame 0 if available
952
  detections = []
@@ -958,6 +959,7 @@ def run_inference(
958
  detections = _build_detection_records(
959
  d_res.boxes, d_res.scores, d_res.labels, queries, d_res.label_names
960
  )
 
961
 
962
  # 2. Frame Rendering
963
  processed = frame.copy()
@@ -981,6 +983,13 @@ def run_inference(
981
  raise RuntimeError("Writer thread died unexpectedly")
982
  if job_id: _check_cancellation(job_id)
983
 
 
 
 
 
 
 
 
984
  batch_accum.clear()
985
  logging.info(f"Worker {gpu_idx} finished flushing batch")
986
 
@@ -1152,7 +1161,11 @@ def run_inference(
1152
 
1153
  # --- SEQUENTIAL TRACKING ---
1154
  # Run tracker FIRST so detections get real track_id from ByteTracker
 
1155
  dets = tracker.update(dets)
 
 
 
1156
  speed_est.estimate(dets)
1157
 
1158
  # --- RELEVANCE GATE (deterministic, fast — stays in writer) ---
 
947
  depth_stats.update(dep_res.depth_map)
948
 
949
  # --- POST PROCESSING ---
950
+ batch_det_summary = []
951
  for i, (idx, frame, dep_res) in enumerate(zip(indices, frames, depth_results)):
952
  # 1. Detections — use cached for frame 0 if available
953
  detections = []
 
959
  detections = _build_detection_records(
960
  d_res.boxes, d_res.scores, d_res.labels, queries, d_res.label_names
961
  )
962
+ batch_det_summary.append((idx, len(detections)))
963
 
964
  # 2. Frame Rendering
965
  processed = frame.copy()
 
983
  raise RuntimeError("Writer thread died unexpectedly")
984
  if job_id: _check_cancellation(job_id)
985
 
986
+ total_dets = sum(c for _, c in batch_det_summary)
987
+ if total_dets == 0 or indices[0] % 90 == 0:
988
+ logging.info("Worker %d batch [frames %s]: %d total detections %s",
989
+ gpu_idx,
990
+ f"{indices[0]}-{indices[-1]}",
991
+ total_dets,
992
+ [(idx, cnt) for idx, cnt in batch_det_summary if cnt > 0])
993
  batch_accum.clear()
994
  logging.info(f"Worker {gpu_idx} finished flushing batch")
995
 
 
1161
 
1162
  # --- SEQUENTIAL TRACKING ---
1163
  # Run tracker FIRST so detections get real track_id from ByteTracker
1164
+ pre_track_count = len(dets)
1165
  dets = tracker.update(dets)
1166
+ if (next_idx % 30 == 0) or (pre_track_count > 0 and len(dets) == 0):
1167
+ logging.info("Writer frame %d: %d detections in -> %d tracked out",
1168
+ next_idx, pre_track_count, len(dets))
1169
  speed_est.estimate(dets)
1170
 
1171
  # --- RELEVANCE GATE (deterministic, fast — stays in writer) ---
models/detectors/drone_yolo.py CHANGED
@@ -20,10 +20,15 @@ class DroneYoloDetector(ObjectDetector):
20
  def __init__(self, score_threshold: float = 0.3, device: str = None) -> None:
21
  self.name = "drone_yolo"
22
  self.score_threshold = score_threshold
 
 
 
 
 
23
  if device:
24
- self.device = device
25
  else:
26
- self.device = "cuda:0" if torch.cuda.is_available() else "cpu"
27
  logging.info(
28
  "Loading drone YOLO from HuggingFace Hub: %s onto %s",
29
  self.REPO_ID,
 
20
  def __init__(self, score_threshold: float = 0.3, device: str = None) -> None:
21
  self.name = "drone_yolo"
22
  self.score_threshold = score_threshold
23
+ # CRITICAL: Store device as torch.device, NOT a string.
24
+ # Ultralytics' select_device() sets CUDA_VISIBLE_DEVICES when it
25
+ # receives a string like "cuda:0", restricting the entire process to
26
+ # one GPU. Passing a torch.device object causes select_device() to
27
+ # return immediately without touching the environment.
28
  if device:
29
+ self.device = torch.device(device)
30
  else:
31
+ self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
32
  logging.info(
33
  "Loading drone YOLO from HuggingFace Hub: %s onto %s",
34
  self.REPO_ID,
models/detectors/yolov8.py CHANGED
@@ -21,10 +21,15 @@ class HuggingFaceYoloV8Detector(ObjectDetector):
21
  def __init__(self, score_threshold: float = 0.3, device: str = None) -> None:
22
  self.name = "hf_yolov8"
23
  self.score_threshold = score_threshold
 
 
 
 
 
24
  if device:
25
- self.device = device
26
  else:
27
- self.device = "cuda:0" if torch.cuda.is_available() else "cpu"
28
  logging.info(
29
  "Loading Hugging Face YOLOv8 weights %s/%s onto %s",
30
  self.REPO_ID,
 
21
  def __init__(self, score_threshold: float = 0.3, device: str = None) -> None:
22
  self.name = "hf_yolov8"
23
  self.score_threshold = score_threshold
24
+ # CRITICAL: Store device as torch.device, NOT a string.
25
+ # Ultralytics' select_device() sets CUDA_VISIBLE_DEVICES when it
26
+ # receives a string like "cuda:0", restricting the entire process to
27
+ # one GPU. Passing a torch.device object causes select_device() to
28
+ # return immediately without touching the environment.
29
  if device:
30
+ self.device = torch.device(device)
31
  else:
32
+ self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
33
  logging.info(
34
  "Loading Hugging Face YOLOv8 weights %s/%s onto %s",
35
  self.REPO_ID,
utils/tracker.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import numpy as np
2
  from scipy.optimize import linear_sum_assignment
3
  import scipy.linalg
@@ -509,14 +510,23 @@ class ByteTracker:
509
  # 4. Init new tracks from unmatched high score detections
510
  # Note: Unmatched low score detections are ignored (noise)
511
  unmatched_dets = [detections[i] for i in u_detection]
 
512
  for track in unmatched_dets:
513
  if track.score < self.new_track_thresh:
 
514
  continue # Not confident enough to start a new track
515
 
516
  track.activate(self.kalman_filter, self.frame_id)
517
  activated_stracks.append(track)
518
  self._sync_data(track, track) # Sync self
519
 
 
 
 
 
 
 
 
520
  # 5. Update state
521
  self.tracked_stracks = [t for t in self.tracked_stracks if t.state == 2]
522
  self.tracked_stracks = join_stracks(self.tracked_stracks, activated_stracks)
@@ -580,6 +590,17 @@ class ByteTracker:
580
 
581
  results.append(d_out)
582
 
 
 
 
 
 
 
 
 
 
 
 
583
  return results
584
 
585
  def _sync_data(self, track, det_source):
 
1
+ import logging
2
  import numpy as np
3
  from scipy.optimize import linear_sum_assignment
4
  import scipy.linalg
 
510
  # 4. Init new tracks from unmatched high score detections
511
  # Note: Unmatched low score detections are ignored (noise)
512
  unmatched_dets = [detections[i] for i in u_detection]
513
+ rejected_by_thresh = 0
514
  for track in unmatched_dets:
515
  if track.score < self.new_track_thresh:
516
+ rejected_by_thresh += 1
517
  continue # Not confident enough to start a new track
518
 
519
  track.activate(self.kalman_filter, self.frame_id)
520
  activated_stracks.append(track)
521
  self._sync_data(track, track) # Sync self
522
 
523
+ if rejected_by_thresh > 0 and self.frame_id <= 5:
524
+ logging.warning(
525
+ "Tracker frame %d: %d detections rejected by new_track_thresh=%.2f (scores: %s)",
526
+ self.frame_id, rejected_by_thresh, self.new_track_thresh,
527
+ [f"{t.score:.3f}" for t in unmatched_dets if t.score < self.new_track_thresh]
528
+ )
529
+
530
  # 5. Update state
531
  self.tracked_stracks = [t for t in self.tracked_stracks if t.state == 2]
532
  self.tracked_stracks = join_stracks(self.tracked_stracks, activated_stracks)
 
590
 
591
  results.append(d_out)
592
 
593
+ if self.frame_id % 30 == 0:
594
+ logging.info(
595
+ "Tracker frame %d: %d tracked (%d activated), %d lost, %d new this frame, output=%d",
596
+ self.frame_id,
597
+ len(self.tracked_stracks),
598
+ sum(1 for t in self.tracked_stracks if t.is_activated),
599
+ len(self.lost_stracks),
600
+ len(activated_stracks),
601
+ len(results),
602
+ )
603
+
604
  return results
605
 
606
  def _sync_data(self, track, det_source):