El procesamiento de imágenes es una rama fundamental de la visión artificial que permite transformar, analizar y manipular imágenes digitales para extraer información valiosa o mejorar su calidad. Comprender los fundamentos del procesamiento de imágenes es crucial para estudiantes y profesionales en campos como la inteligencia artificial, la robótica y la visión por computador.
Este artículo explora las principales transformaciones geométricas, operaciones aritméticas, análisis de histogramas, técnicas de segmentación y filtros morfológicos, todo con ejemplos prácticos utilizando la librería OpenCV en Python. Prepararemos tu base para el estudio avanzado del procesamiento de imágenes.
Fundamentos del Procesamiento de Imágenes: Transformaciones Geométricas
Las transformaciones geométricas son operaciones esenciales que cambian la posición de los píxeles en una imagen, alterando su forma, tamaño u orientación. La transformación afín es una de las más importantes, combinando traslación, rotación, escalado y cambios de perspectiva. Se representa mediante una matriz de transformación homogénea.
Traslación de una Imagen: Desplazamiento
La traslación es el desplazamiento de una imagen a lo largo de un vector dado, moviendo cada píxel una distancia finita en una dirección específica. Es como deslizar un objeto sin cambiar su orientación.
Un punto $^{A}P = [p_x, p_y, p_z]^T$ se traslada mediante un vector de traslación, resultando en un nuevo punto $[x', y', 1]^T = [x+t_x, y+t_y, 1]^T$. La matriz de traslación en coordenadas homogéneas es:
$$\left[ \begin{array}{l} x ^ {\prime} \ y ^ {\prime} \ 1 \end{array} \right] = \left[ \begin{array}{c c c} 1 & 0 & t_x \ 0 & 1 & t_y \ 0 & 0 & 1 \end{array} \right] \quad \cdot \quad \left[ \begin{array}{l} x \ y \ 1 \end{array} \right]$$
En OpenCV, la traslación manual implica recorrer cada píxel, aplicar la matriz de traslación o simplemente sumar los valores $t_x$ y $t_y$ a las coordenadas, y reasignar los píxeles a una nueva matriz de imagen.
import numpy as np
import cv2
imagen = cv2.imread("1.jpg")
imagen2 = cv2.resize(imagen, (100, 50))
alto, ancho, canales = imagen2.shape
tx = 10 # Traslación en x de 10 px
ty = 2 # Traslación en y de 2 px
# Creación de la matriz de traslación homogénea
mT = np.array([[1, 0, tx],
[0, 1, ty],
[0, 0, 1]], dtype=np.uint8)
imgT = np.zeros((alto + ty, ancho + tx, canales), dtype=np.uint8)
for i in range(alto):
for j in range(ancho):
x = j + tx
y = i + ty
imgT[y, x] = imagen2[i, j]
cv2.imshow("Original", imagen2)
cv2.imshow("Trasladada", imgT)
cv2.waitKey(0)
cv2.destroyAllWindows()
OpenCV también ofrece la función cv2.warpAffine para una traslación más eficiente, utilizando una matriz de transformación de 2x3.
import cv2
import numpy as np
imagen = cv2.imread("1.jpeg")
imagen = cv2.resize(imagen, (100, 50))
alto, ancho, canales = imagen.shape
mT = np.float32([[1, 0, 20], [0, 1, 10]]) # Matriz de 2x3: [1,0,tx], [0,1,ty]
imgT = cv2.warpAffine(imagen, mT, (ancho, alto))
cv2.imshow("Original", imagen)
cv2.imshow("Trasladada con warpAffine", imgT)
cv2.waitKey(0)
cv2.destroyAllWindows()
Rotación de Imágenes: Girando Píxeles
La rotación es una operación que gira una imagen alrededor de un punto central (generalmente el centro de la imagen) en un ángulo específico. Se puede interpretar como un operador rotacional que actúa sobre un vector de posición.
La matriz de rotación alrededor del eje Z para un ángulo $\theta$ es:
$$R_k(\theta) = \begin{bmatrix} \cos \theta & -\sin \theta & 0 \ \sin \theta & \cos \theta & 0 \ 0 & 0 & 1 \end{bmatrix}$$
OpenCV simplifica la rotación con cv2.getRotationMatrix2D para construir la matriz de rotación y cv2.warpAffine para aplicarla.
import cv2
import numpy as np
imagen = cv2.imread("2.png")
imagen2 = cv2.resize(imagen, (200, 200))
alto, ancho, canales = imagen2.shape
angle = 80
center = (ancho // 2, alto // 2)
scale = 1
M = cv2.getRotationMatrix2D(center, angle, scale) # Obtiene la matriz de rotación 2x3
imgT = cv2.warpAffine(imagen2, M, (ancho, alto)) # Aplica la transformación afín
cv2.imshow("Original", imagen2)
cv2.imshow("Rotada con warpAffine", imgT)
cv2.waitKey(0)
cv2.destroyAllWindows()
La librería imutils también ofrece una función más directa para rotar imágenes sin recortar el contenido, como imutils.rotate_bound.
Escalado Redimensionado: Cambiando el Tamaño de la Imagen
El escalado modifica el tamaño de una imagen, ya sea aumentándolo (zoom in) o disminuyéndolo (zoom out). Implica una matriz de transformación con modificaciones en la diagonal principal.
La matriz de escalado en coordenadas homogéneas es:
$$S = \begin{bmatrix} c_x & 0 & 0 \ 0 & c_y & 0 \ 0 & 0 & 1 \end{bmatrix}$$
Donde $c_x$ y $c_y$ son los factores de escalado en x e y. OpenCV ofrece la función cv2.resize para escalar imágenes de manera sencilla, permitiendo especificar el tamaño final o factores de escala, y diferentes métodos de interpolación.
import cv2
imagen = cv2.imread("1.jpeg")
escalada = cv2.resize(imagen, (1024, 720), interpolation = cv2.INTER_LINEAR) # Escalado a un tamaño fijo
cv2.imshow("Original", imagen)
cv2.imshow("Escalada", escalada)
cv2.waitKey(0)
cv2.destroyAllWindows()
Transformaciones de Perspectiva: Sesgando la Visión
Las transformaciones de perspectiva, también conocidas como sesgado (skewing), modifican una imagen como si se viera desde un ángulo diferente. Implican modificaciones en los elementos no diagonales de la matriz de transformación afín. Se puede aplicar en el eje vertical (y) o horizontal (x).
Matriz de perspectiva vertical:
$$\left[ \begin{array}{c c c} 1 & s_v & 0 \ 0 & 1 & 0 \ 0 & 0 & 1 \end{array} \right]$$
Matriz de perspectiva horizontal:
$$\left[ \begin{array}{c c c} 1 & 0 & 0 \ s_h & 1 & 0 \ 0 & 0 & 1 \end{array} \right]$$
import cv2
import numpy as np
tMatrix = np.array([[1, 0, 0], [1, 1, 0], [0, 1, 1]], dtype=np.uint8) # Matriz para perspectiva horizontal
mResultante = np.zeros((500, 500, 3), dtype=np.uint8)
imagen = cv2.imread("1.png")
h, w, c = imagen.shape
for i in range(h):
for j in range(w):
pixel = imagen[i, j]
vector = np.array([[j], [i], [1]], dtype=np.uint8) # Vector posición
result = np.dot(tMatrix, vector)
x = result[0][0]
y = result[1][0]
if 0 <= x < 500 and 0 <= y < 500: # Asegurar que las coordenadas estén dentro de los límites
mResultante[y, x] = pixel
cv2.imshow("Original", imagen)
cv2.imshow("Perspectiva Horizontal", mResultante)
cv2.waitKey(0)
cv2.destroyAllWindows()
Operaciones Aritméticas con Imágenes en Procesamiento de Imágenes
Las operaciones aritméticas permiten combinar o manipular dos o más imágenes píxel a píxel, siempre y cuando tengan las mismas dimensiones y número de canales. Son fundamentales para tareas como la detección de movimiento o la mejora de imágenes.
Adición de Imágenes
La adición suma los valores de los píxeles correspondientes en dos imágenes. cv2.add(img1, img2) realiza esta suma. Si la suma excede el valor máximo (255 para 8-bit), se satura. cv2.addWeighted(img1, peso1, img2, peso2, valor_gama) permite controlar la contribución de cada imagen con pesos y añadir un valor gama.
Sustracción de Imágenes
La sustracción calcula la diferencia entre los valores de los píxeles. cv2.subtract(img1, img2) resta los valores. Es útil para detectar cambios entre imágenes, como en la detección de movimiento. cv2.absdiff(img1, img2) calcula la diferencia absoluta, asegurando que el resultado siempre sea positivo, lo cual es ideal para resaltar las áreas donde hubo cambios.
import cv2
img1 = cv2.imread("casaVacia.jpg")
img1 = cv2.resize(img1, (500, 500))
img2 = cv2.imread("casaDibujo.jpg") # Imagen con cambios (ej. ventanas y puertas dibujadas)
img2 = cv2.resize(img2, (500, 500))
resultadoResta = cv2.absdiff(img2, img1) # Resalta las diferencias entre las dos imágenes
cv2.imshow("Resultado Detección de Movimiento", resultadoResta)
cv2.waitKey(0)
cv2.destroyAllWindows()
Multiplicación y División de Imágenes
Similarmente, cv2.multiply(img1, img2) y cv2.divide(img1, img2) realizan la multiplicación y división de píxeles, respectivamente. Estas operaciones pueden utilizarse para ajustar el brillo, el contraste o para normalizar los valores de los píxeles.
Histograma: Análisis de Intensidad en Fundamentos del Procesamiento de Imágenes
El histograma de una imagen es una representación gráfica que muestra la distribución de la intensidad de los píxeles. El eje X representa el nivel de intensidad (0-255 para imágenes de 8 bits), y el eje Y representa el número de píxeles con ese nivel de intensidad.
Es una herramienta fundamental para entender la composición de brillo y contraste de una imagen. Una imagen con más píxeles a la derecha en el histograma es más brillante, mientras que una con más píxeles a la izquierda es más oscura.
Cálculo del Histograma con OpenCV y Matplotlib
La función cv2.calcHist(imágenes, canales, máscara, histSize, rango) permite calcular el histograma. Los parámetros clave son:
- imágenes: Una lista de imágenes de entrada.
- canales: El índice del canal para el que se calcula el histograma (ej.,
[0]para escala de grises,[0],[1],[2]para azul, verde, rojo respectivamente en color). - máscara: Una máscara para calcular el histograma de una región específica;
Nonepara la imagen completa. - histSize: Número de divisiones del histograma;
[256]para cubrir todo el rango de 0 a 255. - rango: El rango de valores de intensidad a medir;
[0, 256]para el rango completo.
import cv2
from matplotlib import pyplot as plt
img_gris = cv2.imread("Lena.png", 0) # Imagen en escala de grises
hist_gris = cv2.calcHist([img_gris], [0], None, [256], [0, 256])
plt.plot(hist_gris)
plt.title("Histograma en Escala de Grises")
plt.xlabel("Valor de Intensidad")
plt.ylabel("Frecuencia")
plt.show()
img_color = cv2.imread("Lena.png") # Imagen a color
color = ('b', 'g', 'r')
for i, col in enumerate(color):
hist = cv2.calcHist([img_color], [i], None, [256], [0, 256])
plt.plot(hist, color = col)
plt.xlim([0, 256])
plt.title("Histograma de Canales de Color")
plt.xlabel("Valor de Intensidad")
plt.ylabel("Frecuencia")
plt.show()
Se puede utilizar una máscara para calcular el histograma de una región de interés específica dentro de la imagen, lo que es útil para análisis localizados.
Ecualización de Histogramas: Mejorando el Contraste
La ecualización de histogramas es una técnica de procesamiento de imágenes utilizada para mejorar el contraste global, especialmente en imágenes con bajo contraste o un rango limitado de intensidades. Redistribuye los valores de intensidad para que llenen todo el rango disponible.
cv2.equalizeHist(img) realiza esta operación. El resultado suele ser una imagen con un aspecto más equilibrado en cuanto a su distribución de brillo y oscuridad.
import cv2
import numpy as np
img = cv2.imread("a.jpg", 0) # Imagen en escala de grises
equ = cv2.equalizeHist(img)
res = np.hstack((img, equ)) # Concatena la imagen original y la ecualizada
cv2.imshow("Imagen Original vs Ecualizada", res)
cv2.waitKey(0)
cv2.destroyAllWindows()
Segmentación de Imágenes: Aislamiento de Objetos
La segmentación es el proceso de dividir una imagen digital en múltiples segmentos (conjuntos de píxeles), o