UI espone tutti i nuovi flag tramite sezione pieghevole "Modalita Halcon" nel pannello impostazioni. Default off = comportamento backward compat. Flag esposti (checkbox + numerici): - use_polarity (F): 16-bin orientation mod 2pi - use_gpu (R): OpenCL UMat con silent fallback CPU - use_soft_score (Y): score continuo cos(theta_t-theta_s) - subpixel_lm (Z): refinement 0.05 px gradient field - refine_pose_joint: Nelder-Mead 3D (cx,cy,theta) - pyramid_propagate: top-K propagation a full-res - min_recall (M): filtro feature-recall - nms_iou_threshold (A): IoU bbox poligonale - greediness: early-exit kernel - coarse_stride: sub-sampling top-level - search_roi: x,y,w,h area di ricerca Persistenza ricette (V): - Endpoint POST /recipes: training + save .npz in recipes/ - Endpoint GET /recipes: lista - UI: campo nome + bottone "Salva" sotto i flag Server SimpleMatchParams esteso con tutti i campi; pipeline match_simple propaga init-flags al cache key (use_polarity/use_gpu = retrain) e find-flags al m.find(). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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:
- Rotazione + scala su canvas con padding diagonale
- Sobel →
magnitude+orientation(atan2) - Quantizzazione orientazione in 8 bin modulo π (edge simmetrici)
- Estrazione N feature sparse: top-magnitude sopra
strong_grad, con spacing minimo per evitare cluster - 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 raggiungemin_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) ewebsecure(:443) - Cert resolver
letsencryptconfigurato
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/imagesnel 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 /imagesogni 30s. - Se nome network Traefik diverso da
traefik, modificadocker-compose.ymlsezionenetworks.
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.