Zion Boggan
repos/Pitch Tracker CV/tools/extract_pci_template.py
zionboggan.com ↗
96 lines · python
History for this file →
1
"""Extract a PCI template from a frame.
2
 
3
Uses HSV green masking to find the PCI cluster near frame center, then crops
4
a bounding box around it and saves it as configs/templates/pci_circle.png
5
so pci_tracker.py will auto-switch to template matching.
6
 
7
Also saves the HSV mask as logs/pci_mask_<frame>.png so you can see what the
8
green threshold is picking up.
9
"""
10
from __future__ import annotations
11
 
12
import argparse
13
import sys
14
from pathlib import Path
15
 
16
import cv2
17
import numpy as np
18
from rich.console import Console
19
 
20
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
21
from cv._common import load_config
22
 
23
console = Console()
24
ROOT = Path(__file__).resolve().parents[1]
25
TEMPLATE_DIR = ROOT / "configs" / "templates"
26
LOG_DIR = ROOT / "logs"
27
 
28
def main() -> int:
29
    ap = argparse.ArgumentParser(description="Extract PCI template from a frame.")
30
    ap.add_argument("frame_path", help="Path to a captured frame PNG.")
31
    ap.add_argument("--pad", type=int, default=10, help="Pixel margin around bbox.")
32
    ap.add_argument("--center-crop-frac", type=float, default=0.6,
33
                    help="Restrict search to the central fraction of the frame (0..1) to avoid UI green.")
34
    args = ap.parse_args()
35
 
36
    frame_path = Path(args.frame_path)
37
    frame = cv2.imread(str(frame_path), cv2.IMREAD_COLOR)
38
    if frame is None:
39
        console.print(f"[red]Could not read {frame_path}[/red]")
40
        return 2
41
 
42
    cfg = load_config()
43
    pci_cfg = cfg["cv"]["pci"]
44
    hsv_low = np.array(pci_cfg["hsv_low"], dtype=np.uint8)
45
    hsv_high = np.array(pci_cfg["hsv_high"], dtype=np.uint8)
46
 
47
    h, w = frame.shape[:2]
48
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
49
    mask = cv2.inRange(hsv, hsv_low, hsv_high)
50
 
51
    cf = float(args.center_crop_frac)
52
    cw = int(w * cf); ch = int(h * cf)
53
    x0 = (w - cw) // 2; y0 = (h - ch) // 2
54
    center_mask = np.zeros_like(mask)
55
    center_mask[y0:y0 + ch, x0:x0 + cw] = 255
56
    mask_c = cv2.bitwise_and(mask, center_mask)
57
 
58
    mask_c = cv2.morphologyEx(mask_c, cv2.MORPH_CLOSE, np.ones((15, 15), np.uint8), iterations=2)
59
    mask_c = cv2.morphologyEx(mask_c, cv2.MORPH_OPEN, np.ones((3, 3), np.uint8), iterations=1)
60
 
61
    ys, xs = np.where(mask_c > 0)
62
    if len(xs) == 0:
63
        console.print("[red]No green pixels found in central region - check HSV range or center-crop.[/red]")
64
        mask_out = LOG_DIR / f"pci_mask_{frame_path.stem}.png"
65
        cv2.imwrite(str(mask_out), mask_c)
66
        console.print(f"[yellow]Saved (empty) mask -> {mask_out}[/yellow]")
67
        return 2
68
 
69
    x_min, x_max = int(xs.min()), int(xs.max())
70
    y_min, y_max = int(ys.min()), int(ys.max())
71
    x_min = max(0, x_min - args.pad)
72
    y_min = max(0, y_min - args.pad)
73
    x_max = min(w - 1, x_max + args.pad)
74
    y_max = min(h - 1, y_max + args.pad)
75
 
76
    console.print(f"PCI bbox: x=({x_min}..{x_max})  y=({y_min}..{y_max})  "
77
                  f"w={x_max-x_min}  h={y_max-y_min}  green_px={len(xs)}")
78
 
79
    crop = frame[y_min:y_max + 1, x_min:x_max + 1].copy()
80
    TEMPLATE_DIR.mkdir(parents=True, exist_ok=True)
81
    tpl_out = TEMPLATE_DIR / "pci_circle.png"
82
    cv2.imwrite(str(tpl_out), crop)
83
    console.print(f"[green]Saved template -> {tpl_out}  ({crop.shape[1]}x{crop.shape[0]})[/green]")
84
 
85
    ann = frame.copy()
86
    cv2.rectangle(ann, (x_min, y_min), (x_max, y_max), (0, 255, 0), 2)
87
    ann_out = LOG_DIR / f"pci_extract_{frame_path.stem}.png"
88
    cv2.imwrite(str(ann_out), ann)
89
    mask_out = LOG_DIR / f"pci_mask_{frame_path.stem}.png"
90
    cv2.imwrite(str(mask_out), mask_c)
91
    console.print(f"[green]Saved annotated -> {ann_out}[/green]")
92
    console.print(f"[green]Saved mask      -> {mask_out}[/green]")
93
    return 0
94
 
95
if __name__ == "__main__":
96
    sys.exit(main())