perf: coarse step angolare auto al top-level (Halcon-style)

Al livello top le feature distano R/2^top dal centro: lo spread tollera
una rotazione ~atan(spread/(max_side_top/2)), molto piu' ampia dello
step full-res. Si valuta al top 1 variante ogni cf_auto (clamp 1..8),
le intermedie vengono riprese dall'espansione ai vicini. Con step 2°
e template 160px: top_eval 180 -> 30 varianti a parita' di recall
(rimosso il forzato cf=1 per step <= 3 che valutava tutto).

Inclusa pulizia lint: variabili/import inutilizzati.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 12:30:46 +00:00
parent 0420c4a863
commit 91a6beb032
2 changed files with 20 additions and 13 deletions
+20 -12
View File
@@ -38,7 +38,6 @@ _GOLDEN = (math.sqrt(5.0) - 1.0) / 2.0 # ≈ 0.618
from pm2d._jit_kernels import (
score_by_shift as _jit_score_by_shift,
score_bitmap as _jit_score_bitmap,
score_bitmap_rescored as _jit_score_bitmap_rescored,
score_bitmap_rescored_window as _jit_score_bitmap_rescored_window,
score_bitmap_greedy as _jit_score_bitmap_greedy,
@@ -326,8 +325,6 @@ class LineShapeMatcher:
n_vars = len(self.variants)
n_levels = len(self.variants[0].levels)
var_meta = np.zeros((n_vars, 6), dtype=np.float32) # ang, scale, kh, kw, cxl, cyl
all_dx, all_dy, all_bin, all_offsets = [], [], [], []
offset = 0
all_offsets_per_level = [[] for _ in range(n_levels)]
all_dx_per_level = [[] for _ in range(n_levels)]
all_dy_per_level = [[] for _ in range(n_levels)]
@@ -1483,12 +1480,9 @@ class LineShapeMatcher:
if nms_radius is None:
nms_radius = max(8, min(self.template_size) // 2)
# Pruning adattivo allo step angolare: con step piccolo (<= 3 deg)
# ci sono molte varianti vicine, gli score top-level sono ravvicinati
# e top_thresh*0.5 e' troppo aggressivo: scarta varianti valide che
# sarebbero state riprese al full-res. Stessa cosa per
# coarse_angle_factor (skip 1 ogni 2): con step fine non e' utile.
# Risultato osservato: precisione "veloce" 10° dava risultati
# migliori di "preciso" 2° proprio perche evitava il pruning.
# ci sono molte varianti vicine e gli score top-level sono
# ravvicinati: top_thresh*0.5 e' troppo aggressivo, scarta varianti
# valide che sarebbero state riprese al full-res.
# Il path windowed (pyramid_propagate) assume che il picco
# top-level localizzi la posizione entro il margine finestra.
# Su template ALLUNGATI (es. lama 40x280, o ROI parziale lungo un
@@ -1503,10 +1497,25 @@ class LineShapeMatcher:
pyramid_propagate = False
eff_step = self._effective_angle_step()
top_factor = self.top_score_factor
cf_eff = max(1, coarse_angle_factor)
if eff_step <= 3.0:
top_factor = max(top_factor, 0.7)
cf_eff = 1
# Coarse step angolare AUTO al top-level (Halcon-style): al livello
# top le feature distano R/2^top dal centro, quindi lo spread
# (raggio in px, costante per livello) tollera una rotazione
# ~atan(spread / (max_side_top/2)) — molto piu' ampia dello step
# richiesto a full-res. Si valuta al top 1 variante ogni cf_eff;
# le intermedie vengono riprese dall'espansione ai vicini.
# Es: template 160 px, 3 livelli, step 2° → tolleranza top ~11°
# → cf 6 → top-pruning ~6x piu' veloce a parita' di recall.
if self.template_size != (0, 0):
max_side_top = max(self.template_size) / (2 ** top)
else:
max_side_top = 64.0
step_top_tol = math.degrees(
math.atan2(float(self.spread_radius), max(8.0, max_side_top / 2.0))
)
cf_auto = int(np.clip(round(step_top_tol / max(eff_step, 1e-6)), 1, 8))
cf_eff = max(1, coarse_angle_factor, cf_auto)
top_thresh = min_score * top_factor
diag["top_thresh_used"] = float(top_thresh)
@@ -1562,7 +1571,6 @@ class LineShapeMatcher:
dtype=bool,
)
if scene_bins.any():
n_scene_active = int(scene_bins.sum())
# Soglia: variante deve avere >= 50% delle sue feature in bin
# presenti nella scena. Sotto = score certamente < 0.5.
pruned_idx_list = []