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>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user