Zion Boggan
repos/Pitch Tracker CV/tools/find_green.py
zionboggan.com ↗
73 lines · python
History for this file →
1
"""Scan a frame for bright saturated green clusters at several HSV tightnesses.
2
 
3
Reports clusters as (x, y, w, h, pixel_count) and saves a mask per tightness.
4
"""
5
from __future__ import annotations
6
 
7
import sys
8
from pathlib import Path
9
 
10
import cv2
11
import numpy as np
12
from rich.console import Console
13
 
14
console = Console()
15
 
16
def report_mask(frame: np.ndarray, hsv: np.ndarray, lo, hi, label: str, out_dir: Path, stem: str) -> None:
17
    lo_a = np.array(lo, np.uint8)
18
    hi_a = np.array(hi, np.uint8)
19
    mask = cv2.inRange(hsv, lo_a, hi_a)
20
    total = int((mask > 0).sum())
21
    if total == 0:
22
        console.print(f"[dim]{label}: 0 pixels match {list(lo)}..{list(hi)}[/dim]")
23
        return
24
 
25
    mask_c = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, np.ones((5, 5), np.uint8), iterations=1)
26
    num, labels_img, stats, _ = cv2.connectedComponentsWithStats(mask_c, connectivity=8)
27
    console.print(f"[bold]{label}: total={total}, clusters={num-1} at range {list(lo)}..{list(hi)}[/bold]")
28
    clusters = []
29
    for i in range(1, num):
30
        x, y, w, h, area = stats[i]
31
        if area < 8:
32
            continue
33
        clusters.append((area, x, y, w, h))
34
    clusters.sort(reverse=True)
35
    for area, x, y, w, h in clusters[:8]:
36
        console.print(f"  cluster: x={x}..{x+w} y={y}..{y+h} area={area}")
37
    out = out_dir / f"green_{label}_{stem}.png"
38
    cv2.imwrite(str(out), mask)
39
    console.print(f"  mask -> {out}")
40
 
41
    ann = frame.copy()
42
    for area, x, y, w, h in clusters[:8]:
43
        cv2.rectangle(ann, (x, y), (x + w, y + h), (255, 0, 255), 2)
44
    ann_out = out_dir / f"green_{label}_{stem}_ann.png"
45
    cv2.imwrite(str(ann_out), ann)
46
 
47
def main() -> int:
48
    if len(sys.argv) < 2:
49
        console.print("usage: find_green.py <frame.png>")
50
        return 2
51
    fp = Path(sys.argv[1])
52
    frame = cv2.imread(str(fp), cv2.IMREAD_COLOR)
53
    if frame is None:
54
        console.print(f"[red]can't read {fp}[/red]")
55
        return 2
56
 
57
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
58
    out_dir = Path(__file__).resolve().parents[1] / "logs"
59
    stem = fp.stem
60
 
61
    ranges = [
62
        ("strict",   [50, 240, 240], [70, 255, 255]),
63
        ("tight",    [48, 200, 200], [72, 255, 255]),
64
        ("medium",   [45, 150, 150], [75, 255, 255]),
65
        ("loose",    [40, 100, 100], [80, 255, 255]),
66
    ]
67
    for label, lo, hi in ranges:
68
        report_mask(frame, hsv, lo, hi, label, out_dir, stem)
69
        console.print("")
70
    return 0
71
 
72
if __name__ == "__main__":
73
    sys.exit(main())