OpenCV

Tutorial OpenCV: Preprocesamiento para machine learning

OpenCV: Aprende a optimizar imágenes mediante normalización y escalado para mejorar la eficiencia de modelos de IA en aprendizaje automático. Eleva la precisión de tus entrenamientos.

Aprende OpenCV GRATIS y certifícate

Normalización y escalado de imágenes

En el preprocesamiento de imágenes para modelos de aprendizaje automático, la normalización y el escalado son pasos esenciales que mejoran la eficiencia y precisión del entrenamiento. Estos procesos ajustan los valores de los píxeles para que los algoritmos puedan interpretar y aprender de los datos de manera más eficaz.

La normalización consiste en ajustar los valores de los píxeles a un rango común, generalmente entre 0 y 1. Esto es importante porque muchos algoritmos de aprendizaje automático funcionan mejor cuando las características de entrada tienen escalas similares. En OpenCV, las imágenes se cargan con valores de píxeles en el rango de 0 a 255. Para normalizarlas, podemos convertir los valores a tipo flotante y dividir por 255:

import cv2

# Cargar la imagen en escala de grises
imagen = cv2.imread('ruta/a/la/imagen.jpg', cv2.IMREAD_GRAYSCALE)

# Convertir a tipo float32 y normalizar entre 0 y 1
imagen_normalizada = imagen.astype('float32') / 255.0

Este proceso asegura que todos los valores de los píxeles estén dentro del rango [0, 1], lo que facilita el procesamiento por parte de los modelos de aprendizaje.

El escalado de imágenes se refiere al cambio de tamaño de las mismas para que todas tengan dimensiones uniformes, lo cual es vital para procesar las imágenes en lotes y para cumplir con los requisitos de entrada de ciertos modelos. Podemos utilizar la función cv2.resize para redimensionar las imágenes:

# Definir las nuevas dimensiones
nuevo_ancho = 224
nuevo_alto = 224
dimensiones = (nuevo_ancho, nuevo_alto)

# Redimensionar la imagen
imagen_escalada = cv2.resize(imagen_normalizada, dimensiones, interpolation=cv2.INTER_AREA)

La elección del método de interpolación es importante. cv2.INTER_AREA es adecuado para reducir el tamaño de la imagen, ya que preserva la calidad al minimizar los artefactos.

Además de la normalización básica, a menudo se aplica la estandarización, que ajusta los píxeles para que tengan una media cero y una desviación estándar de uno. Esto es útil para algoritmos sensibles a la escala de las características:

# Calcular la media y desviación estándar de la imagen
media = np.mean(imagen_escalada)
desviacion_std = np.std(imagen_escalada)

# Estandarizar la imagen
imagen_estandarizada = (imagen_escalada - media) / desviacion_std

La estandarización ayuda a que el modelo aprenda más eficientemente al eliminar sesgos debidos a variaciones en iluminación o contraste entre diferentes imágenes.

Para imágenes en color, es importante normalizar y escalar cada canal (B, G, R) por separado:

# Cargar la imagen en color
imagen_color = cv2.imread('ruta/a/la/imagen_color.jpg')

# Convertir a tipo float32
imagen_color = imagen_color.astype('float32')

# Dividir la imagen en canales B, G, R
B, G, R = cv2.split(imagen_color)

# Normalizar cada canal entre 0 y 1
B /= 255.0
G /= 255.0
R /= 255.0

# Reunir los canales normalizados
imagen_color_normalizada = cv2.merge([B, G, R])

La consistencia en el preprocesamiento es clave para obtener buenos resultados en modelos de aprendizaje profundo.

Es posible que algunos modelos requieran que las imágenes estén en un formato específico, como tener los canales en un orden distinto (RGB en lugar de BGR) o ajustar la forma del tensor. Podemos realizar estas conversiones utilizando NumPy y OpenCV:

# Convertir de BGR a RGB
imagen_rgb = cv2.cvtColor(imagen_color_normalizada, cv2.COLOR_BGR2RGB)

# Reordenar los ejes si es necesario (por ejemplo, para TensorFlow)
imagen_final = np.transpose(imagen_rgb, (2, 0, 1))

# Añadir una dimensión extra para el lote
imagen_final = np.expand_dims(imagen_final, axis=0)

Estos ajustes aseguran que las imágenes estén en el formato adecuado para ser procesadas por los modelos de aprendizaje automático, evitando errores y mejorando la eficiencia del entrenamiento.

Extracción de características

En el ámbito del aprendizaje automático, la extracción de características es un paso esencial que transforma imágenes en representaciones numéricas que los modelos pueden procesar. OpenCV proporciona diversas herramientas para extraer características significativas que capturan información crucial de las imágenes.

Una técnica común es la detección de puntos clave y el cálculo de descriptores. Los puntos clave representan ubicaciones distintivas en la imagen, invariantes a cambios como rotación y escala. Los descriptores son vectores que describen el entorno local de cada punto clave, facilitando la comparación entre imágenes.

OpenCV incluye algoritmos como SIFT (Scale-Invariant Feature Transform) y SURF (Speeded-Up Robust Features) para este propósito.

A partir de la versión 4.x, SIFT y SURF están disponibles sin restricciones de patentes

import cv2

# Crear el objeto detector SIFT
sift = cv2.SIFT_create()

# Cargar la imagen en escala de grises
imagen = cv2.imread('ruta/a/la/imagen.jpg', cv2.IMREAD_GRAYSCALE)

# Detectar puntos clave y calcular descriptores
puntos_clave, descriptores = sift.detectAndCompute(imagen, None)

# Dibujar los puntos clave en la imagen
imagen_puntos = cv2.drawKeypoints(imagen, puntos_clave, None,
                                  flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

El objeto descriptores es una matriz que contiene vectores que describen características locales de la imagen. Estos vectores pueden utilizarse como entradas para modelos de aprendizaje automático.

Otro algoritmo eficiente es ORB (Oriented FAST and Rotated BRIEF), que combina velocidad y buen rendimiento sin restricciones de patentes:

# Crear el objeto detector ORB
orb = cv2.ORB_create()

# Detectar puntos clave y calcular descriptores
puntos_clave, descriptores = orb.detectAndCompute(imagen, None)

ORB genera descriptores binarios, lo que reduce el espacio de almacenamiento y acelera las comparaciones.

Además de puntos clave, es útil extraer características globales. El Histograma de Gradientes Orientados (HOG) es una técnica que captura información sobre los bordes y formas en la imagen:

# Crear el descriptor HOG
hog = cv2.HOGDescriptor()

# Calcular el descriptor HOG
descriptor_hog = hog.compute(imagen)

El vector descriptor_hog representa la distribución de gradientes y es eficaz para tareas de clasificación.

La extracción de momentos es otra forma de capturar propiedades de las formas en la imagen. Los momentos de Hu son invariantes a transformaciones comunes:

import numpy as np

# Calcular los momentos centrales de la imagen
momentos = cv2.moments(imagen)

# Calcular los momentos de Hu
momentos_hu = cv2.HuMoments(momentos)

# Aplicar escala logarítmica para normalización
momentos_hu_log = -np.sign(momentos_hu) * np.log10(np.abs(momentos_hu))

Estos momentos pueden utilizarse como características para distinguir entre diferentes formas y objetos.

Para combinar múltiples características, es común concatenar los vectores resultantes y normalizarlos:

from sklearn.preprocessing import StandardScaler

# Concatenar descriptores y momentos
caracteristicas = np.hstack((descriptores.flatten(), descriptor_hog.flatten(),
                             momentos_hu_log.flatten()))

# Escalar las características
escalador = StandardScaler()
caracteristicas_escaladas = escalador.fit_transform(caracteristicas.reshape(1, -1))

Este vector de características escaladas puede alimentar a algoritmos de aprendizaje automático, como máquinas de soporte vectorial o redes neuronales.

La reducción de dimensionalidad es importante para manejar vectores de características extensos. Técnicas como PCA (Análisis de Componentes Principales) ayudan a reducir el espacio de características manteniendo la información relevante:

from sklearn.decomposition import PCA

# Reducir a 100 componentes principales
pca = PCA(n_components=100)
caracteristicas_reducidas = pca.fit_transform(caracteristicas_escaladas)

Esto mejora la eficiencia computacional y puede aumentar el rendimiento del modelo al evitar el sobreajuste.

La extracción de características mediante modelos pre-entrenados de aprendizaje profundo también es una práctica común. Utilizar redes neuronales como VGG16 o ResNet50 permite obtener representaciones de alto nivel:

from tensorflow.keras.applications import VGG16
from tensorflow.keras.preprocessing import image as keras_image
from tensorflow.keras.applications.vgg16 import preprocess_input

# Cargar el modelo VGG16 sin la capa de clasificación superior
modelo_vgg = VGG16(weights='imagenet', include_top=False)

# Preparar la imagen
imagen_color = cv2.imread('ruta/a/la/imagen.jpg')
imagen_redimensionada = cv2.resize(imagen_color, (224, 224))
x = keras_image.img_to_array(imagen_redimensionada)
x = np.expand_dims(x, axis=0)
x = preprocess_input(x)

# Extraer características
caracteristicas_vgg = modelo_vgg.predict(x)

# Aplanar el tensor de características
caracteristicas_vgg_flat = caracteristicas_vgg.flatten()

Las características extraídas reflejan patrones complejos y son especialmente útiles en tareas de clasificación de alta precisión.

Es crucial asegurarse de que las imágenes se preprocesen de manera consistente antes de la extracción de características. Esto incluye pasos como normalización, escalado y alineación de canales de color.

La elección de las técnicas de extracción depende del tipo de datos y del problema específico. Es recomendable experimentar con diferentes métodos y evaluar su impacto en el rendimiento del modelo.

La implementación de estas técnicas en OpenCV proporciona una base sólida para el desarrollo de sistemas de visión por computadora robustos y eficientes, integrando la extracción de características en el flujo de trabajo de aprendizaje automático.

Conversión a formatos compatibles para scikit learn y TensorFlow

En el preprocesamiento de imágenes para modelos de aprendizaje automático, es fundamental convertir las imágenes a formatos compatibles con scikit-learn y TensorFlow. Cada biblioteca tiene requisitos específicos en cuanto al formato y la estructura de los datos de entrada. A continuación, se detalla cómo preparar las imágenes utilizando OpenCV para que sean adecuadas para ambos entornos.

Preparación de imágenes para scikit-learn

Los algoritmos de scikit-learn generalmente esperan datos en forma de matrices bidimensionales, donde cada fila representa una muestra y cada columna una característica. Para trabajar con imágenes, es necesario convertirlas en vectores unidimensionales (flattening) y asegurarse de que todas las imágenes tengan el mismo tamaño.

Paso 1: Cargar y redimensionar la imagen

Utilizamos OpenCV para cargar la imagen y redimensionarla a un tamaño uniforme:

import cv2
import numpy as np

# Dimensiones deseadas
alto, ancho = 64, 64

# Cargar la imagen en escala de grises
imagen = cv2.imread('ruta/a/la/imagen.jpg', cv2.IMREAD_GRAYSCALE)

# Redimensionar la imagen
imagen_redimensionada = cv2.resize(imagen, (ancho, alto))

Paso 2: Aplanar la imagen

Convertimos la imagen en un vector unidimensional para que pueda ser utilizada como entrada en scikit-learn:

# Aplanar la imagen
imagen_vector = imagen_redimensionada.flatten()

Paso 3: Construir el conjunto de datos

Repetimos el proceso para todas las imágenes y creamos una matriz donde cada fila es una imagen aplanada:

# Lista para almacenar los vectores de imágenes
imagenes_vectores = []

# Suponiendo que tenemos una lista de rutas de imágenes
rutas_imagenes = ['imagen1.jpg', 'imagen2.jpg', 'imagen3.jpg']

for ruta in rutas_imagenes:
    imagen = cv2.imread(ruta, cv2.IMREAD_GRAYSCALE)
    imagen_redimensionada = cv2.resize(imagen, (ancho, alto))
    imagen_vector = imagen_redimensionada.flatten()
    imagenes_vectores.append(imagen_vector)

# Convertir la lista en una matriz de NumPy
X = np.array(imagenes_vectores)

Ahora, X es una matriz de tamaño (n_muestras, n_características) compatible con scikit-learn, donde n_características es alto x ancho.

Paso 4: Normalizar los datos

Es recomendable normalizar los datos para mejorar el rendimiento del modelo:

from sklearn.preprocessing import StandardScaler

# Crear el objeto escalador
escalador = StandardScaler()

# Ajustar y transformar los datos
X_normalizado = escalador.fit_transform(X)

Preparación de imágenes para TensorFlow

En TensorFlow, las imágenes generalmente se representan como tensores de cuatro dimensiones: (tamaño_lote, alto, ancho, canales). Es crucial mantener la forma y el tipo de datos adecuados para utilizar eficientemente las capacidades de procesamiento en paralelo y las GPU.

Paso 1: Cargar y redimensionar la imagen

Para imágenes en color, aseguramos que los canales están en el orden correcto (RGB):

# Cargar la imagen en color
imagen_color = cv2.imread('ruta/a/la/imagen.jpg')

# Convertir de BGR a RGB
imagen_rgb = cv2.cvtColor(imagen_color, cv2.COLOR_BGR2RGB)

# Redimensionar la imagen
imagen_redimensionada = cv2.resize(imagen_rgb, (ancho, alto))

Paso 2: Normalizar la imagen

TensorFlow trabaja de manera más eficiente con valores de píxeles en el rango [0, 1] o [-1, 1]:

# Convertir a tipo float32 y normalizar
imagen_normalizada = imagen_redimensionada.astype('float32') / 255.0

Paso 3: Expandir dimensiones para el lote

Si procesamos una sola imagen, debemos añadir una dimensión extra para representar el tamaño del lote:

import numpy as np

# Añadir una dimensión para el tamaño del lote
imagen_final = np.expand_dims(imagen_normalizada, axis=0)

Ahora, imagen_final tiene la forma (1, alto, ancho, 3), compatible con modelos en TensorFlow.

Paso 4: Crear un flujo de datos para múltiples imágenes

Para entrenar modelos con múltiples imágenes, podemos construir un tensor que agrupe todas las imágenes:

# Lista para almacenar las imágenes procesadas
imagenes_procesadas = []

for ruta in rutas_imagenes:
    imagen = cv2.imread(ruta)
    imagen_rgb = cv2.cvtColor(imagen, cv2.COLOR_BGR2RGB)
    imagen_redimensionada = cv2.resize(imagen_rgb, (ancho, alto))
    imagen_normalizada = imagen_redimensionada.astype('float32') / 255.0
    imagenes_procesadas.append(imagen_normalizada)

# Convertir la lista en un tensor de NumPy
X_tensorflow = np.array(imagenes_procesadas)

El tensor X_tensorflow tiene la forma (n_muestras, alto, ancho, 3) y está listo para ser utilizado en modelos de TensorFlow.

Compatibilidad con modelos pre-entrenados

Los modelos pre-entrenados en TensorFlow, como VGG16 o ResNet50, tienen requisitos específicos en cuanto al tamaño y preprocesamiento de las imágenes.

Por ejemplo, para utilizar VGG16, las imágenes deben ser de tamaño (224, 224, 3) y es necesario aplicar una función de preprocesamiento:

from tensorflow.keras.applications.vgg16 import VGG16, preprocess_input
from tensorflow.keras.preprocessing import image as keras_image

# Cargar el modelo VGG16 sin las capas superiores
modelo_vgg = VGG16(weights='imagenet', include_top=False)

# Preprocesar la imagen
imagen_vgg = cv2.resize(imagen_rgb, (224, 224))
x = keras_image.img_to_array(imagen_vgg)
x = np.expand_dims(x, axis=0)
x = preprocess_input(x)

El uso de preprocess_input aplica las transformaciones necesarias para que la imagen sea compatible con el modelo pre-entrenado.

Manejo de etiquetas y conjunto de datos

Al preparar los datos, también es importante manejar las etiquetas asociadas a cada imagen, especialmente para tareas de clasificación en scikit-learn y TensorFlow.

Para scikit-learn:

# Suponiendo que tenemos una lista de etiquetas
etiquetas = [0, 1, 0]

# Convertir las etiquetas en un array de NumPy
y = np.array(etiquetas)

# Ahora podemos dividir el conjunto en entrenamiento y prueba
from sklearn.model_selection import train_test_split

X_entrenamiento, X_prueba, y_entrenamiento, y_prueba = train_test_split(
    X_normalizado, y, test_size=0.2, random_state=42)

Para TensorFlow:

# Convertir las etiquetas a formato categorical si es necesario
from tensorflow.keras.utils import to_categorical

y_categorical = to_categorical(etiquetas, num_classes=2)

# Dividir el conjunto de datos
X_entrenamiento_tf, X_prueba_tf, y_entrenamiento_tf, y_prueba_tf = train_test_split(
    X_tensorflow, y_categorical, test_size=0.2, random_state=42)

Utilización de generadores de datos

Para manejar grandes conjuntos de datos, es eficiente utilizar generadores que carguen y preprocesen las imágenes en tiempo real.

Con scikit-learn:

Podemos crear un generador personalizado utilizando la clase Sequence de Keras o implementando un iterador en Python.

Con TensorFlow:

Utilizamos ImageDataGenerator para cargar y preprocesar las imágenes sobre la marcha.

Verificación de las dimensiones y tipos de datos

Es esencial verificar que las dimensiones y los tipos de datos sean correctos antes de entrenar los modelos:

print('Forma de X para scikit-learn:', X_entrenamiento.shape)
print('Tipo de X:', X_entrenamiento.dtype)

print('Forma de X para TensorFlow:', X_entrenamiento_tf.shape)
print('Tipo de X:', X_entrenamiento_tf.dtype)

Los modelos de scikit-learn esperan generalmente matrices de tipo float64, mientras que TensorFlow trabaja de forma óptima con tensores de tipo float32.

Aumento de datos (Data Augmentation)

El aumento de datos es una técnica esencial en el aprendizaje automático que consiste en generar nuevas muestras a partir de las existentes mediante transformaciones que preservan las características principales de las imágenes. Esto ayuda a mejorar la generalización de los modelos al proporcionar más diversidad en el conjunto de datos, especialmente cuando se dispone de un número limitado de imágenes.

OpenCV ofrece una amplia gama de funciones para realizar transformaciones que permiten implementar aumento de datos. A continuación, se describen algunas de las técnicas más comunes y cómo aplicarlas utilizando OpenCV.

Rotación y traslación

La rotación y la traslación permiten simular diferentes orientaciones y posiciones de los objetos en las imágenes. Podemos utilizar la función cv2.warpAffine para aplicar estas transformaciones.

Rotación:

import cv2
import numpy as np

# Cargar la imagen
imagen = cv2.imread('ruta/a/la/imagen.jpg')

# Obtener las dimensiones de la imagen
alto, ancho = imagen.shape[:2]

# Definir el centro de rotación
centro = (ancho // 2, alto // 2)

# Definir el ángulo de rotación
angulo = 15  # Grados

# Obtener la matriz de rotación
matriz_rotacion = cv2.getRotationMatrix2D(centro, angulo, 1.0)

# Aplicar la rotación
imagen_rotada = cv2.warpAffine(imagen, matriz_rotacion, (ancho, alto))

En este ejemplo, la imagen se rota 15 grados alrededor de su centro. Podemos generar múltiples versiones de la imagen rotándola en diferentes ángulos para incrementar la variedad.

Traslación:

# Definir las traslaciones en x e y
desplazamiento_x = 10  # Pixels
desplazamiento_y = 5   # Pixels

# Construir la matriz de traslación
matriz_traslacion = np.float32([[1, 0, desplazamiento_x],
                                [0, 1, desplazamiento_y]])

# Aplicar la traslación
imagen_trasladada = cv2.warpAffine(imagen, matriz_traslacion, (ancho, alto))

La traslación desplaza la imagen en el eje x y y, lo que ayuda al modelo a ser invariante a cambios de posición.

Volteo horizontal y vertical

El volteo es otra técnica sencilla que duplica las muestras al reflejar las imágenes sobre un eje. Esto es especialmente útil en imágenes donde la orientación no afecta la clasificación.

# Volteo horizontal
imagen_volteada_h = cv2.flip(imagen, 1)

# Volteo vertical
imagen_volteada_v = cv2.flip(imagen, 0)

# Volteo horizontal y vertical
imagen_volteada_hv = cv2.flip(imagen, -1)

El parámetro de cv2.flip determina el eje de volteo: 1 para horizontal, 0 para vertical, y -1 para ambos.

Cambio de brillo y contraste

Alterar el brillo y el contraste de las imágenes simula diferentes condiciones de iluminación, lo que hace al modelo más robusto frente a variaciones lumínicas.

# Convertir la imagen a espacio de color HSV
imagen_hsv = cv2.cvtColor(imagen, cv2.COLOR_BGR2HSV)

# Aumentar el brillo
incremento_brillo = 30
imagen_hsv[:, :, 2] = cv2.add(imagen_hsv[:, :, 2], incremento_brillo)

# Convertir de vuelta a BGR
imagen_brillo = cv2.cvtColor(imagen_hsv, cv2.COLOR_HSV2BGR)

El canal V en el espacio HSV representa el valor de brillo, y al modificarlo, ajustamos la luminosidad de la imagen.

Adición de ruido

La introducción de ruido en las imágenes puede ayudar al modelo a generalizar mejor en presencia de datos ruidosos. Un ruido comúnmente usado es el ruido gaussiano.

# Generar ruido gaussiano
ruido = np.random.normal(0, 25, imagen.shape).astype(np.uint8)

# Añadir el ruido a la imagen
imagen_con_ruido = cv2.add(imagen, ruido)

Este proceso añade ruido con una desviación estándar de 25, creando imágenes ligeramente corruptas que desafían al modelo.

Escalado y zoom

El escalado modifica el tamaño del objeto dentro de la imagen, mientras que el zoom simula acercamientos. Ambas técnicas ayudan al modelo a ser invariante a cambios de escala.

# Factor de escalado
factor_escala = 1.2

# Obtener nuevas dimensiones
nuevo_ancho = int(ancho * factor_escala)
nuevo_alto = int(alto * factor_escala)

# Redimensionar la imagen
imagen_escalada = cv2.resize(imagen, (nuevo_ancho, nuevo_alto))

# Recortar al tamaño original (simulando zoom)
inicio_x = (nuevo_ancho - ancho) // 2
inicio_y = (nuevo_alto - alto) // 2
imagen_zoom = imagen_escalada[inicio_y:inicio_y+alto, inicio_x:inicio_x+ancho]

Este método preserva las dimensiones originales recortando la imagen escalada.

Variaciones de color

Modificar los canales de color introduce variaciones en las imágenes que pueden ser útiles para modelos que deben reconocer objetos independientemente de su color.

# Multiplicar los canales por factores aleatorios
factor_azul = np.random.uniform(0.8, 1.2)
factor_verde = np.random.uniform(0.8, 1.2)
factor_rojo = np.random.uniform(0.8, 1.2)

# Separar los canales
B, G, R = cv2.split(imagen)

# Aplicar los factores
B = cv2.multiply(B, factor_azul)
G = cv2.multiply(G, factor_verde)
R = cv2.multiply(R, factor_rojo)

# Reunir los canales
imagen_variada = cv2.merge([B, G, R])

Este proceso altera ligeramente los colores, simulando diferentes condiciones ambientales.

Ocultación aleatoria (Random Erasing)

La ocultación aleatoria aumenta la robustez del modelo frente a oclusiones.

# Definir el tamaño del rectángulo
occlusion_width = 50
occlusion_height = 50

# Generar coordenadas aleatorias
x_inicio = np.random.randint(0, ancho - occlusion_width)
y_inicio = np.random.randint(0, alto - occlusion_height)

# Ocultar parte de la imagen
imagen_ocultada = imagen.copy()
imagen_ocultada[y_inicio:y_inicio+occlusion_height, x_inicio:x_inicio+occlusion_width] = 0

Al ocultar partes de la imagen, el modelo aprende a ignorar zonas irrelevantes.

Consideraciones al utilizar aumento de datos:

  • Equilibrio: Es importante no introducir un sesgo en el conjunto de datos al aplicar ciertas transformaciones más que otras.
  • Etiquetas consistentes: Las imágenes aumentadas deben mantener las mismas etiquetas que las originales.
  • Almacenamiento: Generar todas las imágenes aumentadas y almacenarlas puede ser costoso en términos de espacio. Una alternativa es aplicar el aumento de datos en tiempo real durante el entrenamiento.
  • Control de calidad: Es recomendable revisar visualmente algunas imágenes aumentadas para asegurar que las transformaciones son apropiadas y no introducen artefactos indeseados.

Al preparar los datos aumentados, es esencial integrarlos correctamente con los procedimientos de preprocesamiento y formatos de entrada para scikit-learn y TensorFlow, como se describió en las secciones anteriores.

La aplicación de aumento de datos contribuye significativamente a mejorar el rendimiento y la capacidad generalizadora de los modelos, especialmente en conjuntos de datos reducidos o cuando se busca robustez frente a variaciones en las imágenes.

Uso de generadores de imágenes como ImageDataGenerator

El ImageDataGenerator es una herramienta poderosa proporcionada por Keras, parte de TensorFlow, que permite generar lotes de datos de imágenes con aumento en tiempo real. Integrar OpenCV con ImageDataGenerator optimiza el preprocesamiento y facilita el entrenamiento de modelos de aprendizaje profundo con grandes conjuntos de datos.

Al utilizar ImageDataGenerator, evitamos la necesidad de almacenar todas las imágenes aumentadas en disco, lo que ahorra espacio y reduce el tiempo de lectura. Este generador aplica transformaciones aleatorias a las imágenes en cada época, mejorando la generalización del modelo.

Configuración de ImageDataGenerator

Para comenzar, importamos la clase y definimos los parámetros de aumento de datos que deseamos aplicar:

from tensorflow.keras.preprocessing.image import ImageDataGenerator

# Configurar el generador con las transformaciones deseadas
data_gen = ImageDataGenerator(
    rotation_range=20,        # Rotación aleatoria entre 0 y 20 grados
    width_shift_range=0.1,    # Desplazamiento horizontal
    height_shift_range=0.1,   # Desplazamiento vertical
    shear_range=0.15,         # Transformación de corte
    zoom_range=0.1,           # Zoom aleatorio
    horizontal_flip=True,     # Volteo horizontal
    fill_mode='nearest',      # Método de relleno de píxeles vacíos
)

Estos parámetros definen las transformaciones que se aplicarán a las imágenes durante el entrenamiento. El fill_mode determina cómo se rellenan los píxeles generados después de una transformación.

Preparación de imágenes con OpenCV

Aunque ImageDataGenerator trabaja principalmente con imágenes en formato de tensor, podemos integrarlo con imágenes procesadas por OpenCV:

import cv2
import numpy as np

# Cargar la imagen con OpenCV
imagen = cv2.imread('ruta/a/la/imagen.jpg')

# Convertir de BGR a RGB
imagen_rgb = cv2.cvtColor(imagen, cv2.COLOR_BGR2RGB)

# Redimensionar la imagen si es necesario
imagen_rgb = cv2.resize(imagen_rgb, (224, 224))

# Expandir dimensiones para crear un lote de tamaño 1
imagen_array = np.expand_dims(imagen_rgb, axis=0)

Es importante convertir la imagen de BGR a RGB, ya que Keras y TensorFlow esperan imágenes en formato RGB. Además, debemos asegurarnos de que la imagen tenga el tamaño correcto según los requisitos del modelo.

Generación de datos aumentados

Utilizamos el método flow de ImageDataGenerator para crear un generador que producirá lotes de imágenes aumentadas:

# Crear el generador a partir de la imagen
generador = data_gen.flow(
    imagen_array,
    batch_size=1
)

# Generar y visualizar algunas imágenes aumentadas
import matplotlib.pyplot as plt

for i in range(5):
    lote = next(generador)
    imagen_aumentada = lote[0].astype('uint8')
    
    # Mostrar la imagen aumentada
    plt.subplot(1, 5, i+1)
    plt.imshow(imagen_aumentada)
    plt.axis('off')

plt.show()

Este código genera cinco versiones aumentadas de la imagen original y las muestra usando Matplotlib. El método next del generador produce un lote de imágenes con las transformaciones aplicadas.

Integración con el entrenamiento del modelo

Al entrenar un modelo de aprendizaje profundo, podemos utilizar el generador para proporcionar datos en tiempo real:

# Suponiendo que tenemos un modelo compilado llamado 'modelo'

# Configurar el generador para el conjunto de entrenamiento
generador_entrenamiento = data_gen.flow_from_directory(
    'ruta/a/dataset/entrenamiento',
    target_size=(224, 224),
    batch_size=32,
    class_mode='categorical'
)

# Entrenar el modelo utilizando el generador
modelo.fit(
    generador_entrenamiento,
    steps_per_epoch=generador_entrenamiento.samples // 32,
    epochs=50
)

El método flow_from_directory permite generar lotes de imágenes a partir de una estructura de directorios, donde cada subdirectorio corresponde a una clase. El modelo se entrena utilizando estos lotes, beneficiándose del aumento de datos y de una carga eficiente.

Uso de generadores personalizados con OpenCV

Si necesitamos un mayor control sobre el preprocesamiento, podemos crear un generador personalizado que combine OpenCV y ImageDataGenerator:

def generador_personalizado(rutas_imagenes, etiquetas, batch_size):
    indice = 0
    while True:
        imagenes_batch = []
        etiquetas_batch = []
        
        for _ in range(batch_size):
            if indice == len(rutas_imagenes):
                indice = 0
            
            # Cargar y preprocesar la imagen
            imagen = cv2.imread(rutas_imagenes[indice])
            imagen = cv2.cvtColor(imagen, cv2.COLOR_BGR2RGB)
            imagen = cv2.resize(imagen, (224, 224))
            
            # Aplicar preprocesamiento adicional si es necesario
            imagen = imagen.astype('float32') / 255.0
            
            imagenes_batch.append(imagen)
            etiquetas_batch.append(etiquetas[indice])
            indice += 1
        
        # Convertir a arrays de NumPy
        X_batch = np.array(imagenes_batch)
        y_batch = np.array(etiquetas_batch)
        
        # Aplicar aumentación con ImageDataGenerator
        X_batch_augmented = next(data_gen.flow(X_batch, batch_size=batch_size, shuffle=False))
        
        yield X_batch_augmented, y_batch

Este generador lee las imágenes con OpenCV, aplica cualquier preprocesamiento necesario, y luego utiliza ImageDataGenerator para aumentarlas. Esto proporciona flexibilidad adicional para operaciones específicas que no están disponibles directamente en flow_from_directory.

Entrenamiento con el generador personalizado

Podemos utilizar este generador en el método fit del modelo:

# Suponiendo que tenemos listas de rutas y etiquetas
rutas_imagenes = [...]  # Lista de rutas a imágenes
etiquetas = [...]       # Lista de etiquetas correspondientes

batch_size = 32

# Crear el generador
generador_entrenamiento = generador_personalizado(rutas_imagenes, etiquetas, batch_size)

# Calcular el número de pasos por época
steps_per_epoch = len(rutas_imagenes) // batch_size

# Entrenar el modelo
modelo.fit(
    generador_entrenamiento,
    steps_per_epoch=steps_per_epoch,
    epochs=50
)

Al utilizar un generador, el modelo recibe los datos de forma perezosa, es decir, se generan sobre la marcha durante el entrenamiento. Esto es especialmente útil cuando el conjunto de datos es demasiado grande para caber en memoria.

Consideraciones adicionales

  • Compatibilidad de formatos: Asegurarse de que las imágenes están en el formato y rango adecuados antes de ingresar al modelo (por ejemplo, valores entre 0 y 1).
  • Configuración del generador: Los parámetros de ImageDataGenerator deben ajustarse según el problema y las características del conjunto de datos para evitar sobretransformar las imágenes y perder información relevante.
  • Etiquetas y clases: Cuando se utiliza flow_from_directory, las clases se asignan automáticamente según los nombres de los subdirectorios. Es importante mantener una estructura de directorios coherente.

Preprocesamiento personalizado

Si necesitamos aplicar funciones de preprocesamiento específicas, podemos utilizar el parámetro preprocessing_function de ImageDataGenerator:

def mi_preprocesamiento(imagen):
    # Aplicar operaciones de OpenCV adicionales
    imagen = cv2.cvtColor(imagen, cv2.COLOR_RGB2HSV)
    # Normalizar la imagen
    imagen = imagen / 255.0
    return imagen

# Crear el generador con la función personalizada
data_gen_custom = ImageDataGenerator(
    rotation_range=20,
    preprocessing_function=mi_preprocesamiento
)

La función definida se aplicará a cada imagen después de las transformaciones y antes de que se produzca el lote final, permitiendo un preprocesamiento avanzado.

Validación y prueba

Para los conjuntos de validación y prueba, es recomendable utilizar un generador sin aumento de datos:

data_gen_val = ImageDataGenerator()

generador_validacion = data_gen_val.flow_from_directory(
    'ruta/a/dataset/validacion',
    target_size=(224, 224),
    batch_size=32,
    class_mode='categorical'
)

# Entrenar el modelo con validación
modelo.fit(
    generador_entrenamiento,
    steps_per_epoch=generador_entrenamiento.samples // 32,
    validation_data=generador_validacion,
    validation_steps=generador_validacion.samples // 32,
    epochs=50
)

Esto asegura que la evaluación del modelo se realice sobre datos consistentes y sin modificaciones aleatorias.

Ajuste de parámetros

A través de experimentación y validación cruzada, podemos determinar los parámetros óptimos de aumento de datos y preprocesamiento que mejoran el rendimiento del modelo sin incurrir en sobreajuste.

El uso de ImageDataGenerator en conjunto con OpenCV nos proporciona una solución flexible y eficiente para el preprocesamiento y aumento de datos en proyectos de visión por computadora, optimizando el flujo de trabajo y potenciando los algoritmos de aprendizaje automático.

Aprende OpenCV GRATIS online

Todas las lecciones de OpenCV

Accede a todas las lecciones de OpenCV y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a OpenCV y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender la importancia de la normalización y el escalado en el preprocesamiento de imágenes.
  • Aprender a normalizar imágenes entre 0 y 1 utilizando OpenCV.
  • Implementar técnicas de escalado de imágenes para unificar dimensiones.
  • Estandarizar imágenes para mejorar aprendizaje en modelos sensibles a escala.
  • Preprocesar imágenes en color, normalizando cada canal B, G, R por separado.
  • Automatizar preprocesamiento para garantizar consistencia entre imágenes.