Salta el contingut

Tutorial i exercisis

La morfologia matemàtica estudia les imatges a partir de la forma dels objectes. En imatges binàries, és natural interpretar els píxels blancs com el conjunt de l’objecte i els píxels negres com el fons. Les operacions morfològiques modifiquen aquest conjunt utilitzant una forma petita, anomenada element estructurant, que actua com una eina d’exploració geomètrica.

L’element estructurant és una part essencial del mètode. La seva mida determina l’escala dels detalls que s’eliminen o es conserven, i la seva forma determina quines estructures encaixen millor amb l’operació. Per tant, triar un disc, una línia, un quadrat o una altra forma no és només una decisió tècnica, sinó una manera d’expressar quin tipus de geometria es vol analitzar.

Carreguem les imatges binàries que farem servir durant la sessió pràctica

figures = imread("Imagenes\figures.png");
cap = imread("Imagenes\head.png");
forats = imread("Imagenes\agujeros.png");
simbols = imread("Imagenes\Simbols.jpg");
tisores = imread("Imagenes\scissors.tif");

Transformem en imatge binària totes aquelles imatges que no ho són

En aquesta pràctica es fa servir la convenció habitual que els objectes són blancs i el fons és negre. Aquesta convenció és important perquè les operacions morfològiques actuen sobre els píxels del primer pla. Si la imatge està invertida, una operació que esperàvem que fes créixer un objecte pot acabar afectant el fons, i la interpretació del resultat canvia.

figures = rgb2gray(figures);
figures = figures<128;

forats = forats>128;

simbols = rgb2gray(simbols);
simbols = imresize(simbols,[512,512]);
simbols = simbols<128;

montage({figures,cap,forats,simbols,tisores})

figure_0.png

Operadors morfològics primaris

Els operadors primaris són la dilatació i l’erosió. La dilatació fa créixer les regions blanques i pot unir parts properes d’un objecte. L’erosió fa l’efecte contrari: redueix les regions blanques i elimina parts petites on l’element estructurant no pot encaixar completament. Aquests dos operadors són la base de moltes operacions morfològiques més complexes.

Definició d’elements estructurals. S’utilitza la comanda strel, que té com a input el tipus d’element estructural, que pot ser de tipus disc, diamant, recta, rectangle, quadrat i formes definides per matrius.

L’element estructurant defineix quins veïns es consideren en cada operació morfològica. Un disc tracta les direccions de manera més uniforme, una línia potencia una orientació concreta i un quadrat considera un veïnat compacte. Per això, abans d’aplicar l’operació cal pensar quina forma té el detall que es vol conservar o eliminar.

SE = strel("diamond",1);

Tipus d’elements estructurals i els seus paràmetres:

  • diamond -- distància de l’origen a l’extrem del diamant (1 per a creu)
  • disk -- radi
  • line -- longitud i angle
  • rectangle -- longitud i altura (introduir com a vector, és a dir, \(begin:math:display\)l\,a\(end:math:display\))
  • square -- costat
  • nhood -- forma definida per una matriu de 0s i 1s

Dilatació. S’utilitza la comanda imdilate, que té com a input la imatge binària a dilatar i l’element estructural pel qual es dilata. Provar amb diamant 1, quadrat 3, disc 10.

Quan es canvia l’element estructurant, també canvia la manera com creix l’objecte. Un element petit només modifica el contorn lleugerament, mentre que un disc gran pot unir regions properes i eliminar separacions estretes. Aquesta prova permet veure que la morfologia depèn molt de l’escala triada.

SE = strel("square",4);

%SE = strel("disk",10);

tisores_dilate = imdilate(tisores,SE);

montage({tisores,tisores_dilate})

figure_1.png

simbols_dilate = imdilate(simbols,SE);

L’efecte serà l’esperat, és a dir, dilatar els objectes de la imatge.

montage({simbols,simbols_dilate})

figure_2.png

Erosió. S’utilitza la comanda imerode, que té com a input la imatge binària a erosionar i l’element estructural pel qual s’erosiona.

L’erosió elimina píxels del contorn quan l’element estructurant no hi cap completament. Per això pot separar objectes connectats per ponts estrets o eliminar components petites. També pot fer desaparèixer detalls útils si l’element estructurant és massa gran.

simbols_erode = imerode(simbols,SE);

L’efecte és l’esperat: els objectes de la imatge es redueixen.

montage({simbols,simbols_erode})

figure_3.png

Extracció de vores en imatges binàries.

En imatges binàries, una vora es pot obtenir comparant l’objecte original amb una versió erosionada o dilatada. Aquesta idea és diferent de la detecció de vores basada en gradients, perquè aquí no es mesuren canvis d’intensitat, sinó canvis en la forma del conjunt de píxels blancs. Per això les vores morfològiques són especialment adequades quan la imatge ja està segmentada.

Forma 1: imatge-erosio(imatge)

cap_erode = imerode(cap,SE);

cap_bordes = cap-cap_erode;

montage({cap,cap_bordes})

figure_4.png

Això es considera una vora interior, que prové d’un residu interior, que sempre es genera a partir de l’erosió per un element estructural.

Vora exterior, o residu exterior, que sempre es genera a partir de la dilatació per un element estructural.

cap_dilate = imdilate(cap,SE);

Com que la dilatació d’una imatge és sempre més gran que la imatge, recordeu que, de fet, conté la imatge, en comptes de fer imatge -dilatacio(imatge), hem de fer dilatacio(imatge)-imatge.

cap_bordes_2 = cap_dilate-cap;

Comparem els dos tipus de vores. Fixeu-vos en els petits detalls, com les pestanyes i els llavis, per entendre per què una s’anomena vora interior i l’altra exterior.

montage({cap_bordes,cap_bordes_2})

figure_5.png

Residu intern-extern, és a dir, tots dos junts. Es produeix en restar a la dilatació de la imatge l’erosió de la imatge.

cap_bordes_3 = cap_dilate-cap_erode;

Fixeu-vos que aquesta vora és sempre més gruixuda que la vora interior o exterior. Això es deu al fet que aquesta vora conté les vores interior i exterior, ja que s’ha calculat amb els dos residus.

montage({cap_bordes,cap_bordes_2,cap_bordes_3})

figure_6.png

Omplir forats i interiors. S’utilitza la comanda imfill, on l’input és la imatge amb forats. Cal afegir l’opció "holes".

Un forat és una regió de fons completament envoltada per píxels de l’objecte. Omplir forats és útil quan la segmentació ha deixat buits interns que no formen part real del fons. Aquesta operació conserva el contorn exterior però modifica la topologia interna de l’objecte.

forats_fill = imfill(forats,"holes");
montage({forats,forats_fill})

figure_7.png

Reconstrucció de components connexes per dilatació.

La reconstrucció morfològica es pot entendre com un creixement controlat. Es parteix d’un marcador, que indica quins píxels sabem que pertanyen a l’objecte que volem recuperar, i d’una màscara, que limita el creixement a les zones permeses. D’aquesta manera, es poden reconstruir components connexes concretes sense modificar la resta de la imatge.

Descomentar aquesta part quan s’arribi aquí. Aquesta comanda imprimeix la imatge en una pantalla independent perquè pugueu clicar en un dels símbols, que serà el que reconstruirem.

% imshow(simbols)

Aquesta comanda es queda amb les coordenades, en double, del punt marcat amb el ratolí a la imatge simbols. Hem de seleccionar un punt per reconstruir la component connexa associada a aquest punt.

%[x,y] = getpts;

Imatge binària de zeros. La comanda false crea una imatge binària de la mida de l’input. A aquesta imatge, en aquest cas de la mateixa mida que la imatge original, li canviarem només el valor d’un píxel, que serà el valor del píxel marcat amb el ratolí utilitzant la comanda getspts.

%punter = false(size(simbols));

Assignem el punt seleccionat i el convertim en blanc. Recordeu que, per fer la reconstrucció de components connexes, l’algorisme començava amb un punt que sabem que pertany a la component connexa. En aquest cas, serà el punt marcat amb el ratolí.

%punter(uint16(y),uint16(x)) = 1;

La comanda uint16 només converteix x i y en enters, perquè sapiguem quin píxel és el del marcador. No utilitzem uint8 perquè el marcador està en una escala més gran que 255, ja que la comanda getspts es pot utilitzar amb imatges de qualsevol tipus, en particular, de gran intensitat. Reconstruïm l’objecte connex al qual pertany el punt seleccionat. Per a això s’utilitza la comanda imreconstruct, que té com a paràmetres la imatge amb el punt o punts de les components connexes a reconstruir i la imatge original que servirà de patró en la reconstrucció.

%simbols_recons = imreconstruct(punter,simbols);

Si amb el ratolí cliqueu en més d’un símbol, imreconstruct us els reconstruirà tots.

%montage({simbols,simbols_recons});

Operadors morfològics secundaris

L’obertura i la clausura són operacions secundàries perquè combinen erosió i dilatació. L’obertura primer erosiona i després dilata, de manera que tendeix a eliminar objectes petits o sortints fins. La clausura primer dilata i després erosiona, de manera que tendeix a omplir forats petits, tancar separacions estretes i suavitzar entrants del contorn.

Obertura. L’operador obertura s’aplica utilitzant la comanda imopen. Aquesta comanda té com a paràmetres la imatge que es vol obrir i l’element estructural que s’utilitzarà.

SE = strel("disk",5);

figures_open = imopen(figures,SE);

Fixeu-vos que les cantonades exteriors del triangle i el pentàgon ara són romes en comptes de punxegudes. Aquest és un dels efectes típics d’aplicar l’operador obertura.

montage({figures,figures_open})

figure_8.png

Clausura. Igual que l’operador obertura, aquest operador s’aplica utilitzant la comanda imclose, que té els mateixos inputs que l’operador obertura.

figures_close = imclose(figures,SE);

Fixeu-vos que en aquest cas no s’observen canvis a la imatge. Això es deu al fet que l’operador clausura arrodoneix cantonades interiors, que en aquest cas no són presents. Busquem un altre exemple.

montage({figures,figures_close})

figure_9.png

tisores_close = imclose(tisores,SE);

Fixeu-vos que en aquest cas les cantonades interiors de l’objecte apareixen romes en comptes de punxegudes, cosa que és un efecte típic de l’operador clausura.

montage({tisores,tisores_close})

figure_10.png

Filtratge morfològic. És un filtre de "suavitzat", però basat en operadors morfològics. En particular, és molt bo amb soroll de tipus sal i/o pebre.

El filtratge morfològic no fa una mitjana dels valors dels píxels, sinó que utilitza operacions de forma. Això és útil quan el soroll apareix com petits punts blancs o negres, perquè aquests punts poden desaparèixer si són més petits que l’element estructurant. Al mateix temps, les estructures grans i coherents poden conservar-se millor que amb un suavitzat lineal.

Recordeu --> Filtratge = Clausura de l’obertura de la imatge

Aplicar primer una obertura ajuda a eliminar petits punts blancs o sortints fins. Aplicar després una clausura ajuda a recuperar continuïtat i omplir petits buits. La combinació és útil com a filtre morfològic perquè elimina soroll petit sense dependre d’un promig d’intensitats.

empremtes = imread("Imagenes\Huella.jpg");

Binaritzem la imatge Empremta

empremtes = rgb2gray(empremtes);
empremtes = empremtes>128;

SE = strel('square',5);

empremtes_open = imopen(empremtes,SE);

empremtes_filt = imclose(empremtes_open,SE);

montage({empremtes,empremtes_filt})

figure_11.png

Transformada hit-or-miss i reconstrucció per dilatació. La transformada hit-or-miss pren un element estructural que sigui igual a la component connexa que es vol trobar a la imatge original, i retorna un punt de la component, per després reconstruir com hem vist abans. És una manera de detectar automàticament punts de les components connexes que volem reconstruir, sense haver de clicar amb el ratolí.

La transformada hit-or-miss permet buscar una configuració concreta dins d’una imatge binària. Una part del patró ha de coincidir amb píxels de l’objecte i una altra part ha de coincidir amb píxels del fons. Per això és útil per detectar formes molt específiques, tot i que pot fallar si l’objecte està deformat, girat o presenta soroll.

Prenem un element estructural que tingui la forma d’un dels símbols de la imatge.

SE1 = simbols(25:110,285:335);

montage({simbols,SE1})

figure_12.png

Recordeu que el segon element estructural ha de ser el complementari del primer.

SE2 = not(SE1);

montage({SE1,SE2})

figure_13.png

La transformada hit-or-miss s’aplica amb la comanda bwhitmis, que té com a inputs la imatge original i els dos elements estructurals.

punter = bwhitmiss(simbols,SE1,SE2);

Una vegada obtingut el punt de la component connexa a reconstruir, reconstruïm com hem vist abans.

simbols_re=imreconstruct(punter,simbols);

montage({simbols,punter,simbols_re})

figure_14.png

Esqueletització. Una de les aplicacions de la transformada hit-or-miss és calcular l’esquelet dels elements en una imatge binària. Hi ha principalment dues formes.

L’esqueletització intenta reduir un objecte a una estructura prima, idealment d’un sol píxel d’amplada, mantenint la seva connectivitat principal. Aquesta representació és útil per estudiar formes allargades, branques o centres geomètrics. Tanmateix, si el contorn és irregular o té soroll, poden aparèixer petites branques no desitjades, anomenades elements espuris.

1a forma, utilitzant la comanda bwskel, l’input de la qual és la imatge original.

figures_sk = bwskel(figures);

2a forma, utilitzant la comanda bwmorph, els inputs de la qual són la imatge original, "skel" per indicar que la transformació morfològica que volem fer és obtenir l’esquelet, i Inf, que poda elements espuris, és a dir, branques de l’esquelet que no hi haurien de ser.

figures_ske = bwmorph(figures,'skel',Inf);

Comparem ambdues formes. Encara que sembli que bwmorph produeix un millor resultat, en general, la millor manera de calcular l’esquelet és amb bwskel. Vegem altres exemples.

montage({figures,figures_sk,figures_ske})

figure_15.png

tisores_sk = bwskel(tisores);

tisores_ske = bwmorph(tisores,'skel',Inf);

Aquí s’observa que bwmorph produeix un resultat pitjor, i amb elements espuris malgrat la comanda Inf.

montage({tisores,tisores_sk,tisores_ske})

figure_16.png

cap_sk = bwskel(cap);

cap_ske = bwmorph(cap,'skel',Inf);

Mateix comentari que abans.

montage({cap,cap_sk,cap_ske})

figure_17.png

En casos com els anteriors, en què bwmorph mostra un esquelet amb elements indesitjables, espuris, hi ha una comanda per podar encara més l’esquelet. Es tracta de nou de bwmorph. En aquest cas, l’únic que canvia és que en comptes de demanar "skel" perquè ens faci l’esquelet, demanem "spur" perquè ens podi elements espuris.

tisores_poda = bwmorph(tisores_ske,"spur",Inf);
cap_poda = bwmorph(cap_ske,"spur",Inf);

En ambdós casos el resultat continua sent dolent, cosa que confirma que l’opció bwskel és la millor.

montage({tisores_ske,tisores_poda})

figure_18.png

montage({cap_ske,cap_poda})

figure_19.png

El càlcul d’esquelets també es pot utilitzar per segmentar la imatge, si és binària. Per a això, el primer pas és fer not() de la imatge, ja que l’esquelet sempre es calcula a la zona amb píxels de valor u, blancs aquí.

figures = not(figures);

forat = not(forats_fill);

montage({figures,forat})

figure_20.png

Com que la imatge forat té dues franges negres al voltant, utilitzem un petit truc. Aquest consisteix a definir un marc blanc al voltant de la imatge i aplicar l’operador clausura. Aquest, automàticament, convertirà en blanc tot el fons que encara no ho era.

Comanda per fer que la vora exterior de la imatge sigui blanca.

forat(1,:)=1; forat(end,:)=1; forat(:,1)=1; forat(:,end)=1;

forat = imclose(forat,SE);

Resultat.

montage(forat)

figure_21.png

Ara sí, esqueletitzem amb bwskel i veiem que ens ha delimitat les àrees en què es troba cadascuna de les figures o cadascun dels objectes, cosa que de fet és segmentar la imatge en àrees diferents, atenent als objectes.

figures_sk = bwskel(figures);

forat_sk = bwskel(forat);

montage({figures,figures_sk})

figure_22.png

montage({forat,forat_sk})

figure_23.png

bwmorph també es pot utilitzar per fer totes les altres operacions que hem vist abans, dilatació, erosió, obertura, etc., però només amb elements estructurals quadrats 3x3, per la qual cosa a la pràctica no s’utilitza.

Si ara volguéssim segmentar una imatge binària amb elements a la vora, com la mostrada a continuació, descobriríem que la segmentació no funciona. Un truc per arreglar aquest problema consisteix a eliminar els elements de la vora.

test_morph = imread("Morfologia_I.jpg");

test_morph = rgb2gray(test_morph);
test_morph = test_morph > 128;

montage(test_morph)

figure_24.png

Per a això, crearem una imatge binària que només tindrà valor blanc a la vora i la reconstruirem basant-nos en la imatge original. Això ens donarà els elements de la vora, que simplement hem de sostreure de la imatge original.

punter = false(size(test_morph));

Definim vores blanques

punter(1,:)=1; punter(:,1)=1; punter(end,:)=1; punter(:,end)=1;

Reconstruïm

test_morph_recon = imreconstruct(punter,test_morph);

Restem per quedar-nos amb els elements de l’interior.

test_morph_no_bordes = test_morph - test_morph_recon;

montage({test_morph,test_morph_recon,test_morph_no_bordes})

figure_25.png

Transformada de la distància

La transformada de la distància assigna a cada píxel de l’objecte un valor relacionat amb la distància fins al fons més proper. El resultat ja no és una imatge binària, sinó una imatge en escala de grisos que descriu l’interior de les formes. Els valors més alts acostumen a situar-se a les zones centrals dels objectes, fet que és útil per calcular esquelets o separar objectes que es toquen.

Transformada de la distància. Aquesta calcula la distància a la vora més propera de cada element blanc de la imatge original. D’aquesta manera, convertim la imatge binària en una imatge en escala de grisos. L’element més allunyat de les vores serà un 255, i així en proporció, a mesura que ens acostem a les vores.

Com que volem aplicar-ho a la silueta, hem de convertir la silueta en blanc i el fons en negre.

cap_neg = not(cap);

imshow(cap_neg)

figure_26.png

La transformada de la distància s’aplica amb la comanda bwdist, on l’únic input és la imatge original que es vol transformar.

cap_d = bwdist(cap_neg);

Per veure les imatges, necessitem afegir \(begin:math:display\)\(end:math:display\)

imshow(uint8(cap_d))

figure_27.png

Altres distàncies: cityblock -- Manhattan o d4, chessboard -- tauler d’escacs o d8.

La distància euclidiana mesura la distància geomètrica habitual, mentre que cityblock només permet desplaçaments horitzontals i verticals, i chessboard permet també diagonals amb el mateix cost. Canviar la mètrica modifica la forma dels nivells de distància i, per tant, pot afectar esquelets o centres estimats.

cap_d4 = bwdist(cap_neg,"cityblock");
cap_d8 = bwdist(cap_neg,"chessboard");
imshow(uint8(cap_d4))

figure_28.png

imshow(uint8(cap_d8))

figure_29.png