Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d9a40952c4 |
+5
-2
@@ -220,8 +220,11 @@ 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: 5° default; se simmetria, mantengo step ma range ridotto
|
# angle step adattivo (Halcon-style): atan(2/max_side) deg, clampato.
|
||||||
angle_step = 5.0
|
# Template grande → step fine (rotazione minima visibile su perimetro).
|
||||||
|
# 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",
|
||||||
|
|||||||
+24
-26
@@ -197,12 +197,31 @@ 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
|
||||||
if self.angle_step_deg <= 0 or a0 >= a1:
|
step = self._effective_angle_step()
|
||||||
|
if step <= 0 or a0 >= a1:
|
||||||
return [float(a0)]
|
return [float(a0)]
|
||||||
n = int(np.floor((a1 - a0) / self.angle_step_deg))
|
n = int(np.floor((a1 - a0) / step))
|
||||||
return [float(a0 + i * self.angle_step_deg) for i in range(n)]
|
return [float(a0 + i * step) for i in range(n)]
|
||||||
|
|
||||||
# --- Training ------------------------------------------------------
|
# --- Training ------------------------------------------------------
|
||||||
|
|
||||||
@@ -239,8 +258,6 @@ class LineShapeMatcher:
|
|||||||
self._train_mask = mask_full.copy()
|
self._train_mask = mask_full.copy()
|
||||||
|
|
||||||
self.variants.clear()
|
self.variants.clear()
|
||||||
# Invalida cache feature di refine: il template e cambiato.
|
|
||||||
self._refine_feat_cache = {}
|
|
||||||
for s in self._scale_list():
|
for s in self._scale_list():
|
||||||
sw = max(16, int(round(w * s)))
|
sw = max(16, int(round(w * s)))
|
||||||
sh = max(16, int(round(h * s)))
|
sh = max(16, int(round(h * s)))
|
||||||
@@ -417,7 +434,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.angle_step_deg / 2.0
|
search_radius = self._effective_angle_step() / 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)))
|
||||||
@@ -435,24 +452,9 @@ class LineShapeMatcher:
|
|||||||
H, W = spread0.shape
|
H, W = spread0.shape
|
||||||
margin = 3
|
margin = 3
|
||||||
|
|
||||||
# Cache template features per angolo (chiave: int(round(ang*20)) =
|
|
||||||
# bucket di 0.05°). Golden-search ricontratto puo richiedere lo
|
|
||||||
# stesso bucket piu volte; evita re-warp+gradient+extract (costoso).
|
|
||||||
# Cache a livello matcher per riusare tra chiamate find() su scene
|
|
||||||
# diverse: la rotazione del template non dipende dalla scena.
|
|
||||||
if not hasattr(self, '_refine_feat_cache'):
|
|
||||||
self._refine_feat_cache = {}
|
|
||||||
feat_cache = self._refine_feat_cache
|
|
||||||
cache_scale_key = round(scale * 1000)
|
|
||||||
|
|
||||||
def _score_at_angle(off: float) -> tuple[float, float, float]:
|
def _score_at_angle(off: float) -> tuple[float, float, float]:
|
||||||
"""Ritorna (score, best_cx, best_cy) per angolo = angle_deg + off."""
|
"""Ritorna (score, best_cx, best_cy) per angolo = angle_deg + off."""
|
||||||
ang = angle_deg + off
|
ang = angle_deg + off
|
||||||
ck = (round(ang * 20), cache_scale_key)
|
|
||||||
cached = feat_cache.get(ck)
|
|
||||||
if cached is not None:
|
|
||||||
fx, fy, fb = cached
|
|
||||||
else:
|
|
||||||
M = cv2.getRotationMatrix2D(center, ang, 1.0)
|
M = cv2.getRotationMatrix2D(center, ang, 1.0)
|
||||||
gray_r = cv2.warpAffine(gray_p, M, (diag, diag),
|
gray_r = cv2.warpAffine(gray_p, M, (diag, diag),
|
||||||
flags=cv2.INTER_LINEAR,
|
flags=cv2.INTER_LINEAR,
|
||||||
@@ -461,10 +463,6 @@ class LineShapeMatcher:
|
|||||||
flags=cv2.INTER_NEAREST, borderValue=0)
|
flags=cv2.INTER_NEAREST, borderValue=0)
|
||||||
mag, bins = self._gradient(gray_r)
|
mag, bins = self._gradient(gray_r)
|
||||||
fx, fy, fb = self._extract_features(mag, bins, mask_r)
|
fx, fy, fb = self._extract_features(mag, bins, mask_r)
|
||||||
# LRU semplice: limita cache a ~256 angoli (8 angoli * 32 candidati)
|
|
||||||
if len(feat_cache) > 256:
|
|
||||||
feat_cache.pop(next(iter(feat_cache)))
|
|
||||||
feat_cache[ck] = (fx, fy, fb)
|
|
||||||
if len(fx) < 8:
|
if len(fx) < 8:
|
||||||
return (0.0, cx, cy)
|
return (0.0, cx, cy)
|
||||||
dx = (fx - center[0]).astype(np.int32)
|
dx = (fx - center[0]).astype(np.int32)
|
||||||
@@ -823,7 +821,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.angle_step_deg / 2.0,
|
search_radius=self._effective_angle_step() / 2.0,
|
||||||
original_score=score,
|
original_score=score,
|
||||||
)
|
)
|
||||||
if verify_ncc:
|
if verify_ncc:
|
||||||
|
|||||||
Reference in New Issue
Block a user