Mirko Trasciatti commited on
Commit
bd71d20
·
1 Parent(s): 0327f39

Add 5% confidence threshold, limit to top 5 candidates, add tracking logs

Browse files

Detection:
- conf_threshold: 0.0 -> 0.05 (5% minimum to filter noise)
- max_candidates: 5 (keep only top 5 by confidence)

Tracking:
- Confirmed: tracks across ALL frames (for idx, frame in enumerate(frames))
- Added logging to show tracking progress and results

Logs now show:
- [detect_all_balls] Found N candidates (top 5, conf >= 5%)
- [_track_single_ball_candidate] Tracking Ball N across M frames...
- [_track_single_ball_candidate] Ball N done: X/M frames (Y%), max_vel, kick info

Files changed (1) hide show
  1. app.py +24 -10
app.py CHANGED
@@ -135,16 +135,17 @@ def detect_ball_center(
135
  def detect_all_balls(
136
  frame: Image.Image,
137
  model_filename: str = YOLO_DEFAULT_MODEL,
138
- conf_threshold: float = 0.0, # Same as original - accept all detections
139
  iou_threshold: float = YOLO_IOU_THRESHOLD,
140
- max_detections: int = 10, # Increased to catch more candidates
141
- roi_x_min: float = 0.0, # NO ROI filter - accept balls anywhere
142
- roi_x_max: float = 1.0,
143
  ) -> list[dict]:
144
  """
145
  Detect all ball candidates in a frame.
146
 
147
- NO ROI filtering - we show ALL detected balls and let the user/algorithm decide.
 
 
148
 
149
  Returns list of dicts with keys:
150
  - id: int (candidate index)
@@ -219,14 +220,17 @@ def detect_all_balls(
219
  # Sort by confidence descending
220
  candidates.sort(key=lambda c: c["conf"], reverse=True)
221
 
222
- # Re-assign IDs after sorting
 
 
 
223
  for i, c in enumerate(candidates):
224
  c["id"] = i
225
 
226
  # Debug logging
227
- print(f"[detect_all_balls] Found {len(candidates)} ball candidates:")
228
  for c in candidates:
229
- print(f" Ball {c['id']}: center={c['center']}, conf={c['conf']:.3f}, box={c['box']}")
230
 
231
  return candidates
232
 
@@ -788,7 +792,7 @@ def _track_single_ball_candidate(
788
  progress: gr.Progress | None = None,
789
  ) -> dict:
790
  """
791
- Track a single ball candidate across all frames using YOLO.
792
  Uses proximity matching to follow the same ball.
793
 
794
  Returns dict with tracking results:
@@ -807,6 +811,8 @@ def _track_single_ball_candidate(
807
  frames = state.video_frames
808
  total = len(frames)
809
 
 
 
810
  # Initial position from candidate
811
  last_center = candidate["center"]
812
  max_distance_threshold = 100 # Max pixels to consider same ball
@@ -953,7 +959,7 @@ def _track_single_ball_candidate(
953
  kick_frame = frame
954
  break
955
 
956
- return {
957
  "centers": centers,
958
  "boxes": boxes,
959
  "confs": confs,
@@ -969,6 +975,14 @@ def _track_single_ball_candidate(
969
  "has_kick": kick_frame is not None,
970
  "coverage": len(centers) / total if total else 0.0,
971
  }
 
 
 
 
 
 
 
 
972
 
973
 
974
  def _detect_and_track_all_ball_candidates(
 
135
  def detect_all_balls(
136
  frame: Image.Image,
137
  model_filename: str = YOLO_DEFAULT_MODEL,
138
+ conf_threshold: float = 0.05, # Minimum 5% confidence to filter noise
139
  iou_threshold: float = YOLO_IOU_THRESHOLD,
140
+ max_detections: int = 10, # Get more from YOLO, then filter to top 5
141
+ max_candidates: int = 5, # Return only top 5 by confidence
 
142
  ) -> list[dict]:
143
  """
144
  Detect all ball candidates in a frame.
145
 
146
+ - Minimum 5% confidence to filter noise
147
+ - Returns top 5 candidates by confidence
148
+ - No ROI filtering - scoring happens later
149
 
150
  Returns list of dicts with keys:
151
  - id: int (candidate index)
 
220
  # Sort by confidence descending
221
  candidates.sort(key=lambda c: c["conf"], reverse=True)
222
 
223
+ # Keep only top N candidates
224
+ candidates = candidates[:max_candidates]
225
+
226
+ # Re-assign IDs after sorting and filtering
227
  for i, c in enumerate(candidates):
228
  c["id"] = i
229
 
230
  # Debug logging
231
+ print(f"[detect_all_balls] Found {len(candidates)} ball candidates (top {max_candidates}, conf >= {conf_threshold:.0%}):")
232
  for c in candidates:
233
+ print(f" Ball {c['id']}: center={c['center']}, conf={c['conf']:.1%}, box={c['box']}")
234
 
235
  return candidates
236
 
 
792
  progress: gr.Progress | None = None,
793
  ) -> dict:
794
  """
795
+ Track a single ball candidate across ALL frames using YOLO.
796
  Uses proximity matching to follow the same ball.
797
 
798
  Returns dict with tracking results:
 
811
  frames = state.video_frames
812
  total = len(frames)
813
 
814
+ print(f"[_track_single_ball_candidate] Tracking Ball {candidate['id']} across {total} frames...")
815
+
816
  # Initial position from candidate
817
  last_center = candidate["center"]
818
  max_distance_threshold = 100 # Max pixels to consider same ball
 
959
  kick_frame = frame
960
  break
961
 
962
+ result = {
963
  "centers": centers,
964
  "boxes": boxes,
965
  "confs": confs,
 
975
  "has_kick": kick_frame is not None,
976
  "coverage": len(centers) / total if total else 0.0,
977
  }
978
+
979
+ # Summary logging
980
+ kick_info = f"Kick @ frame {kick_frame}" if kick_frame else "No kick"
981
+ print(f"[_track_single_ball_candidate] Ball {candidate['id']} done: "
982
+ f"{len(centers)}/{total} frames ({result['coverage']:.0%}), "
983
+ f"max_vel={max_velocity:.1f}px/s, {kick_info}")
984
+
985
+ return result
986
 
987
 
988
  def _detect_and_track_all_ball_candidates(