Files
Shape_Model_2D/benchmarks/test_suite.py
Adriano ba54b42fdc perf: spread bitmap uint8 + pre-NMS prima refine (3.5x globale, 49x worst case)
Due ottimizzazioni chiave:

1. Spread bitmap uint8 invece di response map (N_BINS, H, W) float32
   - 32x meno memoria, cache-friendly
   - Nuovi kernel Numba: _jit_score_bitmap, _jit_popcount_density
   - Formato: spread[y,x] bit b = bin b attivo nel raggio di spread
   - _refine_angle usa slicing su bitmap con mask & bit

2. Pre-NMS prima di refine_angle/verify_ncc
   - Problema: loop 'for raw in candidati' applicava refine+verify A OGNI
     candidato prima del check NMS → 2000+ refine chiamati per ~25 match
   - Fix: pre-NMS su (cx, cy) subpixel, limita a max_matches*3 candidati,
     poi refine + verify solo su quelli
   - Esempio worst case: lama_full_fast 55.9s → 1.13s (49x)

Benchmark suite 16 scenari (4 immagini x full/part x fast/preciso):
  prima: totale find 94.6s
  dopo:  totale find 27.3s (3.5x globale)
  casi peggiori <5s (prima erano >50s)

ROI parziali (solo metà oggetto) funzionano in tutti i casi.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 02:11:33 +02:00

97 lines
3.2 KiB
Python

"""Test suite esaustivo su Test/*.png con varie configurazioni.
Esegue matrix (immagine, ROI completa/parziale, config) e stampa tempi/match.
"""
from __future__ import annotations
import time
from pathlib import Path
import cv2
import numpy as np
from pm2d import LineShapeMatcher
from pm2d.gui import draw_matches
TEST_DIR = Path(__file__).parent.parent / "Test"
OUT_DIR = Path("/tmp/pm2d_suite"); OUT_DIR.mkdir(exist_ok=True)
# Casi: (nome, immagine, (y0,y1,x0,x1) roi completa, (y0,y1,x0,x1) roi parziale)
CASES = [
("clip", "clip.png", ( 60, 200, 90, 290), ( 60, 135, 90, 290)),
("ruota", "rings_and_nuts.png", ( 55, 175, 90, 215), ( 55, 115, 90, 215)),
("dado", "rings_and_nuts.png", (255, 375, 40, 170), (255, 315, 40, 170)),
("lama", "razors2.png", ( 90, 370, 120, 160), ( 90, 230, 120, 160)),
]
CONFIGS = [
("fast", dict(angle_step_deg=10.0, scale_range=(1.0, 1.0),
pyramid_levels=3, num_features=64)),
("preciso", dict(angle_step_deg=5.0, scale_range=(0.5, 1.1), scale_step=0.05,
pyramid_levels=3, num_features=96)),
]
def bench(case_name: str, img_path: str, roi_box: tuple, roi_kind: str,
cfg_name: str, cfg: dict) -> dict:
scene = cv2.imread(str(TEST_DIR / img_path))
y0, y1, x0, x1 = roi_box
roi = scene[y0:y1, x0:x1].copy()
m = LineShapeMatcher(
angle_range_deg=(0.0, 360.0),
weak_grad=30, strong_grad=60,
spread_radius=5, n_threads=4, **cfg,
)
t0 = time.time()
n_var = m.train(roi)
t_train = time.time() - t0
# warmup (prima call è JIT compile)
m.find(scene, min_score=0.55, max_matches=3, refine_angle=False)
t0 = time.time()
matches = m.find(
scene, min_score=0.55, max_matches=25, nms_radius=None,
refine_angle=True, subpixel=True, verify_threshold=0.4,
)
t_find = time.time() - t0
tag = f"{case_name}_{roi_kind}_{cfg_name}"
overlay = draw_matches(scene, matches,
template_gray=cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY))
cv2.imwrite(str(OUT_DIR / f"{tag}.png"), overlay)
return {
"case": tag,
"roi": f"{roi.shape[1]}x{roi.shape[0]}",
"variants": n_var,
"train_s": t_train,
"find_s": t_find,
"n_match": len(matches),
"score_range": (
f"{min(x.score for x in matches):.2f}..{max(x.score for x in matches):.2f}"
if matches else "-"
),
}
def main():
print(f"{'case':30s} {'roi':>9s} {'var':>4s} "
f"{'train':>6s} {'find':>6s} {'n':>3s} score")
print("-" * 85)
total_find = 0.0
for case_name, img, roi_full, roi_part in CASES:
for roi_kind, roi_box in [("full", roi_full), ("part", roi_part)]:
for cfg_name, cfg in CONFIGS:
r = bench(case_name, img, roi_box, roi_kind, cfg_name, cfg)
print(f"{r['case']:30s} {r['roi']:>9s} {r['variants']:>4d} "
f"{r['train_s']:>5.2f}s {r['find_s']:>5.2f}s "
f"{r['n_match']:>3d} {r['score_range']}")
total_find += r["find_s"]
print("-" * 85)
print(f"totale find: {total_find:.1f}s overlay salvati in {OUT_DIR}/")
if __name__ == "__main__":
main()