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:
|
||||
min_score = 0.45
|
||||
|
||||
# angle step: 5° default; se simmetria, mantengo step ma range ridotto
|
||||
angle_step = 5.0
|
||||
# angle step adattivo (Halcon-style): atan(2/max_side) deg, clampato.
|
||||
# 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 = {
|
||||
"backend": "line",
|
||||
|
||||
+24
-26
@@ -197,12 +197,31 @@ class LineShapeMatcher:
|
||||
n = int(np.floor((s1 - s0) / self.scale_step)) + 1
|
||||
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]:
|
||||
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)]
|
||||
n = int(np.floor((a1 - a0) / self.angle_step_deg))
|
||||
return [float(a0 + i * self.angle_step_deg) for i in range(n)]
|
||||
n = int(np.floor((a1 - a0) / step))
|
||||
return [float(a0 + i * step) for i in range(n)]
|
||||
|
||||
# --- Training ------------------------------------------------------
|
||||
|
||||
@@ -239,8 +258,6 @@ class LineShapeMatcher:
|
||||
self._train_mask = mask_full.copy()
|
||||
|
||||
self.variants.clear()
|
||||
# Invalida cache feature di refine: il template e cambiato.
|
||||
self._refine_feat_cache = {}
|
||||
for s in self._scale_list():
|
||||
sw = max(16, int(round(w * 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:
|
||||
return (angle_deg, original_score, cx, cy)
|
||||
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
|
||||
sw = max(16, int(round(w * scale)))
|
||||
@@ -435,24 +452,9 @@ class LineShapeMatcher:
|
||||
H, W = spread0.shape
|
||||
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]:
|
||||
"""Ritorna (score, best_cx, best_cy) per angolo = 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)
|
||||
gray_r = cv2.warpAffine(gray_p, M, (diag, diag),
|
||||
flags=cv2.INTER_LINEAR,
|
||||
@@ -461,10 +463,6 @@ class LineShapeMatcher:
|
||||
flags=cv2.INTER_NEAREST, borderValue=0)
|
||||
mag, bins = self._gradient(gray_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:
|
||||
return (0.0, cx, cy)
|
||||
dx = (fx - center[0]).astype(np.int32)
|
||||
@@ -823,7 +821,7 @@ 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.angle_step_deg / 2.0,
|
||||
search_radius=self._effective_angle_step() / 2.0,
|
||||
original_score=score,
|
||||
)
|
||||
if verify_ncc:
|
||||
|
||||
Reference in New Issue
Block a user