Saltar a contenido

Tutorial y ejercicios

En esta práctica se parte de una idea fundamental: una imagen digital puede entenderse como una matriz de píxeles. Cada píxel tiene una posición dentro de la imagen y contiene información visual, ya sea un nivel de gris o varios valores asociados al color. Por este motivo, conceptos como la resolución espacial, la cuantificación y el modelo de color son importantes antes de aplicar cualquier técnica de procesado.

A lo largo del tutorial se verán operaciones muy básicas, pero todas tienen una interpretación conceptual clara. Algunas modifican solo el valor de los píxeles, mientras que otras cambian el tamaño, la posición o la forma aparente de la imagen. Entender esta diferencia ayuda a conectar esta primera práctica con temas posteriores como el filtrado, la segmentación y la descripción de objetos.

Manipulación básica de imágenes

Cargamos las imágenes

I = imread("Imagenes\lena_gray_512.tif");
F = imread("Imagenes\lena_color_512.tif");

Mostramos las imágenes

imshow(I)

figure_0.png

imshow(F)

figure_1.png

Calculamos la resolución espacial de cada imagen

La resolución espacial indica el número de píxeles que forman la imagen en anchura y altura. Una resolución mayor puede representar más detalle si la captura original también lo contiene, pero cambiar el tamaño de una imagen ya digitalizada no añade información real. Cuando ampliamos o reducimos, el sistema debe estimar valores nuevos a partir de los píxeles existentes, y esto puede suavizar detalles o generar artefactos.

size(I)
ans = 1x2
   512   512
size(F)
ans = 1x3
   512   512     3

Calculamos la resolución de intensidad, o cuantificación, de cada imágen

La resolución de intensidad, también llamada cuantificación, indica cuántos niveles distintos puede representar cada píxel. En una imagen de 8 bits hay 256 valores posibles, de 0 a 255. Si se reduce este número de niveles, distintas intensidades de la escena se representan con el mismo valor, y esto puede provocar pérdida de contraste fino o bandas visibles en zonas suaves.

max(max(I))
ans = uint8245
max(max(F))
ans = 1x1x3 uint8 array
ans(:,:,1) =

   255

ans(:,:,2) =

   248

ans(:,:,3) =

   225

Reescalamos espacialmente la imagen

Reescalar espacialmente significa cambiar el número de píxeles de la imagen. Si reducimos la imagen, se pierden muestras y por tanto puede perderse detalle. Si la ampliamos, hay que estimar valores intermedios mediante interpolación; por eso una ampliación no recupera detalle real que no estuviera en la imagen original.

I = imresize(I,[128,128]);
imshow(I)

figure_2.png

I = imresize(I,[64,64]);
imshow(I)

figure_3.png

I = imresize(I,[16,16]);
imshow(I)

figure_4.png

I = imread("Imagenes\lena_gray_512.tif");
I = imresize(I,[1028,1028]);
imshow(I)

figure_5.png

Reescalamos los niveles de intensidad de la imagen

Reescalar los niveles de intensidad significa cambiar el rango de valores de los píxeles. Esta operación puede hacer que una imagen se vea más clara, más oscura o con más contraste, pero no cambia la posición de los objetos. Es una transformación útil para preparar imágenes antes de aplicar umbrales o filtros.

levels_gray = [128,32,4];

I_128 = imquantize(I, linspace(0, 255, levels_gray(1)));
I_32 = imquantize(I, linspace(0, 255, levels_gray(2)));
I_4 = imquantize(I, linspace(0, 255, levels_gray(3)));

imshow(I_128,[])

figure_6.png

imshow(I_32,[])

figure_7.png

imshow(I_4,[])

figure_8.png

Calculamos el histograma

El histograma es una herramienta sencilla para estudiar la distribución de los niveles de intensidad de una imagen. Muestra cuántos píxeles hay en cada valor, pero no conserva información sobre su posición. Aun con esta limitación, es muy útil para detectar si una imagen es oscura, clara, tiene poco contraste o presenta grupos de intensidades que podrían separarse mediante un umbral.

histograma = imhist(I);

Visualizamos el histograma

bar(histograma)

figure_9.png

Calculamos cada uno de los canales de color de la imagen mediante "eliminar" los otros dos

Separar los canales de color ayuda a entender que el color de un píxel es una combinación de varias componentes. En el modelo RGB, cada canal representa la contribución de rojo, verde o azul. En cambio, en el modelo HSV se describe el color mediante tono, saturación e intensidad percibida, lo que puede ser más adecuado en algunas tareas de análisis visual.

F_rojo = F;
F_rojo(:,:,2)=0;
F_rojo(:,:,3)=0;

F_verde = F;
F_verde(:,:,1)=0;
F_verde(:,:,3)=0;

F_azul = F;
F_azul(:,:,1)=0;
F_azul(:,:,2)=0;

imshow(F_rojo)

figure_10.png

imshow(F_verde)

figure_11.png

imshow(F_azul)

figure_12.png

Convertimos la imagen de RGB a escala de grises

La conversión a escala de grises reduce una imagen de tres canales a una sola imagen de intensidad. Normalmente no se hace una media simple de los canales, sino una combinación ponderada que tiene en cuenta la sensibilidad visual al rojo, verde y azul. Se pierde información cromática, pero se simplifica el análisis cuando el color no es esencial.

F_gris = rgb2gray(F);
imshow(F_gris)

figure_13.png

Conversion la imagen de RGB a HSV

El modelo HSV separa la información de color en tres componentes más interpretables: el tono indica el tipo de color, la saturación indica la pureza del color y el valor indica el brillo. Esta separación puede facilitar tareas en las que interesa analizar el color independientemente de la iluminación.

F_hsi = rgb2hsv(F);
imshow(F_hsi)

figure_14.png

Calculamos los canales H, S y V

imshow(F_hsi(:,:,1))

figure_15.png

imshow(F_hsi(:,:,2))

figure_16.png

imshow(F_hsi(:,:,3))

figure_17.png

Ruido gaussiano

El ruido representa alteraciones no deseadas de los valores de los píxeles. Puede aparecer por limitaciones del sensor, de la iluminación, de la transmisión o del proceso de digitalización. En el caso del ruido gaussiano, muchos píxeles sufren cambios pequeños alrededor de un valor medio, de modo que la imagen conserva su estructura general pero adquiere un aspecto más granulado.

Definimos los parámetros del ruido gaussiano, es decir, la media y varianza

media = 0;
varianza = 0.01;

La función imnoise añade ruido gaussiano multiplicativo, lo que puede detectarse fácilmente por su aspecto granulado

En el ruido multiplicativo, la perturbación depende del valor del píxel. Esto significa que las zonas más intensas pueden verse afectadas de forma diferente que las zonas oscuras. Este modelo es útil para entender ruidos que no se añaden igual a toda la imagen.

I_gaussian_mult = imnoise(I,"gaussian",media,varianza);

imshow(I_gaussian_mult)

figure_18.png

Mientras que el ruido gaussiano aditivo, que se añade tal cual a la imagen, solo difumina levemente los detalles

En el ruido aditivo, a cada píxel se le suma una variación aleatoria independiente de su valor original. La imagen conserva su estructura general, pero las pequeñas diferencias locales pueden quedar contaminadas. Por eso muchos filtros de suavizado intentan reducir este tipo de variación local.

ruido_gaussiano = uint8(randn(size(I)) * sqrt(varianza) + media);

I_gaussian_add = I + ruido_gaussiano;

imshow(I_gaussian_add)

figure_19.png

Ruido sal y pimienta

El ruido de sal y pimienta es un ejemplo de ruido impulsional. A diferencia del ruido gaussiano, no afecta a todos los píxeles de forma suave, sino que sustituye algunos valores por intensidades extremas, normalmente negro o blanco. Este comportamiento explica por qué, en prácticas posteriores, los filtros no lineales como la mediana pueden ser más adecuados que un simple promedio.

Definimos los parámetros del ruido sal y pimienta, a saber, la probabilidad de que un cierto pixel pase a ser un grano de sal o de pimienta

probabilidad = 0.05;

Generar una imagen aleatoria que usamos para cambiar los pixeles de la imagen original por puntos sal o pimienta (si un pixel aleatorio esta por encima o debajo de la probabilidad definida, el pixel correspondiente en la imagen original se cambia)

ruido_sal_pimienta = rand(size(I));
I_ruido = I;

I_ruido(ruido_sal_pimienta < probabilidad/2) = 0; % Ruido "sal"
I_ruido(ruido_sal_pimienta > 1 - probabilidad/2) = 255; % Ruido "pimienta"

imshow(I_ruido)

figure_20.png

Operaciones aritméticas

Las operaciones aritméticas actúan directamente sobre los valores numéricos de los píxeles. Sumar o restar puede modificar el brillo, mientras que multiplicar o dividir puede cambiar el contraste de forma global. Hay que tener presente que las imágenes digitales tienen un rango de valores limitado; si una operación produce valores fuera de este rango, se puede perder información por saturación.

I = imread("Imagenes\lena_gray_512.tif");

F = imread("Imagenes\Contornos.tif");

G = imread("Imagenes\Office1.png");

H = imread("Imagenes\Office2.png");

G = rgb2gray(G);

H = rgb2gray(H);

montage({I,F,G,H})

figure_21.png

Suma aritmética por una constante

I_50 = I + 50;

Reescalamos para poder operar con las imágenes y usamos la función add para sumarlas

s = size(G);

H = imresize(H,[s(1),s(2)]);

GH_add = imadd(G,H);

montage({I_50,GH_add})

figure_22.png

Resta por una constante

I_75 = I - 75;

Reescalamos para poder operar con las imágenes y usamos la función substract para restarlas

F = rgb2gray(imresize(F,[512,512]));

IF_resta = imsubtract(I,F);

montage({I_75,IF_resta})

figure_23.png

Producto por una constante

I_2 = I.*2;

Reescalamos para poder operar con las imágenes y usamos la función immultiply para multiplicarlas

IG_prod = immultiply(I,imresize(G,[512,512]));

montage({I_2,IG_prod})

figure_24.png

División por una constante

I__2 = I./2;

Reescalamos para poder operar con las imágenes y usamos la función imdivide para dividirlas

I_div = imdivide(I,imresize(G,[512,512]));

montage({I__2,I_div})

figure_25.png

Operaciones lógicas

En una imagen binaria, cada píxel solo puede pertenecer al fondo o al objeto. Por eso, las operaciones lógicas se pueden interpretar como operaciones entre conjuntos de píxeles: AND corresponde a una intersección, OR a una unión y NOT al complemento. Esta forma de pensar será especialmente importante cuando se introduzca la morfología matemática.

Binarizamos las imágenes por umbralización

G_bin = G>128;

H_bin = H>128;

Calculamos el AND lógico (equivalente a la intersección si los objetos estan definidos por 1)

A = and(G_bin,H_bin);

imshow(A

figure_26.png

Calculamos el OR lógico (equivalente a la unión si los objetos estan definidos por 1)

O = or(G_bin,H_bin);

imshow(O)

figure_27.png

Calculamos el XOR lógico

X = xor(G_bin,H_bin);

imshow(X)

figure_28.png

Calculamos el NOT lógico

N = not(G_bin);

imshow(N)

figure_29.png

Operaciones con la intensidad de los pixeles

Las transformaciones de intensidad son operaciones puntuales, porque cada píxel se modifica según su propio valor y no según el vecindario. Este tipo de operación no mueve los objetos ni cambia la geometría de la imagen, sino que redistribuye los niveles de gris. Esto puede servir para aclarar zonas oscuras, comprimir zonas muy brillantes o ajustar el contraste global.

Calculamos la imagen negativa

La imagen negativa invierte los niveles de intensidad: los valores oscuros pasan a ser claros y los claros pasan a ser oscuros. Esta transformación es simple, pero ayuda a ver que muchas operaciones puntuales solo redistribuyen valores sin cambiar la geometría de la escena.

I_neg = 255-I;

imshow(I_neg)

figure_30.png

Calculamos la transf. logarítmica (con constante c = L-1/log(L) y L niveles de gris de la imagen). La transf. logarítmica aclara zonas oscuras respetando las zonas que ya eran claras.

imagen = imread("Imagenes\Espalda.jpg");
imagen = rgb2gray(imagen);
imshow(imagen)

figure_31.png

imagen_log = (255/(log(256)))*log(double(imagen) + 1);

imshow(uint8(imagen_log))

figure_32.png

Calculamos la transf. exponencial (con constante c = L-1/log(L) y L niveles de gris de la imagen). La transf. exponencial oscurece zonas claras respetando las zonas que ya eran oscuras. Es la inversa de la transf. logarítmica.

imagen_1 = imread("Imagenes\cells_internet.jpg");

imshow(imagen_1)

figure_33.png

imagen_exp = exp((log(256)/255)*double(imagen_1))-1;

imshow(uint8(imagen_exp))

figure_34.png

Transf. potencia-raiz o corrección gamma: Aglutina y generaliza las transf. logarítmica y exponencial

La corrección gamma permite controlar de forma flexible la relación entre intensidades de entrada y salida. Si gamma es menor que 1, se aclaran sobre todo las zonas oscuras; si es mayor que 1, se oscurecen sobre todo las zonas claras. Es muy utilizada porque se adapta mejor a la percepción visual y a los dispositivos de visualización.

Primer paso: Transformo los valores de intensidad del intervalo [0,255] a [0,1]

imagen_1_gamma = (1./255.)*double(imagen_1);

Segundo paso: Defino el valor de gamma y transformo

Tercer paso: Transformo los valores de intensidad de vuelta al intervalo [0,255]

gamma = 20;

imagen_1_gamma = uint8(imagen_1_gamma.^gamma * 255);

imshow(imagen_1_gamma)

figure_35.png

Expansión y ecualización del histograma

La expansión y la ecualización del histograma intentan utilizar mejor el rango disponible de intensidades. La expansión reparte los valores entre un mínimo y un máximo, mientras que la ecualización busca una distribución más uniforme. Estas técnicas pueden mejorar el contraste, pero también pueden exagerar el ruido o dar resultados poco naturales cuando la imagen tiene iluminación desigual.

Calculamos el histograma

h = imhist(I);
bar(h)

figure_36.png

Calculamos los valores de intensidad máximo y mínimo para expandir el histograma

maxi = double(max(max(I)));
mini = double(min(min(I)));
I_exp = 255.*(double(I)-mini)./(maxi-mini);

imshow(uint8(I_exp))

figure_37.png

h_exp = imhist(uint8(I_exp));
bar(h_exp)

figure_38.png

Ecualizamos el histograma, lo cual distribuye de forma uniforme los valores de intensidad en todo el intervalo

I_eq = histeq(I,256);

imshow(I_eq)

figure_39.png

h_eq = imhist(I_eq);
bar(h_eq)

figure_40.png

Operaciones geométricas

Las operaciones geométricas cambian la posición de los píxeles o el tamaño aparente de los objetos. En rotaciones, traslaciones o cambios de escala, las nuevas posiciones no siempre coinciden exactamente con la rejilla discreta de píxeles. Por eso hay que interpolar valores, y el método de interpolación puede influir en la nitidez, los bordes y la calidad visual del resultado.

Rotación de ángulo theta

I_rot = imrotate(I,45);

imshow(I_rot)

figure_41.png

Traslación en la dirección (x,y)

I_tras = imtranslate(I,[50,150]);

imshow(I_tras)

figure_42.png

Translación con imagen completa

I_tras = imtranslate(I,[50,150],'OutputView','full');

imshow(I_tras)

figure_43.png

Simetrías vertical y horizontal

I_simv = flip(I,1);
I_simh = flip(I,2);

montage({I_simv,I_simh})

figure_44.png

Dilatación y reducción de factores d y r.

dilatacion_factor = 2;
reduccion_factor = 0.5;

I_dil = imresize(I,dilatacion_factor);
I_red = imresize(I,reduccion_factor);

montage({I_dil,I_red})

figure_45.png

Para algunas de las transformaciones geométricas, hay que escoger el método de interpolación (e.g., rotaciones y traslaciones)

La interpolación define cómo se calcula el valor de un píxel cuando la transformación geométrica sitúa una coordenada entre píxeles existentes. La interpolación nearest es rápida pero puede producir bordes dentados; la bilinear da resultados más suaves; y la bicúbica suele conservar mejor la continuidad, aunque es más costosa.

I_rot_n = imrotate(I,45,'nearest');
I_rot_b = imrotate(I,45,'bilinear');
I_rot_bb = imrotate(I,45,'bicubic');

imshow(I_rot_n)

figure_46.png

imshow(I_rot_b)

figure_47.png

imshow(I_rot_bb)

figure_48.png