| 1 | """Capture live frames for one-class MLB baseball YOLO training. |
| 2 | |
| 3 | Saves raw frames to datasets/mlb26_ball_yolo/images/all. Label these images |
| 4 | with class 0 = ball, then run yolo_split_dataset.py. |
| 5 | """ |
| 6 | from __future__ import annotations |
| 7 | |
| 8 | import argparse |
| 9 | import shutil |
| 10 | import sys |
| 11 | import time |
| 12 | from pathlib import Path |
| 13 | |
| 14 | import cv2 |
| 15 | from rich.console import Console |
| 16 | |
| 17 | sys.path.insert(0, str(Path(__file__).resolve().parents[1])) |
| 18 | from capture.ingest import open_capture |
| 19 | from cv._common import load_config |
| 20 | |
| 21 | ROOT = Path(__file__).resolve().parents[1] |
| 22 | OUT_DIR = ROOT / "datasets" / "mlb26_ball_yolo" / "images" / "all" |
| 23 | console = Console() |
| 24 | |
| 25 | def main() -> int: |
| 26 | ap = argparse.ArgumentParser(description="Capture frames for YOLO ball labeling.") |
| 27 | ap.add_argument("--duration", type=float, default=120.0, help="Capture duration in seconds.") |
| 28 | ap.add_argument("--fps", type=float, default=12.0, help="Saved frame rate.") |
| 29 | ap.add_argument("--clear", action="store_true", help="Clear images/all before capture.") |
| 30 | ap.add_argument("--prefix", default="mlb26", help="Output filename prefix.") |
| 31 | args = ap.parse_args() |
| 32 | |
| 33 | if args.clear and OUT_DIR.exists(): |
| 34 | shutil.rmtree(OUT_DIR) |
| 35 | OUT_DIR.mkdir(parents=True, exist_ok=True) |
| 36 | |
| 37 | cap = open_capture(load_config()) |
| 38 | if cap is None or not cap.isOpened(): |
| 39 | console.print("[red]Could not open capture card.[/red]") |
| 40 | return 2 |
| 41 | |
| 42 | interval = 1.0 / max(args.fps, 0.1) |
| 43 | deadline = time.perf_counter() + args.duration |
| 44 | next_save = time.perf_counter() |
| 45 | saved = 0 |
| 46 | read_count = 0 |
| 47 | |
| 48 | console.print(f"[cyan]Capturing {args.duration:.0f}s at {args.fps:.1f} saved fps -> {OUT_DIR}[/cyan]") |
| 49 | try: |
| 50 | while time.perf_counter() < deadline: |
| 51 | ok, frame = cap.read() |
| 52 | if not ok or frame is None: |
| 53 | continue |
| 54 | read_count += 1 |
| 55 | now = time.perf_counter() |
| 56 | if now < next_save: |
| 57 | continue |
| 58 | out = OUT_DIR / f"{args.prefix}_{int(time.time() * 1000)}_{saved:05d}.jpg" |
| 59 | cv2.imwrite(str(out), frame, [cv2.IMWRITE_JPEG_QUALITY, 95]) |
| 60 | saved += 1 |
| 61 | next_save = now + interval |
| 62 | if saved % 50 == 0: |
| 63 | console.print(f" saved {saved} frames") |
| 64 | finally: |
| 65 | cap.release() |
| 66 | |
| 67 | console.print(f"[green]Saved {saved} frames. Capture reads: {read_count}.[/green]") |
| 68 | return 0 |
| 69 | |
| 70 | if __name__ == "__main__": |
| 71 | raise SystemExit(main()) |