Cache LRU (chiave: angolo arrotondato a 0.05deg, scale) di
(fx, fy, fb) per evitare warpAffine + gradient + extract ripetuti
durante golden-search refine. Bucket condiviso tra match della stessa
find() e tra find() consecutive sulla stessa ricetta.
Cache invalidata in train(): il template puo essere cambiato.
Limite 256 entry (sufficiente per 32 candidati x 8 valutazioni).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ottimizzazioni cumulative (225s -> 73s sul bench suite, 3.07x):
pm2d/line_matcher.py:
- Sub-pixel + plateau centroid spostati DOPO il pre-NMS (prima: 58k chiamate
per clip_preciso anche su candidati poi scartati dalla NMS; ora solo sui
~75 preliminary sopravvissuti). Coordinate intere OK per la decisione
reject, dato che nms_radius >= 8 px.
- Usa nuovo kernel fuso score+rescore (no allocazione intermedia).
- Adaptive plateau_radius + propagazione train_mask per NCC coerente.
- Local crop NCC (diag template invece di intera scena).
- Fallback adattivo se bg_rescore azzera tutti gli score top-level.
pm2d/_jit_kernels.py:
- Nuovo kernel _jit_score_bitmap_rescored: fonde scoring bitmap e rescore
(score - bg) / (1 - bg) in un singolo pass parallelo. Evita allocazione
e passata aggiuntiva (era ~15% del tempo find sul preciso).
pm2d/auto_tune.py:
- LRU cache in-memory sui risultati auto_tune (chiave md5 ROI + mask):
richiamate successive con stessa ROI sono O(1).
- Downsample a 128px prima della correlazione rotazionale
(O(n_angles * H * W) -> insensibile su sample moderati).
- Soglie weak/strong da percentili reali (p55/p85) senza clamp a 100,
con clamp massimo 400 per evitare saturazione su template ad alto contrasto.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
V1 Coarse-to-fine angolare:
- Al top-level valuta solo 1 variante ogni coarse_angle_factor (default 2)
- Espande ai vicini nel full-res per preservare accuracy
- Safe anche per template allungati (factor=2 non perde match)
V11 Cache matcher in-memory (LRU, capacita 8):
- Key = md5(ROI bytes + params tecnici che influenzano il training)
- Re-match con stessi parametri: train_time = 0s (era 0.5-1.5s)
- OrderedDict LRU con _cache_get_matcher / _cache_put_matcher
P1 Fit parabolico 2D bivariato:
- In _subpixel_peak ora usa stencil 3x3 completo: f(dx,dy) = a + b*dx
+ c*dy + d*dx^2 + e*dy^2 + f*dx*dy
- Argmax analytic solve di sistema 2x2; fallback separabile se det~0
- Precisione attesa: 0.1-0.3 px (era 0.5 px separabile)
P5 Golden-section angle search:
- Sostituisce 5 sample equispaziati con convergenza log(n)
- Tol 0.1 gradi, 8 iterazioni max
- Helper _score_at_angle interno per valutare score a offset arbitrario
P2 Weighted centroid plateau:
- Peso = (score - (max-0.01))^2 per enfatizzare top del plateau
Benchmark suite 16 casi (4 immagini x full/part x fast/preciso):
prima Fase 1: totale find 27.3s
dopo Fase 1: totale find 25.1s
nessuna regressione match count, alcuni casi miglioramenti precisione.
ROADMAP.md aggiornato con checklist Fase 1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug: modello == scena non sovrapponeva perfettamente.
1. refine_angle trovava angoli spurious -2.5 deg con score saturo 1.0
perche' parabolic fit su picco saturo estrapola rumore.
Fix: skip refine quando original_score >= 0.99
2. Subpixel peak su plateau (spread_radius=5 satura picco su area)
sceglieva pixel random via cv2.minMaxLoc.
Fix: se >1 pixel a score >= max-0.01 nel raggio 10 usa CENTROIDE
del plateau invece del parabolic fit.
Test self-match tooth_rim foro piccolo:
prima: pos=(355, 111.50) delta=(0, -3.50) ang=-2.5 deg
dopo: pos=(355, 115.00) delta=(0, +0.00) ang=+0.0 deg
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
- max_vars_full = max(max_matches*8, n_variants // 2): protegge perf con
molte scale mantenendo metà delle varianti al full-res (vs intero senza cap
che dava 22s su 864 varianti, vs 80s screenshot utente)
- Dialog tkinter: resizable, minsize 360x420, Entry col peso 2 espandibile
- Finestra risultati cv2: WINDOW_NORMAL con resizeWindow iniziale 1600x900
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Problema: in scenari con molte scale (ring detection), il matcher perdeva
istanze a scale estreme:
1. Cap max_vars_full (default max_matches*8) escludeva la pose corretta
2. bg_map usava box fissa = template_size, penalizzando varianti a scala
grande dove il template reale è più grande del box
Fix:
- Rimosso cap hard sul numero di varianti full-res (Numba compensa velocità)
- bg_map PER-SCALA: cache {scale: bg_map} con box size scalata
appropriatamente (tw*scale, th*scale). Calcolato una volta per scala
unica, poi ogni variante usa il suo bg_map
Benchmark rings_and_nuts (template ruota grande, 3 ruote nella scena a
dimensioni diverse):
prima: 2/3 match (persa la grande)
dopo: 3/3 match score 1.0 a scale 1.00, 0.95, 0.80
Regression:
clip→clip: 13/13 invariato (0.93s)
ring→clip FP: 3 (era 1 con bg fisso, era 10 senza bg)
compromesso ragionevole: verify_threshold=0.5 elimina gli ultimi FP
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Problema: matcher linemod con solo orientamento gradient può dare score alto
su texture dense/rumore che per caso accumulano orientamenti compatibili.
Esempio: template ruota dentata su scena clip → match a score 0.9 (errati).
Fix in 2 livelli:
1. Background score LOCALE nel find()
- _bg_map(resp, box_size) = densità media bin attivi in bbox template
- Rinormalizza score: s' = max(0, (s - bg) / (1 - bg))
- Annulla contributo di zone sature ma preserva pattern puliti
2. Verify NCC post-hoc
- _verify_ncc(): warpa template alla pose (cx, cy, angle, scale) e
calcola NCC classico su intensità con la scena sottostante
- Threshold di default 0.4 elimina FP con edge orientati casualmente
- Parametro esposto in GUI (verify_threshold)
Rimossa penalty di saturazione nel response_map (ridondante).
Test regression (ruote dentate vs clip, clip vs ruote dentate):
no verify: 12+ falsi positivi con score ~0.7
verify 0.4: 1-2 falsi positivi rimanenti, true positive invariati
verify 0.5: 0 falsi positivi, 1 TP scale piccola perso
Benchmark clip→clip (13 istanze):
full pipeline (Numba + threads + refine + subpix + verify): 1.12s
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Nuovo modulo pm2d/_jit_kernels.py con _jit_score_by_shift Numba njit
parallel + fastmath + boundscheck=False
- Parallelizzazione per riga output (no race condition su acc)
- Fallback automatico numpy se numba non installato
- Warmup automatico al module import (evita JIT lag al 1 match)
Benchmark clip.png (13 istanze):
prima (numpy + threads): 1.55s
dopo (numba + threads): 0.72s
speedup: 2.1x
Pipeline totale full (refine+subpix): 0.80s
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>