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>
Bug visibili da screenshot:
1. UCS match diverso da UCS anteprima modello (centro pose vs baricentro)
2. Numero feature disegnate < di quelle anteprima modello
Cause:
1. Match UCS era posto su (cx, cy) = centro template, mentre l'anteprima
modello mostra UCS sul baricentro feature (mean fx, fy).
2. _draw_matches estraeva feature dal template warpato → re-quantizza
gradient su immagine warp+interp, perdendo precisione vs feature
pre-computate del matcher.
Fix:
- Match.variant_idx: nuovo field con indice variante usata dal find()
- _draw_matches usa lvl0.dx/dy/bin pre-computati invece di re-estrarre:
* applica delta-rotation (m.angle_deg - var.angle_deg) per refine
sub-step
* proietta in scene coords intorno a (m.cx, m.cy)
* stesso identico set di feature dell'anteprima modello (modulo
rotazione+traslazione)
- UCS match calcolato sul baricentro delle feature warpate, non su
(cx, cy) → coerente con UCS anteprima
Fallback (variant_idx == -1, es. ricetta caricata da save_model
prima di questo commit): usa estrazione warpata legacy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Flag opzionale use_gpu=False/True su LineShapeMatcher e helper:
- opencl_available() per probe runtime
- set_gpu_enabled(bool) per attivare/disattivare globalmente
Quando attivo + cv2.ocl.haveOpenCL() True: Sobel + dilate +
warpAffine usano UMat con dispatch automatico kernel GPU
(Intel UHD, AMD, NVIDIA via OpenCL ICD). Speedup tipico 1.5-3x
sui filtri OpenCV (sec 1080p), gain finale ~10-15% sul total
find() perche' kernel JIT score-bitmap rimane CPU (Numba).
Path silently fallback CPU se OpenCL non disponibile (es. build
opencv-python senza ICD). Non rompe niente in ambienti non-GPU.
Per veri 20-50x speedup servirebbe kernel CUDA dedicato del
score-bitmap (out of scope, CPU + Numba e gia' molto buono).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aggiunge una view extra al matcher gia addestrato. Le varianti
della nuova view vengono APPENDATE a self.variants col tag view_idx
e partecipano al pruning/matching come le altre.
NCC verify usa il template della view che ha matchato (via
_get_view_template + parametro view_idx propagato a _verify_ncc).
Halcon-equivalent: create_aniso_shape_model con fusione N viste.
Use case: pezzo che cambia aspetto (chiaro/scuro, prima/dopo
trattamento, illuminazioni diverse) → un solo matcher robusto
invece di N matcher distinti.
API:
m.train(template_chiaro)
m.add_template_view(template_scuro)
m.find(scene) # match su entrambi gli aspetti
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Halcon-equivalent write_shape_model / read_shape_model. Salva su
file .npz compresso:
- Tutti i parametri matcher (incluso use_polarity)
- Template gray + maschera training
- Tutte le varianti pre-computate (con piramide flat per scrittura
efficiente, ~12KB per template 80x80 con 28 varianti)
Caso d'uso: training offline su workstation, deploy a runtime
production senza re-train. load_model() istantaneo: skip training
(che e' il costo dominante per molte scale/angoli).
Format version 1, np.savez_compressed (zlib).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_subpixel_refine_lm: per ogni feature template, calcola normale
gradient nella scena (bilineare) e stima shift (dx, dy) globale
che minimizza errore direzionale gradient field. Iterazione damped
(max 1px/iter) per stabilita.
Halcon-equivalent SubPixel='least_squares_high'. Precisione attesa
0.05 px (vs 0.5 px del fit quadratico 2D plain). Costo: ~5ms per
match aggiuntivi (negligibile vs total find).
Default off (subpixel_lm=False, backward compat). Attivare per
applicazioni di alignment/dimensional inspection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_compute_soft_score: cos(theta_template - theta_scena) continuo
(non quantizzato a bin) pesato per magnitude. Polarity-aware se
use_polarity=True (mod 2pi) else |cos| (mod pi).
Quando use_soft_score=True (default off, backward compat), lo score
finale e' fuso con quello shape: piu discriminante per match a
piccola rotazione (penalita' graduale invece di binaria on/off).
Equivalente a Halcon Metric='use_polarity' / 'ignore_global_polarity'
in find_shape_model.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_compute_recall calcola hits/N feature template alla pose finale
(post sub-pixel refine). Equivalente Halcon MinScore originale:
quante feature shape effettivamente combaciano sul match accettato.
Param min_recall (default 0 = off, backward compat). Util quando
NCC e' alto ma poche feature reali matchano (es. match parziale
su zona di simil-tessitura). Soglia 0.7-0.9 raccomandata per
filtri stringenti.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug osservato: con precisione "veloce" (10 deg) il matching dava
risultati migliori che con "preciso" (2 deg). Causa: con step fine
ci sono molte varianti vicine, score top-level ravvicinati e:
- top_thresh = min_score * 0.5 troppo aggressivo: scartava varianti
valide che sarebbero state scelte al full-res
- coarse_angle_factor=2 (skip 1 ogni 2): col fine vicini sono quasi
identici, ma il pruning skippava la migliore
Fix: quando angle_step <= 3 deg, automatic:
- top_score_factor min 0.7 (vs default 0.5)
- coarse_angle_factor = 1 (no skip varianti)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug: score finale = (shape + ncc) / 2 puo scendere sotto min_score
impostato dall'utente. La UI mostrava match con score < soglia
perche il filtro min_score era applicato solo allo shape-score
iniziale, non al risultato finale post-NCC.
Aggiunto re-check dopo averaging: scarta match con score finale
< min_score. Coerenza filtro user-facing ripristinata.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 fix per match spuri ad alto score visti su scena reale:
1. NCC con guard varianza minima: se template-patch o scene-patch
hanno std quasi-zero (zone uniformi bianche/nere) NCC e instabile
e da false-correlation alta. Ora ritorna 0 sotto soglia varianza.
2. Reject post-bbox: se il bounding-box ruotato del match sfora
la scena per piu del 25%, scarto (centro derivato male o scala
incoerente). Tollera 25% out-of-bounds (bordi).
3. FILTRO_FP_MAP alzato: leggero 0.20→0.30, medio 0.35→0.50,
forte 0.50→0.70. Default piu conservativo per evitare match
spuri su zone con pochi edge.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LineShapeMatcher.set_angle_range_around(center, tol): restringe
angle_range a (center-tol, center+tol). Use case: feeder/posizionamento
meccanico noto a priori. Esempio:
m.set_angle_range_around(0, 20) # cerca solo in [-20, +20]
auto_tune accetta angle_tolerance_deg + angle_center_deg: emette
angle_min/angle_max ristretti se hint fornito. Cache key include
hint per non collidere con tune default.
Beneficio misurato: angle_step=5 deg, template 80x80
- range 360°: 72 varianti
- range ±15°: 6 varianti (12x meno = matching ~12x piu veloce)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Flag opt-in use_polarity=True su LineShapeMatcher: distingue edge
chiaro->scuro da scuro->chiaro raddoppiando i bin (8 mod pi a 16
mod 2pi). Riduce match accidentali quando il template e direzionale
ma scena ha bordo opposto (es. pezzo nero su bg chiaro vs pezzo
chiaro su bg nero).
Implementazione:
- _gradient calcola atan2 mod 2pi quando use_polarity
- _spread_bitmap usa uint16 (16 bit) invece di uint8 (8 bit)
- Nuovi kernel JIT _jit_score_bitmap_rescored_u16 e
_jit_popcount_density_u16
- Wrapper Python score_bitmap_rescored / popcount_density fanno
dispatch su dtype dello spread
Default off (use_polarity=False) = backward compat completo, 8 bin.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_poly_iou via cv2.intersectConvexConvex: IoU esatto tra bbox
orientati. Sostituisce distanza-centro nel NMS post-refine.
Vantaggio: due pezzi adiacenti con centri vicini (entro nms_radius)
ma orientamenti diversi non vengono piu fusi se overlap reale e
basso. Stesso pezzo trovato da varianti angolari diverse (centri
uguali, IoU ~1) viene correttamente droppato.
Param nms_iou_threshold default 0.3. Fallback distanza centro
(r2/4) come safety per bbox degeneri.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 problemi visibili da test interattivo:
1. Match duplicati: stesso oggetto trovato da varianti angolari
diverse, NMS pre-refine non basta perche refine sposta i match.
Aggiunto NMS post-refine cross-variant.
2. Score sempre alto/saturato a 1.0: NCC era opzionale (skip>=0.85)
e non veniva mescolato nello score. Ora ncc_skip_above=1.01
(NCC sempre) e score finale = (shape + NCC) / 2: piu discriminante.
3. Angolo impreciso: _refine_angle aveva early-exit per shape-score
>= 0.99, ma quel valore satura facile (con pyramid_propagate o
spread ampio) senza garantire angolo preciso. Rimosso early-exit:
refine angolare e' sempre essenziale per orientamento sub-step.
Inoltre: pyramid_propagate default False (era True), riduce duplicati
da picchi propagati su angle-vicini. propagate_topk default 4 (era 8).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hash byte-exact su (dx, dy, bin) ordinati + scale. Se due varianti
post-rasterizzazione hanno lo stesso feature-set, ne tiene solo una.
Tipico caso d'uso: template con simmetrie discrete (quadrati, croci,
forme regolari) generano duplicati esatti per rotazioni multiple
del periodo. Su quadrato 80x80 con angle_step=10 deg: 36 -> 27 varianti
(~25% in meno di lavoro top-pruning).
Approccio conservativo (byte-exact): zero rischio di rimuovere varianti
distinte. Forme arrotondate (cerchi) o template asimmetrici non beneficiano
ma non vengono compromessi.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nuovo kernel _jit_top_max_per_variant: prange esterno sulle varianti
invece di n_vars chiamate JIT separate via ThreadPoolExecutor.
Wrapper Python top_max_per_variant prepara buffer flat (offsets +
dx_flat/dy_flat/bins_flat) e bg per scala.
Default batch_top=False perche su benchmark realistici (Linux 13 core,
72-180 varianti) ThreadPoolExecutor + kernel singolo che rilascia GIL
e gia ottimale. Path batch_top=True utile come opzione per scenari
con n_vars >>> n_threads o overhead chiamate JIT dominante.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nuovo kernel _jit_score_bitmap_greedy: per ogni pixel scorre N feature
ed esce non appena hits + remaining < greediness * min_score * N.
Esposto in find() come greediness in [0..1], default 0 (backward compat).
Sostituisce il kernel rescored al top-level quando attivo: salta il
rescore background ma early-exit pixel impossibili. Util su template
con molte feature (>100) e scena con pochi pattern competitivi.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Alternativa al refine angolare 1D + subpixel quadratico: ottimizza
simultaneamente posizione e angolo con Nelder-Mead 3D inline (no
scipy). Default off (refine_pose_joint=False) per backward compat.
Vantaggio Halcon-style: un singolo iter LM/simplex stila il match a
precisione sub-pixel + sub-step in modo congiunto invece di alternare
assi. Convergenza tipica ~24 valutazioni vs ~15 (golden+quadratico)
ma piu robusto su template asimmetrici dove pose e angolo sono
fortemente correlati.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ncc_skip_above (default 0.85): se lo score shape e gia molto alto,
salta la verifica NCC (costosa: warp + corr per ogni match). I match
borderline 0.6-0.85 vengono comunque verificati.
Comportamento Halcon-style: NCC come tie-breaker per casi ambigui,
non come gate generalizzato. Su scene con molti match netti riduce
sensibilmente il costo della fase post-NMS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Halcon-style: angle_step_deg=0 attiva derivazione automatica
step = atan(2/max_side) deg, clampato [0.5, 10]. Template grande
ottiene step fine, piccolo step grosso. auto_tune emette il valore
calcolato direttamente.
_refine_angle ora usa _effective_angle_step() per coerenza con
training quando la modalita auto e attiva.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Top-level ritorna top-K picchi locali invece di solo max. Fase full-res
valuta solo crop locali attorno ai picchi propagati (margine =
sf_top + spread + nms_radius/2) invece di scansionare intera scena.
Su scene 1920x1080 con pochi candidati: ~20-30% piu veloce mantenendo
identici match. Vantaggio cresce con scene piu grandi e meno candidati.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nuovo kernel JIT _jit_score_bitmap_rescored_strided: valuta solo
pixel su griglia stride x stride al top della piramide. NMS + fase
full-res recuperano precisione. Speed-up ~stride^2 sulla fase coarse,
specie su scene grandi (1920x1080).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Equivalente a Halcon set_aoi: matching opera su crop locale, coord
output ri-traslate al sistema scena. Costo proporzionale a w*h del
ROI invece di W*H scena intera.
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>