OpenCV

Tutorial OpenCV: Clasificación de imágenes con ML

Aprende a clasificar imágenes integrando OpenCV y técnicas de ML con Scikit Learn y TensorFlow. Guía paso a paso para optimizar modelos de aprendizaje automático con Python y OpenCV.

Aprende OpenCV GRATIS y certifícate

Clasificación de imágenes con scikit-learn

La clasificación de imágenes es una tarea fundamental en visión por computadora que consiste en asignar una etiqueta o categoría a una imagen dada. En esta sección, exploraremos cómo utilizar scikit-learn para construir modelos de clasificación de imágenes, integrando OpenCV para el procesamiento y preprocesamiento de las imágenes.

Para comenzar, es necesario convertir las imágenes en formatos que puedan ser interpretados por los algoritmos de scikit-learn. Esto implica transformar las imágenes en vectores de características que representen adecuadamente el contenido visual. Una técnica común es aplanar las imágenes, convirtiendo las matrices multidimensionales en vectores unidimensionales.

A continuación, importamos las bibliotecas necesarias y cargamos el conjunto de datos de imágenes. Suponiendo que tenemos un conjunto de imágenes organizadas en directorios según sus clases, podemos utilizar OpenCV para leer las imágenes y NumPy para manipularlas:

import cv2
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn import svm
import os

Es fundamental mantener un equilibrio entre la dimensionalidad de los datos y la capacidad del modelo para generalizar. Imágenes de alta resolución pueden generar vectores de características muy grandes, lo que podría llevar a un sobreajuste. Por ello, es recomendable redimensionar las imágenes a una escala más manejable:

IMAGE_SIZE = (64, 64)  # Tamaño al que redimensionaremos las imágenes
data = []
labels = []
classes = ['perros', 'gatos']

for idx, class_name in enumerate(classes):
    class_dir = os.path.join('ruta_a_las_imagenes', class_name)
    for img_name in os.listdir(class_dir):
        img_path = os.path.join(class_dir, img_name)
        img = cv2.imread(img_path)
        img = cv2.resize(img, IMAGE_SIZE)
        data.append(img.flatten())
        labels.append(idx)

En este ejemplo, las imágenes se redimensionan a 64x64 píxeles y se aplanan, convirtiéndolas en vectores de tamaño 12,288 (64x64x3). Las etiquetas se almacenan en la lista labels como enteros que representan cada clase.

Tras preparar los datos, dividimos el conjunto en entrenamiento y prueba utilizando train_test_split() de scikit-learn:

X_train, X_test, y_train, y_test = train_test_split(data, labels, test_size=0.2, random_state=42)

Elegimos un algoritmo de clasificación adecuado para nuestro problema. Las Máquinas de Vectores de Soporte (SVM) son una opción popular para tareas de clasificación. Entrenamos el modelo con los datos de entrenamiento:

clf = svm.SVC(kernel='linear')
clf.fit(X_train, y_train)

Una vez entrenado, evaluamos el rendimiento del modelo en los datos de prueba:

accuracy = clf.score(X_test, y_test)
print(f'Precisión del modelo: {accuracy * 100:.2f}%')

La precisión obtenida nos indica la eficacia del modelo en la clasificación correcta de nuevas imágenes. Para mejorar el rendimiento, podríamos considerar técnicas como la extracción de características más avanzadas en lugar de simplemente aplanar las imágenes.

Una alternativa es utilizar histogramas de colores como características. Los histogramas capturan la distribución de colores en una imagen, ofreciendo una representación más robusta:

def extract_color_histogram(image, bins=(8, 8, 8)):
    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
    hist = cv2.calcHist([hsv], [0, 1, 2], None, bins,
                        [0, 180, 0, 256, 0, 256])
    cv2.normalize(hist, hist)
    return hist.flatten()

data = []
labels = []

for idx, class_name in enumerate(classes):
    class_dir = os.path.join('ruta_a_las_imagenes', class_name)
    for img_name in os.listdir(class_dir):
        img_path = os.path.join(class_dir, img_name)
        img = cv2.imread(img_path)
        img = cv2.resize(img, IMAGE_SIZE)
        features = extract_color_histogram(img)
        data.append(features)
        labels.append(idx)

Con los histogramas de colores como vectores de características, repetimos el proceso de entrenamiento y evaluación:

X_train, X_test, y_train, y_test = train_test_split(data, labels, test_size=0.2, random_state=42)
clf = svm.SVC(kernel='linear')
clf.fit(X_train, y_train)
accuracy = clf.score(X_test, y_test)
print(f'Precisión del modelo con histogramas de color: {accuracy * 100:.2f}%')

El uso de características basadas en histogramas puede ofrecer una mejor discriminación entre clases, mejorando la performance del modelo. Además, ajustar parámetros como el tipo de kernel en el SVM o utilizar otros algoritmos de clasificación como árboles de decisión o k-NN puede afectar significativamente los resultados.

Es importante también realizar una validación cruzada para asegurar que el modelo generaliza bien y no está sobreajustado a los datos de entrenamiento:

from sklearn.model_selection import cross_val_score

scores = cross_val_score(clf, data, labels, cv=5)
print(f'Precisión media en validación cruzada: {np.mean(scores) * 100:.2f}%')

La validación cruzada proporciona una estimación más fiable de la precisión del modelo al utilizar diferentes particiones de los datos para entrenamiento y prueba.

Es esencial preprocesar adecuadamente las imágenes y considerar técnicas de normalización y estandarización de las características para mejorar el rendimiento del modelo. La biblioteca scikit-learn ofrece herramientas como StandardScaler y MinMaxScaler para este propósito.

Redes neuronales convolucionales con TensorFlow

Las Redes Neuronales Convolucionales (CNNs) son una arquitectura de redes neuronales especialmente eficaz para tareas de clasificación y reconocimiento en imágenes. En esta sección, aprenderemos a implementar una CNN utilizando TensorFlow, combinando el preprocesamiento de imágenes con OpenCV.

Comenzamos importando las bibliotecas necesarias:

import cv2
import numpy as np
import tensorflow as tf
import os

Definimos las clases y preparamos listas para almacenar los datos y las etiquetas:

IMAGE_SIZE = (64, 64)  # Tamaño al que redimensionaremos las imágenes
data = []
labels = []
classes = ['perros', 'gatos']

Cargamos y preprocesamos las imágenes utilizando OpenCV:

for idx, class_name in enumerate(classes):
    class_dir = os.path.join('ruta_a_las_imagenes', class_name)
    for img_name in os.listdir(class_dir):
        img_path = os.path.join(class_dir, img_name)
        img = cv2.imread(img_path)
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        img = cv2.resize(img, IMAGE_SIZE)
        data.append(img)
        labels.append(idx)

Convertimos las listas a arrays de NumPy y normalizamos las imágenes:

data = np.array(data, dtype='float32') / 255.0  # Normalización de píxeles
labels = np.array(labels)

Dividimos los datos en conjuntos de entrenamiento y validación utilizando train_test_split:

from sklearn.model_selection import train_test_split

X_train, X_val, y_train, y_val = train_test_split(data, labels, test_size=0.2, random_state=42)

Codificamos las etiquetas en formato categórico para la clasificación multiclase:

y_train = tf.keras.utils.to_categorical(y_train, num_classes=len(classes))
y_val = tf.keras.utils.to_categorical(y_val, num_classes=len(classes))

Diseñamos el modelo de CNN utilizando la API Keras de TensorFlow:

model = tf.keras.models.Sequential([
    tf.keras.layers.Conv2D(32, (3, 3), activation='relu', input_shape=IMAGE_SIZE + (3,)),
    tf.keras.layers.MaxPooling2D(2, 2),
    tf.keras.layers.Conv2D(64, (3, 3), activation='relu'),
    tf.keras.layers.MaxPooling2D(2, 2),
    tf.keras.layers.Conv2D(128, (3, 3), activation='relu'),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dense(len(classes), activation='softmax')
])

Compilamos el modelo definiendo el optimizador, la función de pérdida y las métricas:

model.compile(optimizer='adam',
              loss='categorical_crossentropy',
              metrics=['accuracy'])

Entrenamos el modelo con los datos de entrenamiento y validamos su rendimiento:

history = model.fit(X_train, y_train,
                    epochs=10,
                    batch_size=32,
                    validation_data=(X_val, y_val))

El historial de entrenamiento almacena información útil sobre la pérdida y la precisión en cada época, lo que permite analizar el desempeño del modelo.

Para evaluar el modelo en el conjunto de validación, utilizamos:

val_loss, val_accuracy = model.evaluate(X_val, y_val)
print(f'Precisión en validación: {val_accuracy * 100:.2f}%')

La precisión obtenida refleja la capacidad de generalización del modelo a nuevos datos.

Implementamos Data Augmentation para aumentar la variabilidad del conjunto de entrenamiento y mejorar la robustez del modelo:

from tensorflow.keras.preprocessing.image import ImageDataGenerator

datagen = ImageDataGenerator(
    rotation_range=20,
    width_shift_range=0.1,
    height_shift_range=0.1,
    horizontal_flip=True
)

datagen.fit(X_train)

Reentrenamos el modelo utilizando el generador de datos:

history = model.fit(datagen.flow(X_train, y_train, batch_size=32),
                    epochs=10,
                    validation_data=(X_val, y_val))

El aumento de datos permite al modelo aprender de imágenes modificadas, reduciendo el riesgo de sobreajuste.

Podemos guardar el modelo entrenado para futuras predicciones:

model.save('modelo_cnn.h5')

Para realizar predicciones con nuevas imágenes, preprocesamos la imagen y utilizamos el modelo cargado:

from tensorflow.keras.models import load_model

model = load_model('modelo_cnn.h5')

def prepare_image(img_path):
    img = cv2.imread(img_path)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img = cv2.resize(img, IMAGE_SIZE)
    img = img.astype('float32') / 255.0
    img = np.expand_dims(img, axis=0)
    return img

img = prepare_image('ruta_a_la_nueva_imagen.jpg')
prediction = model.predict(img)
clase_predicha = classes[np.argmax(prediction)]
print(f'La imagen se ha clasificado como: {clase_predicha}')

La función prepare_image se encarga de cargar y preprocesar la imagen para que sea compatible con el modelo.

Visualizamos la curva de aprendizaje para analizar el rendimiento durante el entrenamiento:

import matplotlib.pyplot as plt

plt.plot(history.history['accuracy'], label='Precisión de entrenamiento')
plt.plot(history.history['val_accuracy'], label='Precisión de validación')
plt.xlabel('Épocas')
plt.ylabel('Precisión')
plt.legend()
plt.show()

Las gráficas permiten identificar tendencias y posibles problemas, como el sobreajuste si la precisión en entrenamiento aumenta mientras que la de validación se estanca o disminuye.

Es posible mejorar el modelo ajustando hiperparámetros, añadiendo capas de regularización como Dropout, o utilizando arquitecturas más complejas. La integración de OpenCV y TensorFlow proporciona una poderosa combinación para abordar desafíos en visión por computadora y aprendizaje profundo.

Uso de modelos pre-entrenados

Los modelos pre-entrenados son redes neuronales que han sido entrenadas previamente en grandes conjuntos de datos y pueden reutilizarse para resolver tareas similares. Este enfoque aprovecha los conocimientos adquiridos por el modelo, reduciendo el tiempo de entrenamiento y mejorando el rendimiento en problemas con datos limitados.

Una de las ventajas de utilizar modelos pre-entrenados es la capacidad de transferir aprendizaje. En lugar de entrenar una red desde cero, podemos importar un modelo existente y adaptarlo a nuestra tarea específica. Frameworks como TensorFlow y Keras facilitan la carga y modificación de estos modelos.

Empezamos importando las librerías necesarias:

import tensorflow as tf
import cv2
import numpy as np
import os

Seleccionamos un modelo pre-entrenado, como MobileNetV2, optimizado para dispositivos con recursos limitados y adecuado para clasificación de imágenes. Cargamos el modelo sin incluir las capas superiores (top layers), ya que añadiremos nuestras propias capas para la clasificación personalizada:

base_model = tf.keras.applications.MobileNetV2(input_shape=(128, 128, 3),
                                               include_top=False,
                                               weights='imagenet')
base_model.trainable = False  # Congelamos las capas del modelo base

Preparamos nuestro conjunto de datos. Supongamos que tenemos imágenes organizadas en carpetas según sus categorías:

IMAGE_SIZE = (128, 128)
BATCH_SIZE = 32

data_dir = 'ruta_a_las_imagenes'
datagen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255,
                                                         validation_split=0.2)

train_generator = datagen.flow_from_directory(data_dir,
                                              target_size=IMAGE_SIZE,
                                              batch_size=BATCH_SIZE,
                                              subset='training',
                                              class_mode='categorical')

val_generator = datagen.flow_from_directory(data_dir,
                                            target_size=IMAGE_SIZE,
                                            batch_size=BATCH_SIZE,
                                            subset='validation',
                                            class_mode='categorical')

Utilizamos ImageDataGenerator para generar lotes de imágenes y aplicar una normalización a los píxeles. Es importante resaltar la división en entrenamiento y validación mediante el parámetro validation_split.

Construimos el modelo añadiendo capas superiores sobre el modelo base:

inputs = tf.keras.Input(shape=(128, 128, 3))
x = base_model(inputs, training=False)
x = tf.keras.layers.GlobalAveragePooling2D()(x)
x = tf.keras.layers.Dense(128, activation='relu')(x)
outputs = tf.keras.layers.Dense(train_generator.num_classes, activation='softmax')(x)
model = tf.keras.Model(inputs, outputs)

En este modelo, utilizamos una capa de GlobalAveragePooling2D para reducir las dimensiones, seguida de una capa densa intermedia y la capa de salida con activación softmax para la clasificación multiclase.

Compilamos el modelo especificando el optimizador, la función de pérdida y las métricas:

model.compile(optimizer=tf.keras.optimizers.Adam(),
              loss='categorical_crossentropy',
              metrics=['accuracy'])

Entrenamos el modelo utilizando los generadores de datos:

history = model.fit(train_generator,
                    epochs=10,
                    validation_data=val_generator)

Tras entrenar las capas superiores, podemos descongelar algunas capas del modelo base para ajustar finamente el modelo a nuestro conjunto de datos:

base_model.trainable = True
fine_tune_at = 100  # Descongelamos desde la capa número 100

for layer in base_model.layers[:fine_tune_at]:
    layer.trainable = False

model.compile(optimizer=tf.keras.optimizers.Adam(1e-5),
              loss='categorical_crossentropy',
              metrics=['accuracy'])

history_fine = model.fit(train_generator,
                         epochs=5,
                         validation_data=val_generator)

Al ajustar finamente el modelo, utilizamos una tasa de aprendizaje baja para no sobrescribir los pesos pre-entrenados de forma abrupta. Este proceso puede mejorar significativamente la precisión del modelo en tareas específicas.

Evaluamos el modelo en el conjunto de validación:

val_loss, val_accuracy = model.evaluate(val_generator)
print(f'Precisión de validación: {val_accuracy * 100:.2f}%')

Para predecir nuevas imágenes, preprocesamos la imagen y utilizamos el modelo entrenado:

def prepare_image(img_path):
    img = cv2.imread(img_path)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img = cv2.resize(img, IMAGE_SIZE)
    img = img.astype('float32') / 255.0
    img = np.expand_dims(img, axis=0)
    return img

img = prepare_image('ruta_a_la_nueva_imagen.jpg')
prediction = model.predict(img)
clase_predicha = train_generator.class_indices
clase_predicha = {v: k for k, v in clase_predicha.items()}
print(f'La imagen se ha clasificado como: {clase_predicha[np.argmax(prediction)]}')

Es fundamental entender que los modelos pre-entrenados fueron entrenados en grandes conjuntos de datos como ImageNet, que contiene millones de imágenes y mil clases. Esto permite que los modelos aprendan características ricas y generales, que son útiles para numerosas tareas de visión por computadora.

Además de MobileNetV2, existen otros modelos pre-entrenados disponibles en TensorFlow, como VGG16, ResNet50, InceptionV3, entre otros. La elección del modelo depende de las necesidades, considerando factores como la precisión y el rendimiento.

Podemos explorar los modelos disponibles en tensorflow.keras.applications:

from tensorflow.keras.applications import VGG16, ResNet50, InceptionV3

# Ejemplo de carga de VGG16
vgg_base = VGG16(input_shape=(224, 224, 3),
                 include_top=False,
                 weights='imagenet')
vgg_base.trainable = False

Es posible utilizar los modelos pre-entrenados como extractores de características. En este enfoque, extraemos las características de las imágenes utilizando el modelo base y luego entrenamos un clasificador tradicional como una máquina de vectores de soporte (SVM) con scikit-learn:

def extract_features(directory, sample_count):
    features = np.zeros(shape=(sample_count, 7, 7, 512))  # Ajustar según el modelo
    labels = np.zeros(shape=(sample_count))
    generator = datagen.flow_from_directory(directory,
                                            target_size=(224, 224),
                                            batch_size=BATCH_SIZE,
                                            class_mode='binary',
                                            shuffle=False)
    i = 0
    for inputs_batch, labels_batch in generator:
        features_batch = vgg_base.predict(inputs_batch)
        features[i * BATCH_SIZE: (i + 1) * BATCH_SIZE] = features_batch
        labels[i * BATCH_SIZE: (i + 1) * BATCH_SIZE] = labels_batch
        i += 1
        if i * BATCH_SIZE >= sample_count:
            break
    return features, labels

train_features, train_labels = extract_features('ruta_a_las_imagenes_entrenamiento', 2000)
val_features, val_labels = extract_features('ruta_a_las_imagenes_validacion', 1000)

# Aplanamos las características
train_features = np.reshape(train_features, (2000, 7 * 7 * 512))
val_features = np.reshape(val_features, (1000, 7 * 7 * 512))

Con las características extraídas, entrenamos un clasificador con scikit-learn:

from sklearn.svm import SVC

classifier = SVC(kernel='linear')
classifier.fit(train_features, train_labels)

accuracy = classifier.score(val_features, val_labels)
print(f'Precisión del clasificador SVM: {accuracy * 100:.2f}%')

De esta manera combinamos la potencia de los modelos pre-entrenados para extracción de características con la flexibilidad de los algoritmos de scikit-learn.

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

  1. Comprender la clasificación de imágenes con scikit-learn.
  2. Aprender a convertir imágenes en vectores de características.
  3. Saber cómo redimensionar y procesar imágenes con OpenCV.
  4. Emplear máquinas de vectores de soporte para clasificación.
  5. Evaluar modelos con técnicas de validación cruzada.