Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6da4dd5329 |
+2
-5
@@ -220,11 +220,8 @@ def auto_tune(template_bgr: np.ndarray, mask: np.ndarray | None = None) -> dict:
|
|||||||
else:
|
else:
|
||||||
min_score = 0.45
|
min_score = 0.45
|
||||||
|
|
||||||
# angle step adattivo (Halcon-style): atan(2/max_side) deg, clampato.
|
# angle step: 5° default; se simmetria, mantengo step ma range ridotto
|
||||||
# Template grande → step fine (rotazione minima visibile su perimetro).
|
angle_step = 5.0
|
||||||
# Template piccolo → step grosso (over-sampling = sprecato).
|
|
||||||
max_side = max(h, w)
|
|
||||||
angle_step = float(np.clip(np.degrees(np.arctan2(2.0, max_side)), 1.0, 8.0))
|
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
"backend": "line",
|
"backend": "line",
|
||||||
|
|||||||
+39
-24
@@ -197,31 +197,12 @@ class LineShapeMatcher:
|
|||||||
n = int(np.floor((s1 - s0) / self.scale_step)) + 1
|
n = int(np.floor((s1 - s0) / self.scale_step)) + 1
|
||||||
return [float(s0 + i * self.scale_step) for i in range(n)]
|
return [float(s0 + i * self.scale_step) for i in range(n)]
|
||||||
|
|
||||||
def _auto_angle_step(self) -> float:
|
|
||||||
"""Step angolare derivato da dimensione template (Halcon-style).
|
|
||||||
|
|
||||||
Formula: step ≈ atan(2 / max_side) gradi. Garantisce che la
|
|
||||||
rotazione minima produca uno spostamento di ≥2 px sul perimetro
|
|
||||||
del template (sotto sample il matching coarse perde candidati).
|
|
||||||
Clampato in [0.5°, 10°].
|
|
||||||
"""
|
|
||||||
max_side = max(self.template_size) if self.template_size != (0, 0) else 64
|
|
||||||
step = math.degrees(math.atan2(2.0, float(max_side)))
|
|
||||||
return float(np.clip(step, 0.5, 10.0))
|
|
||||||
|
|
||||||
def _effective_angle_step(self) -> float:
|
|
||||||
"""Risolve angle_step_deg gestendo modalità auto (<=0)."""
|
|
||||||
if self.angle_step_deg <= 0:
|
|
||||||
return self._auto_angle_step()
|
|
||||||
return self.angle_step_deg
|
|
||||||
|
|
||||||
def _angle_list(self) -> list[float]:
|
def _angle_list(self) -> list[float]:
|
||||||
a0, a1 = self.angle_range_deg
|
a0, a1 = self.angle_range_deg
|
||||||
step = self._effective_angle_step()
|
if self.angle_step_deg <= 0 or a0 >= a1:
|
||||||
if step <= 0 or a0 >= a1:
|
|
||||||
return [float(a0)]
|
return [float(a0)]
|
||||||
n = int(np.floor((a1 - a0) / step))
|
n = int(np.floor((a1 - a0) / self.angle_step_deg))
|
||||||
return [float(a0 + i * step) for i in range(n)]
|
return [float(a0 + i * self.angle_step_deg) for i in range(n)]
|
||||||
|
|
||||||
# --- Training ------------------------------------------------------
|
# --- Training ------------------------------------------------------
|
||||||
|
|
||||||
@@ -312,8 +293,42 @@ class LineShapeMatcher:
|
|||||||
kh=kh, kw=kw,
|
kh=kh, kw=kw,
|
||||||
cx_local=float(cx_local), cy_local=float(cy_local),
|
cx_local=float(cx_local), cy_local=float(cy_local),
|
||||||
))
|
))
|
||||||
|
self._dedup_variants()
|
||||||
return len(self.variants)
|
return len(self.variants)
|
||||||
|
|
||||||
|
def _dedup_variants(self) -> int:
|
||||||
|
"""Rimuove varianti con feature-set identico (post-quantizzazione).
|
||||||
|
|
||||||
|
Halcon-style: con angle range = (0, 360) e simmetrie del template,
|
||||||
|
molte rotazioni producono lo stesso set quantizzato di feature.
|
||||||
|
Es: quadrato a 0/90/180/270 deg → stesse features (modulo permutazione).
|
||||||
|
Hash su feature ordinate (livello 0, full-res) elimina i duplicati.
|
||||||
|
|
||||||
|
Vantaggio: meno varianti = meno chiamate kernel JIT al top-level
|
||||||
|
senza perdere copertura angolare effettiva. Per template asimmetrici
|
||||||
|
non rimuove nulla.
|
||||||
|
"""
|
||||||
|
seen: dict[bytes, int] = {}
|
||||||
|
kept: list[_Variant] = []
|
||||||
|
removed = 0
|
||||||
|
for var in self.variants:
|
||||||
|
lvl0 = var.levels[0]
|
||||||
|
order = np.lexsort((lvl0.bin, lvl0.dy, lvl0.dx))
|
||||||
|
key = (
|
||||||
|
lvl0.dx[order].tobytes()
|
||||||
|
+ b"|" + lvl0.dy[order].tobytes()
|
||||||
|
+ b"|" + lvl0.bin[order].tobytes()
|
||||||
|
+ b"|" + str(round(var.scale, 4)).encode()
|
||||||
|
)
|
||||||
|
h = key # diretto, senza hash crypto (collision ok solo se identici)
|
||||||
|
if h in seen:
|
||||||
|
removed += 1
|
||||||
|
continue
|
||||||
|
seen[h] = len(kept)
|
||||||
|
kept.append(var)
|
||||||
|
self.variants = kept
|
||||||
|
return removed
|
||||||
|
|
||||||
# --- Matching ------------------------------------------------------
|
# --- Matching ------------------------------------------------------
|
||||||
|
|
||||||
def _response_map(self, gray: np.ndarray) -> np.ndarray:
|
def _response_map(self, gray: np.ndarray) -> np.ndarray:
|
||||||
@@ -434,7 +449,7 @@ class LineShapeMatcher:
|
|||||||
if original_score is not None and original_score >= 0.99:
|
if original_score is not None and original_score >= 0.99:
|
||||||
return (angle_deg, original_score, cx, cy)
|
return (angle_deg, original_score, cx, cy)
|
||||||
if search_radius is None:
|
if search_radius is None:
|
||||||
search_radius = self._effective_angle_step() / 2.0
|
search_radius = self.angle_step_deg / 2.0
|
||||||
|
|
||||||
h, w = template_gray.shape
|
h, w = template_gray.shape
|
||||||
sw = max(16, int(round(w * scale)))
|
sw = max(16, int(round(w * scale)))
|
||||||
@@ -821,7 +836,7 @@ class LineShapeMatcher:
|
|||||||
ang_f, score_f, cx_f, cy_f = self._refine_angle(
|
ang_f, score_f, cx_f, cy_f = self._refine_angle(
|
||||||
spread0, bit_active_full, self.template_gray, cx_f, cy_f,
|
spread0, bit_active_full, self.template_gray, cx_f, cy_f,
|
||||||
var.angle_deg, var.scale, mask_full,
|
var.angle_deg, var.scale, mask_full,
|
||||||
search_radius=self._effective_angle_step() / 2.0,
|
search_radius=self.angle_step_deg / 2.0,
|
||||||
original_score=score,
|
original_score=score,
|
||||||
)
|
)
|
||||||
if verify_ncc:
|
if verify_ncc:
|
||||||
|
|||||||
Reference in New Issue
Block a user