All'indirizzo https://it.mathworks.com/content/dam/mathworks/tag-team/Objects/c/88365_93002v00_Correcting_Nonuniform_Illumination_2016.pdf è presente un'esercitazione che mostra un esempio di segmentazione di un'immagine.

In particolare, l'immagine contiene dei chicchi di riso su uno sfondo non uniforme. Una volta effettuata la segmentazione, esercitazione mostra come è possibile ottenere una statistica della dimensione dei grani presenti nell'immagine.

Apertura dell'immagine

L'immagine di esempio è distribuita con Matlab e può essere facilmente caricata e visulizzata digitando

>> I = imread('rice.png');
>> imshow(I);

Si nota che l'illuminazione dello sfondo è più luminosa al centro dell'immagine rispetto alla parte inferiore.

Apertura morfologica dell'immagine

Tramite la funzione imopen() che esegue un'apertura morfologica di un'immagine è possibile stimare l'illuminazione dello fondo.

 

L'operazione di apertura morfologica è una delle operazioni fondamentali della morfologia matematica, che viene utilizzata per eliminare le piccole irregolarità e le aree non desiderate dell'immagine, come il rumore o i dettagli insignificanti. In pratica, l'apertura morfologica viene eseguita mediante l'applicazione sequenziale di due operazioni morfologiche fondamentali: l'erosione e la dilatazione.

 

L'erosione è un'operazione che riduce le dimensioni degli oggetti nell'immagine. Consiste nell'applicare un elemento strutturale (structuring element) all'immagine originale, come un cerchio, un quadrato, rettangolo e nel sostituire ogni pixel dell'immagine con il valore minimo dei pixel nell'area corrispondente all'elemento strutturale. L'erosione viene utilizzata per rimuovere dettagli insignificanti e oggetti di piccole dimensioni dall'immagine.

La dilatazione, invece, aumenta le dimensioni degli oggetti nell'immagine. Consiste nell'applicare l'elemento strutturale all'immagine originale e nel sostituire ogni pixel dell'immagine con il valore massimo dei pixel nell'area corrispondente all'elemento strutturale. La dilatazione viene utilizzata per riempire le piccole lacune e unire gli oggetti separati nell'immagine.

 

>> background = imopen(I,strel('disk',15));

La funzione imopen() prende come input un'immagine e un elemento strutturale (structuring element) e restituisce l'immagine che viene aperta utilizzando l'elemento strutturale specificato. Nel caso in oggetto strel('disk',15) crea un elemento strutturale tondo di raggio 15 pixel .

Il risultato può essere visualizzato come superficie:

>> figure
>> surf(double(background(1:8:end,1:8:end))),zlim([0 255]);

>>
colormap gray;
>> view(0,90);
>> ax = gca;
>> ax.YDir =
'reverse';

Si osserva come nella parte inferiore dell'immagine lo sfondo è più scuro.

Eliminazione delle disuniformità dello sfondo dell'immagine

E' possibile a questo punto "sottrarre" il colore dello sfondo all'immagine:

>> I2 = I - background;
>> imshow(I2)
;

ottenendo un'immagine con uno sfondo più scuro ed uniforme, anche se meno contrastata.

OSSERVAZIONE: Le operazioni di apertura morfologica e sottrazione dello sfondo poteva essere fatta in un'unica operazione grazie alla finzione imtophat():

>> I2 = imtophat(I,strel('disk',15));
Aumento del contrasto

Il contrasto dell'immagine può essere incrementato utilizzando la funzione imadjust() .

La funzione accetta come input un'immagine e restituisce un'immagine di output modificata in modo che l'intervallo di intensità dei pixel dell'immagine in modo che l'1% dei dati sia saturato a bassa e alta intensità.

>> I3 = imadjust(I2);
>> imshow(I3);

La funzione imadjust() è particolarmente utile per migliorare l'aspetto visivo delle immagini prima di utilizzarle per l'analisi o la visualizzazione. Ad esempio, è possibile utilizzare la funzione "imadjust" per migliorare il contrasto di un'immagine radiografica o di un'immagine a raggi X prima di utilizzarla per l'analisi dei tessuti o delle ossa.

Trasformazione in immagine binaria

Il passaggio successivo è di creare una nuova immagine binaria impostando la soglia dell'immagine regolata con imbinarize() e rimuovendo il rumore di fondo con bwareaopen().

La funzione imbinarize() viene utilizzata per convertire un'immagine in scala di grigi in un'immagine binaria. La funzione confronta il valore di intensità di ogni pixel dell'immagine con il valore di soglia e imposta il pixel a 1 se il valore di intensità è superiore al valore di soglia, altrimenti imposta il pixel a 0. Il valore di soglia ottimale è calcolato con il metodo di Otsu, un metodo automatico che calcola la soglia ottimale dell'immagine in base alla varianza dei livelli di intensità dell'immagine.

La funzione bwareaopen() viene utilizzata per eliminare gli oggetti di piccole dimensioni in un'immagine binaria. La funzione accetta come input un'immagine binaria e un valore numerico "minArea". L'immagine binaria viene analizzata e tutti gli oggetti di dimensioni inferiori a "minArea" espressa in  numero di pixel vengono rimossi. In questo caso

>> bw = imbinarize(I3);
>> imshow(bw);
>> bw = bwareaopen(bw, 50);
>> imshow(bw);
 

Confrontando le immagini si osserva che la funzione bwareaopen() ha permesso di eliminare dal bordo dei chicchi di riso non completamente contenuti nell'immagine originale.

Identificazione degli oggetti nell'immagine

La funzione bwconncomp() è utilizzata per identificare le componenti connesse (cioè gli oggetti) in un'immagine binaria. In altre parole, questa funzione analizza un'immagine binaria e trova tutti i gruppi di pixel contigui che hanno lo stesso valore (nero o bianco).

Accetta come input un'immagine binaria e restituisce un oggetto struct che contiene diverse informazioni sulle componenti connesse dell'immagine. Tra queste informazioni ci sono il numero totale di componenti connesse, la dimensione di ciascuna componente connessa, l'elenco dei pixel che compongono ciascuna componente connessa, e così via. L'accuratezza  dei risultati dipende dalla dimensione degli oggetti, dal parametro di connettività (4, 8 o arbitrario) e se gli oggetti si toccano o meno (in questo caso potrebbero venir etichettati come un unico oggetto). Nella figura campione, per esempio, alcuni grani di riso si toccano.

 

>> cc = bwconncomp(bw, 4)
cc =
struct with fields:
Connectivity: 4
ImageSize: [256 256]
NumObjects: 101
PixelIdxList: {1×101 cell}
Esame di ogni singolo oggetto

Ogni oggetto identificato tramite bwconncomp() può essere esaminato. Per esempio per vedere il cinquantesimo oggetto identificato nell'immagine la procedura da effettuare è la seguente:

>> grain = false(size(bw));
>> grain(cc.PixelIdxList{50}) = true;
>> imshow(grain);
 

Il comando false(size(bw)) costruisce una matrice di 0 (zero) delle stesse dimensioni dell'immagine oggetto di studio. Il comando grain(cc.PixelIdxList{50}) = true inserisce 1 (uno) nei pixel indicati come componenti del cinquantesimo oggetto trovato.

Visualizzazione degli oggetti identificati

Un modo per visualizzare gli oggetti è creare una matrice di etichette e quindi visualizzarla come se fosse un'immagine con una colormap fittizia di pseudo-colori.

Questo può essere fatto tramite la funzione labelmatrix() che accetta come input un'oggetto struct di componenti connesse restituito dalla funzione bwconncomp() e restituisce una matrice di etichette. La matrice di etichette ha le stesse dimensioni dell'immagine originale, ma ogni pixel dell'immagine viene sostituito con l'etichetta della componente connessa a cui appartiene.

 

>> labeled = labelmatrix(cc);
>> whos labeled
Name Size Bytes Class Attributes
labeled 256x256 65536 uint8

Successivamente, la funzione label2rgb() viene utilizzata per creare un'immagine RGB colorata dalla matrice di etichette prodotta dalla segmentazione di un'immagine tramite labelmatrix().

La funzione label2rgb() accetta come input una matrice di etichette e un insieme di parametri opzionali che permettono di personalizzare la visualizzazione dell'immagine colorata.

>> RGB_label = label2rgb(labeled, @spring, 'c', 'shuffle');
>> imshow(RGB_label);

Nell'esempio label2rgb() utilizza la mappa dei colori 'spring', imposta i pixel di sfondo sul colore ciano e randomizza il modo in cui i colori vengono assegnati alle etichette.

Calcolo dell'area di ciascun oggetto

Per calcolare l'area degli oggetti connessi individuati con bwconncomp() si può utilizzare la funzione regionprops().

La funzione restituisce una struttura contenente un elemento per ciascuna regione nell'immagine segmentata, in cui ogni elemento contiene i valori delle proprietà calcolate per quella regione per esempio la posizione, l'area, la forma, l'orientamento, la complessità, la luminosità, la convessità e altre proprietà.

>> graindata = regionprops(cc,'basic')
graindata =
101×1 struct array with fields:
Area
Centroid
BoundingBox

Per visualizzare in modo testuale tutte le proprietà è possibile utilizzare il seguente codice:

props = regionprops(cc, 'Area', 'Perimeter', 'Eccentricity', 'Centroid');
for i = 1:numel(props)
fprintf('Regione %d: area = %d, perimetro = %f, eccentricità = %f, centroide = (%f, %f)\n', i, props(i).Area, props(i).Perimeter, props(i).Eccentricity, props(i).Centroid(1), props(i).Centroid(2));
end
Creazione della statistica riguardante la dimensione dei chicchi di riso nella figura

Per semplificare l'analisi è conveniente creare un vettore contenente l'area di ciascun oggetto connesso (cioè di ogni chicco di riso):

>> grain_areas = [graindata.Area];

Per esempio, per vedere l'area della cinquantesima componente è sufficiente accedere al cinquantesimo elemento

>> grain_areas(50)
ans = 194

Per identificare un elemento particolare nell'insieme si possono usare le funzioni di Matlab. Per esempio, il chicco con dimensione più piccola si trova digitando:

>> [min_area, idx] = min(grain_areas)
min_area = 61
idx = 16

Per visualizzarlo è possibile utilizzare la sequenza di comandi

>> grain = false(size(bw));   

che crea una matrice di elementi "falso" delle stesse dimensioni della matrice bw (che rappresenta l'immagine binaria ottenuta dall'immagine di partenza)

>> grain(cc.PixelIdxList{idx}) = true;

che cambia in "vero" i pixel corrispondenti alla posizione dell'indice idx dell'elemento più piccolo, infine l'immagine viene visualizzata con

>> imshow(grain)

Infine, è possibile creare finalmente un istogramma della distribuzione delle grandezze dei grani di riso:

>> histogram(grain_areas)
>> title('Histogram of Rice Grain Area')
Ultime modifiche: lunedì, 1 gennaio 2024, 21:59