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
-1
View File
@@ -12,7 +12,6 @@ Tutta la logica algoritmica vive in pm2d.matcher.EdgeShapeMatcher.
from __future__ import annotations from __future__ import annotations
import sys
from pathlib import Path from pathlib import Path
from tkinter import Tk, filedialog from tkinter import Tk, filedialog
import tkinter as tk import tkinter as tk
+20 -12
View File
@@ -38,7 +38,6 @@ _GOLDEN = (math.sqrt(5.0) - 1.0) / 2.0 # ≈ 0.618
from pm2d._jit_kernels import ( from pm2d._jit_kernels import (
score_by_shift as _jit_score_by_shift, 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 as _jit_score_bitmap_rescored,
score_bitmap_rescored_window as _jit_score_bitmap_rescored_window, score_bitmap_rescored_window as _jit_score_bitmap_rescored_window,
score_bitmap_greedy as _jit_score_bitmap_greedy, score_bitmap_greedy as _jit_score_bitmap_greedy,
@@ -326,8 +325,6 @@ class LineShapeMatcher:
n_vars = len(self.variants) n_vars = len(self.variants)
n_levels = len(self.variants[0].levels) n_levels = len(self.variants[0].levels)
var_meta = np.zeros((n_vars, 6), dtype=np.float32) # ang, scale, kh, kw, cxl, cyl 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_offsets_per_level = [[] for _ in range(n_levels)]
all_dx_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)] all_dy_per_level = [[] for _ in range(n_levels)]
@@ -1483,12 +1480,9 @@ class LineShapeMatcher:
if nms_radius is None: if nms_radius is None:
nms_radius = max(8, min(self.template_size) // 2) nms_radius = max(8, min(self.template_size) // 2)
# Pruning adattivo allo step angolare: con step piccolo (<= 3 deg) # Pruning adattivo allo step angolare: con step piccolo (<= 3 deg)
# ci sono molte varianti vicine, gli score top-level sono ravvicinati # ci sono molte varianti vicine e gli score top-level sono
# e top_thresh*0.5 e' troppo aggressivo: scarta varianti valide che # ravvicinati: top_thresh*0.5 e' troppo aggressivo, scarta varianti
# sarebbero state riprese al full-res. Stessa cosa per # valide che sarebbero state riprese al full-res.
# 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.
# Il path windowed (pyramid_propagate) assume che il picco # Il path windowed (pyramid_propagate) assume che il picco
# top-level localizzi la posizione entro il margine finestra. # top-level localizzi la posizione entro il margine finestra.
# Su template ALLUNGATI (es. lama 40x280, o ROI parziale lungo un # Su template ALLUNGATI (es. lama 40x280, o ROI parziale lungo un
@@ -1503,10 +1497,25 @@ class LineShapeMatcher:
pyramid_propagate = False pyramid_propagate = False
eff_step = self._effective_angle_step() eff_step = self._effective_angle_step()
top_factor = self.top_score_factor top_factor = self.top_score_factor
cf_eff = max(1, coarse_angle_factor)
if eff_step <= 3.0: if eff_step <= 3.0:
top_factor = max(top_factor, 0.7) 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 top_thresh = min_score * top_factor
diag["top_thresh_used"] = float(top_thresh) diag["top_thresh_used"] = float(top_thresh)
@@ -1562,7 +1571,6 @@ class LineShapeMatcher:
dtype=bool, dtype=bool,
) )
if scene_bins.any(): if scene_bins.any():
n_scene_active = int(scene_bins.sum())
# Soglia: variante deve avere >= 50% delle sue feature in bin # Soglia: variante deve avere >= 50% delle sue feature in bin
# presenti nella scena. Sotto = score certamente < 0.5. # presenti nella scena. Sotto = score certamente < 0.5.
pruned_idx_list = [] pruned_idx_list = []