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",
|
||||||
|
|||||||
+27
-31
@@ -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 ------------------------------------------------------
|
||||||
|
|
||||||
@@ -415,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)))
|
||||||
@@ -574,7 +593,6 @@ class LineShapeMatcher:
|
|||||||
verify_threshold: float = 0.4,
|
verify_threshold: float = 0.4,
|
||||||
coarse_angle_factor: int = 2,
|
coarse_angle_factor: int = 2,
|
||||||
scale_penalty: float = 0.0,
|
scale_penalty: float = 0.0,
|
||||||
search_roi: tuple[int, int, int, int] | None = None,
|
|
||||||
) -> list[Match]:
|
) -> list[Match]:
|
||||||
"""
|
"""
|
||||||
scale_penalty: se > 0, riduce lo score per match a scala diversa da 1.0:
|
scale_penalty: se > 0, riduce lo score per match a scala diversa da 1.0:
|
||||||
@@ -582,30 +600,11 @@ class LineShapeMatcher:
|
|||||||
Utile se l'operatore vuole che match "identico al template anche per
|
Utile se l'operatore vuole che match "identico al template anche per
|
||||||
dimensione" abbia score più alto di match "stessa forma, dimensione
|
dimensione" abbia score più alto di match "stessa forma, dimensione
|
||||||
diversa". scale_penalty=0 (default) = comportamento shape puro.
|
diversa". scale_penalty=0 (default) = comportamento shape puro.
|
||||||
|
|
||||||
search_roi: (x, y, w, h) limita la ricerca a una regione della scena.
|
|
||||||
Equivalente a Halcon set_aoi: il matching opera su crop locale e le
|
|
||||||
coordinate output sono ri-traslate al sistema scena originale. Usare
|
|
||||||
quando si conosce a priori l'area in cui il pezzo può apparire (es.
|
|
||||||
feeder a posizione fissa) → costo proporzionale a w·h invece di W·H.
|
|
||||||
"""
|
"""
|
||||||
if not self.variants:
|
if not self.variants:
|
||||||
raise RuntimeError("Matcher non addestrato: chiamare train() prima.")
|
raise RuntimeError("Matcher non addestrato: chiamare train() prima.")
|
||||||
|
|
||||||
gray_full = self._to_gray(scene_bgr)
|
gray0 = self._to_gray(scene_bgr)
|
||||||
# Applica ROI di ricerca: restringe scena a crop, ricorda offset per
|
|
||||||
# ri-traslare le coordinate dei match a fine pipeline.
|
|
||||||
if search_roi is not None:
|
|
||||||
rx, ry, rw, rh = search_roi
|
|
||||||
H_s, W_s = gray_full.shape
|
|
||||||
rx = max(0, int(rx)); ry = max(0, int(ry))
|
|
||||||
rw = max(1, min(int(rw), W_s - rx))
|
|
||||||
rh = max(1, min(int(rh), H_s - ry))
|
|
||||||
gray0 = gray_full[ry:ry + rh, rx:rx + rw]
|
|
||||||
roi_offset = (rx, ry)
|
|
||||||
else:
|
|
||||||
gray0 = gray_full
|
|
||||||
roi_offset = (0, 0)
|
|
||||||
grays = [gray0]
|
grays = [gray0]
|
||||||
for _ in range(self.pyramid_levels - 1):
|
for _ in range(self.pyramid_levels - 1):
|
||||||
grays.append(cv2.pyrDown(grays[-1]))
|
grays.append(cv2.pyrDown(grays[-1]))
|
||||||
@@ -822,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:
|
||||||
@@ -830,11 +829,8 @@ class LineShapeMatcher:
|
|||||||
if ncc < verify_threshold:
|
if ncc < verify_threshold:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Ri-traslo coord da spazio crop ROI a spazio scena originale.
|
|
||||||
cx_out = cx_f + roi_offset[0]
|
|
||||||
cy_out = cy_f + roi_offset[1]
|
|
||||||
poly = _oriented_bbox_polygon(
|
poly = _oriented_bbox_polygon(
|
||||||
cx_out, cy_out, tw * var.scale, th * var.scale, ang_f,
|
cx_f, cy_f, tw * var.scale, th * var.scale, ang_f,
|
||||||
)
|
)
|
||||||
# Penalità scala opzionale: score degrada con distanza da 1.0
|
# Penalità scala opzionale: score degrada con distanza da 1.0
|
||||||
if scale_penalty > 0.0 and var.scale != 1.0:
|
if scale_penalty > 0.0 and var.scale != 1.0:
|
||||||
@@ -842,7 +838,7 @@ class LineShapeMatcher:
|
|||||||
0.0, 1.0 - scale_penalty * abs(var.scale - 1.0)
|
0.0, 1.0 - scale_penalty * abs(var.scale - 1.0)
|
||||||
)
|
)
|
||||||
kept.append(Match(
|
kept.append(Match(
|
||||||
cx=cx_out, cy=cy_out,
|
cx=cx_f, cy=cy_f,
|
||||||
angle_deg=ang_f,
|
angle_deg=ang_f,
|
||||||
scale=var.scale,
|
scale=var.scale,
|
||||||
score=score_f,
|
score=score_f,
|
||||||
|
|||||||
Reference in New Issue
Block a user