Skip to content

PRÁCTICA 5: SEGMENTACIÓN DE IMÁGENES

Cargamos las imágenes que usaremos durante esta parte de la sesión práctica.

objetos = imread("Imagenes\objects.tif");
monedas = imread("Imagenes\coins.png");
lake = imread("Imagenes\lake.tif");
cameraman = imread("Imagenes\cameraman.tif");

Como lake tiene dos canales, cogemos uno!

lake = lake(:,:,1);

Mostramos las imagenes

montage({objetos,monedas,lake,cameraman})

figure_0.png

Segmentación por umbralización

Selección de umbrales mediante el estudio directo del histograma

h_objetos = imhist(objetos);

bar(h_objetos)

figure_1.png

Umbral (calculado a ojo a partir del histograma)

T=75;

objetos_bin = objetos>T;

Tambien se puede usar el comando imbinarize si hacemos T/255.

montage({objetos,objetos_bin})

figure_2.png

Otro ejemplo

h_lake = imhist(lake);

bar(h_lake)

figure_3.png

Umbrales (calculados a ojo a partir del histograma)

T1 = 130;
T2 = 205;

lake_bin = imquantize(lake,[0,T1,T2,255]);

imshow(lake_bin,[])

figure_4.png

Metodo de Otsu

T_otsu = graythresh(objetos);

objetos_bin = imbinarize(objetos,T_otsu);

montage({objetos,objetos_bin})

figure_5.png

Etiquetar y colorear componentes conexas

Primero hacemos not(objetos), puesto que queremos que las componentes conexas sean blancas sobre fondo negro.

objetos_neg = not(objetos_bin);

Usamos el comando bwlabel, que etiqueta las componentes conexas en una imagen binaria.

comps_conexas = bwlabel(objetos_neg);

Para visualizar el resultado (lo que es totalmente opcional), hacemos label2rgb, que colorea cada componente conexa atendiendo a una paleta de colores.

objetos_col = label2rgb(comps_conexas,"jet","k","shuffle");

montage({objetos,objetos_neg,objetos_col})

figure_6.png

Metodos de segmentación basados en la detección de bordes

Detección de rectas usando el Laplaciano

lineas = imread("Imagenes\lineas.png");
lineas = rgb2gray(lineas);

Kernel Laplaciano para detectar rectas verticales

w_verticales = [-1,2,-1;-1,2,-1;-1,2,-1];

lineas_filt = abs(imfilter(lineas,w_verticales));

Umbral para discriminar las rectas calculadas y eliminar las más débiles (que probablemente o no serán rectas o no serán rectas verticales).

maxi = max(lineas_filt(:));

lineas_filt_bin = lineas_filt>=maxi;

montage({lineas,lineas_filt,lineas_filt_bin})

figure_7.png

Se usa el comando edge para la detección de bordes con el gradiente digital y como alternativa a los comandos específicos de Roberts/Sobel y Prewitt que se obtenían con el comando fspecial.

objetos_contornos_roberts = edge(objetos,"roberts");

objetos_contornos_sobel = edge(objetos,"sobel");

objetos_contornos_prewitt = edge(objetos,"prewitt");

montage({objetos,objetos_contornos_roberts,objetos_contornos_sobel,objetos_contornos_prewitt})

figure_8.png

Metodo de Marr-Hildreth o de paso por cero para detectar bordes

house = imread("Imagenes\house.tif");
house = house(:,:,1);
w_log = fspecial("log",25,1);

T = 0.018 (empirico, es decir, calculado vía prueba y error).

T = 0.018;

Se usa edge para MH con la opción zerocross añadiendo el umbral T y el kernel de un filtro, en este caso un filtro Laplacian of a Gaussian (LoG).

house_contornos = edge(house,"zerocross",T,w_log);

montage({house,house_contornos})

figure_9.png

Metodo de Canny

Definimos los umbrales inferior y superior (tambien calculados empíricamente).

T_canny = [0.04,0.2];

Se usa el comando edge para aplicar el método de Canny (con la opción "canny"). Como inputs extra, hay que añadir un umbral (en este caso un vector de dos umbrales (inferior y superior)) y la varianza del primer paso, que sirve para suavizar la imagen con un filtro Gaussiano.

sigma = 4;

house_canny = edge(house,"canny",T_canny,sigma);

montage({house,house_canny})

figure_10.png

Jugamos con los umbrales inferior y superior (Importante! T1 siempre tiene que ser menor que T2 y T2<1)

T1 = 0.01:0.03:0.07;
T2 = 0.1:0.2:0.9;
sigma = 4;

% for i = 1:length(T1)
% 
%     for j = 1:length(T2)
% 
%         if T1(i)<T2(j)
% 
%             house_canny_loop = edge(house,"canny",[T1(i),T2(j)],sigma);
% 
%             figure;
%             imshow(house_canny_loop)
% 
%         end
% 
%     end
%
% end

Metodos de segmentación basados en la similitud

Cargamos la imagen que usaremos.

monedas = imread("Imagenes\coins.png");

imshow(monedas)

figure_11.png

Watershed --> Método de segmentación basado en un proceso de inundación de cuencas, es decir, de los distintos mínimos de la imagen, y de construcción de diques, es decir, bordes, separando las distintas regiones.

monedas_watershed = watershed(monedas);

Nos quedamos con las lineas de separación (que son negras) en el output del comando watershed. Es decir, "bordes" es una imagen binaria que tiene todos los pixeles negros del output, "monedas_watershed", que son precisamente dichas lineas de separación, como pixeles con valor de intensidad 1, es decir, blancos.

bordes = monedas_watershed == 0;

Lo juntamos con la imagen original y convertimos la lineas de separación en lineas blancas para que destaquen cuando sean superpuestas a la imagen original. Primero hacemos una copia de la imagen original para no alterarla.

monedas_copia = monedas;

Y luego cambiamos los pixeles que en la imagen binaria "bordes" no pertenecen al fondo de la imagen por pixeles blancos, es decir, con nivel de intensidad 255.

monedas_copia(bordes) = 255;

Finalmente, mostramos el resultado.

montage({monedas,monedas_copia})

figure_12.png

Claramente hay sobresegmentación, probamos formas diferentes de resolver dicha sobresegmentación.

1º forma --> opción conectivitidad. El comando "watershed" acepta un segundo input, que es la conectividad de la imagen original. Esta conectividad puede mejorar el resultado, por medio de eliminar las los diques asociados a componentes conexas no afines con la connectividad impuesta.

monedas_watershed_2 = watershed(monedas,8);

bordes_2 = monedas_watershed_2 == 0;

monedas_copia_2 = monedas;

monedas_copia_2(bordes_2) = 255;

montage({monedas_copia_2})

figure_13.png

2º forma --> uso de transformada de la distancia. La transformada de la distancia permite encontrar el centro geométrico de los objetos (no de las componentes conexas, sino de los objetos (dos objetos que se tocan pueden conformar una única componente conexa)). De hecho, esta es una de las grandes ventajas del watershed, que permite segmentar imagenes con objetos que se tocan o estan parcialmente solapados.

Para aplicar la transformada de la distancia, necesitamos una imagen binaria. Para ello, binarizamos usando el método de Otsu.

T_otsu_monedas = graythresh(monedas);

monedas_bin = imbinarize(monedas,T_otsu_monedas);

Calculamos la transformada de la distancia.

D = bwdist(monedas_bin);

Aplicamos al resultado el algoritmo watershed.

D_watershed = watershed(D);

bordes_D = D_watershed == 0;

monedas_copia_3 = monedas;

monedas_copia_3(bordes_D) = 255;

montage(monedas_copia_3)

figure_14.png

3º forma --> uso del gradiente. La magnitud del gradiente de la imagen permite detectar TODOS los minimos de la imagen (los reales y los que producen la sobresegmentación).

w_sobel = fspecial("sobel");

gradient_mag = abs(imfilter(double(monedas),w_sobel))+abs(imfilter(double(monedas),w_sobel'));

Filtramos los minimos de forma que solo los mas profundos sobreviven. El comando "imextendedmin" toma una imagen de mínimos regionales (como la que devuelve el gradiente) y elimina todos los pixeles con un valor de intensidad mayor que el segundo input. El output del comando es una imagen binaria con los mínimos más profundos.

minimos = imextendedmin(gradient_mag, 200);

Forzamos que los mínimos del gradiente sean solo los que sobreviven al comando anterior. Para ello se usa el comando "imimposemin" que impone al primer input un conjunto de mínimos, que es el segundo input.

minimos_forzados = imimposemin(gradient_mag, minimos);

monedas_watershed_3 = watershed(minimos_forzados);

bordes_3 = monedas_watershed_3 == 0;

monedas_copia_3 = monedas;

monedas_copia_3(bordes_3) = 255;

montage({bordes_3,monedas_copia_3})

figure_15.png