76 lines
2.6 KiB
Python
76 lines
2.6 KiB
Python
"""Soccer broadcast profile — pitch hoardings, kits, scoreboards."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from detect.models import BrandDetection, BrandStats, DetectionReport, PipelineStats
|
|
|
|
from .base import (
|
|
CropContext,
|
|
DetectionConfig,
|
|
FrameExtractionConfig,
|
|
OCRConfig,
|
|
ResolverConfig,
|
|
SceneFilterConfig,
|
|
)
|
|
|
|
|
|
class SoccerBroadcastProfile:
|
|
name = "soccer_broadcast"
|
|
|
|
def frame_extraction_config(self) -> FrameExtractionConfig:
|
|
return FrameExtractionConfig(fps=2.0, max_frames=500)
|
|
|
|
def scene_filter_config(self) -> SceneFilterConfig:
|
|
return SceneFilterConfig(hamming_threshold=8, enabled=True)
|
|
|
|
def detection_config(self) -> DetectionConfig:
|
|
return DetectionConfig(
|
|
model_name="yolov8n.pt",
|
|
confidence_threshold=0.3,
|
|
target_classes=[], # empty = accept all COCO classes (until custom model)
|
|
)
|
|
|
|
def ocr_config(self) -> OCRConfig:
|
|
return OCRConfig(languages=["en", "es"], min_confidence=0.5)
|
|
|
|
def resolver_config(self) -> ResolverConfig:
|
|
return ResolverConfig(fuzzy_threshold=75)
|
|
|
|
def vlm_prompt(self, crop_context: CropContext) -> str:
|
|
hint = f" Position: {crop_context.position_hint}." if crop_context.position_hint else ""
|
|
text = f" Nearby text: '{crop_context.surrounding_text}'." if crop_context.surrounding_text else ""
|
|
return (
|
|
f"Identify the brand or sponsor visible in this cropped region "
|
|
f"from a soccer broadcast.{hint}{text} "
|
|
f"Respond with: brand, confidence (0-1), reasoning."
|
|
)
|
|
|
|
def aggregate(self, detections: list[BrandDetection]) -> DetectionReport:
|
|
brands: dict[str, BrandStats] = {}
|
|
for d in detections:
|
|
if d.brand not in brands:
|
|
brands[d.brand] = BrandStats()
|
|
s = brands[d.brand]
|
|
s.total_appearances += 1
|
|
s.total_screen_time += d.duration
|
|
s.avg_confidence = (
|
|
(s.avg_confidence * (s.total_appearances - 1) + d.confidence)
|
|
/ s.total_appearances
|
|
)
|
|
if s.first_seen == 0.0 or d.timestamp < s.first_seen:
|
|
s.first_seen = d.timestamp
|
|
if d.timestamp > s.last_seen:
|
|
s.last_seen = d.timestamp
|
|
|
|
return DetectionReport(
|
|
video_source="",
|
|
content_type=self.name,
|
|
duration_seconds=0.0,
|
|
brands=brands,
|
|
timeline=sorted(detections, key=lambda d: d.timestamp),
|
|
pipeline_stats=PipelineStats(),
|
|
)
|
|
|
|
def auxiliary_detections(self, source: str) -> list[BrandDetection]:
|
|
return []
|