ba54b42fdc
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>
97 lines
3.2 KiB
Python
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()
|