Mirko Trasciatti
commited on
Commit
·
bd71d20
1
Parent(s):
0327f39
Add 5% confidence threshold, limit to top 5 candidates, add tracking logs
Browse filesDetection:
- 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
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.
|
| 139 |
iou_threshold: float = YOLO_IOU_THRESHOLD,
|
| 140 |
-
max_detections: int = 10, #
|
| 141 |
-
|
| 142 |
-
roi_x_max: float = 1.0,
|
| 143 |
) -> list[dict]:
|
| 144 |
"""
|
| 145 |
Detect all ball candidates in a frame.
|
| 146 |
|
| 147 |
-
|
|
|
|
|
|
|
| 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 |
-
#
|
|
|
|
|
|
|
|
|
|
| 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']:.
|
| 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
|
| 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 |
-
|
| 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(
|