Preprocesamiento de Imágenes con OpenCV
Pipeline completo de procesamiento de imágenes: espacios de color, mejora de contraste, detección de características y extracción de descriptores usando OpenCV y scikit-image.
Preprocesamiento de Imágenes con OpenCV
Objetivos de Aprendizaje
- Comprender la representación matricial de imágenes digitales
- Dominar los espacios de color (RGB, HSV, CIE Lab) y sus aplicaciones
- Implementar técnicas de mejora de contraste (ecualización global vs CLAHE adaptativo)
- Aplicar filtros de suavizado para reducción de ruido
- Detectar y describir características (corners, keypoints) con Harris, Shi-Tomasi, ORB/SIFT
- Evaluar métricas de calidad usando histogramas y repetibilidad de features
Contexto de Negocio
El preprocesamiento de imágenes es fundamental en aplicaciones de visión por computadora, desde sistemas de reconocimiento facial hasta vehículos autónomos y análisis médico.
Como ingeniero de datos trabajando con imágenes, necesitas:
- Normalizar y limpiar datos visuales antes del análisis
- Extraer características robustas para tareas de clasificación y detección
- Mejorar la calidad visual para facilitar el procesamiento posterior
- Optimizar el rendimiento de modelos de machine learning
Dataset de Ejemplo
- Fuente: Imágenes clásicas de scikit-image
- Registros: ~7 imágenes de prueba
- Formatos: PNG, color (RGB) y escala de grises
- Aplicación: Demostración de técnicas de preprocesamiento
Proceso de Análisis
1. Preparación del Entorno
import cv2
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from skimage import exposure, filters, feature, color, img_as_float
import warnings
warnings.filterwarnings('ignore')
# Configurar visualización
plt.style.use('seaborn-v0_8-darkgrid')
pd.set_option('display.max_columns', None)
print("✅ Librerías listas")
print(f"OpenCV: {cv2.__version__}")Librerías clave:
cv2(OpenCV): Procesamiento de imágenes de alto rendimientoscikit-image: Algoritmos de procesamiento de imágenes en Pythonnumpy: Manipulación de matricesmatplotlib: Visualización
2. Carga de Datos
from skimage import data as skdata, io as skio
import pathlib
# Crear directorio de trabajo
RAW_DIR = pathlib.Path("data/raw")
RAW_DIR.mkdir(parents=True, exist_ok=True)
# Descargar imágenes de ejemplo
samples_sk = {
"camera.png": skdata.camera(),
"astronaut.png": skdata.astronaut(),
"coffee.png": skdata.coffee(),
"coins.png": skdata.coins(),
"checkerboard.png": skdata.checkerboard(),
"rocket.png": skdata.rocket(),
"page.png": skdata.page()
}
for name, img in samples_sk.items():
out = RAW_DIR / name
skio.imsave(out.as_posix(), img)3. Representación Matricial
Una imagen digital es una matriz de píxeles donde cada valor representa intensidad o color.
def read_image_bgr(path):
img_bgr = cv2.imread(str(path), cv2.IMREAD_COLOR)
assert img_bgr is not None, f"No se pudo leer: {path}"
return img_bgr
# Cargar imagen
img_path = images[0]
img_bgr = read_image_bgr(img_path)
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
img_gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)
# Inspeccionar dimensiones
height, width = img_gray.shape[:2]
channels = img_rgb.shape[2] if img_rgb.ndim == 3 else 1
dtype = img_bgr.dtype
min_val, max_val = int(img_gray.min()), int(img_gray.max())
print(f"H, W, C: {height} {width} {channels}")
print(f"dtype: {dtype} rango: ({min_val}, {max_val})")Resultados:
- Formato típico:
uint8(0-255) - Dimensión:
[altura, ancho, canales] - OpenCV usa BGR por defecto, no RGB
4. Espacios de Color
4.1 ¿Por qué cambiar de espacio de color?
Diferentes espacios de color facilitan distintas tareas:
- RGB: Visualización natural, canales correlacionados
- HSV: Separación de color (Hue) y luminosidad (Value), útil para segmentación
- CIE Lab: Percepción uniforme, bueno para comparación de colores
# Convertir RGB a diferentes espacios
img_hsv = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2HSV)
img_lab = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2LAB)
# Visualizar canales HSV separados
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
axes[0].imshow(img_hsv[:,:,0], cmap='hsv')
axes[0].set_title('Hue (Tono)')
axes[1].imshow(img_hsv[:,:,1], cmap='gray')
axes[1].set_title('Saturation (Saturación)')
axes[2].imshow(img_hsv[:,:,2], cmap='gray')
axes[2].set_title('Value (Brillo)')
plt.tight_layout()
5. Análisis de Histogramas
Los histogramas muestran la distribución de intensidades en la imagen.
def plot_histogram(img, title="Histograma"):
plt.figure(figsize=(10, 4))
if len(img.shape) == 2: # Escala de grises
plt.hist(img.ravel(), bins=256, range=(0, 256), color='gray', alpha=0.7)
else: # RGB
colors = ('r', 'g', 'b')
for i, color in enumerate(colors):
plt.hist(img[:,:,i].ravel(), bins=256, range=(0, 256),
color=color, alpha=0.5, label=color.upper())
plt.legend()
plt.xlabel('Intensidad de píxel')
plt.ylabel('Frecuencia')
plt.title(title)
plt.show()
plot_histogram(img_gray, "Histograma - Escala de Grises")
6. Mejora de Contraste
6.1 Ecualización de Histograma (Global)
Redistribuye las intensidades para usar todo el rango dinámico.
# Ecualización global
img_eq = cv2.equalizeHist(img_gray)
# Comparar
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
axes[0].imshow(img_gray, cmap='gray')
axes[0].set_title('Original')
axes[1].imshow(img_eq, cmap='gray')
axes[1].set_title('Ecualizada')
plt.tight_layout()Ventajas: Mejora el contraste global Desventajas: Puede sobre-amplificar ruido, no considera contexto local
6.2 CLAHE (Adaptive Histogram Equalization)
Mejora el contraste localmente usando ventanas (tiles).
# CLAHE
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
img_clahe = clahe.apply(img_gray)
# Comparar
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
axes[0].imshow(img_gray, cmap='gray')
axes[0].set_title('Original')
axes[1].imshow(img_eq, cmap='gray')
axes[1].set_title('Ecualización Global')
axes[2].imshow(img_clahe, cmap='gray')
axes[2].set_title('CLAHE (Adaptativa)')
plt.tight_layout()
Decisión: CLAHE generalmente produce mejores resultados porque preserva detalles locales sin sobre-amplificar ruido.
7. Filtros de Suavizado
Reducen ruido y preparan la imagen para detección de características.
# Gaussian Blur
img_gaussian = cv2.GaussianBlur(img_gray, (5, 5), 0)
# Median Filter (mejor para ruido sal y pimienta)
img_median = cv2.medianBlur(img_gray, 5)
# Bilateral Filter (preserva bordes)
img_bilateral = cv2.bilateralFilter(img_gray, 9, 75, 75)
# Visualizar
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
axes[0,0].imshow(img_gray, cmap='gray')
axes[0,0].set_title('Original')
axes[0,1].imshow(img_gaussian, cmap='gray')
axes[0,1].set_title('Gaussian Blur')
axes[1,0].imshow(img_median, cmap='gray')
axes[1,0].set_title('Median Filter')
axes[1,1].imshow(img_bilateral, cmap='gray')
axes[1,1].set_title('Bilateral Filter')
plt.tight_layout()
Recomendación:
- Gaussian: Suavizado general
- Median: Ruido impulsivo
- Bilateral: Cuando se necesita preservar bordes
8. Detección de Características
8.1 Detector de Esquinas Harris
Detecta puntos donde hay cambios de intensidad en múltiples direcciones.
# Harris Corner Detection
dst = cv2.cornerHarris(img_gray, blockSize=2, ksize=3, k=0.04)
dst = cv2.dilate(dst, None) # Dilatar para marcar esquinas
# Visualizar
img_harris = img_rgb.copy()
img_harris[dst > 0.01 * dst.max()] = [255, 0, 0] # Marcar en rojo
plt.figure(figsize=(10, 8))
plt.imshow(img_harris)
plt.title('Detector Harris - Esquinas en Rojo')
plt.axis('off')8.2 Shi-Tomasi (Good Features to Track)
Mejora del detector Harris, selecciona las N mejores esquinas.
# Shi-Tomasi
corners = cv2.goodFeaturesToTrack(
img_gray,
maxCorners=100,
qualityLevel=0.01,
minDistance=10
)
# Visualizar
img_corners = img_rgb.copy()
for corner in corners:
x, y = corner.ravel()
cv2.circle(img_corners, (int(x), int(y)), 5, (0, 255, 0), -1)
plt.figure(figsize=(10, 8))
plt.imshow(img_corners)
plt.title(f'Shi-Tomasi - {len(corners)} esquinas detectadas')
plt.axis('off')
9. Descriptores de Características
9.1 ORB (Oriented FAST and Rotated BRIEF)
Detector y descriptor rápido, libre de patentes, robusto a rotación.
# ORB
orb = cv2.ORB_create(nfeatures=500)
keypoints, descriptors = orb.detectAndCompute(img_gray, None)
# Visualizar
img_orb = cv2.drawKeypoints(
img_rgb, keypoints, None,
color=(0, 255, 0),
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS
)
plt.figure(figsize=(12, 10))
plt.imshow(img_orb)
plt.title(f'ORB - {len(keypoints)} keypoints')
plt.axis('off')
print(f"Descriptores shape: {descriptors.shape}")
print(f"Cada descriptor: 256 bits binarios")Propiedades ORB:
- Rápido: Tiempo real
- Invariante: Rotación y escala
- Binario: Descriptores compactos
9.2 SIFT (Scale-Invariant Feature Transform)
Más robusto, mejor para matching preciso (requiere opencv-contrib).
# SIFT (si está disponible)
try:
sift = cv2.SIFT_create()
keypoints_sift, descriptors_sift = sift.detectAndCompute(img_gray, None)
img_sift = cv2.drawKeypoints(
img_rgb, keypoints_sift, None,
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS
)
plt.figure(figsize=(12, 10))
plt.imshow(img_sift)
plt.title(f'SIFT - {len(keypoints_sift)} keypoints')
plt.axis('off')
except:
print("SIFT no disponible (requiere opencv-contrib-python)")10. Matching de Características
Comparar características entre dos imágenes.
# Cargar segunda imagen (rotada/escalada)
img2_gray = cv2.cvtColor(cv2.imread('data/raw/camera_rotated.png'),
cv2.COLOR_BGR2GRAY)
# Detectar features en ambas
kp1, des1 = orb.detectAndCompute(img_gray, None)
kp2, des2 = orb.detectAndCompute(img2_gray, None)
# Matcher
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.match(des1, des2)
matches = sorted(matches, key=lambda x: x.distance)
# Visualizar top 50 matches
img_matches = cv2.drawMatches(
img_gray, kp1, img2_gray, kp2,
matches[:50], None,
flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS
)
plt.figure(figsize=(16, 8))
plt.imshow(img_matches)
plt.title(f'Top 50 Matches - {len(matches)} total')
plt.axis('off')Análisis Crítico y Decisiones
Elección de Espacio de Color
¿Cuándo usar cada espacio?
- RGB: Procesamiento general, neural networks
- HSV: Segmentación por color (ej: detectar objetos rojos)
- Lab: Comparación perceptual de colores, corrección cromática
Decisión: HSV para segmentación y Lab para análisis de similitud.
Ecualización Global vs CLAHE
CLAHE es superior porque:
- Adapta el contraste localmente
- Reduce sobre-amplificación de ruido
- Preserva detalles en regiones oscuras y claras simultáneamente
Único caso para global: Imágenes con contraste uniformemente bajo.
Selección de Detector de Features
| Detector | Velocidad | Robustez | Uso |
|---|---|---|---|
| Harris | Rápido | Básica | Tracking simple |
| Shi-Tomasi | Rápido | Media | Optical flow |
| ORB | Muy rápido | Buena | Tiempo real, móviles |
| SIFT | Lento | Excelente | Matching preciso |
Decisión:
- ORB para aplicaciones en tiempo real
- SIFT cuando se necesita máxima robustez
Conclusión
El preprocesamiento de imágenes con OpenCV permite extraer información valiosa de datos visuales mediante técnicas de detección de bordes, segmentación y reconocimiento de patrones. Las transformaciones de espacios de color (RGB, HSV, Lab) facilitan tareas específicas como segmentación por color y análisis perceptual. La mejora de contraste mediante CLAHE adaptativo preserva detalles locales mejor que la ecualización global. Los detectores de características (Harris, Shi-Tomasi, ORB, SIFT) y sus descriptores son fundamentales para aplicaciones de matching, tracking y reconocimiento de objetos en visión por computadora.
Próximos pasos: Implementar detección de objetos con YOLO o Faster R-CNN para aplicaciones en tiempo real. Explorar técnicas de deep learning con CNNs (Convolutional Neural Networks) para clasificación y segmentación semántica de imágenes. Aplicar técnicas de aumento de datos (data augmentation) para mejorar robustez de modelos de visión. Investigar métodos de super-resolución para mejorar calidad de imágenes de baja resolución. Desarrollar pipelines de procesamiento de video para análisis de secuencias temporales y detección de movimiento.
Análisis Geoespacial con GeoPandas
Pipeline completo de análisis geoespacial: carga de datos, gestión de CRS, joins espaciales, agregaciones zonales y visualización de datos sobre CABA.
Audio como Dato: Procesamiento y Análisis
Pipeline completo de procesamiento de audio digital: carga de señales, análisis temporal y frecuencial, extracción de características (MFCCs, espectrogramas) y aplicaciones de machine learning con librosa.