Con il termine segmentation ci si riferisce a un insieme di metodi il cui obiettivo è partizionare un'immagine di input in regioni con proprietà comuni (es. colore, edge) — lo stesso obiettivo del capitolo 3. Due varianti profonde:
È il passo successivo alla object detection: invece di una bounding box per oggetto, si produce una maschera densa a livello di pixel.
Numerosi ambiti applicativi traggono beneficio dalla semantic segmentation: le immagini medicali (segmentazione di organi e lesioni), le auto a guida autonoma (separazione di strada, veicoli, pedoni a livello di pixel) e l'analisi di immagini satellitari (classificazione del suolo e delle aree urbane).
In letteratura esistono vari indicatori per misurare le prestazioni di un sistema di segmentazione. I più semplici: l'accuratezza di classificazione (la percentuale di pixel correttamente classificati) e l'errore di classificazione (il complemento dell'accuratezza). Attenzione ai problemi con cardinalità delle classi molto sbilanciata: se una classe copre il 95% dei pixel (es. lo sfondo), classificare tutto come quella classe dà un'accuratezza del 95% pur essendo un sistema inutile — l'accuratezza grezza è fuorviante.
Un'immagine con 1000 pixel: regola la dimensione dello sfondo (in %) e l'accuratezza del classificatore sui pixel di primo piano. Un sistema che classifica tutto come sfondo può avere un'accuratezza altissima senza riconoscere nulla.
Confusion matrix — ogni cella (i, j) riporta la percentuale di casi in cui il sistema ha assegnato la classe j a un pixel la cui classe reale è i. Le slide mostrano l'esempio con 5 classi (gatto, cane, coniglio, capra, maiale): il gatto è classificato correttamente al 71% (il 29% come cane), il cane al 38%, il coniglio al 92%, la capra all'83% e il maiale al 54%. La matrice evidenzia le confusioni sistematiche tra classi simili.
Due indici basati sull'overlap tra la maschera restituita dal sistema e quella attesa:
IoU (Jaccard index) = area(A_t ∩ R_e) / area(A_t ∪ R_e)
Dice index (DSC/F1) = 2 · area(A_t ∩ R_e) / (area(A_t) + area(R_e))
Il Dice index misura il rapporto tra i pixel in comune e il numero di pixel presente in ogni maschera; è una misura molto usata nelle applicazioni mediche.
La maschera attesa (verde) e quella restituita (cobalto) si sovrappongono parzialmente. Regola l'overlap per vedere come si comportano i due indici: il Dice è sempre ≥ IoU (a parità di overlap).
Il primo approccio «naïve» alla semantic segmentation con CNN: per ogni pixel, si estrae una patch centrata sul pixel, la si dà in input a una CNN di classificazione, e la classe assegnata al pixel centrale della patch diventa la classe del pixel. La procedura viene ripetuta per ogni pixel dell'immagine. Problemi: molto inefficiente (una forward per pixel!) e si usano solo le informazioni contenute nell'intorno del pixel (contesto limitato).
L'evoluzione: una rete senza livelli fully-connected (pienamente convoluzionale) che produce direttamente una mappa di classi. Il problema: l'output è più piccolo dell'input a causa della riduzione delle dimensioni spaziali (convoluzioni e pooling). Serve quindi una parte che riporti l'output alle dimensioni originali: nasce la struttura encoder-decoder.
La maggior parte delle reti per la semantic segmentation condivide la medesima struttura generale: una rete di codifica (encoder) seguita da una rete di decodifica (decoder).
Per ottenere un output delle stesse dimensioni dell'immagine di input, il decoder deve «invertire» le operazioni di convoluzione e pooling dell'encoder, avvalendosi di due nuove operazioni: unpooling e transposed convolution.
L'obiettivo dell'unpooling è invertire una precedente operazione di pooling aumentando le dimensioni della feature map di input. Mentre il pooling riduce la risoluzione condensando una sotto-finestra in un singolo valore, l'unpooling amplia la risoluzione distribuendo un singolo valore su una sotto-finestra. Nel caso del max pooling si ricordano le posizioni dei massimi: l'unpooling riposiziona ogni valore nella posizione in cui era stato selezionato il massimo, riempiendo di zeri le altre celle. Nel caso dell'average pooling, il valore viene replicato su tutta la sotto-finestra (es. 3.25 → 3.25 3.25 su due celle). L'esempio delle slide: max pooling 2 × 2 stride 2 su una 4 × 4 (valori 1 1 2 4 / 5 6 7 8 / 3 2 1 0 / 1 2 3 4) → 2 × 2 (6, 8, 3, 4); l'unpooling con le posizioni dei massimi rimette 6, 8, 3, 4 nelle celle giuste e zeri altrove.
Input 4×4 (i valori delle slide). Il max pooling 2×2 stride 2 seleziona il massimo di ogni finestra; l'unpooling usa le posizioni ricordate per rimettersi al posto giusto (il resto è zero).
Data una feature map M, ottenuta applicando una convoluzione a una feature map iniziale I, la transposed convolution è l'operazione che, applicata a M, consente di ottenere una feature map I′ con le stesse dimensioni di I. Mentre nella convoluzione «diretta» l'input definisce la posizione in cui applicare il filtro, nella transposed convolution l'input definisce il peso del filtro e i contributi dei filtri si sommano tra loro (scatter-add). Per una convoluzione con kernel F, stride S e padding P:
convoluzione: W_out = (W_in − F + 2·P)/S + 1
transposed conv: W_out = (W_in − 1)·S + F − 2·P
L'esempio delle slide: input 2 × 2 (0 1 / 2 3), kernel 3 × 3 (0 1 2 / 3 4 5 / 6 7 8), stride 1, padding 0 → output 4 × 4. Ogni valore dell'input viene «spalmato» sul kernel pesato e i contributi si sommano: per l'input 2×2, output = [[0, 0, 1, 2], [0, 6, 11, 11]…] secondo lo schema delle slide. Con stride 2 l'output diventa 5 × 5 e i kernel pesati sono distanziati di 2 celle, con overlap che si sommano; con padding 1 l'output torna 3 × 3. L'esempio di decoder completo nelle slide: 224 × 224 → (encoder) 14 × 14 → unpooling 28 × 28 → transposed conv 28 × 28 → unpooling 56 × 56 → transposed conv 56 × 56 → unpooling 112 × 112 → transposed conv 112 × 112 → unpooling 224 × 224 → transposed conv 224 × 224.
Se l'encoder e il decoder venissero messi semplicemente in sequenza, si correrebbe il rischio di perdere informazioni di basso livello (dettagli): il decoder produrrebbe un output piuttosto impreciso, soprattutto nelle zone di confine tra classi diverse. Per ovviare a questa perdita, si può dare al decoder la possibilità di accedere alle feature di basso livello prodotte dagli strati dell'encoder. Ciò si ottiene tramite le skip connection: gli output intermedi dell'encoder vengono aggiunti/concatenati (in posizioni opportune) agli input dei livelli intermedi del decoder. Il risultato è una segmentazione molto più precisa ai bordi (come mostrato dal confronto «senza skip / con skip connections» delle slide).
Fully Convolutional Network (FCN) — Long, Shelhamer e Darrell sono stati i primi (2015) a proporre una FCN per la segmentazione di immagini. Non essendoci livelli fully-connected, può essere usata con immagini di qualsiasi dimensione; è utilizzabile con diverse architetture (backbone) allo stato dell'arte: VGG, AlexNet, GoogLeNet; l'addestramento è effettuato tramite fine-tuning.
DeconvNet — Noh, Hong e Han (2015) proposero un modello composto da due parti connesse tra loro: encoder e decoder. L'encoder è l'architettura VGG16 (adattabile ad altre); il decoder è speculare all'encoder; presenza di due livelli fully-connected; batch normalization dopo ogni livello di convoluzione per aumentare la velocità di apprendimento; addestramento tramite fine-tuning.
U-Net — proposta da Ronneberger, Fischer e Brox (2015) per segmentare immagini medicali; combina informazioni locali (encoder) e contestuali (decoder) grazie alle skip connections (la struttura a U del nome); non avendo livelli FC può usare immagini di qualsiasi dimensione; è efficiente e non necessita di grandi quantità di dati; nata per la medicina, è facilmente utilizzabile anche in altri contesti; addestramento end-to-end.
Nel 2017, He, Gkioxari, Dollar e Girshick proposero Mask R-CNN per risolvere problemi di instance segmentation. È composta da: una Faster R-CNN, un RoI align layer e una Mask head. La Faster R-CNN restituisce una bounding box e una label per ogni oggetto; il RoI pooling della Faster R-CNN è sostituito da un RoI align layer per meglio preservare le informazioni spaziali; l'output del RoI align è dato in input alla Mask head, che genera una maschera binaria per ogni RoI. La multi-task loss per ogni RoI:
L = L_cls + L_box + L_mask
Le componenti Lcls e Lbox sono identiche a quelle della Faster R-CNN; Lmask è la media della binary cross-entropy calcolata sull'intera maschera. Mask R-CNN è facile da addestrare, efficiente, con performance elevate e molto flessibile: facilmente adattabile ad altri task.
Il RoI pooling restituisce un volume di output di dimensione prefissata indipendentemente dalla dimensione della regione di interesse, ma soffre del problema del disallineamento (poco influente per l'object detection) causato da due arrotondamenti: l'arrotondamento di x, y, w e h della regione e l'arrotondamento della divisione in celle. Per la segmentazione il disallineamento è estremamente dannoso: il RoI align evita i due arrotondamenti usando l'interpolazione bilineare.
La Mask head è composta da due layer: transposed convolution e convoluzione. Restituisce una maschera binaria che si riferisce alla classe restituita dalla Faster R-CNN.
I dataset di riferimento per la segmentazione:
| Dataset | # immagini | # classi | Caratteristiche |
|---|---|---|---|
| PASCAL VOC | ~20K | 20 | Dataset storico, semplice, ideale per training rapido e sperimentazioni didattiche |
| Cityscapes | 5K | 30 | Scene urbane, standard per la guida autonoma |
| ADE20K | ~27K | ~150 | Scene indoor/outdoor |
| COCO-Stuff | 164K | ~170 | Molto complesso |
| Mapillary Vistas | 25K | 124 | Scene stradali |
Modelli recenti: DeepLabV3+ (ottimo per multi-scale context e contorni precisi, ma computazionalmente pesante per il real-time); SegFormer (alta accuratezza e generalizzazione eccellente, richiede GPU potenti per il training); SAM (Segment Anything Model: non richiede retraining e segmenta qualsiasi oggetto, ma non è ottimizzato sui dataset classici — è progettato per la versatilità); PointRend (ottimo per immagini ad alta risoluzione e video, più complesso da integrare).
La semantic segmentation classifica ogni pixel di un'immagine sulla base di un insieme predefinito di classi (senza distinguere le istanze). L'instance segmentation classifica ogni pixel identificando le diverse istanze delle varie classi (es. «persona 1», «persona 2», …).
Con cardinalità delle classi molto sbilanciata, se una classe copre il 95% dei pixel un sistema che classifica tutto come quella classe ottiene un'accuratezza del 95% pur essendo inutile. Servono indicatori che tengano conto delle singole classi (confusion matrix, IoU, Dice).
Ogni cella (i, j) riporta la percentuale di casi in cui il sistema ha assegnato la classe j a un pixel la cui classe reale è i. Evidenzia le confusioni sistematiche tra classi simili (es. nel caso delle slide il gatto è confuso col cane nel 29% dei casi).
IoU = area(A_t ∩ R_e) / area(A_t ∪ R_e) (Jaccard index) e DSC = 2·area(A_t ∩ R_e) / (area(A_t) + area(R_e)) (Dice/F1): misurano l'overlap tra la maschera restituita e quella attesa. Il Dice è sempre ≥ IoU a parità di overlap.
È molto inefficiente (una forward della CNN per ogni pixel) e usa solo le informazioni contenute nell'intorno del pixel (contesto limitato).
Per la riduzione delle dimensioni spaziali causata dalle operazioni di convoluzione e pooling. Il problema è risolto dal decoder, che riporta l'output alla risoluzione originale.
L'encoder effettua un mapping a bassa risoluzione dell'immagine aggregando feature altamente discriminanti; il decoder proietta le feature a bassa risoluzione sullo spazio originale dei pixel per ottenere una classificazione densa, invertendo convoluzioni e pooling con unpooling e transposed convolution.
Amplia la risoluzione distribuendo un singolo valore su una sotto-finestra, invertendo il pooling. Nel max pooling si ricordano le posizioni dei massimi: l'unpooling riposiziona ogni valore nella posizione del massimo e mette zeri altrove; nell'average pooling il valore è replicato su tutta la sotto-finestra.
L'operazione che, applicata a una feature map M ottenuta da una convoluzione di I, produce una feature map I′ con le stesse dimensioni di I. L'input definisce il peso del filtro e i contributi si sommano tra loro (scatter-add). Formula: W_out = (W_in − 1)·Stride + F − 2·Padding.
Senza skip, il decoder perderebbe le informazioni di basso livello (dettagli) e produrrebbe output imprecisi soprattutto ai confini tra classi. Le skip connection aggiungono/concatenano gli output intermedi dell'encoder agli input dei livelli intermedi del decoder, recuperando i dettagli.
FCN (2015): prima rete pienamente convoluzionale, qualsiasi dimensione di input, backbone VGG/AlexNet/GoogLeNet, fine-tuning. DeconvNet (2015): encoder VGG16 + decoder speculare, due FC, batch normalization. U-Net (2015): encoder+decoder con skip connections (struttura a U), efficiente, pochi dati necessari, nata per le immagini medicali, end-to-end.
Una Faster R-CNN (bounding box + label), un RoI align layer al posto del RoI pooling e una Mask head (transposed convolution + convoluzione) che genera una maschera binaria per ogni RoI. Loss: L = L_cls + L_box + L_mask con L_mask media della binary cross-entropy sull'intera maschera.
Il RoI pooling introduce due arrotondamenti (di x, y, w, h e della divisione in celle) che causano disallineamento: poco influente per la detection, estremamente dannoso per la segmentazione. Il RoI align evita gli arrotondamenti usando l'interpolazione bilineare.
PASCAL VOC (~20K, 20 classi, storico e didattico), Cityscapes (5K, 30 classi, scene urbane per la guida autonoma), ADE20K (~27K, ~150 classi, scene indoor/outdoor), COCO-Stuff (164K, ~170 classi, molto complesso), Mapillary Vistas (25K, 124 classi, scene stradali).
DeepLabV3+: ottimo per multi-scale context e contorni precisi, pesante per il real-time. SegFormer: alta accuratezza e generalizzazione, richiede GPU potenti. SAM: segmenta qualsiasi oggetto senza retraining, non ottimizzato sui dataset classici (versatilità). PointRend: ottimo per immagini ad alta risoluzione e video, più complesso da integrare.