Normali di superficie

La stima delle normali di superficie predice la direzione verso cui è orientata ogni superficie visibile. LibreYOLO la espone come task normal, che restituisce un campo denso di vettori unitari sul canvas dell'immagine originale.

Definizione

Il task normal predice un vettore unitario a tre componenti per pixel a partire da una singola immagine RGB: la direzione verso cui è orientata la superficie in quel pixel. A differenza della profondità, l'output non ha una scala libera, quindi due predizioni sono direttamente confrontabili senza allineamento.

Una predizione riempie result.normal_map, un payload NormalMap che contiene un array float32 (H, W, 3) sul canvas dell'immagine originale, raggiungibile anche come result.normals. I vettori usano il sistema di riferimento della fotocamera OpenCV di LibreYOLO, con +x a destra, +y in basso e +z verso la scena, e sono rivolti verso la fotocamera, quindi una superficie fronto-parallela si legge come (0, 0, -1). .assert_normalized() controlla che ogni pixel sia finito e di lunghezza unitaria entro una tolleranza. result.boxes resta vuoto, quindi conf, iou e max_det non hanno effetto, e Results.plot() copre questo task.

Modelli

Due famiglie servono normal.

MoGe-2 è quella dedicata: un modello di geometria monoculare a singolo forward in tre taglie di encoder. LibreYOLO non copia questi checkpoint nella propria organizzazione; caricarne uno scarica la taglia corrispondente dai repository ufficiali a una revisione fissata e la verifica contro uno SHA-256 registrato.

LibreMODUS produce le normali come uno dei target di un modello any-to-any, e può prendere in input una mappa di profondità invece di un'immagine RGB. Richiede l'extra modus e un tuo account Hugging Face autenticato, e non offre né val()export(), quindi non partecipa alle sezioni di validazione ed esportazione qui sotto.

Predizione

I pesi di MoGe-2 si scaricano al primo uso e restano in cache in locale.

Predire un campo di normali
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreMoGe2s-normal.pt")result = model(SAMPLE_IMAGE, save=True) normals = result.normal_mapprint(normals.data.shape)      # (H, W, 3) vettori unitari float32normals.assert_normalized()    # solleva un errore se un pixel non ha lunghezza unitaria
Leggere un singolo pixel
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreMoGe2s-normal.pt")result = model(SAMPLE_IMAGE) # Riferimento fotocamera OpenCV: +x a destra, +y in basso, +z verso la scena.# Una superficie rivolta verso la fotocamera si legge vicino a (0, 0, -1).field = result.normals.datah, w = field.shape[:2]print(field[h // 2, w // 2])
Salvare la visualizzazione
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreMoGe2s-normal.pt")result = model(SAMPLE_IMAGE) # plot() disegna il campo; è definito per i risultati normal ed edge.result.plot().save("normals.png")

imgsz deve essere divisibile per la dimensione delle patch dell'encoder ViT, cosa che LibreYOLO controlla prima che l'esecuzione inizi. Predire una lista di immagini esegue un forward pass per immagine; questo task non ha un percorso rapido a batch impilati. Vedi predizione per sorgenti, streaming e gestione dei risultati.

Formato del dataset

La validazione delle normali accoppia ogni immagine con un PNG a 16 bit e tre canali con lo stesso nome base e la stessa risoluzione, più una maschera di validità opzionale.

dataset/
  data.yaml
  images/
    val/room.jpg
  normals/
    val/room.png
  masks/
    val/room.png
yaml
path: dataset
train: images/train
val: images/val
normals_dir: normals
masks_dir: masks
nc: 1
names: {0: normal}

Il PNG di target è esattamente uint16 a tre canali, con i canali memorizzati come RGB. La decodifica è n = png / 65535 * 2 - 1 seguita dalla rinormalizzazione di ogni vettore, e i vettori decodificati usano lo stesso sistema di riferimento della fotocamera OpenCV delle predizioni. Un pixel della maschera conta come valido quando è diverso da zero; senza un file di maschera, ogni vettore decodificato finito e diverso da zero è valido. I pixel di target non validi e quelli di padding sono rappresentati internamente da (0, 0, 0) e non contribuiscono mai a una metrica. Vedi formati dei dataset per il contratto completo.

Addestramento

Nessuna delle due famiglie normal ha un'implementazione dell'addestramento: train() solleva NotImplementedError su entrambe. La pagina di MoGe-2 indica i suoi checkpoint ufficiali fissati per predizione, validazione ed esportazione.

Validazione

val() misura l'angolo tra ogni vettore predetto e il suo vettore di ground truth, sui pixel che il dataset segna come validi.

Validare e leggere le chiavi delle metriche
from libreyolo import LibreYOLO model = LibreYOLO("LibreMoGe2s-normal.pt")metrics = model.val(data="my-dataset.yaml", imgsz=518) print(metrics["metrics/mean_angular_error"])     # gradiprint(metrics["metrics/median_angular_error"])   # gradiprint(metrics["metrics/within_11_25"])           # percentuale di pixelprint(metrics["metrics/within_22_5"], metrics["metrics/within_30"])

metrics/mean_angular_error e metrics/median_angular_error sono quell'angolo in gradi, e più basso è meglio. metrics/within_11_25, metrics/within_22_5 e metrics/within_30 sono la percentuale di pixel validi il cui errore angolare sta entro 11.25, 22.5 e 30 gradi, quindi più alto è meglio. Attenzione all'unità: quei tre valori sono percentuali, non frazioni. fitness è metrics/within_11_25 diviso 100, il che porta la selezione del checkpoint migliore sulla stessa scala [0, 1] di ogni altro task.

Esportazione

Un modello normal esportato si ricarica tramite LibreYOLO() in base al suffisso del file, quindi un file .onnx si comporta come un checkpoint e restituisce lo stesso Results.

Esportare
from libreyolo import LibreYOLO model = LibreYOLO("LibreMoGe2s-normal.pt")model.export(format="onnx", imgsz=518)
Eseguire il file esportato
from libreyolo import LibreYOLO, SAMPLE_IMAGE # La factory instrada in base al suffisso del file, quindi un artefatto# esportato si carica come qualsiasi checkpoint e restituisce lo stesso Results.model = LibreYOLO("LibreMoGe2s-normal.onnx")result = model(SAMPLE_IMAGE) print(result.normal_map.data.shape)

L'esportazione delle normali usa un contratto di runtime a risoluzione fissa e batch 1: dynamic e un batch diverso da 1 vengono rifiutati, e imgsz deve essere divisibile per la dimensione delle patch dell'encoder. La copertura per formato è nella pagina di MoGe-2 e nella matrice completa delle esportazioni. Esportazione elenca gli argomenti che ogni formato accetta.

Verificato con LibreYOLO v1.5.0.