perf: piramide al training, refinement sub-step, multithreading
LineShapeMatcher: - Feature piramidate precomputate al training (_LevelFeatures per livello piramide, dedup risolto una volta) - Refinement angolare: 5 offset ±step/2 + parabolic fit → precisione ~0.5° con angle_step=5° (10x fine rispetto a step training) - Subpixel posizione: parabolic fit 2D sul picco → frazione pixel - Multithreading: n_threads auto=CPU-1, parallelizza top-level pruning e full-res matching tramite ThreadPoolExecutor (numpy/cv2 rilasciano GIL) GUI: - Dialog edit_params con bottone Auto-tune - Legenda numerata match con pallino colore (#i, coords, angle, scala, score) - Hotkey finestra: r=params, o=nuovo ROI, m=nuovo modello, s=nuova scena - Pannello con train/find time + HOTKEY in basso auto_tune.py: - Analisi template: soglie grad da percentili, num_features da densità edge, pyramid_levels da min_side, min_score da entropia orientation, rilevazione simmetria rotazionale (soglia 0.75 NCC su magnitude) Benchmark clip.png (13 istanze, 72 varianti angolari): prima: 5.84s, precisione 5° (step training) ora: 1.67s, precisione ~0.5°, subpixel posizione speed-up: 3.5x, precisione angolare 10x Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+10
-3
@@ -16,6 +16,8 @@ from dataclasses import dataclass
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from pm2d.line_matcher import _oriented_bbox_polygon
|
||||
|
||||
|
||||
@dataclass
|
||||
class Match:
|
||||
@@ -26,7 +28,7 @@ class Match:
|
||||
angle_deg: float # rotazione [0, 360)
|
||||
scale: float # fattore scala (1.0 = template originale)
|
||||
score: float # similarità NCC [0, 1]
|
||||
bbox: tuple[int, int, int, int] # x, y, w, h del template ruotato/scalato
|
||||
bbox_poly: np.ndarray # (4, 2) float32 - vertici bbox orientato
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -67,6 +69,7 @@ class EdgeShapeMatcher:
|
||||
self.top_score_factor = top_score_factor
|
||||
self.templates: list[Template] = []
|
||||
self.template_size: tuple[int, int] = (0, 0) # w, h originale
|
||||
self.template_gray: np.ndarray | None = None
|
||||
|
||||
@staticmethod
|
||||
def _to_gray(img: np.ndarray) -> np.ndarray:
|
||||
@@ -96,6 +99,7 @@ class EdgeShapeMatcher:
|
||||
gray = self._to_gray(template_bgr)
|
||||
h, w = gray.shape
|
||||
self.template_size = (w, h)
|
||||
self.template_gray = gray.copy()
|
||||
edge_orig = self._edges(gray)
|
||||
mask_orig = np.full((h, w), 255, dtype=np.uint8)
|
||||
|
||||
@@ -249,20 +253,23 @@ class EdgeShapeMatcher:
|
||||
# NMS spaziale baricentri
|
||||
kept: list[Match] = []
|
||||
r2 = nms_radius * nms_radius
|
||||
tw0, th0 = self.template_size
|
||||
for score, x, y, ti in refined:
|
||||
tpl = self.templates[ti]
|
||||
cx = x + tpl.cx_local
|
||||
cy = y + tpl.cy_local
|
||||
if any((k.cx - cx) ** 2 + (k.cy - cy) ** 2 < r2 for k in kept):
|
||||
continue
|
||||
th, tw = tpl.edge.shape
|
||||
poly = _oriented_bbox_polygon(
|
||||
cx, cy, tw0 * tpl.scale, th0 * tpl.scale, tpl.angle_deg,
|
||||
)
|
||||
kept.append(
|
||||
Match(
|
||||
cx=cx, cy=cy,
|
||||
angle_deg=tpl.angle_deg,
|
||||
scale=tpl.scale,
|
||||
score=score,
|
||||
bbox=(x, y, tw, th),
|
||||
bbox_poly=poly,
|
||||
)
|
||||
)
|
||||
if len(kept) >= max_matches:
|
||||
|
||||
Reference in New Issue
Block a user