b9a4d51fac
Programma standalone Pattern Matching 2D con GUI cv2/tk + algoritmo puro riusabile. Due backend: - LineShapeMatcher (default): porting Python di line2Dup (linemod-style) - Gradient orientation quantized 8-bin modulo π + spreading - Feature sparse top-magnitude con spacing minimo - Score via shift-add vettorizzato numpy (O(N_features·H·W)) - Piramide multi-risoluzione con pruning varianti al top-level - Supporto mask binaria per modello non-rettangolare - EdgeShapeMatcher (fallback): Canny + matchTemplate multi-rotazione GUI separata da algoritmo. Benchmark clip.png (13 istanze): - Edge backend: 84s, 6/13 score ~0.3 - Line backend: 4.1s, 13/13 score 0.98-1.00 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1355 lines
45 KiB
Markdown
1355 lines
45 KiB
Markdown
# Engine `shape_model_2d` — Documento tecnico operativo
|
|
|
|
**Progetto di appartenenza**: Tielogic Vision Suite
|
|
**Scope**: implementazione del primo matching engine della suite
|
|
**Tipo di matching**: shape-based 2D invariante a rotazione, scala, illuminazione
|
|
**Equivalente Halcon**: `create_shape_model`, `find_shape_model`, varianti `scaled`, `aniso`
|
|
**Stato**: documento operativo — integra e approfondisce la sezione 4 del documento di progetto Vision Suite
|
|
**Data**: 23 aprile 2026
|
|
|
|
---
|
|
|
|
## 1. Cosa deve fare questo engine
|
|
|
|
Il motore `shape_model_2d` risolve il problema classico del vision industriale 2D:
|
|
|
|
> Dato un template di riferimento (un'immagine esempio di un oggetto, oppure un DXF del suo profilo), trovare tutte le istanze di quel template in immagini di scena, restituendo per ciascuna istanza posizione (x, y), angolo di rotazione, scala e score di confidenza, con precisione subpixel.
|
|
|
|
Caratteristiche richieste:
|
|
|
|
- **Invariante a illuminazione**: lavora su gradienti/edge, non su intensità assolute
|
|
- **Invariante a rotazione**: range angolare configurabile (tipicamente 0-360°)
|
|
- **Invariante a scala**: range di scala configurabile (tipicamente ±5-20%)
|
|
- **Multi-istanza**: trova N istanze indipendenti dello stesso template
|
|
- **Robusto a occlusione**: fino al 40-50% dell'oggetto coperto
|
|
- **Performance real-time**: >10 Hz su CPU moderna per immagini 1920x1080
|
|
- **Subpixel accuracy**: <0.5 px di precisione posizionale, <0.5° angolare
|
|
|
|
Applicazioni industriali tipiche:
|
|
|
|
- Ispezione presenza/assenza di pezzi su nastro trasportatore
|
|
- Allineamento preciso prima di pick-and-place robotico
|
|
- Verifica orientamento componenti
|
|
- Controllo qualità di sagome tagliate (laser, fresatura, tranciatura)
|
|
- Conteggio istanze multiple dello stesso pezzo
|
|
|
|
---
|
|
|
|
## 2. Stato dell'open-source e scelte tecnologiche
|
|
|
|
### 2.1 Opzioni disponibili
|
|
|
|
Il panorama open-source per shape-based matching include diverse alternative, con diversa maturità e diverse trade-off.
|
|
|
|
**Opzione 1 — `meiqua/shape_based_matching`** (https://github.com/meiqua/shape_based_matching)
|
|
|
|
Reimplementazione open-source del shape-based matching di Halcon, basata su linemod di OpenCV con miglioramenti significativi. 1.4k star, 512 fork. Autore dell'SJTU robotics institute.
|
|
|
|
Caratteristiche:
|
|
- Scritto in C++ con OpenCV
|
|
- Accelerazione SIMD via MIPP (x86 SSE/AVX, ARM Neon)
|
|
- Branch dedicati per pose refinement con ICP, subpixel, gestione scale
|
|
- Può processare ~1000 template in ~20ms
|
|
|
|
Stato manutenzione:
|
|
- Sviluppo principale 2018-2020
|
|
- Attualmente non attivamente mantenuto ma stabile
|
|
- Molti fork derivati attivi
|
|
|
|
Problemi pratici:
|
|
- No Python binding ufficiali (solo C++)
|
|
- Build CMake da sistemare a mano
|
|
- Documentazione essenziale, esempi solo in `test.cpp`
|
|
|
|
**Opzione 2 — `mwwzbinf/mwwz-shape-match`** (https://github.com/mwwzbinf/mwwz-shape-match)
|
|
|
|
Implementazione più recente con focus esplicito sulle varianti Halcon (scaled, aniso).
|
|
|
|
Caratteristiche:
|
|
- Supporto esplicito a `find_scaled_shape_model` e `find_aniso_shape_model`
|
|
- Creazione modello da cerchio o rettangolo
|
|
- Subpixel nativo
|
|
|
|
Stato:
|
|
- Più recente del repo meiqua
|
|
- Base di utenti più piccola, meno testato in produzione
|
|
- Documentazione in cinese predominante
|
|
|
|
**Opzione 3 — OpenCV `cv::linemod` (opencv_contrib)**
|
|
|
|
Modulo base da cui deriva l'algoritmo di meiqua. Incluso in `opencv-contrib-python`.
|
|
|
|
Caratteristiche:
|
|
- Installazione banale (`pip install opencv-contrib-python`)
|
|
- Manutenzione OpenCV garantita
|
|
- Documentazione ufficiale
|
|
|
|
Limiti:
|
|
- Algoritmo "raw" senza raffinamenti
|
|
- No subpixel nativo
|
|
- Performance peggiori (no SIMD custom)
|
|
- Precisione angolare di ~1-2°, non sotto come i raffinamenti di meiqua
|
|
|
|
**Opzione 4 — OpenCV `matchTemplate`**
|
|
|
|
Correlation matching classico.
|
|
|
|
Limiti stringenti:
|
|
- No rotation invariance nativa (va wrappata manualmente con rotazioni multiple del template)
|
|
- No scale invariance nativa
|
|
- Non robusto a illuminazione
|
|
|
|
Non adatto come engine production. Utile solo come baseline di confronto.
|
|
|
|
### 2.2 Scelta raccomandata
|
|
|
|
**Strategia a tre fasi implementative**, ciascuna con un motore diverso:
|
|
|
|
**Fase Alpha** (2-3 settimane): prototipo con **OpenCV linemod**. L'obiettivo non è la precisione finale ma validare l'architettura Vision Suite, i contratti API, il workflow web UI. Precisione target: "funziona ed è utilizzabile".
|
|
|
|
**Fase Beta** (3-4 settimane): upgrade a **fork Tielogic di `meiqua/shape_based_matching`**. Precisione production, performance ottimizzate. Questa è la versione destinata ai clienti.
|
|
|
|
**Fase Gamma** (opzionale, futuro): se emergono requisiti specifici non coperti (deformazioni, matching planari prospettici, ecc.) si valuta integrazione di motori aggiuntivi complementari senza sostituire Beta.
|
|
|
|
### 2.3 Perché forkare il repo meiqua invece di usarlo direttamente
|
|
|
|
Per un prodotto commerciale destinato a clienti industriali, dipendere da un repo non attivamente mantenuto è un rischio operativo inaccettabile. Il fork Tielogic garantisce:
|
|
|
|
- **Continuità**: se l'autore rimuovesse il repo domani, il fork resta indipendente
|
|
- **Controllo qualità**: bug fix applicabili in autonomia
|
|
- **Adattamenti specifici**: aggiunte di feature richieste dai clienti Tielogic
|
|
- **Python bindings ufficiali**: il repo originale non ha binding Python; il fork li aggiunge come first-class citizen
|
|
- **CI/CD**: build riproducibili in container Docker verificati
|
|
- **Supporto commerciale**: possibilità di offrire SLA ai clienti paganti
|
|
|
|
Il costo iniziale del fork è ~1-2 settimane per sistemare build, aggiungere binding Python, test di non-regressione rispetto al repo originale. Da confrontare con il rischio di trovarsi bloccati in futuro.
|
|
|
|
**Strategia di fork pragmatica**:
|
|
|
|
1. Fork del repo originale sotto l'organization Tielogic (es. `tielogic/shape_based_matching`)
|
|
2. Branch `tielogic/main` che traccia `upstream/master`
|
|
3. Branch `tielogic/production` dove applichi patch Tielogic (binding Python, CMake multi-platform, bug fix)
|
|
4. Aggiornamenti periodici da upstream via merge controllato
|
|
|
|
---
|
|
|
|
## 3. Specifica funzionale dell'engine
|
|
|
|
### 3.1 Ingressi
|
|
|
|
L'engine accetta due tipi di asset sorgente per la creazione di un modello:
|
|
|
|
**Tipo 1 — Immagine di riferimento**
|
|
|
|
- Formato: PNG, JPG, BMP, TIFF
|
|
- Colore: accetta RGB/BGR o grayscale (conversione automatica a grayscale internamente)
|
|
- Risoluzione minima: 50x50 px (sotto questa soglia il modello è troppo povero)
|
|
- Risoluzione massima: 2000x2000 px (sopra rallenta creazione senza benefici)
|
|
- ROI opzionale: l'operatore può ritagliare una regione di interesse dall'immagine fornita
|
|
|
|
**Tipo 2 — DXF (Drawing Exchange Format)**
|
|
|
|
- Versioni supportate: DXF da R12 a AutoCAD 2022
|
|
- Entità supportate: LINE, POLYLINE, LWPOLYLINE, ARC, CIRCLE, SPLINE, ELLIPSE
|
|
- Entità ignorate: TEXT, MTEXT, DIMENSION, HATCH, INSERT (blocchi), LAYER invisibili
|
|
- Filtering: selezione esplicita dei layer da includere
|
|
- Unità: detection automatica da `$INSUNITS` header, override manuale disponibile
|
|
|
|
### 3.2 Parametri di creazione modello
|
|
|
|
```json
|
|
{
|
|
"pyramid_levels": 4,
|
|
"angle_range_deg": [0, 360],
|
|
"angle_step_deg": 1.0,
|
|
"scale_range": [1.0, 1.0],
|
|
"scale_step": 0.05,
|
|
"min_contrast": 30,
|
|
"greediness": 0.9,
|
|
"num_features": 150,
|
|
|
|
"dxf_params": {
|
|
"resolution_px_per_mm": 5.0,
|
|
"layers_included": ["CONTORNO", "0"],
|
|
"tessellation_tolerance_mm": 0.1,
|
|
"line_thickness_px": 2
|
|
}
|
|
}
|
|
```
|
|
|
|
**Significato dei parametri principali**:
|
|
|
|
- `pyramid_levels`: livelli della piramide multi-risoluzione. Più livelli = ricerca più rapida ma meno sensibile a feature piccole. Default 4 è buono per oggetti 100-800 px.
|
|
- `angle_range_deg`: range di rotazioni da cercare. Se sai che l'oggetto è sempre ±30° dalla posizione nominale, limitare il range velocizza 10x.
|
|
- `angle_step_deg`: risoluzione angolare dei template precomputati. 1° è standard; 0.5° per precisione superiore ma doppio tempo/memoria.
|
|
- `scale_range` + `scale_step`: range e risoluzione di scala. Lasciare `[1.0, 1.0]` se la scala è fissa (setup camera fisso) per massime performance.
|
|
- `min_contrast`: soglia gradiente minimo per considerare un pixel come feature. Basso (20-30) per immagini a basso contrasto, alto (60-80) per immagini pulite.
|
|
- `greediness`: trade-off tra velocità e accuracy. 0.9 è default, 0.7 più accurato ma più lento.
|
|
- `num_features`: numero di feature estratte dal template. Più feature = più robusto ma più lento.
|
|
|
|
### 3.3 Uscite del matching
|
|
|
|
Per ogni istanza trovata:
|
|
|
|
```json
|
|
{
|
|
"x_px": 452.37,
|
|
"y_px": 301.84,
|
|
"angle_deg": 45.2,
|
|
"scale": 1.01,
|
|
"score": 0.94,
|
|
"template_id": 0,
|
|
"contour_polygon": [[x1,y1], [x2,y2], ...]
|
|
}
|
|
```
|
|
|
|
Dove:
|
|
|
|
- `(x_px, y_px)`: coordinate subpixel del punto origine del template, nell'immagine di scena
|
|
- `angle_deg`: rotazione dell'istanza rispetto al template canonico (0° = template non ruotato)
|
|
- `scale`: fattore di scala (1.0 = scala del template nominale)
|
|
- `score`: similarità normalizzata 0-1, dove 1.0 = match perfetto
|
|
- `template_id`: identificatore del sub-template usato (utile se un modello contiene varianti)
|
|
- `contour_polygon`: poligono di contorno del template trasformato con la pose trovata, utile per visualizzazione e per IoU downstream
|
|
|
|
---
|
|
|
|
## 4. Pipeline tecnica end-to-end
|
|
|
|
### 4.1 Pipeline di creazione modello
|
|
|
|
```
|
|
[Asset sorgente]
|
|
│
|
|
▼
|
|
[Pre-processing]
|
|
- Se DXF: parsing + tassellazione + rasterizzazione
|
|
- Se immagine: conversione grayscale + eventuale ROI crop
|
|
│
|
|
▼
|
|
[Edge extraction]
|
|
- Calcolo gradiente (Sobel o Scharr)
|
|
- Binning dell'orientamento su 8 direzioni (linemod)
|
|
- Soglia su magnitude
|
|
│
|
|
▼
|
|
[Feature selection]
|
|
- Sampling di N feature spaziate sul template
|
|
- Ordinamento per magnitude gradient
|
|
│
|
|
▼
|
|
[Pyramid building]
|
|
- Downscaling 2x ricorsivo
|
|
- Ri-estrazione feature a ogni livello
|
|
│
|
|
▼
|
|
[Template database]
|
|
- Per ogni angolo nel range: ruota + ricampiona
|
|
- Per ogni scala nel range: ridimensiona + ricampiona
|
|
- Per ogni (angolo, scala): salva feature set
|
|
│
|
|
▼
|
|
[Serializzazione]
|
|
- Salvataggio binary su disco (model_cache.bin)
|
|
- Metadata JSON
|
|
```
|
|
|
|
### 4.2 Pipeline di matching runtime
|
|
|
|
```
|
|
[Scena input]
|
|
│
|
|
▼
|
|
[Pre-processing scena]
|
|
- Conversione grayscale
|
|
- Eventuale ROI crop
|
|
│
|
|
▼
|
|
[Edge extraction]
|
|
- Stesso processo del template
|
|
- Response map costruito
|
|
│
|
|
▼
|
|
[Matching piramidale]
|
|
- Livello più alto piramide:
|
|
- Correla ogni template ruotato/scalato contro la scena
|
|
- Trova candidati con score > threshold
|
|
- Livelli successivi:
|
|
- Raffinamento locale intorno ai candidati
|
|
- Trasferimento pose dal livello superiore
|
|
│
|
|
▼
|
|
[Non-Maximum Suppression]
|
|
- Elimina candidati duplicati spazialmente
|
|
- Mantiene solo il best score per area
|
|
│
|
|
▼
|
|
[Subpixel refinement]
|
|
- Interpolazione parabolic sui peak di score
|
|
- Ottimizzazione least-squares su pose
|
|
│
|
|
▼
|
|
[Output: lista di match]
|
|
```
|
|
|
|
### 4.3 Pipeline DXF → PNG dettagliata
|
|
|
|
Questo sub-sistema è critico e merita specifica puntuale.
|
|
|
|
**Step 1 — Parsing**
|
|
|
|
```python
|
|
import ezdxf
|
|
|
|
doc = ezdxf.readfile(dxf_path)
|
|
msp = doc.modelspace()
|
|
|
|
# Filtra layer se richiesto
|
|
if layers_included:
|
|
entities = [e for e in msp if e.dxf.layer in layers_included]
|
|
else:
|
|
entities = list(msp)
|
|
```
|
|
|
|
**Step 2 — Detection unità**
|
|
|
|
```python
|
|
# $INSUNITS: 0=unitless, 1=inch, 4=mm
|
|
insunits_map = {1: 25.4, 2: 304.8, 4: 1.0, 5: 10.0, 6: 1000.0}
|
|
header_insunits = doc.header.get('$INSUNITS', 0)
|
|
unit_to_mm = insunits_map.get(header_insunits, 1.0)
|
|
|
|
# Override manuale se specificato
|
|
if force_unit == 'mm':
|
|
unit_to_mm = 1.0
|
|
elif force_unit == 'inch':
|
|
unit_to_mm = 25.4
|
|
```
|
|
|
|
**Step 3 — Tassellazione entità**
|
|
|
|
Ogni tipo di entità va convertita in polyline di punti:
|
|
|
|
```python
|
|
def entity_to_polyline(entity, tessellation_tol_mm):
|
|
if entity.dxftype() == 'LINE':
|
|
return [entity.dxf.start[:2], entity.dxf.end[:2]]
|
|
|
|
elif entity.dxftype() == 'LWPOLYLINE':
|
|
return [(v[0], v[1]) for v in entity.vertices_in_wcs()]
|
|
|
|
elif entity.dxftype() == 'CIRCLE':
|
|
# Tassellazione con step angolare calibrato su tolleranza
|
|
from math import cos, sin, pi, acos
|
|
cx, cy = entity.dxf.center[:2]
|
|
r = entity.dxf.radius
|
|
# Step che garantisce chord error < tolerance
|
|
max_step = 2 * acos(1 - tessellation_tol_mm / r)
|
|
n_segments = max(16, int(2 * pi / max_step))
|
|
return [(cx + r*cos(2*pi*i/n_segments),
|
|
cy + r*sin(2*pi*i/n_segments))
|
|
for i in range(n_segments + 1)]
|
|
|
|
elif entity.dxftype() == 'ARC':
|
|
# Analogo al cerchio ma con start_angle e end_angle
|
|
...
|
|
|
|
elif entity.dxftype() == 'SPLINE':
|
|
# ezdxf ha metodo flattening con tolerance
|
|
return [(p[0], p[1]) for p in entity.flattening(tessellation_tol_mm)]
|
|
|
|
elif entity.dxftype() == 'ELLIPSE':
|
|
return [(p[0], p[1]) for p in entity.flattening(tessellation_tol_mm)]
|
|
|
|
# Altri tipi: skippa
|
|
return []
|
|
```
|
|
|
|
**Step 4 — Calcolo bounding box geometria**
|
|
|
|
```python
|
|
all_points = [p for polyline in polylines for p in polyline]
|
|
xs, ys = zip(*all_points)
|
|
min_x, max_x = min(xs), max(xs)
|
|
min_y, max_y = min(ys), max(ys)
|
|
|
|
# Aggiungi margine (5% per lato)
|
|
margin = 0.05 * max(max_x - min_x, max_y - min_y)
|
|
min_x -= margin; max_x += margin
|
|
min_y -= margin; max_y += margin
|
|
```
|
|
|
|
**Step 5 — Rasterizzazione**
|
|
|
|
```python
|
|
import numpy as np
|
|
import cv2
|
|
|
|
# Dimensioni canvas in pixel
|
|
width_mm = max_x - min_x
|
|
height_mm = max_y - min_y
|
|
canvas_w_px = int(width_mm * resolution_px_per_mm)
|
|
canvas_h_px = int(height_mm * resolution_px_per_mm)
|
|
|
|
# Crea canvas bianco (sfondo)
|
|
canvas = np.ones((canvas_h_px, canvas_w_px), dtype=np.uint8) * 255
|
|
|
|
# Trasforma polyline in coordinate pixel (flip Y per convention image)
|
|
for polyline in polylines:
|
|
pixel_points = [
|
|
(int((x - min_x) * resolution_px_per_mm),
|
|
int((max_y - y) * resolution_px_per_mm)) # flip Y
|
|
for x, y in polyline
|
|
]
|
|
# Disegna con antialiasing
|
|
pts = np.array(pixel_points, dtype=np.int32).reshape((-1, 1, 2))
|
|
cv2.polylines(canvas, [pts], isClosed=False,
|
|
color=0, thickness=line_thickness_px,
|
|
lineType=cv2.LINE_AA)
|
|
|
|
return canvas
|
|
```
|
|
|
|
**Step 6 — Validazione finale**
|
|
|
|
```python
|
|
# Verifica che ci siano edge sufficienti per il matching
|
|
edges = cv2.Canny(canvas, 50, 150)
|
|
edge_pixel_count = np.count_nonzero(edges)
|
|
if edge_pixel_count < 100:
|
|
raise ValueError("DXF produces too few edges for reliable matching")
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Implementazione Fase Alpha: OpenCV linemod
|
|
|
|
Primo stadio implementativo con obiettivo di validare l'architettura. Precisione limitata, ma zero complessità di build.
|
|
|
|
### 5.1 Dipendenze
|
|
|
|
```
|
|
opencv-contrib-python>=4.8.0
|
|
ezdxf>=1.1.0
|
|
numpy>=1.24
|
|
pillow>=10.0
|
|
```
|
|
|
|
Nessuna build C++, tutto via pip.
|
|
|
|
### 5.2 Wrapper `ShapeModel2DMatcher`
|
|
|
|
```python
|
|
import cv2
|
|
import numpy as np
|
|
import pickle
|
|
|
|
|
|
class ShapeModel2DMatcher:
|
|
"""Wrapper attorno cv2.linemod per Fase Alpha.
|
|
|
|
Espone API simile a Halcon: add_template, match, save, load.
|
|
Sacrifica precisione subpixel per semplicità implementativa.
|
|
"""
|
|
|
|
def __init__(self, pyramid_levels=4):
|
|
self.detector = cv2.linemod.getDefaultLINE()
|
|
self.templates = {} # template_id -> metadata
|
|
self.pyramid_levels = pyramid_levels
|
|
|
|
def add_template(self, template_image, class_id="default",
|
|
angle_range=(0, 360), angle_step=1.0,
|
|
scale_range=(1.0, 1.0), scale_step=0.1):
|
|
"""Aggiunge template con rotazioni e scale precomputate."""
|
|
|
|
# Prepara mask (tutti i pixel = foreground)
|
|
mask = np.ones_like(template_image, dtype=np.uint8) * 255
|
|
|
|
angles = np.arange(angle_range[0], angle_range[1] + angle_step, angle_step)
|
|
scales = np.arange(scale_range[0], scale_range[1] + scale_step, scale_step)
|
|
|
|
for scale in scales:
|
|
for angle in angles:
|
|
# Applica rotazione + scala
|
|
h, w = template_image.shape
|
|
M = cv2.getRotationMatrix2D((w/2, h/2), angle, scale)
|
|
rotated = cv2.warpAffine(template_image, M, (w, h),
|
|
borderValue=255)
|
|
rotated_mask = cv2.warpAffine(mask, M, (w, h),
|
|
borderValue=0)
|
|
|
|
# Estrai feature e aggiungi template a linemod
|
|
sources = [rotated]
|
|
tid = self.detector.addTemplate(sources, class_id, rotated_mask)
|
|
|
|
# Salva metadata per recupero
|
|
self.templates[tid] = {
|
|
"angle": angle,
|
|
"scale": scale,
|
|
"class_id": class_id,
|
|
"template_size": (w, h),
|
|
}
|
|
|
|
return len(self.templates)
|
|
|
|
def match(self, scene_image, min_score=0.7, max_matches=10):
|
|
"""Esegue matching sulla scena."""
|
|
|
|
sources = [scene_image]
|
|
matches = self.detector.match(sources, min_score * 100)
|
|
|
|
# Converti format: linemod usa 0-100, noi 0-1
|
|
results = []
|
|
for m in matches[:max_matches]:
|
|
tid = m.template_id
|
|
meta = self.templates.get(tid, {})
|
|
results.append({
|
|
"x_px": float(m.x),
|
|
"y_px": float(m.y),
|
|
"angle_deg": meta.get("angle", 0.0),
|
|
"scale": meta.get("scale", 1.0),
|
|
"score": float(m.similarity) / 100.0,
|
|
"template_id": tid,
|
|
})
|
|
|
|
return results
|
|
|
|
def save(self, path):
|
|
"""Serializza matcher su disco."""
|
|
# linemod non ha native serialization completa, usiamo hack
|
|
# con FileStorage + pickle per metadata
|
|
fs = cv2.FileStorage(path + ".xml", cv2.FILE_STORAGE_WRITE)
|
|
self.detector.write(fs)
|
|
fs.release()
|
|
|
|
with open(path + ".meta", 'wb') as f:
|
|
pickle.dump(self.templates, f)
|
|
|
|
def load(self, path):
|
|
"""Carica matcher da disco."""
|
|
fs = cv2.FileStorage(path + ".xml", cv2.FILE_STORAGE_READ)
|
|
self.detector.read(fs.root())
|
|
fs.release()
|
|
|
|
with open(path + ".meta", 'rb') as f:
|
|
self.templates = pickle.load(f)
|
|
```
|
|
|
|
### 5.3 Integrazione nell'architettura Vision Suite
|
|
|
|
La classe `ShapeModel2DEngine` (scheletro in Appendice B del documento Vision Suite) usa `ShapeModel2DMatcher` internamente. Zero cambiamenti all'interfaccia pubblica quando si passa da Alpha a Beta.
|
|
|
|
### 5.4 Prestazioni attese Fase Alpha
|
|
|
|
Su CPU moderna (Intel i7-12xxx o AMD Ryzen 7 5xxx, 8 core):
|
|
|
|
- Creazione modello: 5-15 secondi per template 300x300 px con range angolare 0-360° step 2°
|
|
- Memory modello: 50-200 MB per modello tipico
|
|
- Matching runtime: 50-200 ms per immagine 1920x1080
|
|
- Precisione posizionale: 1-3 px (no subpixel)
|
|
- Precisione angolare: 2° (risoluzione angolare step)
|
|
|
|
Queste performance sono sufficienti per validare l'architettura, non per produzione finale.
|
|
|
|
### 5.5 Limitazioni note della Fase Alpha
|
|
|
|
- No subpixel refinement
|
|
- No SIMD acceleration specifica
|
|
- Scale invariance implementata "a mano" (multiplicazione template precomputati) — poco efficiente memoria
|
|
- Angular precision limitata dal step scelto
|
|
- Non gestisce bene template con molta simmetria
|
|
|
|
Queste limitazioni sono il motivo per cui è uno **stadio di prototipo**, non il prodotto finale.
|
|
|
|
---
|
|
|
|
## 6. Implementazione Fase Beta: fork `meiqua/shape_based_matching`
|
|
|
|
### 6.1 Strategia di fork
|
|
|
|
**Preparazione repository**:
|
|
|
|
```bash
|
|
# Fork su organization Tielogic
|
|
# GitHub: Settings → Fork → Create as tielogic/shape_based_matching
|
|
|
|
git clone https://github.com/tielogic/shape_based_matching.git
|
|
cd shape_based_matching
|
|
|
|
# Traccia upstream
|
|
git remote add upstream https://github.com/meiqua/shape_based_matching.git
|
|
|
|
# Branch Tielogic
|
|
git checkout -b tielogic/production
|
|
```
|
|
|
|
**Patch Tielogic da applicare**:
|
|
|
|
1. **CMakeLists.txt multi-platform**:
|
|
- Rimuovere hardcoded paths ROS
|
|
- Supporto Windows + Linux + macOS
|
|
- Detection automatica OpenCV version
|
|
|
|
2. **Python bindings via pybind11**:
|
|
- Creazione `python_bindings/` sub-directory
|
|
- Wrapping API C++ principali
|
|
- Setup.py per pip-installable package
|
|
|
|
3. **API helpers addizionali**:
|
|
- Export `save_to_json` / `load_from_json` per metadata
|
|
- Helper per subpixel refinement con ICP integrato
|
|
- Batch matching API per scenari multi-template
|
|
|
|
4. **Testing**:
|
|
- Dataset di test industriali (pezzi Tielogic reali)
|
|
- Benchmark automatici di precisione vs Halcon (se disponibile per riferimento)
|
|
- Continuous integration GitHub Actions
|
|
|
|
5. **Documentazione**:
|
|
- README completo con esempi Python e C++
|
|
- Guide migration da Halcon
|
|
- Performance tuning guide
|
|
|
|
### 6.2 Struttura del fork
|
|
|
|
```
|
|
tielogic/shape_based_matching/
|
|
├── CMakeLists.txt # aggiornato multi-platform
|
|
├── line2Dup.h / .cpp # codice originale (minimamente toccato)
|
|
├── MIPP/ # SIMD library (originale)
|
|
├── tielogic_extensions/ # codice Tielogic aggiuntivo
|
|
│ ├── subpixel_refiner.h / .cpp
|
|
│ ├── batch_matcher.h / .cpp
|
|
│ └── json_serializer.h / .cpp
|
|
├── python_bindings/ # NEW: pybind11 wrapping
|
|
│ ├── CMakeLists.txt
|
|
│ ├── bindings.cpp
|
|
│ └── setup.py
|
|
├── tests/
|
|
│ ├── cpp/ # test C++ originali + estensioni
|
|
│ └── python/ # test Python del wrapper
|
|
├── benchmarks/
|
|
│ ├── datasets/ # pezzi reali Tielogic
|
|
│ └── scripts/
|
|
├── docs/
|
|
│ ├── README.md
|
|
│ ├── python_api.md
|
|
│ ├── cpp_api.md
|
|
│ └── migration_from_halcon.md
|
|
└── .github/workflows/
|
|
├── ci.yml # build e test automatici
|
|
└── release.yml # packaging e release
|
|
```
|
|
|
|
### 6.3 Python bindings
|
|
|
|
Scheletro del wrapping pybind11 in `python_bindings/bindings.cpp`:
|
|
|
|
```cpp
|
|
#include <pybind11/pybind11.h>
|
|
#include <pybind11/stl.h>
|
|
#include <pybind11/numpy.h>
|
|
#include "line2Dup.h"
|
|
#include "tielogic_extensions/subpixel_refiner.h"
|
|
|
|
namespace py = pybind11;
|
|
|
|
py::array_t<uint8_t> cv_mat_to_numpy(const cv::Mat& mat) {
|
|
// Conversione cv::Mat -> numpy array
|
|
// ...
|
|
}
|
|
|
|
cv::Mat numpy_to_cv_mat(py::array_t<uint8_t> arr) {
|
|
// Conversione numpy array -> cv::Mat
|
|
// ...
|
|
}
|
|
|
|
PYBIND11_MODULE(shape_based_matching, m) {
|
|
m.doc() = "Tielogic shape-based matching (Halcon-equivalent)";
|
|
|
|
py::class_<line2Dup::Detector>(m, "Detector")
|
|
.def(py::init<int, std::vector<int>, float, float>(),
|
|
py::arg("num_features") = 128,
|
|
py::arg("T") = std::vector<int>{4, 8},
|
|
py::arg("weak_thresh") = 30.0f,
|
|
py::arg("strong_thresh") = 60.0f)
|
|
.def("add_template", [](line2Dup::Detector& self,
|
|
py::array_t<uint8_t> image,
|
|
const std::string& class_id,
|
|
py::array_t<uint8_t> mask) {
|
|
cv::Mat img = numpy_to_cv_mat(image);
|
|
cv::Mat msk = numpy_to_cv_mat(mask);
|
|
std::vector<cv::Mat> sources = {img};
|
|
return self.addTemplate(sources, class_id, msk);
|
|
})
|
|
.def("match", [](line2Dup::Detector& self,
|
|
py::array_t<uint8_t> image,
|
|
float threshold,
|
|
const std::vector<std::string>& class_ids) {
|
|
cv::Mat img = numpy_to_cv_mat(image);
|
|
std::vector<cv::Mat> sources = {img};
|
|
auto matches = self.match(sources, threshold, class_ids);
|
|
|
|
// Converti risultato in dizionari Python
|
|
py::list result;
|
|
for (const auto& m : matches) {
|
|
py::dict d;
|
|
d["x"] = m.x;
|
|
d["y"] = m.y;
|
|
d["similarity"] = m.similarity;
|
|
d["class_id"] = m.class_id;
|
|
d["template_id"] = m.template_id;
|
|
result.append(d);
|
|
}
|
|
return result;
|
|
}, py::arg("image"),
|
|
py::arg("threshold") = 80.0f,
|
|
py::arg("class_ids") = std::vector<std::string>{})
|
|
.def("save", &line2Dup::Detector::writeClasses)
|
|
.def("load", &line2Dup::Detector::readClasses);
|
|
|
|
// Subpixel refiner Tielogic extension
|
|
py::class_<tielogic::SubpixelRefiner>(m, "SubpixelRefiner")
|
|
.def(py::init<>())
|
|
.def("refine", [](tielogic::SubpixelRefiner& self,
|
|
py::array_t<uint8_t> scene,
|
|
py::array_t<uint8_t> template_img,
|
|
float x, float y, float angle) {
|
|
cv::Mat scn = numpy_to_cv_mat(scene);
|
|
cv::Mat tpl = numpy_to_cv_mat(template_img);
|
|
auto refined = self.refine(scn, tpl, x, y, angle);
|
|
py::dict d;
|
|
d["x"] = refined.x;
|
|
d["y"] = refined.y;
|
|
d["angle"] = refined.angle;
|
|
return d;
|
|
});
|
|
}
|
|
```
|
|
|
|
Build via `setup.py`:
|
|
|
|
```python
|
|
from setuptools import setup, Extension
|
|
from pybind11.setup_helpers import Pybind11Extension, build_ext
|
|
import sys
|
|
|
|
ext_modules = [
|
|
Pybind11Extension(
|
|
"shape_based_matching",
|
|
sources=[
|
|
"python_bindings/bindings.cpp",
|
|
"line2Dup.cpp",
|
|
"tielogic_extensions/subpixel_refiner.cpp",
|
|
],
|
|
include_dirs=["./", "MIPP/"],
|
|
libraries=["opencv_core", "opencv_imgproc"],
|
|
language="c++",
|
|
cxx_std=17,
|
|
),
|
|
]
|
|
|
|
setup(
|
|
name="tielogic-shape-matching",
|
|
version="0.1.0",
|
|
ext_modules=ext_modules,
|
|
cmdclass={"build_ext": build_ext},
|
|
zip_safe=False,
|
|
)
|
|
```
|
|
|
|
### 6.4 Usage Python dopo build
|
|
|
|
```python
|
|
import shape_based_matching as sbm
|
|
import cv2
|
|
|
|
# Creazione detector
|
|
detector = sbm.Detector(num_features=150, T=[4, 8])
|
|
|
|
# Add template
|
|
template = cv2.imread("flangia_ref.png", cv2.IMREAD_GRAYSCALE)
|
|
mask = (template < 200).astype('uint8') * 255
|
|
template_id = detector.add_template(template, "flangia", mask)
|
|
|
|
# Generate rotation variants (Tielogic helper)
|
|
variants = sbm.generate_rotation_variants(
|
|
template=template,
|
|
angle_range=(0, 360),
|
|
angle_step=1.0,
|
|
)
|
|
for v in variants:
|
|
detector.add_template(v.image, f"flangia_rot_{v.angle}", v.mask)
|
|
|
|
# Match on scene
|
|
scene = cv2.imread("production_scene.png", cv2.IMREAD_GRAYSCALE)
|
|
matches = detector.match(scene, threshold=80.0)
|
|
|
|
# Subpixel refinement
|
|
refiner = sbm.SubpixelRefiner()
|
|
for m in matches:
|
|
refined = refiner.refine(scene, template, m["x"], m["y"], 0.0)
|
|
print(f"Match at ({refined['x']:.3f}, {refined['y']:.3f})")
|
|
```
|
|
|
|
### 6.5 Integrazione nel Dockerfile Vision Suite
|
|
|
|
```dockerfile
|
|
FROM python:3.11-slim-bookworm
|
|
|
|
# Dipendenze build C++
|
|
RUN apt-get update && apt-get install -y \
|
|
build-essential \
|
|
cmake \
|
|
libopencv-dev \
|
|
libopencv-contrib-dev \
|
|
git \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Installa pybind11 per Python bindings
|
|
RUN pip install --no-cache-dir pybind11
|
|
|
|
# Clone fork Tielogic
|
|
RUN git clone --depth 1 --branch tielogic/production \
|
|
https://github.com/tielogic/shape_based_matching.git /opt/sbm
|
|
|
|
# Build e installa modulo Python
|
|
WORKDIR /opt/sbm/python_bindings
|
|
RUN python setup.py build_ext --inplace && \
|
|
pip install --no-cache-dir .
|
|
|
|
# Verifica import
|
|
RUN python -c "import shape_based_matching; print(shape_based_matching.__version__)"
|
|
|
|
# ... resto del Vision Suite Dockerfile
|
|
```
|
|
|
|
### 6.6 Prestazioni attese Fase Beta
|
|
|
|
Sullo stesso hardware di riferimento:
|
|
|
|
- Creazione modello: 2-5 secondi per template 300x300 px con range angolare 0-360° step 1°
|
|
- Memory modello: 10-50 MB (più efficiente di linemod puro)
|
|
- Matching runtime: 5-30 ms per immagine 1920x1080
|
|
- Precisione posizionale: 0.1-0.3 px (subpixel)
|
|
- Precisione angolare: 0.1-0.5° (dopo refinement)
|
|
|
|
Guadagno 10x in velocità runtime, 3-5x in precisione rispetto a Fase Alpha.
|
|
|
|
---
|
|
|
|
## 7. Analisi di distintività automatica
|
|
|
|
Feature che distingue questa implementazione da un semplice wrapper: un'analisi offline che segnala problemi nel template prima che diventino problemi in produzione.
|
|
|
|
### 7.1 Metriche calcolate
|
|
|
|
**Simmetrie rotazionali**
|
|
|
|
Calcolate via auto-correlation del template rotato su sé stesso:
|
|
|
|
```python
|
|
def detect_rotational_symmetries(template, angle_step=5.0):
|
|
"""Rileva simmetrie rotazionali nel template.
|
|
|
|
Ritorna lista di tuple (n, tolerance_deg) dove n è l'ordine della simmetria
|
|
(es. 4 = simmetria quadrangolare 90°, 2 = simmetria bilaterale 180°).
|
|
"""
|
|
import cv2
|
|
import numpy as np
|
|
|
|
h, w = template.shape
|
|
center = (w // 2, h // 2)
|
|
|
|
correlations = []
|
|
for angle in np.arange(0, 360, angle_step):
|
|
M = cv2.getRotationMatrix2D(center, angle, 1.0)
|
|
rotated = cv2.warpAffine(template, M, (w, h))
|
|
# NCC tra originale e ruotato
|
|
result = cv2.matchTemplate(
|
|
template, rotated, cv2.TM_CCOEFF_NORMED
|
|
)
|
|
correlations.append((angle, float(result.max())))
|
|
|
|
# Trova picchi (>0.85 correlation con ±tolerance)
|
|
peaks = []
|
|
for angle, corr in correlations:
|
|
if corr > 0.85 and angle > 0:
|
|
peaks.append((angle, corr))
|
|
|
|
# Deduci ordine di simmetria dai picchi
|
|
symmetries = []
|
|
for angle, corr in peaks:
|
|
if abs(angle - 180) < 10:
|
|
symmetries.append({"type": "bilateral", "n": 2})
|
|
elif abs(angle - 90) < 10 or abs(angle - 270) < 10:
|
|
if not any(s["n"] == 4 for s in symmetries):
|
|
symmetries.append({"type": "rotational", "n": 4})
|
|
elif abs(angle - 120) < 10 or abs(angle - 240) < 10:
|
|
symmetries.append({"type": "rotational", "n": 3})
|
|
|
|
return symmetries
|
|
```
|
|
|
|
**Entropia degli orientamenti edge**
|
|
|
|
Basso valore → template ripetitivo/ambiguo:
|
|
|
|
```python
|
|
def edge_orientation_entropy(template, num_bins=16):
|
|
"""Entropia dell'istogramma orientamenti edge.
|
|
|
|
Alto valore (vicino a log(num_bins)) = edge in tutte le direzioni, distintivo.
|
|
Basso valore = edge concentrati in poche direzioni, template ambiguo.
|
|
"""
|
|
import cv2
|
|
import numpy as np
|
|
|
|
gx = cv2.Sobel(template, cv2.CV_32F, 1, 0, ksize=3)
|
|
gy = cv2.Sobel(template, cv2.CV_32F, 0, 1, ksize=3)
|
|
|
|
magnitude = np.sqrt(gx**2 + gy**2)
|
|
angle = np.arctan2(gy, gx) * 180 / np.pi
|
|
|
|
# Solo pixel con edge forte
|
|
mask = magnitude > np.percentile(magnitude, 80)
|
|
angles_valid = angle[mask]
|
|
|
|
hist, _ = np.histogram(angles_valid, bins=num_bins, range=(-180, 180))
|
|
hist_normalized = hist / (hist.sum() + 1e-9)
|
|
|
|
# Entropia di Shannon
|
|
entropy = -np.sum(
|
|
hist_normalized * np.log(hist_normalized + 1e-9)
|
|
)
|
|
max_entropy = np.log(num_bins)
|
|
|
|
return entropy / max_entropy # Normalizzata 0-1
|
|
```
|
|
|
|
**Self-similarity score**
|
|
|
|
Indica quanto il template è simile a sé stesso in posizioni diverse:
|
|
|
|
```python
|
|
def self_similarity_score(template, offset_range=None):
|
|
"""Quanto il template assomiglia a versioni traslate di sé stesso.
|
|
|
|
Alto valore = template ripetitivo (checkerboard, griglia).
|
|
Basso valore = template distintivo.
|
|
"""
|
|
import cv2
|
|
import numpy as np
|
|
|
|
h, w = template.shape
|
|
if offset_range is None:
|
|
offset_range = (w // 20, w // 4) # 5-25% della dimensione
|
|
|
|
max_similarity = 0.0
|
|
for dx in range(offset_range[0], offset_range[1], 5):
|
|
shifted = np.roll(template, dx, axis=1)
|
|
result = cv2.matchTemplate(template, shifted, cv2.TM_CCOEFF_NORMED)
|
|
max_similarity = max(max_similarity, float(result.max()))
|
|
|
|
return max_similarity
|
|
```
|
|
|
|
### 7.2 Output dell'analisi
|
|
|
|
```json
|
|
{
|
|
"distinctiveness_score": 0.74,
|
|
"edge_orientation_entropy": 0.88,
|
|
"self_similarity": 0.23,
|
|
"num_features_extracted": 147,
|
|
"symmetries_detected": [
|
|
{"type": "bilateral", "n": 2, "tolerance_deg": 3.0}
|
|
],
|
|
"warnings": [
|
|
"Il modello mostra simmetria bilaterale. L'angolo stimato potrebbe avere ambiguità di ±180°.",
|
|
"Considera di includere feature asimmetriche (marcature, fori, incisioni) per eliminare l'ambiguità."
|
|
],
|
|
"expected_accuracy": {
|
|
"positional_px": 0.25,
|
|
"angular_deg": 0.5
|
|
},
|
|
"recommended_matching_options": {
|
|
"min_score": 0.75,
|
|
"overlap_threshold": 0.3
|
|
}
|
|
}
|
|
```
|
|
|
|
### 7.3 Esposizione all'utente nella web UI
|
|
|
|
Questa analisi viene mostrata durante la creazione della ricetta, prima del salvataggio finale:
|
|
|
|
```
|
|
┌─ Analisi qualità modello ─────────────────────────────┐
|
|
│ │
|
|
│ Score distintività: ██████████░░ 74% │
|
|
│ │
|
|
│ ⚠ Attenzione: rilevata simmetria bilaterale │
|
|
│ │
|
|
│ Suggerimenti: │
|
|
│ • Aggiungi feature asimmetriche per ridurre │
|
|
│ ambiguità a ±180° │
|
|
│ │
|
|
│ Accuracy attesa in produzione: │
|
|
│ • Posizione: ±0.25 px │
|
|
│ • Angolo: ±0.5° │
|
|
│ │
|
|
│ [Prosegui comunque] [Torna indietro e modifica] │
|
|
└────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Benchmark e validazione
|
|
|
|
### 8.1 Dataset di test
|
|
|
|
Per validare l'engine servono dataset rappresentativi. Suggerimenti:
|
|
|
|
**Dataset sintetico**:
|
|
- 20-30 template rappresentativi di pezzi industriali (flange, staffe, cinghie, PCB)
|
|
- Per ogni template generazione di 100+ scene con:
|
|
- Pose GT note
|
|
- Rumore gaussiano variabile (5%, 10%, 20%)
|
|
- Occlusioni parziali (0%, 20%, 40%)
|
|
- Variazioni di illuminazione (± 30%)
|
|
- Sfondi variabili
|
|
|
|
**Dataset reale Tielogic**:
|
|
- 10-20 pezzi cliente reali fotografati in condizioni di produzione
|
|
- Ground truth via misurazione manuale o altro sistema di riferimento
|
|
- Condivisione con clienti come beneficio (loro ottengono audit qualità, Tielogic arricchisce dataset)
|
|
|
|
### 8.2 Metriche di valutazione
|
|
|
|
Per ogni match ritornato dall'engine:
|
|
|
|
- **Posizionale**: `|pose_stimata - pose_GT|` in pixel
|
|
- **Angolare**: differenza angolo modulo 360° (considerando simmetrie)
|
|
- **Precision**: percentuale di match ritornati che sono true positive
|
|
- **Recall**: percentuale di istanze GT correttamente rilevate
|
|
- **F1 score**: media armonica di precision e recall
|
|
- **Latency**: tempo medio di matching per immagine
|
|
|
|
### 8.3 Target di qualità
|
|
|
|
Per essere accettabile in produzione, l'engine Beta deve raggiungere:
|
|
|
|
- F1 score >0.95 su dataset sintetico senza occlusione
|
|
- F1 score >0.85 su dataset sintetico con 30% occlusione
|
|
- F1 score >0.90 su dataset reale Tielogic
|
|
- Precisione posizionale mediana <0.5 px
|
|
- Precisione angolare mediana <1.0°
|
|
- Latency mediana <50 ms su immagini 1920x1080
|
|
|
|
Se questi target non sono raggiunti dopo la Fase Beta, si attiva la Fase Gamma con motori complementari.
|
|
|
|
### 8.4 Continuous validation
|
|
|
|
Una volta in produzione, ogni release candidate deve passare suite di benchmark automatici:
|
|
|
|
```
|
|
CI Pipeline:
|
|
1. Build Vision Suite container
|
|
2. Scarica dataset di test dal registry interno
|
|
3. Esegui suite di match su tutti i casi
|
|
4. Confronta con risultati golden (tolleranza di degrado <5%)
|
|
5. Pubblica report dettagliato
|
|
6. Se regression: block merge
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Casi d'uso validazione con clienti Tielogic
|
|
|
|
### 9.1 Scenari tipici di deployment
|
|
|
|
**Scenario A — Ispezione linea pezzi stampati**:
|
|
- Camera industriale fissa sopra nastro trasportatore
|
|
- Lighting LED uniforme, sfondo contrastato
|
|
- Pezzi ~50-200 mm, velocità nastro 0.5 m/s
|
|
- Esigenza: localizzare pose per pick-and-place robot
|
|
- Target: 95% pick success rate, latency <100 ms
|
|
|
|
**Scenario B — Controllo qualità sagoma taglio laser**:
|
|
- Camera calibrata metrologicamente su banco misura
|
|
- Backlight per contorno pulito
|
|
- Confronto sagoma tagliata vs DXF progetto
|
|
- Esigenza: verifica tolleranze dimensionali ±0.1 mm
|
|
- Target: ripetibilità <0.05 mm, discriminazione scarti >99%
|
|
|
|
**Scenario C — Assemblaggio componenti elettronici**:
|
|
- Camera su robot SCARA
|
|
- Illuminazione frontale con diffusore
|
|
- PCB con componenti multipli da riconoscere
|
|
- Esigenza: localizzare e orientare 10-20 componenti per istanza
|
|
- Target: 1 Hz di throughput, accuracy ±0.1 mm
|
|
|
|
### 9.2 Iteration con primi clienti
|
|
|
|
Il processo di validation con cliente reale:
|
|
|
|
1. **Acquisizione requisiti dettagliata** — hardware disponibile, precisione richiesta, throughput, ambiente
|
|
2. **Fornitura dataset di test** — il cliente cattura 50-100 immagini rappresentative con GT
|
|
3. **Creazione ricette iniziali** — Tielogic crea prime ricette via web UI
|
|
4. **Validation loop** — iterazione su parametri fino a raggiungere target
|
|
5. **Pilot on-site** — deployment iniziale con monitoring intensivo (1-4 settimane)
|
|
6. **Go-live** — passaggio a produzione con SLA
|
|
|
|
Ogni pilot diventa **case study** e dataset di riferimento per la suite. Con 3-5 pilot il sistema ha solidità statistica per confronto con Halcon.
|
|
|
|
---
|
|
|
|
## 10. Roadmap implementativa specifica
|
|
|
|
### Settimana 1-2: Setup Fase Alpha
|
|
|
|
- [ ] Struttura progetto engine dentro Vision Suite
|
|
- [ ] Integrazione OpenCV contrib nel Dockerfile
|
|
- [ ] Implementazione `ShapeModel2DMatcher` wrapper su linemod
|
|
- [ ] Unit test della classe wrapper
|
|
- [ ] Integrazione con `MatchingEngine` ABC
|
|
|
|
### Settimana 3: Pipeline DXF → PNG
|
|
|
|
- [ ] Installazione ezdxf nel container
|
|
- [ ] Implementazione `dxf_rasterizer.py` completo
|
|
- [ ] Test con 10+ DXF campione di diverse provenienze
|
|
- [ ] Gestione edge case (layer invisibili, unità miste, entità non supportate)
|
|
|
|
### Settimana 4: Web UI Recipe Wizard
|
|
|
|
- [ ] Pagina upload asset con detection automatica tipo
|
|
- [ ] Editor ROI per immagini
|
|
- [ ] Preview live rasterizzazione DXF con parametri tunabili
|
|
- [ ] Form parametri matching con valori default
|
|
- [ ] Submission a API backend
|
|
|
|
### Settimana 5: Analisi distintività
|
|
|
|
- [ ] Implementazione metriche (simmetrie, entropia, self-similarity)
|
|
- [ ] Integrazione in flusso creazione ricetta
|
|
- [ ] UI di visualizzazione warning
|
|
- [ ] Test su template noti ambigui per calibrare soglie
|
|
|
|
### Settimana 6-7: Endpoint API matching completo
|
|
|
|
- [ ] Validazione completa input runtime
|
|
- [ ] Routing `/api/match` a `ShapeModel2DEngine.match()`
|
|
- [ ] Serializzazione risultati JSON
|
|
- [ ] Debug store snapshot
|
|
- [ ] Playground web UI funzionante
|
|
|
|
### Settimana 8-9: Setup Fase Beta
|
|
|
|
- [ ] Fork `meiqua/shape_based_matching` su org Tielogic
|
|
- [ ] Aggiornamento CMakeLists.txt multi-platform
|
|
- [ ] Implementazione Python bindings base
|
|
- [ ] Build test Linux (CI Ubuntu) e Windows
|
|
- [ ] Pacchetto Python installabile via pip
|
|
|
|
### Settimana 10-11: Migration Alpha → Beta
|
|
|
|
- [ ] Integrazione pacchetto fork nel Dockerfile
|
|
- [ ] Aggiornamento `ShapeModel2DMatcher` per usare fork
|
|
- [ ] Compatibilità: ricette Alpha devono essere riutilizzabili in Beta
|
|
- [ ] Benchmark: confronto precisione Alpha vs Beta
|
|
- [ ] Documentazione upgrade path
|
|
|
|
### Settimana 12-14: Subpixel, ottimizzazioni, documentazione
|
|
|
|
- [ ] Integrazione subpixel refiner (estensione Tielogic)
|
|
- [ ] Ottimizzazioni performance specifiche
|
|
- [ ] Test di ripetibilità su hardware diverso
|
|
- [ ] Documentazione API Python completa
|
|
- [ ] Guida migration da Halcon per programmatori
|
|
|
|
**Totale: 12-14 settimane per Fase Alpha + Beta complete.**
|
|
|
|
---
|
|
|
|
## 11. Criteri di uscita / done
|
|
|
|
L'engine `shape_model_2d` è considerato pronto per il rilascio quando:
|
|
|
|
- [ ] Tutte le funzionalità Fase Beta implementate e testate
|
|
- [ ] Target di qualità (sezione 8.3) raggiunti su dataset sintetico e reale
|
|
- [ ] Suite di test automatici verdi (>95% code coverage componenti core)
|
|
- [ ] Documentazione API Python completa con esempi eseguibili
|
|
- [ ] Guida Docker deployment per cliente verificata da persona esterna al team sviluppo
|
|
- [ ] 3 case study completi su pezzi industriali reali
|
|
- [ ] Fork `tielogic/shape_based_matching` pubblicato con release v1.0 taggata
|
|
- [ ] Benchmark comparativo con almeno un altro strumento (Halcon se accessibile, altrimenti OpenCV linemod baseline)
|
|
- [ ] Web UI Recipe Wizard utilizzabile da operatore non-programmatore (validato con test utente)
|
|
- [ ] Issue tracking attivo per regression e feedback clienti
|
|
|
|
---
|
|
|
|
## 12. Riferimenti tecnici
|
|
|
|
**Repository principale di riferimento**:
|
|
- `meiqua/shape_based_matching`: https://github.com/meiqua/shape_based_matching
|
|
- Fork Tielogic (da creare): https://github.com/tielogic/shape_based_matching
|
|
|
|
**Alternative valutate**:
|
|
- `mwwzbinf/mwwz-shape-match`: https://github.com/mwwzbinf/mwwz-shape-match
|
|
- `daxiaHuang/shape_based_matching_subpixel`: https://github.com/daxiaHuang/shape_based_matching_subpixel
|
|
- OpenCV linemod: https://docs.opencv.org/4.x/d7/d00/tutorial_line_descriptor_main.html
|
|
|
|
**Librerie Python supporto**:
|
|
- ezdxf: https://ezdxf.readthedocs.io/
|
|
- opencv-contrib-python: https://pypi.org/project/opencv-contrib-python/
|
|
- pybind11: https://pybind11.readthedocs.io/
|
|
|
|
**Riferimento scientifico originale**:
|
|
- Linemod paper: Hinterstoisser et al., "Gradient Response Maps for Real-Time Detection of Textureless Objects", TPAMI 2012
|
|
- "Machine Vision Algorithms and Applications" (libro di riferimento Halcon engineers)
|
|
|
|
**Riferimento commerciale (per confronto)**:
|
|
- MVTec Halcon shape matching: https://www.mvtec.com/products/halcon
|
|
|
|
---
|
|
|
|
## Appendice A — Esempio completo workflow utente
|
|
|
|
Dalla prospettiva di un operatore Tielogic che configura un nuovo task di ispezione.
|
|
|
|
### A.1 Creazione della ricetta via web UI
|
|
|
|
Operatore apre browser su `http://vision-service.tielogic.local:8080/ui`:
|
|
|
|
1. Click su **"Crea nuova ricetta"**
|
|
2. Selezione tipo: **"Shape Model 2D"**
|
|
3. Upload asset:
|
|
- Opzione scelta: caricamento DXF `flangia_80mm.dxf`
|
|
- Sistema rileva unità: mm (confermato)
|
|
- Preview rasterizzazione mostrata con edge in verde
|
|
4. Configurazione layer DXF:
|
|
- Layer `CONTORNO` selezionato (contiene il profilo esterno)
|
|
- Layer `QUOTE`, `TESTI`, `CENTRINI` deselezionati
|
|
5. Parametri matching:
|
|
- Range angolare: 0-360° (default)
|
|
- Scale: fissa a 1.0 (camera fissa calibrata)
|
|
- Pyramid levels: 4 (default)
|
|
6. Click **"Analizza"**:
|
|
- Sistema genera modello in 3 secondi
|
|
- Mostra preview con feature in rosso
|
|
- Score distintività: 91% (buono)
|
|
- Nessun warning di simmetria
|
|
- Accuracy attesa: ±0.2 px, ±0.3°
|
|
7. Compilazione metadata:
|
|
- recipe_id: `flangia_80mm_v1`
|
|
- Display name: `Flangia 80mm Cliente Acme`
|
|
- Tags: `cliente_acme`, `linea_produzione_3`
|
|
8. Click **"Salva"**. Ricetta disponibile in lista.
|
|
|
|
### A.2 Test in Playground
|
|
|
|
Operatore va su pagina **"Playground"**:
|
|
|
|
1. Selezione ricetta: `flangia_80mm_v1`
|
|
2. Upload immagine di test: `sample_production_scene.png`
|
|
3. Parametri runtime:
|
|
- Min score: 0.7
|
|
- Max matches: 10
|
|
4. Click **"Match"**:
|
|
- Risultato in 18 ms
|
|
- 3 istanze trovate con score 0.94, 0.91, 0.87
|
|
- Visualizzazione con overlay rettangoli colorati sui match
|
|
- Tabella sottostante con dati numerici
|
|
|
|
Se risultati soddisfacenti, la ricetta è pronta per il deployment.
|
|
|
|
### A.3 Uso da applicazione client
|
|
|
|
Codice Python che l'integratore installa sulla macchina di produzione:
|
|
|
|
```python
|
|
from tielogic_vision import VisionClient
|
|
import cv2
|
|
import pyrealsense2 as rs # o qualsiasi SDK camera
|
|
|
|
# Connessione al servizio vision
|
|
client = VisionClient(service_url="http://vision-service.tielogic.local:8080")
|
|
|
|
# Setup acquisizione camera (esempio generico)
|
|
camera = setup_camera()
|
|
|
|
while True:
|
|
# Acquisisci frame
|
|
frame = camera.capture()
|
|
|
|
# Matching
|
|
matches = client.match(
|
|
recipe_id="flangia_80mm_v1",
|
|
image=frame,
|
|
options={"min_score": 0.8, "max_matches": 5}
|
|
)
|
|
|
|
# Invia posizioni al robot
|
|
for m in matches:
|
|
robot.queue_pick(
|
|
x_px=m.x_px,
|
|
y_px=m.y_px,
|
|
angle_deg=m.angle_deg
|
|
)
|
|
|
|
# Log telemetria
|
|
log_metrics(num_matches=len(matches),
|
|
latency_ms=matches[0].inference_time_ms)
|
|
```
|
|
|
|
---
|
|
|
|
## Appendice B — Esempio di test industriale
|
|
|
|
Test di validazione su pezzo cliente reale (esempio fittizio ma realistico).
|
|
|
|
### B.1 Contesto
|
|
|
|
Cliente: stamperia metalli di precisione.
|
|
Pezzo: flangia rotonda 80mm con 6 fori M8 su cerchio di 60mm.
|
|
Esigenza: localizzare 10-20 flange su nastro 500x300mm per pick-and-place.
|
|
|
|
### B.2 Setup test
|
|
|
|
- Camera: Basler ace acA2440-75um (5MP monocromatica)
|
|
- Ottica: 12mm, WD 500mm
|
|
- Illuminazione: ring light LED bianco
|
|
- Nastro fermo durante acquisizione (ciclo stop-capture-pick)
|
|
|
|
### B.3 Dataset
|
|
|
|
- 100 immagini con flange distribuite casualmente
|
|
- GT tramite misurazione manuale con marker ArUco temporanei
|
|
- Condizioni di illuminazione variabili (±15%)
|
|
- Alcuni casi con occlusione parziale (altre flange sovrapposte)
|
|
|
|
### B.4 Risultati attesi
|
|
|
|
Target da raggiungere per considerare deploy riuscito:
|
|
|
|
- Recall: >99% (nessuna flangia persa)
|
|
- Precision: >99% (nessun falso positivo)
|
|
- Precisione posizionale mediana: <0.3 px
|
|
- Precisione angolare mediana: <0.5°
|
|
- Latency mediana: <30 ms
|
|
- Robustezza a occlusioni fino al 30%
|
|
|
|
### B.5 Protocollo di test
|
|
|
|
1. Creazione ricetta da DXF CAD del pezzo
|
|
2. Analisi distintività deve dare score >80%
|
|
3. Run batch su 100 immagini
|
|
4. Calcolo metriche aggregate
|
|
5. Review manuale di tutti i match con score <0.8
|
|
6. Iterazione parametri se target non raggiunti
|
|
7. Test di ripetibilità: stesso stream 10 volte, verifica deviation standard pose <0.1 px
|
|
|
|
Test passato → ricetta approvata per produzione.
|