Adriano a78884f950 feat(web): anteprima edge sul modello + tracker pulizia rumore + UCS baricentro
Pannello "🔬 Anteprima edge / pulizia rumore" sotto il canvas modello.
Permette tuning interattivo dei parametri di selezione edge per
togliere "sporcizie" (rumore di sfondo, edge spuri) prima di
trainare il matcher.

Server:
- POST /preview_edges: dato modello+ROI+param edge, ritorna immagine
  ROI con overlay:
  * heatmap magnitude gradient (sfondo)
  * verde scuro: pixel sopra hysteresis edge
  * cerchietti colorati per bin: feature scelte (palette 16 bin)
  * UCS rosso/verde sul baricentro feature (richiesta utente):
    asse X destra, Y giu' (image y-down)
  Ritorna anche stats: n_features, n_edge_strong, percentili magnitude,
  ucs_baricentro {cx, cy}

UI:
- Slider weak_grad/strong_grad/num_features/spacing + checkbox polarity
- Re-fetch debounced (200ms) ad ogni input → preview live
- Bottone "Applica ai parametri Avanzate": copia i valori scelti
  nei campi Avanzate del matcher principale
- Auto-fetch quando il pannello viene aperto

Use case: operatore vede SUBITO quali edge il matcher userebbe,
regola soglie per escludere rumore, applica e poi MATCH.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 10:48:58 +02:00

Shape Model 2D — Standalone PM 2D

Programma standalone Pattern Matching 2D shape-based.

Due backend algoritmici:

Backend Modulo Algoritmo Tempo clip.png (13 istanze)
line (default) pm2d.line_matcher.LineShapeMatcher Linemod-style: gradient orient quantizzata + spread + response map + feature sparse 3.5 s, 12/13 score 1.0
edge pm2d.matcher.EdgeShapeMatcher Edge Canny + matchTemplate multi-rotazione 84 s, 6/13 score ~0.3

Porting algoritmico (non SIMD) di meiqua/shape_based_matching/line2Dup. MIPP (wrapper SIMD C++) non ha senso in Python — la vettorizzazione la fa già NumPy.

Struttura

Shape_model_2d/
├── pm2d/
│   ├── __init__.py
│   ├── matcher.py        # EdgeShapeMatcher (fallback, semplice)
│   ├── line_matcher.py   # LineShapeMatcher (default, ottimizzato)
│   └── gui.py            # GUI OpenCV + tk file dialog
├── main.py               # entry point
├── Test/                 # immagini di test
├── pyproject.toml
└── README.md

GUI e algoritmo separati: i matcher sono riusabili da qualsiasi script/backend.

Setup

uv sync

Esecuzione

uv run python main.py

Flusso: file dialog modello → ROI → file dialog scena → risultati.

API algoritmo (backend line, raccomandato)

import cv2
from pm2d import LineShapeMatcher

template = cv2.imread("model.png")
scene    = cv2.imread("scene.png")

m = LineShapeMatcher(
    num_features=96,          # feature sparse per variante
    weak_grad=30,             # soglia gradiente per spread
    strong_grad=60,           # soglia gradiente per estrazione feature
    angle_range_deg=(0, 360),
    angle_step_deg=5.0,
    scale_range=(0.9, 1.1),   # invarianza a scala
    scale_step=0.05,
    spread_radius=5,          # raggio dilate per robustezza
    pyramid_levels=3,         # velocità via pruning top-level
    top_score_factor=0.5,     # soglia top = min_score * factor
)
m.train(template)             # ~0.2 s
matches = m.find(scene, min_score=0.55, max_matches=25)

for x in matches:
    print(x.cx, x.cy, x.angle_deg, x.scale, x.score)

Modello su regione parziale (non blob distinto)

train() accetta una maschera binaria opzionale per limitare le feature a una porzione della ROI (es. parte interna di un oggetto complesso, dettaglio distintivo, ecc.):

mask = np.zeros_like(template[:, :, 0])
cv2.fillPoly(mask, [poligono_utente], 255)
m.train(template, mask=mask)

Solo i gradienti dentro la maschera contribuiscono alle feature.

Come funziona il backend line

Training (costoso, ~0.2 s / 72 varianti)

Per ogni coppia (angolo, scala) del template:

  1. Rotazione + scala su canvas con padding diagonale
  2. Sobel → magnitude + orientation (atan2)
  3. Quantizzazione orientazione in 8 bin modulo π (edge simmetrici)
  4. Estrazione N feature sparse: top-magnitude sopra strong_grad, con spacing minimo per evitare cluster
  5. Feature salvate come (dx, dy, bin) relative al centro-modello

Matching (veloce)

Scena processata una volta per livello piramide:

  • Sobel → mag → orient quantizzato → bin invalidato dove mag < weak_grad
  • Spread: dilate morfologica per bin (tolleranza localizzazione)
  • Response map (8, H, W): response[b][y,x] = 1 dove orient b è presente

Per ogni variante:

score[y, x] = Σ_i  resp[bin_i][y + dy_i, x + dx_i]  /  N_features

Implementato con shift+add vettorizzato NumPy (O(N_features · H · W) invece di O(kh·kw·H·W) come matchTemplate).

Piramide multi-risoluzione

  • Top-level (risoluzione /4 di default con pyramid_levels=3): score ridotto per pruning varianti. Se nessun pixel raggiunge min_score * top_score_factor, la variante è scartata.
  • Full-res: calcolato solo per le varianti sopravvissute → nel benchmark clip: ~5-10 varianti su 72 = 7-14× speed-up rispetto a full-res per tutte.

Parametri principali

Parametro Default Significato
num_features 96 feature sparse per variante
weak_grad 30 threshold debole (per spread)
strong_grad 60 threshold forte (per estrazione feature)
spread_radius 5 raggio dilate spread (tolleranza posizionale)
min_feature_spacing 3 spacing minimo tra feature per evitare cluster
angle_range_deg (0,360) range rotazioni
angle_step_deg 5.0 passo angolare
scale_range (1,1) range scale
scale_step 0.1 passo scala
pyramid_levels 3 livelli piramide (più = pruning più aggressivo)
top_score_factor 0.5 soglia top-level = min_score * factor
min_score 0.55 soglia score finale [0..1]
max_matches 25 numero max di match
nms_radius min(w,h)/2 raggio NMS baricentri

Roadmap

  • Subpixel refinement (interpolazione parabolic sui picchi)
  • ICP locale per raffinamento pose
  • Vincoli di orientamento: clustering delle pose per eliminare duplicati cross-variante
  • Numba JIT per il ciclo shift+add (eventuale 3-5× su scene grandi)

Deploy VPS con Docker + Traefik

Assume che sulla VPS siano già attivi:

  • Traefik come reverse proxy su network Docker esterna traefik
  • Entrypoints web (:80) e websecure (:443)
  • Cert resolver letsencrypt configurato

Build e push al registry

# Build locale
docker build -t vps-ip:5000/pm2d:latest .
docker push vps-ip:5000/pm2d:latest

Sulla VPS

# Cartella deploy (immagini persistenti qui)
mkdir -p /opt/docker/pm2d/images
cd /opt/docker/pm2d

# Copia docker-compose.yml
# Imposta REGISTRY / TAG se necessario via .env
echo "REGISTRY=vps-ip:5000" > .env
echo "TAG=latest" >> .env

docker compose pull
docker compose up -d

Servizio raggiungibile: https://pm.tielogic.xyz

Note operative

  • Volume ./images: persistenza delle immagini caricate tramite UI (IMAGES_DIR=/data/images nel container). Sopravvive a restart.
  • Upload max 50MB: middleware Traefik pm2d-bodysize. Adattare se serve.
  • Cache matcher in-memory: si svuota a restart container (no problema, viene ri-popolata al primo match).
  • Healthcheck: HTTP GET /images ogni 30s.
  • Se nome network Traefik diverso da traefik, modifica docker-compose.yml sezione networks.

Adattamenti config Traefik non-standard

Se la VPS ha convenzioni diverse (es. cert resolver chiamato le, entrypoint https), modifica i labels nel docker-compose.yml.

S
Description
No description provided
Readme 11 MiB
Languages
Python 82.3%
JavaScript 11.3%
HTML 4%
CSS 2%
Dockerfile 0.4%