6d6dcc3b7a
4 ottimizzazioni performance + visibilita': GGG. find(profile=True) → timing per fase - _checkpoint() registra ms tra: to_gray, spread_top, top_pruning, full_kernel, refine_verify_nms - get_last_profile() ritorna dict ms per identificare bottleneck - Costo runtime trascurabile (~5 us per call) HHH. pm2d.bench - benchmark suite eseguibile - 3 scenarios (rect/L/circle x scene clean/cluttered) - 5 configs (baseline, polarity, propagate, greedy, stride) - Auto-aggiunge gpu_umat se opencl_available() - Tabella ms/find + profile per ogni combo - Entry-point pm2d-bench (--quick per smoke test 2 iter) XX. Skip dilate per bin vuoti in _spread_bitmap - Pre-calcolo bin presenti via np.unique sui pixel valid - Su scene a bassa varianza orientation skip 50-70% delle dilate - Misurato benchmark: spread_top da ~0.3ms a ~0.1ms in molti casi VV. Variant pruning preliminare via histogramma orientation - Per ogni variante calcolo overlap (feature bins ∩ scene bins) / total feature bins - Se overlap < 0.5 * min_score → skip variante (no kernel call) - Counter n_variants_pruned_histogram nel diag - Vantaggio: scene focalizzate (poche direzioni dominanti) skippano varianti template con bin assenti dalla scena Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
180 lines
5.5 KiB
Python
180 lines
5.5 KiB
Python
"""Benchmark suite per LineShapeMatcher.
|
||
|
||
Usage:
|
||
python -m pm2d.bench [--quick]
|
||
|
||
Misura tempi find() su 3 template-tipo × 3 scene-tipo × N config:
|
||
- Template: rettangolo 80×80, L-shape 120×120, cerchio 150×150
|
||
- Scene: pulita 800×600, cluttered 1080×1920, multi-pezzo 1080×1920
|
||
- Config: baseline, polarity, gpu, pyramid_propagate, greediness=0.7
|
||
|
||
Per ogni config stampa: ms/find, ms per fase (profile), n. match.
|
||
Output tabellare per detectare regressioni in CI.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import time
|
||
|
||
import cv2
|
||
import numpy as np
|
||
|
||
from pm2d.line_matcher import LineShapeMatcher, opencl_available
|
||
|
||
|
||
# ---------- Sintetizzatori template/scena ----------
|
||
|
||
def _tpl_rect() -> np.ndarray:
|
||
t = np.zeros((80, 80, 3), np.uint8)
|
||
cv2.rectangle(t, (15, 15), (65, 65), (255, 255, 255), 3)
|
||
return t
|
||
|
||
|
||
def _tpl_lshape() -> np.ndarray:
|
||
t = np.zeros((120, 120, 3), np.uint8)
|
||
cv2.rectangle(t, (20, 20), (50, 100), (255, 255, 255), -1)
|
||
cv2.rectangle(t, (20, 70), (100, 100), (255, 255, 255), -1)
|
||
return t
|
||
|
||
|
||
def _tpl_circle() -> np.ndarray:
|
||
t = np.zeros((150, 150, 3), np.uint8)
|
||
cv2.circle(t, (75, 75), 60, (255, 255, 255), 4)
|
||
return t
|
||
|
||
|
||
def _scene_clean(W: int, H: int, n_pieces: int = 1) -> np.ndarray:
|
||
np.random.seed(0)
|
||
s = np.zeros((H, W, 3), np.uint8)
|
||
for _ in range(n_pieces):
|
||
cx = np.random.randint(80, W - 80)
|
||
cy = np.random.randint(80, H - 80)
|
||
cv2.rectangle(s, (cx - 25, cy - 25), (cx + 25, cy + 25), (255, 255, 255), 3)
|
||
return s
|
||
|
||
|
||
def _scene_cluttered(W: int, H: int) -> np.ndarray:
|
||
np.random.seed(0)
|
||
s = np.random.randint(50, 200, (H, W, 3), np.uint8)
|
||
cv2.rectangle(s, (300, 200), (350, 250), (255, 255, 255), 3)
|
||
cv2.rectangle(s, (1500, 800), (1550, 850), (255, 255, 255), 3)
|
||
return s
|
||
|
||
|
||
# ---------- Single benchmark ----------
|
||
|
||
def _bench_config(template, scene, config_name: str,
|
||
init_kw: dict, find_kw: dict,
|
||
n_iter: int = 5) -> dict:
|
||
m = LineShapeMatcher(**init_kw)
|
||
t0 = time.perf_counter()
|
||
n_var = m.train(template)
|
||
t_train = time.perf_counter() - t0
|
||
|
||
# Warmup (Numba JIT)
|
||
m.find(scene, **find_kw)
|
||
m.find(scene, **find_kw)
|
||
|
||
# Run
|
||
times_ms = []
|
||
for _ in range(n_iter):
|
||
t0 = time.perf_counter()
|
||
matches = m.find(scene, **find_kw)
|
||
times_ms.append((time.perf_counter() - t0) * 1000.0)
|
||
|
||
# Profile (1 iter)
|
||
m.find(scene, profile=True, **find_kw)
|
||
prof = m.get_last_profile() or {}
|
||
|
||
return {
|
||
"config": config_name,
|
||
"n_variants": n_var,
|
||
"t_train_s": round(t_train, 3),
|
||
"ms_avg": round(float(np.mean(times_ms)), 1),
|
||
"ms_min": round(float(np.min(times_ms)), 1),
|
||
"ms_max": round(float(np.max(times_ms)), 1),
|
||
"n_matches": len(matches),
|
||
"profile_ms": {k: round(v, 1) for k, v in prof.items()},
|
||
}
|
||
|
||
|
||
# ---------- Suite ----------
|
||
|
||
CONFIGS = [
|
||
("baseline",
|
||
{"angle_step_deg": 10, "pyramid_levels": 2},
|
||
{"min_score": 0.4, "verify_threshold": 0.2}),
|
||
("polarity",
|
||
{"angle_step_deg": 10, "pyramid_levels": 2, "use_polarity": True},
|
||
{"min_score": 0.4, "verify_threshold": 0.2}),
|
||
("propagate",
|
||
{"angle_step_deg": 10, "pyramid_levels": 3},
|
||
{"min_score": 0.4, "verify_threshold": 0.2,
|
||
"pyramid_propagate": True, "propagate_topk": 4}),
|
||
("greedy_07",
|
||
{"angle_step_deg": 10, "pyramid_levels": 2},
|
||
{"min_score": 0.4, "verify_threshold": 0.2, "greediness": 0.7}),
|
||
("stride2",
|
||
{"angle_step_deg": 10, "pyramid_levels": 2},
|
||
{"min_score": 0.4, "verify_threshold": 0.2, "coarse_stride": 2}),
|
||
]
|
||
|
||
if opencl_available():
|
||
CONFIGS.append(
|
||
("gpu_umat",
|
||
{"angle_step_deg": 10, "pyramid_levels": 2, "use_gpu": True},
|
||
{"min_score": 0.4, "verify_threshold": 0.2})
|
||
)
|
||
|
||
|
||
SCENARIOS = [
|
||
("rect_80 vs scene_800x600", _tpl_rect, lambda: _scene_clean(800, 600, 1)),
|
||
("lshape_120 vs scene_1080x1920_clutter",
|
||
_tpl_lshape, lambda: _scene_cluttered(1920, 1080)),
|
||
("circle_150 vs scene_clean_3pieces",
|
||
_tpl_circle, lambda: _scene_clean(1920, 1080, 3)),
|
||
]
|
||
|
||
|
||
def run(quick: bool = False) -> int:
|
||
n_iter = 2 if quick else 5
|
||
print(f"=== PM2D Benchmark Suite ({len(SCENARIOS)} scenarios x "
|
||
f"{len(CONFIGS)} configs, n_iter={n_iter}) ===\n")
|
||
rows = []
|
||
for sc_name, tpl_fn, scn_fn in SCENARIOS:
|
||
template = tpl_fn()
|
||
scene = scn_fn()
|
||
print(f"--- Scenario: {sc_name} (tpl={template.shape}, "
|
||
f"scn={scene.shape}) ---")
|
||
for cfg_name, init_kw, find_kw in CONFIGS:
|
||
r = _bench_config(template, scene, cfg_name, init_kw, find_kw,
|
||
n_iter=n_iter)
|
||
r["scenario"] = sc_name
|
||
rows.append(r)
|
||
prof_str = " ".join(
|
||
f"{k}={v:.1f}" for k, v in r["profile_ms"].items()
|
||
)
|
||
print(f" {cfg_name:14s} {r['ms_avg']:6.1f}ms "
|
||
f"(min {r['ms_min']:.1f} max {r['ms_max']:.1f}) "
|
||
f"vars={r['n_variants']:3d} "
|
||
f"matches={r['n_matches']:2d}")
|
||
if prof_str:
|
||
print(f" profile: {prof_str}")
|
||
print()
|
||
print("=== Done ===")
|
||
return 0
|
||
|
||
|
||
def main(argv: list[str] | None = None) -> int:
|
||
p = argparse.ArgumentParser(description="PM2D benchmark suite")
|
||
p.add_argument("--quick", action="store_true",
|
||
help="2 iterazioni per config invece di 5 (smoke test)")
|
||
args = p.parse_args(argv)
|
||
return run(quick=args.quick)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
import sys
|
||
sys.exit(main())
|