Spaces:
Sleeping
Sleeping
File size: 11,628 Bytes
fbeda03 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 | # Flag Detection Implementation Plan
## Overview
This document outlines the implementation plan for detecting penalty flags ("FLAG" indicator) on the scorebug and using this information to enhance play capture. When a flag is thrown, the system should persist video capture beyond normal play boundaries to include the referee's penalty announcement.
---
## Requirements
### Core Requirements
1. **FLAG Region Selection**: User selects the "FLAG" region during initialization (same location as "1st and 10" marker on scorebug)
2. **Yellow Detection**: Search for strong yellow presence in the FLAG region throughout the video to detect when flags are thrown
3. **Decision Override**: FLAG detection should override any other play-capturing decisions:
- If a flag is detected but it looks like "weird clock behavior" (normally rejected), capture it anyway
- If a flag is detected during enforced quiet time, capture it anyway
- **All FLAG events should be captured, no matter what**
4. **Extended Capture Duration**: When a flag is thrown, persist video capture until EITHER:
- The yellow "FLAG" indicator disappears (scorebug is present but FLAG is gone)
- **OR** a configurable timeout (default: 15 seconds) after the play normally ends
- Note: If FLAG appears β replay β FLAG still visible when scorebug returns, continue capturing
5. **Pre-Play Penalties**: Capture penalties that occur before a play (false start, delay of game) even though no "real football play" occurred
6. **Single-Pass Detection**: FLAG detection must be done in the same pass as normal play detection (no second video pass)
### Expected FLAG Scenarios
| Scenario | Expected Behavior |
|----------|------------------|
| Flag during play | Extend capture until FLAG clears or timeout |
| Flag before play (false start, delay of game) | Capture as play even without snap |
| Flag on replay | Ignore FLAG during replay (no scorebug) |
| Multiple flags on same play | Capture extends to last FLAG clearing |
| Flag during quiet time | Override quiet time and capture |
| Flag with weird clock behavior | Override rejection and capture |
---
## Implementation Steps
### Step 1: Test Script for FLAG Region Selection β
COMPLETE
**Goal**: Create a short standalone script to interactively select the FLAG region and save it.
**File**: `scripts/test_flag_region_selection.py`
**Tasks**:
- [x] Load sample frames from the test video
- [x] Display frame with existing scorebug region highlighted for reference
- [x] Prompt user to click/drag to select the FLAG region (typically where "1st & 10" appears)
- [x] Save the selected region to `data/config/flag_region.json`
- [x] Display a cropped preview of the selected region for verification
**Output**: `data/config/flag_region.json`
```json
{
"flag_region": {
"x_offset": 701,
"y_offset": -27,
"width": 260,
"height": 35
}
}
```
### Step 2: Integrate FLAG Region into Main Pipeline
**Goal**: Add FLAG region selection to the `main.py` interactive setup flow.
**File Changes**:
- `src/config/models.py` - Add FLAG region fields to `SessionConfig`
- `src/ui/__init__.py` - Export new `select_flag_region` function
- `src/ui/api.py` - Add `select_flag_region` function (similar to `select_playclock_region`)
- `main.py` - Add FLAG region selection after timeout region selection (respect `--use-saved-regions`)
**SessionConfig Additions**:
```python
# FLAG region (relative to scorebug, like play clock)
flag_x_offset: int = 0
flag_y_offset: int = 0
flag_width: int = 0
flag_height: int = 0
```
### Step 3: FLAG Detection Ground Truth Script β
COMPLETE
**Goal**: Create a script to scan the video and identify potential FLAG timestamps for manual verification.
**File**: `scripts/find_flag_ground_truth.py`
**Tasks**:
- [x] Load FLAG region from config
- [x] Scan video at 2 FPS
- [x] For each frame, extract FLAG region and compute "yellow-ness" score
- [x] Apply section-level filters (duration, peak yellow, avg yellow, mean hue)
- [x] Merge sections into FLAG events with gap tolerance for replays
- [x] Output candidates to `output/cache/flag_candidates.json`
**Results** (from `docs/flag_ground_truth.md`):
- **Precision**: 100% (8/8 true positives, 0 false positives)
- **Recall**: 100%
- **Key filters**: min 3s duration, peak>=70%, avg>=60%, hue>=22 (rejects orange)
### Step 4: Create FlagReader Module
**Goal**: Implement the core FLAG reading logic.
**File**: `src/readers/flags.py` (NOT detection/flags.py - FlagReader goes in readers)
**Class**: `FlagReader`
**Key Methods**:
```python
class FlagReader:
"""Reads FLAG indicator from scorebug using yellow color detection."""
# Detection parameters (from ground truth analysis)
YELLOW_HUE_MIN = 15
YELLOW_HUE_MAX = 35
YELLOW_SAT_MIN = 100
YELLOW_VAL_MIN = 100
YELLOW_RATIO_THRESHOLD = 0.25
MIN_MEAN_HUE = 22 # Distinguishes yellow from orange
def __init__(self, flag_region_offset: Tuple[int, int, int, int]):
"""Initialize with configured FLAG region offset (relative to scorebug)."""
def read(self, frame: np.ndarray, scorebug_bbox: Tuple[int, int, int, int]) -> FlagReading:
"""
Read FLAG status from a frame.
Args:
frame: Full video frame (BGR)
scorebug_bbox: Current scorebug bounding box (x, y, w, h)
Returns:
FlagReading with detected status, yellow_ratio, and mean_hue
"""
```
**Reading Model**:
```python
class FlagReading(BaseModel):
"""Result of reading FLAG region."""
detected: bool
yellow_ratio: float
mean_hue: float
is_valid_yellow: bool # True if mean_hue >= MIN_MEAN_HUE (not orange)
```
### Step 5: Integrate FLAG Detection into Play Tracking
**Goal**: Modify the play tracker to use FLAG detection for extended capture.
**File Changes**:
- `src/tracking/models.py` - Add `FlagInfo` model and flag-related fields to state
- `src/tracking/play_tracker.py` - Add FLAG state tracking
- `src/tracking/normal_play_tracker.py` - Modify end-of-play logic for FLAG override
- `src/pipeline/play_extractor.py` - Integrate FlagReader into frame processing
**Key Logic Changes**:
1. **FlagInfo Model**:
```python
class FlagInfo(BaseModel):
"""Flag information for a frame."""
detected: bool = False
yellow_ratio: float = 0.0
mean_hue: float = 0.0
is_valid_yellow: bool = False
```
2. **State Tracking Additions**:
```python
# In NormalTrackerState
flag_detected_at: Optional[float] = None # When FLAG first appeared
flag_last_seen_at: Optional[float] = None # Last frame with FLAG visible
flag_cleared_at: Optional[float] = None # When FLAG disappeared (scorebug present, no FLAG)
```
3. **Play End Override Logic**:
```python
def _should_extend_for_flag(self, timestamp: float, scorebug_visible: bool, flag_detected: bool) -> bool:
"""
Determine if play capture should extend due to FLAG.
Returns True if:
- FLAG was seen during this play AND
- Either FLAG is still visible OR we're within the flag timeout window
"""
```
4. **Override quiet time and weird clock behavior**:
- If FLAG detected, ignore quiet_time restrictions
- If FLAG detected, capture even if clock behavior looks weird
### Step 6: Add Configuration Options
**Goal**: Make FLAG-related parameters configurable.
**File Changes**:
- `src/tracking/models.py` - Add to `TrackPlayStateConfig`
**Configuration Additions**:
```python
class TrackPlayStateConfig(BaseModel):
# ... existing fields ...
# FLAG detection settings
flag_extension_timeout: float = Field(15.0, description="Max seconds to extend capture after FLAG detected")
flag_yellow_threshold: float = Field(0.25, description="Yellow pixel ratio threshold for FLAG detection")
flag_min_mean_hue: float = Field(22.0, description="Min mean hue to distinguish yellow from orange")
capture_pre_play_penalties: bool = Field(True, description="Capture penalties without plays (false start, etc.)")
```
### Step 7: Test FLAG Detection
**Goal**: Validate FLAG detection against ground truth.
**File**: `scripts/test_flag_detection.py`
**Note**: Ground truth already validated in Step 3 with 100% precision/recall.
### Step 8: Integration Testing
**Goal**: Test full pipeline with FLAG detection enabled.
**Test Cases**:
1. Standard play with flag β extended capture includes penalty announcement
2. Pre-play penalty (false start) β captured as play
3. Multiple flags in quick succession β handles correctly
4. Flag during replay β not double-counted
5. Plays without flags β unchanged behavior
---
## File Structure After Implementation
```
src/
βββ config/
β βββ models.py # + flag region fields in SessionConfig
βββ readers/
β βββ __init__.py # + export FlagReader
β βββ flags.py # NEW: FlagReader, FlagReading
β βββ models.py # + FlagReading model
βββ tracking/
β βββ models.py # + FlagInfo, flag state fields
β βββ play_tracker.py # + FLAG coordination
β βββ normal_play_tracker.py # + FLAG override logic
βββ ui/
β βββ __init__.py # + export select_flag_region
β βββ api.py # + select_flag_region function
βββ pipeline/
βββ play_extractor.py # + FlagReader integration
data/config/
βββ flag_region.json # Default FLAG region config
scripts/
βββ test_flag_region_selection.py # Step 1 β
βββ find_flag_ground_truth.py # Step 3 β
```
---
## Implementation Order
| Step | Description | Dependencies | Status |
|------|-------------|--------------|--------|
| 1 | Test FLAG region selection script | None | β
Complete |
| 2 | Integrate into main.py | Step 1 | In Progress |
| 3 | Ground truth scanning script | Step 1 | β
Complete |
| 4 | FlagReader module | Step 1 | In Progress |
| 5 | Play tracker integration | Steps 3, 4 | In Progress |
| 6 | Configuration options | Step 5 | In Progress |
| 7 | Detection testing | Steps 3, 4 | Skipped (100% in Step 3) |
| 8 | Integration testing | Steps 5, 6 | Pending |
---
## Success Criteria
1. **Region Selection**: User can select FLAG region in <30 seconds during setup β
2. **Detection Accuracy**: β₯90% recall on verified ground truth FLAGS β
(100%)
3. **False Positive Rate**: <10% of detected FLAGS are false positives β
(0%)
4. **Extended Capture**: FLAG plays include penalty announcement in 90%+ of cases
5. **Pre-Play Penalties**: False starts and delay of game penalties captured
6. **No Regressions**: Existing play detection performance unchanged for non-FLAG plays
7. **Single Pass**: No second video pass required β
(integrated into main processing)
---
## Validated Detection Parameters
From ground truth analysis (`docs/flag_ground_truth.md`):
```python
# Yellow detection (HSV color space)
YELLOW_HUE_MIN = 15
YELLOW_HUE_MAX = 35
YELLOW_SAT_MIN = 100
YELLOW_VAL_MIN = 100
# Detection threshold
YELLOW_RATIO_THRESHOLD = 0.25 # 25% of region must be yellow
# Orange discrimination
MIN_MEAN_HUE = 22 # Rejects orange (hue ~16-17), accepts yellow (hue ~28-29)
```
### Orange vs Yellow Discrimination
| Metric | True FLAG Yellow | False Positive Orange |
|--------|-----------------|----------------------|
| Mean Hue | 27-29 | 16-17 |
| Mean Value | 205-207 (bright) | 161-162 (darker) |
The MIN_MEAN_HUE filter is essential for distinguishing true FLAG yellow from team colors (e.g., Tennessee orange).
|