From 41976f574d6f2eb520125a955661c35f7cc912c4 Mon Sep 17 00:00:00 2001 From: AdrianoDev Date: Mon, 4 May 2026 16:33:58 +0200 Subject: [PATCH] fix: duplicati, score saturato e angolo impreciso 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) --- pm2d/line_matcher.py | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/pm2d/line_matcher.py b/pm2d/line_matcher.py index 19fc2dc..a5195df 100644 --- a/pm2d/line_matcher.py +++ b/pm2d/line_matcher.py @@ -570,9 +570,11 @@ class LineShapeMatcher: l'angolo con score massimo (parabolic fit sulle 3 score centrali). Ritorna (angle_refined, score, cx_refined, cy_refined). """ - # Se il match grezzo è già quasi perfetto, NON refinare - if original_score is not None and original_score >= 0.99: - return (angle_deg, original_score, cx, cy) + # NB: rimosso early-skip su score >= 0.99. Lo score linemod/shape + # satura facilmente a 1.0 (specie con pyramid_propagate o spread + # ampio) ma NON garantisce angolo preciso: l'angolo grezzo della + # variante e' quantizzato a multipli di angle_step (5 deg default). + # Refine angolare e' essenziale per orientamento sub-step. if search_radius is None: search_radius = self._effective_angle_step() / 2.0 @@ -750,13 +752,13 @@ class LineShapeMatcher: subpixel: bool = True, verify_ncc: bool = True, verify_threshold: float = 0.4, - ncc_skip_above: float = 0.85, + ncc_skip_above: float = 1.01, # disabilitato di default: NCC sempre coarse_angle_factor: int = 2, coarse_stride: int = 1, scale_penalty: float = 0.0, search_roi: tuple[int, int, int, int] | None = None, - pyramid_propagate: bool = True, - propagate_topk: int = 8, + pyramid_propagate: bool = False, # off di default: meno duplicati + propagate_topk: int = 4, refine_pose_joint: bool = False, greediness: float = 0.0, batch_top: bool = False, @@ -1096,14 +1098,18 @@ class LineShapeMatcher: search_radius=self._effective_angle_step() / 2.0, original_score=score, ) - # NCC verify lazy (Halcon-style): skip se shape-score gia molto - # alto (probabilita falso positivo trascurabile). NCC e l'op - # piu costosa per match (warp + corr), quindi vale la pena - # saltarlo quando il gradiente shape e gia conclusivo. + # NCC verify (Halcon-style): se ncc_skip_above < 1.0 salta + # il calcolo per shape-score gia alti. Default 1.01 = NCC sempre, + # piu sicuro contro falsi positivi (lo shape-score satura facile). + # Quando NCC viene calcolato, lo score finale e' la MEDIA tra + # shape-score e NCC: rende lo score piu discriminante per + # ranking/visualizzazione (uno score 1.0 vero richiede sia + # match shape sia template gray identici). if verify_ncc and float(score_f) < ncc_skip_above: ncc = self._verify_ncc(gray0, cx_f, cy_f, ang_f, var.scale) if ncc < verify_threshold: continue + score_f = (float(score_f) + max(0.0, ncc)) * 0.5 # Ri-traslo coord da spazio crop ROI a spazio scena originale. cx_out = cx_f + roi_offset[0] @@ -1116,6 +1122,16 @@ class LineShapeMatcher: score_f = float(score_f) * max( 0.0, 1.0 - scale_penalty * abs(var.scale - 1.0) ) + # NMS post-refine: refine puo spostare il match di nms_radius; + # ricontrollo overlap su match gia accettati per evitare + # duplicati (stesso oggetto trovato da varianti angolari diverse). + dup = False + for k in kept: + if (k.cx - cx_out) ** 2 + (k.cy - cy_out) ** 2 < r2: + dup = True + break + if dup: + continue kept.append(Match( cx=cx_out, cy=cy_out, angle_deg=ang_f,