Spaces:
Running
Running
File size: 5,188 Bytes
6f3fe10 22df1ea 6f3fe10 22df1ea 12b424e 8e8d804 12b424e 8e8d804 e17df6f 22df1ea e17df6f 22df1ea 6f3fe10 22df1ea 6f3fe10 22df1ea | 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 | """Validate SAM card detection (classic vs prompt) on sample-04-12.
Prompt-based SAM depends on MediaPipe running first to provide a hand mask
for seed derivation, so we run `segment_hand()` on each image before timing
the two detectors.
Outputs per-image rows and a summary with success counts + mean wall time.
Debug overlays saved under `output/sam_val/<stem>/`.
"""
from __future__ import annotations
import sys
import time
import traceback
from pathlib import Path
import cv2
import numpy as np
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from src.card_detection import compute_scale_factor, detect_credit_card # noqa: E402
from src.finger_segmentation import segment_hand # noqa: E402
from src.sam_card_detection import ( # noqa: E402
detect_credit_card_sam_prompt,
suggest_card_seeds,
)
SAMPLE_DIR = Path("input/sample-04-12")
OUT_DIR = Path("output/sam_val")
def _negatives_from_landmarks(landmarks: np.ndarray):
palm_idx = [0, 5, 9, 13, 17]
c = np.mean(landmarks[palm_idx, :2], axis=0)
return [(int(round(c[0])), int(round(c[1])))]
def run_one(img_path: Path) -> dict:
bgr = cv2.imread(str(img_path))
if bgr is None:
return {"file": img_path.name, "error": "load_failed"}
rec = {"file": img_path.name, "shape": bgr.shape[:2]}
# --- MediaPipe + SAM hand (needed for prompt-SAM seeds) ---
t0 = time.time()
try:
hand_data = segment_hand(bgr, finger="index", use_sam_mask=True)
except Exception as e:
hand_data = None
rec["hand_error"] = repr(e)[:120]
rec["hand_time_s"] = round(time.time() - t0, 2)
if hand_data is None:
rec["hand_detected"] = False
canonical = bgr
else:
rec["hand_detected"] = True
canonical = hand_data.get("canonical_image", bgr)
# --- Classic ---
t0 = time.time()
try:
classic = detect_credit_card(canonical)
if classic is not None:
px_cm, _ = compute_scale_factor(classic["corners"])
rec["classic_px_per_cm"] = px_cm
else:
rec["classic_px_per_cm"] = None
except Exception as e:
rec["classic_error"] = repr(e)[:120]
rec["classic_time_s"] = round(time.time() - t0, 2)
# --- SAM prompt ---
rec["prompt_px_per_cm"] = None
rec["prompt_time_s"] = None
if hand_data is not None:
prompt_debug = OUT_DIR / img_path.stem / "sam_card_prompt"
landmarks = hand_data.get("landmarks")
if landmarks is None or len(landmarks) <= 9:
return rec
middle_mcp = landmarks[9, :2]
y_limit = int(round(middle_mcp[1]))
seed_info = suggest_card_seeds(hand_data["mask"], canonical.shape[:2], y_limit)
seeds = seed_info["kept"]
rec["prompt_n_seeds"] = len(seeds)
negs = _negatives_from_landmarks(hand_data["landmarks"])
t0 = time.time()
try:
pr = detect_credit_card_sam_prompt(
canonical,
seed_points=seeds,
negative_points=negs,
debug_dir=str(prompt_debug),
hand_mask=hand_data["mask"],
)
if pr is not None:
px_cm, _ = compute_scale_factor(pr["corners"])
rec["prompt_px_per_cm"] = px_cm
except Exception as e:
rec["prompt_error"] = repr(e)[:120]
traceback.print_exc()
rec["prompt_time_s"] = round(time.time() - t0, 2)
return rec
def main() -> int:
OUT_DIR.mkdir(parents=True, exist_ok=True)
images = sorted(SAMPLE_DIR.glob("*.jpg"))
if not images:
print(f"No images found in {SAMPLE_DIR}")
return 1
print(f"Validating {len(images)} images from {SAMPLE_DIR}\n")
results = []
for img in images:
print(f"=== {img.name} ===")
rec = run_one(img)
results.append(rec)
print(rec)
print()
# --- Summary table ---
print("\n===== SUMMARY =====")
header = (
f"{'file':<18}"
f"{'classic':>10}{'classicT':>10}"
f"{'prompt':>10}{'promptT':>10}"
)
print(header)
print("-" * len(header))
counts = {"classic": 0, "prompt": 0}
times = {"classic": [], "prompt": []}
for r in results:
def _fmt(v, fmt="{:.2f}"):
return fmt.format(v) if v is not None else "FAIL"
c = r.get("classic_px_per_cm")
p = r.get("prompt_px_per_cm")
ct = r.get("classic_time_s")
pt = r.get("prompt_time_s")
print(
f"{r['file']:<18}"
f"{_fmt(c):>10}{_fmt(ct):>10}"
f"{_fmt(p):>10}{_fmt(pt):>10}"
)
if c is not None:
counts["classic"] += 1
times["classic"].append(ct)
if p is not None:
counts["prompt"] += 1
times["prompt"].append(pt)
n = len(results)
print("-" * len(header))
for k in ("classic", "prompt"):
ok = counts[k]
mean_t = (sum(times[k]) / len(times[k])) if times[k] else float("nan")
print(f"{k:<8} success: {ok}/{n} mean_time_s: {mean_t:.2f}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
|