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)

imshow(F)

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)

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

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

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

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,[])

imshow(I_32,[])

imshow(I_4,[])

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)

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)

imshow(F_verde)

imshow(F_azul)

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)

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)

Calculamos los canales H, S y V
imshow(F_hsi(:,:,1))

imshow(F_hsi(:,:,2))

imshow(F_hsi(:,:,3))

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)

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)

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)

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})

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})

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})

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})

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})

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

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)

Calculamos el XOR lógico
X = xor(G_bin,H_bin);
imshow(X)

Calculamos el NOT lógico
N = not(G_bin);
imshow(N)

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)

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)

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

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)

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

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)

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)

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))

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

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)

h_eq = imhist(I_eq);
bar(h_eq)

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)

Traslación en la dirección (x,y)
I_tras = imtranslate(I,[50,150]);
imshow(I_tras)

Translación con imagen completa
I_tras = imtranslate(I,[50,150],'OutputView','full');
imshow(I_tras)

Simetrías vertical y horizontal
I_simv = flip(I,1);
I_simh = flip(I,2);
montage({I_simv,I_simh})

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})

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)

imshow(I_rot_b)

imshow(I_rot_bb)
