• Non ci sono risultati.

5.5 Implementazione: Face Parsing

5.5.4 Caricamento e processing dei dati

Una delle fasi più frustranti relative al training di una rete neurale è si- curamente la fase di caricamento dei dati. Ogni dataset, infatti, presenta caratteristiche uniche a seconda di come è stato organizzato; spetta quindi al programmatore trovare una logica di caricamento adeguata.

Formato delle maschere di segmentazione Nell’ambito dell’image seg- mentation, generalmente le maschere di segmentazione possono essere fornite nei seguenti formati:

• Formato binario (black/white): utilizzato nei problemi di segmentazione binaria in cui l’obiettivo consiste nel separare un oggetto dallo sfondo; • Formato RGB: tipico di dataset in cui il numero di classi da segmentare

è superiore a 1 e inferiore a 3 (compreso il background). In questo caso si sfruttano i tre canali RGB al fine di veicolare informazioni relative alle tre classi da segmentare;

• Formato RGB compatto: utilizzato per veicolare informazioni di più clas- si codificandole sotto forma di colori RGB. Sebbene questo metodo per- metta di definire un numero arbitrario di classi risparmiando spazio su disco, richiede uno sforzo computazionale maggiore al fine di estrarre le varie maschere dall’immagine RGB;

• Formato binario multi-classe: ogni singola maschera viene codificata sotto forma di immagine binaria e salvata su disco. È un approccio

5.5. Implementazione: Face Parsing Capitolo 5. Sviluppo del tool

molto costoso a livello di memoria ma garantisce tempi di caricamento notevolmente inferiori.

Il dataset in questione, come è facile intuire, utilizza l’ultima tipologia di for- mato di codifica per le maschere di segmentazione; come vedremo ora questa scelta ha permesso di personalizzare notevolmente il training del modello. Scelta delle classi da segmentare Sebbene 19 classi siano un numero veramente grande di etichette, come si è visto, molte di esse possono essere “compresse” all’interno di singole maschere. Per garantire un maggiore livello di personalizzazione ed evitare di dover implementare più volte le stesse logiche di caricamento, è stata predisposta una configurazione iniziale dedicata. Tale configurazione è molto semplice e consiste in un insieme di mappe chiave-valore tramite le quali è possibile definire vari aspetti relativi alla segmentazione (vedi listato 2).

Listato 2: Configurazione classi da segmentare

c l a s s e s _ t o _ s e g m e n t = {’ s k i n ’: True , ’ n o s e ’: True , ’ eye ’: True , ’ b r o w ’: True ,

’ ear ’: True , ’ m o u t h ’: True , ’ h a i r ’: True , ’ n e c k ’: True , ’ c l o t h ’: F a l s e } a l l _ c o l o r s = {’ b l u e ’: [0 , 0 , 204] , ’ g r e e n ’: [0 , 153 , 76] , . . . }

c l a s s _ l a b e l s _ m a p p i n g = {’ s k i n ’: [’ s k i n ’] , ’ eye ’: [’ l _ e y e ’, ’ r _ e y e ’] , . . . } c l a s s _ c o l o r _ m a p p i n g = {’ s k i n ’: ’ b l u e ’, ’ n o s e ’: ’ g r e e n ’, ’ eye ’: ’ v i o l e t ’, . . . }

Con class_labels_mapping vengono definite le logiche di accorpamento fra maschere (eye = eyer+ eyel) in macro-classi, mentre con

classes_to_segmentsi definisce quali macro-classi segmentare. Le rimanenti configurazioni settano il colore delle maschere da utilizzare in formato RGB. In figura 5.10 viene mostrata la configurazione utilizzata per la fase di training del modello.

Figura 5.10: Configurazione classe-colore.

È importante sottolineare come, una volta decisa una configurazione, non sia possibile utilizzare il modello in modalità di inferenza con numero e tipo diverso di classi.

Caricamento delle immagini Passiamo ora a vedere come è stato impo- stato il caricamento delle immagini, task di norma molto semplice ma che in questo caso ha generato non pochi grattacapi.

• Complessità spaziale e temporale: data la dimensione di CelebA- Mask-HQ (30,000 immagini), è intuibile come ci si trovi di fronte ad un problema di gestione delle risorse. Infatti, a meno che non si possegga dell’hardware di altissimo livello, caricare in memoria un tale numero di immagini porterebbe presto un qualsiasi sistema al collasso. Allo stesso modo, se si decidesse di effettuare il caricamento in real-time, senza sovraccaricare la memoria, ci si troverebbe ad attenere un quantitativo di tempo esagerato al fine di processare tutte le immagini.

A livello teorico, le due problematiche riscontrate ricadono sotto due concetti fondamentali: quello della complessità spaziale e quello della complessità temporale. Un task complesso spazialmente richiede enormi quantitativi di memoria per essere portato a termine ma garantisce una grande efficienza temporale. Dal lato opposto, un task complesso tem- poralmente richiederà un tempo maggiore di elaborazione dato che non si potranno sfruttare le strutture di memoria per velocizzare il processo. • Scelta effettuata: analizzando il tradeoff espresso sopra si è deciso di ridurre il numero di immagini da caricare a 10,000 affinché si po- tesse sfruttare la velocità della cache RAM mantenendo comunque un buon quantitativo di immagini. Ovviamente questa scelta ha richiesto un costo; è stato infatti sottoscritto un abbonamento alla piattaforma Colab Pro di Google [63, colab-pro:2021] al fine di poter sfruttare un quantitativo maggiore di risorse.

• Caricamento effettivo: l’operazione di caricamento è molto semplice: infatti essa consiste essenzialmente nel ricavare il path di immagini e ma- schere corrispondenti, caricando poi le immagini stesse tramite chiamata OpenCV cv2.imread.

Stacking delle maschere Caricate le singole maschere, queste non possono ancora essere utilizzate per la fase di training: infatti, è necessario processarle al fine di renderle compatibili con l’architettura di rete richiesta. Fortunata- mente, la libreria utilizzata definisce, per tutte le tipologie di modelli, una sola strutturazione logica per il volume di output. Tale volume è definito come segue:

Sia (w, h, 3) la dimensionalità delle immagini in input, allora out= (w, h, n)

5.5. Implementazione: Face Parsing Capitolo 5. Sviluppo del tool

dove n corrisponde al numero di classi da segmentare + 1 per la classe di background (vedi figura 5.11).

Figura 5.11: Rappresentazione visiva del volume di output.

La costruzione del volume di output è stata effettuata definendo la funzione assemble_mask_levels() (vedi listato 3).

Listato 3: Stacking delle maschere

def a s s e m b l e _ m a s k _ l e v e l s ( m a s k _ f i l e ) : m a s k s _ s t a c k = np . z e r o s (( i m a g e _ s i z e , i m a g e _ s i z e , 1) ) # B a s e to s t a c k m a s k s _ f o r e g r o u n d = np . z e r o s (( i m a g e _ s i z e , i m a g e _ s i z e , 1) , ’ u i n t 8 ’) # A s s e m b l i n g sub - l a b e l s for e a c h c l a s s d e f i n e n d by u s e r . for idx , c l a s s _ n a m e in e n u m e r a t e( c l a s s e s _ l i s t ) : c l a s s _ m a s k = np . z e r o s (( i m a g e _ s i z e , i m a g e _ s i z e , 1) , ’ u i n t 8 ’) c l a s s _ l a b e l s = c l a s s _ l a b e l s _ m a p p i n g [ c l a s s _ n a m e ]

# I t e r a t e o v e r sub - l a b e l s and c r e a t e a u n i q u e m a s k l e v e l for c l a s s .

for l a b e l in c l a s s _ l a b e l s : f i l e n a m e = m a s k _ f i l e + ’ _ ’ + l a b e l + ’ . png ’ if os . p a t h . e x i s t s ( f i l e n a m e ) : im = cv2 . i m r e a d ( f i l e n a m e , cv2 . I M R E A D _ G R A Y S C A L E ) im = cv2 . r e s i z e ( im , ( i m a g e _ s i z e , i m a g e _ s i z e ) ) c l a s s _ m a s k [ im != 0] = 255 # S t a c k to o t h e r m a s k s m a s k s _ s t a c k = c l a s s _ m a s k if idx == 0 e l s e np . d s t a c k (( m a s k s _ s t a c k , c l a s s _ m a s k ) ) # I n c r e a s e t o t a l f o r e g r o u n d p i x e l s m a s k s _ f o r e g r o u n d [ c l a s s _ m a s k != 0] = 255 # A d d i n g b a c k g r o u n d m a s k at the end b a c k g r o u n d _ m a s k = ( 2 5 5 - m a s k s _ f o r e g r o u n d ) m a s k s _ s t a c k = np . d s t a c k (( m a s k s _ s t a c k , b a c k g r o u n d _ m a s k ) ) r e t u r n m a s k s _ s t a c k

La funzione opera un processo di stacking in base alla configurazione re- lativa alle classi da segmentare; essenzialmente il funzionamento può essere riassunto come segue:

1. Definizione base stack: viene inizializzato il primo livello di ouput, com- presa l’immagine di foreground che verrà popolata progressivamente con tutti i pixel di foreground dei vari livelli;

2. Caricamento: dalla configurazione vengono estratte le classi da segmen- tare e si procede a caricare e ridimensionare le singole maschere (si è scelto 256 come dimensione standard);

3. Stacking: le maschere caricate vengono poi impilate sulla base dello stack e si procede a popolare l’immagine di foreground settando a 255 i pixel di foreground di ogni maschera. Nel caso in cui non sia disponibile la ma- schera (per esempio occlusione di un occhio) viene impilata una maschera vuota;

4. Aggiunta background: in chiusura viene impilato il livello di background che è ottenuto semplicemente invertendo i valori della maschera di fore- ground.

Al termine dell’algoritmo viene prodotta una struttura dati di dimensione pari a n dove i singoli layer rappresentano le maschere relative alle classi da seg- mentare. Applicando questo procedimento a 10,000 immagini si ottengono due dataset, che chiameremo images e masks, di dimensionalità (10000, 256, 256, 3) e (10000, 256, 256, n).

Parallelamente al processo di caricamento, è stata definita una funzione dedicata alla conversione di maschere in formato RGB (vedi figura 5.12); tale funzione è fondamentale per produrre un output visivamente comprensibile.

5.5. Implementazione: Face Parsing Capitolo 5. Sviluppo del tool