({{}})

({{, ({{"Lezione 8"}, {"Elaborazione immagini}, {Crescenzio Gallo - Università di Foggia
crescenzio.gallo@unifg.it}})}})

Note:

Il materiale visualizzato durante questo seminario è disponibile per il download all'indirizzo http://www.crescenziogallo.it/unifg/seminario-mathematica-2014/

Per una migliore visione ingrandire lo schermo mediante il pulsante in alto a destra "Schermo intero"

11-25 Marzo 2014

Agenda

Introduzione

Creare le immagini

Proprietà elementari delle immagini

Manipolazione di immagini

Alcune funzioni elementari

Operazioni dinamiche su immagini

Operazioni sui singoli punti o sui canali

Applicare operazioni aritmentiche o più generali

Tonalità, saturazione e luminosità

Esempi di applicazioni dell’image processing

Conclusioni

Introduzione: creare le immagini

I dati di partenza

Le immagini su cui operare possono essere create o importate. Per creare immagini si possono usare diversi strumenti e diversi metodi. Vediamo alcuni esempi.

Creare immagini a partire dai dati.

La funzione da usare per creare immagini si chiama Image. Una matrice genera un'immagine con un singolo canale:

Image[im=RandomReal[1,{5,5}]]

MatrixForm[im]

Mentre un array con profondità 3 genera un'immagine multicanale:

Image[im3=RandomReal[1,{5,5,3}]]

MatrixForm[im3]

Per default le immagini create con Image sono di tipo “Real” e non assumono uno specifico valore per lo spazio colori, ossia l’opzione ColorSpace assume valore Automatic. Comunque si possono specificare sia il tipo di immagine sia il suo spazio colori:

im4=Image[{{128,255,0},{255,128,255},{0,255,128}},"Byte"]

InputForm[im4]

im5=Image[CellularAutomaton[30,{{1},0},20],"Bit"]

InputForm[im5]

Si può anche usare RandomImage per generare immagini random a partire da dati di una certa distribuzione:

RandomImage[UniformDistribution[], {200,200}]

RandomImage[BinomialDistribution[1,.5], {200,200}]

Introduzione: creare le immagini

Il tipo “Real”

In una matrice che genera un’immagine di tipo “Real” i valori possono anche essere esterni all’intervallo [0, 1] ma nella visualizzazione dell’immagine essi verrano considerati comunque nel range [0,1]:

dati=Table[i/10.,{20},{i,-10,20}];

dati[[1]]

im=Image[dati]

Comunque i valori internamente alla variabile non sono modificati fisicamente, ossia sono sempre quelli originali:

ImageData[im][[1]]===dati[[1]]

Se si desidera scalare i valori originali nell’intervallo [0, 1] si può usare la funzione ImageAdjust

im1=ImageAdjust[im]

Questi sono i valori scalati:

ImageData[im1][[1]]

Introduzione: creare le immagini

I tipi “Integer”

I tipi di immagini “Bit”, “Byte”, “Bit16” possono accettare solo valori interi in specifici intervalli. Altri valori sono arrotondati (reali) o tagliati (esterni)

Ad esempio il tipo "Bit" può contenere solo valori 0 e 1

Quit[]

i=Image[{{-1,-0.3,0,.3,.5,1,1.5,2}},"Bit"]

ImageData[i]

Come si vede dalla InputForm i dati sono effettivamente modificati

InputForm[i]

Introduzione: creare le immagini

Immagini a partire da grafici di Mathematica

Si possono anche generare immagini con le funzioni native di Mathematica dedicate alla grafica:

im=Image[Graphics[{Pink,Disk[],Cyan,Rectangle[]},ImageSize->100]]

Image[Graphics3D[{Sphere[],Cuboid[]}],ImageSize->100]

i=Image[Plot[Evaluate[Table[x Sin[n x],{n,1,3}]],{x,0,2Pi},ImageSize->150]]

Mentre Image necessita di una matrice di valori o di un oggetto Graphics, Rasterize fornisce analoghe funzionalità per qualsiasi oggetto di Mathematica e dunque può essere usata per creare immagini da espressioni:

Rasterize[((Exp[(a^2)])/(π (Sqrt[2]))),"Image", ImageResolution->400]

Molte funzioni della sezione “image processing” sono in grado di accettare sia immagini che grafici di Mathematica. Esempio di filtro di Gauss:

GaussianFilter[Plot[Evaluate[Table[x Sin[n x],{n,1,3}]],{x,0,2Pi}],3]

Introduzione: creare le immagini

Import da file esterni

Ovviamente le immagini possono anche essere importate da file esterni a Mathematica

$ImportFormats

Import legge sia dal file system del computer sia da qualsiasi URL accessibile:

SetDirectory[NotebookDirectory[]];

Import["Plot3D.jpg"]

Import["http://photojournal.jpl.nasa.gov/jpeg/PIA12708.jpg"]

Import["http://1.bp.blogspot.com/_FJsabaNYeEU/SwguHlv0JYI/AAAAAAAAAAs/mDcZOJ-wzCo/s1600/leone-leonessa.jpg"]

Ci sono anche alcune immagini di test in Mathematica nella banca dati ExampleData

Import["ExampleData/Lena.tif"]

ExampleData["TestImage"]

Map[ExampleData,ExampleData["TestImage"]]

Introduzione: proprietà elementari delle immagini

Lo spazio colori

Quando si generano immagini dai dati e non si specifica lo spazio colori, Mathematica cerca di definire uno schema di mix dei canali per visualizzare al meglio l’immagine. Comunque si può specificare lo spazio colori che si vuole usare:

im=Image[RandomReal[1,{5,5,3}],ColorSpace-> "RGB"]

Se non specifichiamo un ColorSpace a 4 canali l’opzione ColorSpace->Automatic fa sì che si assuma la quarta dimensione come il valore per il canale alfa:

im=Image[RandomReal[1,{5,5,4}]]

InputForm[im]

im=Image[RandomReal[1,{5,5,4}],ColorSpace-> "CMYK"]

Con profondità 5 si può specificare il grado di opacità (canale alfa) per gli spazi colore a 4 canali:

im=Image[RandomReal[1,{5,5,5}],ColorSpace-> "CMYK"]

Gli spazi colore riconosciuti da Image sono:

Grayscale -> Livelli di grigio
RGB -> Rosso, verde, blu
CMYK -> Ciano, Magenta, Giallo, Nero
HSB -> Tonalità, saturazione, luminosità

Introduzione: proprietà elementari delle immagini

Interallacciata vs. planare

Mathematica supporta due modi di combinazione dei canali: interleaved (interallacciata) e planar (planare). Per default, i dati sono considerati nella forma interallacciata:

im={{{1,0,0},{0,1,0}},{{0,0,1},{1,1,0}}};

ii=Image[im,ColorSpace->"RGB",Interleaving->True]

MatrixForm[im]

Un’immagine in forma “planare” può essere rappresentata da una lista di matrici, ciascuna rappresentante un singolo canale:

pm={{{1,0},{0,1}},{{0,1},{0,1}},{{0,0},{1,0}}};

pi=Image[pm,ColorSpace->"RGB",Interleaving->False]

MatrixForm[pm]

MatrixForm/@pm

ImageData restituisce sempre i dati in forma interallacciata, anche se l’immagine è in forma planare:

ImageData[pi]//MatrixForm

Volendo si può usare anche in ImageData l’opzione Interleaving:

ImageData[pi,Interleaving->False]//MatrixForm

ColorSeparate genera una lista di immagini ciascuna rappresentante i singoli canali:

i = ;

ColorSeparate[i]

Al contrario, ColorCombine combina una lista di immagini di singoli canali in un’immagine multicanale:

ColorCombine[{,,}]

Introduzione: proprietà elementari delle immagini

Dimensione immagine e zoom

ImageSize e Magnification sono due opzioni che modificano il modo in cui un’immagine viene visualizzata. Per default ImageSize è impostata al valore Automatic, che generalmente ingrandisce immagini piccole e ridimensiona immagini che non entrano nelle dimensioni della finestra del notebook.

Image[{{0,1,0},{1,0,1},{0,1,0}}]

Ad esempio il valore Automatic permette di visualizzare un’immagine ad una dimensione più grande se si trova da sola in una cella:

Mentre la ridimensiona se si inserisce in una riga di testo, ad esempio per un input:

Image[,ImageSize->All]

Magnification solitamente si usa per scalare l’immagine o fare uno zoom di ingrandimento:

Image[,Magnification->2]

Se si usano entrambi le opzioni, ImageSize prevale:

Image[,ImageSize->10,Magnification->3]

Manipolazione di immagini: proprietà elementari delle immagini

Alcune funzioni elementari

Usando il menu contestuale che compare su un’immagine, si può selezionare l’opzione Get Coordinates dal menù contestuale per prelevare le singole coordinate di un punto nell’immagine o di una regione definita da due o più coordinate:

i=

ImageTrim estrae una regione definita in termini di coordinate:

ImageTrim[i,{{10,10},{107,143}}]

ImageCrop può essere usata allo stesso modo definendo le righe/colonne da eliminare

ImageCrop[i,{100,120},Bottom]

ImageCrop è una delle funzioni disponibili nel menu contestuale delle immagini

i

ImageValue consente di estrarre valori numerici dei singoli pixel:

ImageValue[i, {100,100}]

ImagePartition può essere utilizzata per suddividere un’immagine in tante piccole immagini:

g=ImagePartition[i,40];

Grid[g,Frame->All]

ImageAssemble ricostruisc e un’immagine da una lista di “tasselli”:

ImageAssemble[Partition[RandomSample[Flatten[g],Length[Flatten[g]]],5]]

ImageRotate ruota un’immagine di un angolo arbitrario. Il centro dell’immagine è il centro di rotazione e l’angolo è considerato antiorario:

Manipulate[ImageRotate[,Θ],{Θ,0,2 π}]

Manipolazione di immagini: proprietà elementari delle immagini

Operazioni dinamiche su immagini

Molte operazioni si possono eseguire dinamicamente sulle immagini così da rendere più semplice lo studio di particolare caratteristiche delle immagini:

Module[{i=},
Manipulate[
ImageResize[ImageCrop[i,{300,300}/zoom,-pts],{300,300},Resampling->ControlActive["Nearest","Lanczos"]],
{{pts,{0,0}},{-1,-1},{1,1}},
{{zoom,1},1,10}
]]

Un esempio di tecniche di segmentazione dove Manipulate consente di collocare i punti focali di una segmentazione “watershed” (bacino) per evidenziare un particolare da una certa immagine. Nell’esempio, il particolare da evidenziare con la tecnica di watershed segmentation sono i due tubercoli articolari che si trovano in corrispondenza dei bulbi oculari nell’immagine:

i = ;

Manipulate[Row[{Show[i], Image[WatershedComponents[i, pts]//Colorize, ImageSize->All]}], {{pts,RandomReal[Min[ImageDimensions[i]],{3,2}]},{0,0},ImageDimensions[i],Locator, Appearance->Graphics[{Yellow,Disk[{0,0}]}, ImageSize->10],LocatorAutoCreate->{2,10}}]

Una volta individuata la posizione utile, si usa Paste Snapshot dal menù della Manipulate per prelevare i valori numerici dei locator mossi manualmente:

DynamicModule[{pts={{154.5`,137.5`},{51.5`,141.5`},{167.46081666362903`,40.241060512337015`}}},Row[{Show[i],Image[Colorize[WatershedComponents[i,pts]],ImageSize->All]}]]

pts={{154.5`,137.5`},{51.5`,141.5`},{167.46081666362903`,40.241060512337015`}}

label=WatershedComponents[i,pts];

Colorize[label]

Un altro esempio con ImageAdjust che modifica i valori dei singoli canali applicando particolari formule. Alcune operazioni comuni sono contrasto, luminosità, gamma.

Manipulate[With[{i=ImageAdjust[,{c,b,g}]},{i,ImageHistogram[i]}],{{c,0,"Contrasto"},0,1},{{b,0,"Luminosità"},0,1},{{g,1,"Gamma"},1,4}]

Operazioni sui singoli punti o sui canali

Applicare operazioni aritmetiche o più generali

ImageAdd, ImageSubtract, e ImageMultiply consenono di eseguire semplici operazioni aritmetiche sui valori dei singoli canali:

i=;

ImageAdd[i,.5]

ImageMultiply[i,.5]

ImageDifference restituisce l’immagine con le distanze tra pixel corrispondenti nelle due immagini:

ImageDifference[, ]

ImageCompose sovrappone la seconda alla prima immagine combinando i valori sui canali delle due immagini (opera sul canale alfa con un’eventuale fattore di trasparenza, nell’esempio del 50%):

ImageCompose[,{,.5}]

ImageApply consente di applicare qualsiasi funzione a ciascun pixel:

f[x_,σ_]:=(1/(1+(E^(-((x-0.5)/σ)))));

Manipulate[Plot[f[x,s],{x,0,1},PlotRange->{0,1}],{{s,0.1},.01,0.1}]

Manipulate[ImageApply[f[#,s]&,i],{{s,0.1},.01,0.1}]

Se l’immagine ha i canali nella forma interallacciata, la funzione Max applicata con ImageApply restituisce il massimo valore dei canali per ciascun pixel; come risultato si ha l’immagine con un solo canale:

ImageApply[Max,i]

Se la funzione è planare il massimo si applica a ciascun singolo canale, restituendo di fatto il suo valore stesso, dunque l’immagine di partenza:

ImageApply[Max,i,Interleaving->False]

Operazioni sui singoli punti o sui canali

Tonalità, saturazione e luminosità

In questo esempio si converte un’immagine nello spazio colore "HSB" moltiplicando i valori di ciascun canale per uno scalare e poi combinandoli insieme:

With[{hsb=ColorConvert[,"HSB"]},
Manipulate[ImageApply[{Mod[#[[1]]+h,1],s#[[2]],b#[[3]]}&,hsb],
{{h,0},0,1},{{s,1},0,1},{{b,1},0,1}]]

Esempi di applicazioni dell’image processing

Trovare le dimensioni di una parte dell’immagine

In questo esempio, a partire dall’immagine

i = ;

bisogna calcolare la dimensione della regione del cervello.

Si utilizza prima RegionBinarize, che evidenzia regioni connesse che sono all’interno di una fissata distanza di colore (in questo caso .1).

rb = Image[RegionBinarize[i, {{92,91}}, .1], ImageSize->All]

?RegionBinarize

L’esempio di sopra non risulta molto efficace, perchè nella parte di destra non ha separato correttamente il cervello dal resto dell’immagine. Pertanto si può provare con altre funzioni, quali DistanceTransform (che calcola la distanza tra ciascun pixel ed il colore dello sfondo) e SkeletonTransform che restituisce una traformazione “skeleton” dell’immagine:

Row[ImageAdjust/@{DistanceTransform[rb], skeleton = SkeletonTransform[rb]}]

A questo punto, usando un’altra funzione del menu contestuale delle imamgini chiamata GetIndices, possiamo determinare che il pixel di coordinate {132, 132} può essere quello giusto per individuare la regione del cervello sulla parte anteriore destra, staccando così l’area del cervello dal resto dell’immagine:

dat = ImageData[skeleton];
dat[[132,132]] = 0;
cut = Image[dat]

(segments =MorphologicalComponents[cut])//Colorize

?MorphologicalComponents

?Colorize

Moltiplicando il segmento così estratto con lo skeleton prima memorizzato, otteniamo una ricostruzione della sola area di pertinenza del cervello:

brainskeleton = ImageMultiply[skeleton, Image[SelectComponents[segments, "Label",#==2&], "Bit"]]

?ImageMultiply

brainregion = InverseDistanceTransform[brainskeleton]

?InverseDistanceTransform

Questa è la sua misura:

ComponentMeasurements[brainregion, "Count"]

?ComponentMeasurements

Row[Image[#, ImageSize->All]&/@{i, }]

Esempi di applicazioni dell’image processing

Riconoscere il punteggio ottenuto dai dadi

Questo esempio mostra un’altra serie di funzioni di image processing, applicate al caso di riconoscimento automatico del punteggio ottenuto da una serie di dadi ricavato dalla manipolazione della fotografia dei dadi stessi.

i = ;

Il primo passo effettua ancora una segmentazione per ricavare i riquadri relativi ai singoli dadi:

b=DeleteBorderComponents[MorphologicalBinarize[i,{.65,.8}]]

?MorphologicalBinarize

?DeleteBorderComponents

ComponentMeasurements permette di trovare molte proprietà delle componenti, tra cui una molto utile in questo caso che si chiama “MinimalBoundingBox” che ci aiuta a trovare i riquadri di maggiore interesse nell’immagine:

bounds= #[[2]]&/@ComponentMeasurements[b, "MinimalBoundingBox"]

Show[i,Graphics[{EdgeForm[Thick],Transparent,Polygon@#& /@ bounds}]]

I lati dei dadi non sono ancora ripuliti da valori di “disturbo” che mostrano i punteggi delle altre facce del dado. Applicando ulteriori filtri si rimuove questo problema:

squareBB[box_List, center_List]:= Module[{ratio, p1,p2,p3,p4},
ratio = Divide @@ Sort[{EuclideanDistance[box[[1]], box[[2]]], EuclideanDistance[box[[2]], box[[3]]]}];
p1 = SortBy[box, EuclideanDistance[#, center]&][[4]];
{p1,p2,p3,p4} = SortBy[box, EuclideanDistance[#, p1]&];
p3 = ratio p3 + (1 - ratio) p1;
p4 = ratio p4 + (1 - ratio) p2;
{p1,p2,p4,p3}
]

squarebounds = squareBB[#,ImageDimensions[i]/2]&/@ bounds

Show[i,Graphics[{EdgeForm[Thick],Transparent,Polygon@#& /@ squarebounds}]]

Una volta “ripulita” l’immagine con alcune altre trasformazioni si possono ricavare i soli punti corrispondenti ai punteggi dei dadi:

boxmask = Binarize[ColorNegate@Graphics[{Polygon@#& /@ squarebounds}, PlotRange->Transpose[{{1,1}, ImageDimensions[i]}], ImageSize->ImageDimensions[i]]]

cleaned=ImageMultiply[FillingTransform[boxmask],b]

dots=DeleteBorderComponents[ColorNegate[cleaned]]//DistanceTransform//MaxDetect

dotteddice=ImageSubtract[FillingTransform[cleaned],Dilation[dots,1]]

L'opzione Dilation (dilatazione) rimuove le caratteristiche scure più piccole.

ComponentMeasurements[dotteddice,"Holes"][[All,2]]

Infine, conteggiando i buchi neri (“Holes”) per ciascuna componente dell’immagine, otteniamo i valori delle facce dei dadi ;-)

Esempi di applicazioni dell’image processing

Clustering di tasselli di immagine

im=;

ImagePartition[im,80]//Grid

im2=SortBy[Flatten[ImagePartition[im,80]],Total[ImageData[#],3]&]

colorMeans=Map[Mean[Mean[ImageData[#]]]&,im2]

BubbleChart[colorMeans,ChartElements->im2]

nf=Nearest[colorMeans->im2];

GraphPlot[Flatten[Table[Thread[im2[[i]]->nf[colorMeans[[i]],3]],{i,Length[im2]}]],
VertexRenderingFunction->(Inset[#2,#,Center,.5]&),SelfLoopStyle->None,ImageSize->500]

Manipulate[GraphPlot[Flatten[Table[Thread[im2[[i]]->nf[colorMeans[[i]],n]],{i,Length[im2]}]],
VertexRenderingFunction->(Inset[#2,#,Center,.5]&),SelfLoopStyle->None,ImageSize->500],{n,1,10,1}]

Conclusioni

Mathematica offre non solo funzionalità general purpose ma anche verticalizzazione in specifici settori quali questo appena visto della elaborazione delle immagini. Il valore aggiunto di strumenti come Mathematica in tali casi diventa proprio il fatto di avere a disposizione non solo i migliori algoritmi e le più avanzate funzionalità di uno specifico settore ma anche centinaia di funzioni aggiuntive. Ad esempio l’interattività dei controller o la enorme flessibilità dei documenti consente di realizzare applicazioni anche complesse in qualsiasi campo.
Infine, in particolare per quanto riguarda l’elaborazione delle immagini va sottolineato che sono disponibili molte funzioni avanzate di image processing implementate in linguaggio Cuda direttamente come funzioni native di Mathematica.