Compare commits

..

4 Commits

Author SHA1 Message Date
Adriano 452810b67a merge: fix overlay shift 2026-05-05 12:45:11 +02:00
Adriano 8c46a6ca9b fix: rimossa traslazione fissa edge overlay match
Causa principale: erode di (2*spread_radius+1) sulla maschera warpata
toglieva troppo bordo. Per spread_radius=8 → kernel 17x17 = -8px da
ogni lato. L'edge map applicata sopra mostrava i bordi spostati di ~8px
verso l'interno del pezzo, creando apparente "traslazione fissa".

Soluzione: erode 3x3 solo per rimuovere ~1px di bordo nero residuo
da warpAffine borderValue=0 (artefatto di padding). Bordi del pezzo
ora visualizzati nelle posizioni corrette.

Bonus fix: cx_t calcolato come w/2 invece di (w-1)/2, coerente con
center=diag/2.0 usato in training (era 0.5px di shift residuo per
template di lato pari).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 12:45:11 +02:00
Adriano d335f866a3 merge: refine veloce + UCS Y visibile 2026-05-05 12:38:47 +02:00
Adriano 88f80a2cad fix: refine angolo piu' veloce + edge overlay ciano (no clash con asse Y)
Bug visibili dallo screenshot:
1. Rallentamento sostanziale: il fix precedente aggiungeva 16 iter golden
   (era 8) + 3 chiamate parabolic fit = ~19 _score_at_angle vs 11 prima.
2. Asse Y dell'UCS invisibile sul match: edge overlay era verde brillante
   (0,220,0) e si sovrapponeva esattamente al verde dell'asse Y dell'UCS.
3. Angolo non corretto: il parabolic fit finale era instabile su template
   simmetrici (multiple local max ravvicinati lo facevano divergere fuori
   dal vero picco trovato dal golden).

Fix:
- _refine_angle: 10 iter golden con tol 0.05 (compromesso tra precisione
  e velocita'). Rimosso parabolic fit finale instabile. search_radius
  resta a step pieno (utile per recuperare estremi del bin).
- Edge overlay color: ciano (BGR 255,200,0) invece di verde brillante.
  L'asse Y verde dell'UCS ora ben visibile sopra l'overlay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 12:38:47 +02:00
2 changed files with 21 additions and 29 deletions
+9 -23
View File
@@ -1042,11 +1042,12 @@ class LineShapeMatcher:
# Score all'origine come riferimento (ang offset 0) # Score all'origine come riferimento (ang offset 0)
s0, cx0_s, cy0_s = _score_at_angle(0.0) s0, cx0_s, cy0_s = _score_at_angle(0.0)
best = (angle_deg, s0, cx0_s, cy0_s) best = (angle_deg, s0, cx0_s, cy0_s)
# Precisione angolare: tol piu' stretto + piu' iterazioni golden. # Precisione angolare: 10 iter golden con tol 0.05 deg.
# 16 iter golden coprono ~1e-3 della search_radius -> precisione # Compromesso speed/accuracy: il parabolic fit aggiuntivo era
# ~0.005 deg per step=10 deg. # instabile su score map non-smooth (template simmetrici producono
tol = 0.02 # multipli local max ravvicinati che lo facevano divergere).
for _ in range(16): tol = 0.05
for _ in range(10):
if s1 > best[1]: if s1 > best[1]:
best = (angle_deg + x1, s1, cx1, cy1) best = (angle_deg + x1, s1, cx1, cy1)
if s2 > best[1]: if s2 > best[1]:
@@ -1063,22 +1064,6 @@ class LineShapeMatcher:
x1 = x2; s1 = s2; cx1 = cx2; cy1 = cy2 x1 = x2; s1 = s2; cx1 = cx2; cy1 = cy2
x2 = a_lo + _GOLDEN * (a_hi - a_lo) x2 = a_lo + _GOLDEN * (a_hi - a_lo)
s2, cx2, cy2 = _score_at_angle(x2) s2, cx2, cy2 = _score_at_angle(x2)
# Parabolic fit finale sui 3 punti vicini al best per refinement
# sub-step ulteriore (precisione <0.01 deg quando lo score map e'
# smooth attorno al picco).
best_off = best[0] - angle_deg
delta = max(0.05, abs(a_hi - a_lo) * 0.5)
sm, _, _ = _score_at_angle(best_off - delta)
sp, _, _ = _score_at_angle(best_off + delta)
# Vertice parabola su 3 punti (sm, sb, sp) a (-delta, 0, +delta)
denom = sm - 2 * best[1] + sp
if abs(denom) > 1e-9:
shift = 0.5 * (sm - sp) / denom * delta
shift = max(-delta, min(delta, shift))
new_off = best_off + shift
sn, cxn, cyn = _score_at_angle(new_off)
if sn >= best[1]:
return (angle_deg + new_off, sn, cxn, cyn)
return best return best
def _get_view_template( def _get_view_template(
@@ -1320,8 +1305,9 @@ class LineShapeMatcher:
if t is None: if t is None:
return 1.0 return 1.0
h, w = t.shape h, w = t.shape
cx_t = (w - 1) / 2.0 # Coerente con training (center = diag / 2.0, no -1)
cy_t = (h - 1) / 2.0 cx_t = w / 2.0
cy_t = h / 2.0
# Bounding box del template ruotato/scalato attorno a (cx, cy) # Bounding box del template ruotato/scalato attorno a (cx, cy)
diag = int(np.ceil(np.hypot(w, h) * scale)) + 8 diag = int(np.ceil(np.hypot(w, h) * scale)) + 8
+12 -6
View File
@@ -168,7 +168,10 @@ def _draw_matches(scene: np.ndarray, matches: list[Match],
if template_gray is not None and matcher is not None: if template_gray is not None and matcher is not None:
t = template_gray t = template_gray
th, tw = t.shape th, tw = t.shape
cx_t = (tw - 1) / 2.0; cy_t = (th - 1) / 2.0 # Centro template coerente col training: in train si usa
# `center = (diag / 2.0, diag / 2.0)` (no -1). Usare (tw-1)/2
# introduceva uno shift di 0.5px per template di lato pari.
cx_t = tw / 2.0; cy_t = th / 2.0
M = cv2.getRotationMatrix2D((cx_t, cy_t), m.angle_deg, m.scale) M = cv2.getRotationMatrix2D((cx_t, cy_t), m.angle_deg, m.scale)
M[0, 2] += m.cx - cx_t M[0, 2] += m.cx - cx_t
M[1, 2] += m.cy - cy_t M[1, 2] += m.cy - cy_t
@@ -181,10 +184,11 @@ def _draw_matches(scene: np.ndarray, matches: list[Match],
warped_mask = cv2.warpAffine( warped_mask = cv2.warpAffine(
mask_src, M, (W_scene, H_scene), mask_src, M, (W_scene, H_scene),
flags=cv2.INTER_NEAREST, borderValue=0) flags=cv2.INTER_NEAREST, borderValue=0)
# Erode di spread_radius per scartare la fascia di transizione # Erode minimo (3x3) per togliere SOLO artefatti border-padding
# bordo che produce gradient spurio # (~1px di bordo nero da warpAffine borderValue=0). Erode piu'
er_k = max(3, 2 * matcher.spread_radius + 1) # grande spostava visualmente l'edge verso l'interno e creava
kernel_er = np.ones((er_k, er_k), np.uint8) # apparente "traslazione fissa" rispetto al bordo del pezzo.
kernel_er = np.ones((3, 3), np.uint8)
warped_mask = cv2.erode(warped_mask, kernel_er) warped_mask = cv2.erode(warped_mask, kernel_er)
mag, _ = matcher._gradient(warped_gray) mag, _ = matcher._gradient(warped_gray)
if matcher.weak_grad < matcher.strong_grad: if matcher.weak_grad < matcher.strong_grad:
@@ -194,7 +198,9 @@ def _draw_matches(scene: np.ndarray, matches: list[Match],
edge_mask = edge_mask & (warped_mask > 0) edge_mask = edge_mask & (warped_mask > 0)
if edge_mask.any(): if edge_mask.any():
edge_overlay = np.zeros_like(out) edge_overlay = np.zeros_like(out)
edge_overlay[edge_mask] = (0, 220, 0) # verde brillante # Ciano (cambiato da verde): non collide col verde dell'asse
# Y dell'UCS che altrimenti scompariva nell'overlay edge.
edge_overlay[edge_mask] = (255, 200, 0) # ciano (BGR)
out = cv2.addWeighted(out, 1.0, edge_overlay, 0.6, 0) out = cv2.addWeighted(out, 1.0, edge_overlay, 0.6, 0)
L = max(20, int(L_base * m.scale)) L = max(20, int(L_base * m.scale))
# X axis = rotazione di (1, 0) con cv2 matrix → (cos, -sin) # X axis = rotazione di (1, 0) con cv2 matrix → (cos, -sin)