Compare commits

...

2 Commits

Author SHA1 Message Date
Adriano d52d0d0489 merge: precisione rotazione + default Nessuna 2026-05-05 12:32:17 +02:00
Adriano 9451a418a6 fix: precisione rotazione +UI simmetria default Nessuna
Precisione rotazione:
- _refine_angle: tol 0.1 -> 0.02 deg, 8 -> 16 iter golden-section
- search_radius default = step pieno (era step/2): copre il caso peggiore
  in cui il picco vero e' all'estremo del bin angolare grezzo
- Aggiunto parabolic fit finale sui 3 punti vicini al best (precisione
  <0.01 deg quando lo score map e' smooth attorno al picco)

Default UI:
- Simmetria "Nessuna" come default (era "Invariante" che limitava
  matching a una singola pose - confondente per l'operatore tipico).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 12:32:17 +02:00
2 changed files with 27 additions and 5 deletions
+26 -4
View File
@@ -954,7 +954,7 @@ class LineShapeMatcher:
# 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
search_radius = self._effective_angle_step()
h, w = template_gray.shape
sw = max(16, int(round(w * scale)))
@@ -1042,8 +1042,11 @@ class LineShapeMatcher:
# Score all'origine come riferimento (ang offset 0)
s0, cx0_s, cy0_s = _score_at_angle(0.0)
best = (angle_deg, s0, cx0_s, cy0_s)
tol = 0.1 # gradi
for _ in range(8):
# Precisione angolare: tol piu' stretto + piu' iterazioni golden.
# 16 iter golden coprono ~1e-3 della search_radius -> precisione
# ~0.005 deg per step=10 deg.
tol = 0.02
for _ in range(16):
if s1 > best[1]:
best = (angle_deg + x1, s1, cx1, cy1)
if s2 > best[1]:
@@ -1060,6 +1063,22 @@ class LineShapeMatcher:
x1 = x2; s1 = s2; cx1 = cx2; cy1 = cy2
x2 = a_lo + _GOLDEN * (a_hi - a_lo)
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
def _get_view_template(
@@ -1825,7 +1844,10 @@ class LineShapeMatcher:
ang_f, score_f, cx_f, cy_f = self._refine_angle(
spread0, bit_active_full, self.template_gray, cx_f, cy_f,
var.angle_deg, var.scale, mask_full,
search_radius=self._effective_angle_step() / 2.0,
# Search radius esteso allo step pieno (era step/2):
# copre il caso peggiore in cui il picco vero e' all'estremo
# del bin angolare della variante grezza.
search_radius=self._effective_angle_step(),
original_score=score,
)
# Halcon SubPixel='least_squares_high': refinement iterativo
+1 -1
View File
@@ -102,8 +102,8 @@
<div class="field">
<label>Simmetria</label>
<select id="p-simmetria">
<option value="nessuna" selected>Nessuna (0..360°)</option>
<option value="invariante">Invariante (cerchi — no rotazione)</option>
<option value="nessuna">Nessuna (0..360°)</option>
<option value="bilaterale">Bilaterale (speculare 180°)</option>
<option value="rot_3">Rotazionale 3× (120°)</option>
<option value="rot_4">Rotazionale 4× (90°)</option>