Icono del sitio OMES

🤖 ¡Tu red neuronal cobra vida! Reconoce números en tiempo real con tu cámara (MNIST + TensorFlow)

🤖 ¡Tu red neuronal cobra vida! Reconoce números en tiempo real con tu cámara (MNIST + TensorFlow)

¡Hola, hola, Omesitos! En este post vamos a llevar al siguiente nivel el modelo que entrenamos con el dataset MNIST. ¿Cómo? Conectándolo a nuestra webcam 🧠📷 para que reconozca números escritos a mano en vivo. En el video anterior preparamos los datos, entrenamos una red neuronal densa y guardamos el modelo. Si no has visto ese tutorial, te recomendamos empezar por ahí.

NOTA: Para una explicación más detallada del código, por favor dirígete al videotutorial.

🔧 Instalación de paquetes

Para llevar a cabo esta práctica debemos instalar OpenCV y tensorflow, a través de:

pip install opencv-python tensorflow

Puedes verificar su instalación y sus versiones con: pip freeze

🎥 Captura desde la webcam

Usamos OpenCV para capturar los fotogramas en tiempo real y luego aplicamos una serie de transformaciones para aislar el dígito:

  1. Convertir a escala de grises.
  2. Binarizar la imagen con un umbral.
  3. Invertir los colores (para que los números aparezcan en blanco).
  4. Detectar contornos y seleccionar los más grandes.
  5. Extraer las regiones con posibles dígitos y redimensionarlas a 28×28.
  6. Normalizar y aplanar los datos para hacer la predicción con el modelo entrenado.

🤖 Código completo

Aquí te comparto el código utilizado. ¡Puedes copiarlo, probarlo y adaptarlo a tus necesidades!

import cv2
import tensorflow as tf
import numpy as np

# Cargar el modelo entrenado
model = tf.keras.models.load_model("mnist_model.keras")

# Iniciar la captura del video
cap = cv2.VideoCapture(0)

while True:
    ret, frame = cap.read()
    if ret == False:
        break
    # Crear una imagen vacía donde se mostrará la predicción
    image_prediction = np.zeros(shape=(250, 200, 3))

    # Convertir el frame a escala de grises, aplicar umbral binario inverso,
    # detectar contornos externos y seleccionar los 5 más grandes por área 
    frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    _, binary = cv2.threshold(frame_gray, 127, 255, cv2.THRESH_BINARY_INV)
    contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    contours = sorted(contours, key=cv2.contourArea, reverse=True)[:5]
    # cv2.drawContours(frame, contours, -1, (255, 0, 0), 3)
    for cnt in contours:
        # Filtrar contornos pequeños
        if cv2.contourArea(cnt) > 3000:
            x, y, w, h = cv2.boundingRect(cnt)

            # Considerar los contornos más altos que anchos (dígitos)
            if w / h < 1:
                # Ampliar el rectángulo de recorte verticalmente
                y_ini = y - h//10
                y_fin = y + h + h//10
                
                # Calcular el margen horizontal para centrar el recorte
                w_portion = (int(y_fin - y_ini) - w) // 2

                # Validar que el recorte esté dentro de la imagen
                if y_ini > 0 and (x - w_portion) > 0:
                    # Reiniciar la imagen de predicción
                    image_prediction = np.zeros(shape=(250, 200, 3))

                    # Recortar la región de interés del dígito y 
                    # redimensionarlo a 28 x 28
                    crop_image = binary[y_ini: y_fin, x - w_portion: x + w + w_portion]
                    crop_resize_image = cv2.resize(crop_image, (28, 28))

                    # Normalizar los valores de píxel entre 0 y 1, 
                    # luego aplanarla, como se hizo en el entrenamiento del modelo
                    input_image = crop_resize_image.astype("float32") / 255.0
                    input_image = input_image.reshape(1, 28 * 28)

                    # Realizar la predicción con el modelo
                    prediction = model.predict(input_image)
                    predicted_class = prediction.argmax(axis=1)[0]

                    # Dibujar rectángulos en el frame: el original y el extendido
                    cv2.rectangle(frame, (x, y), (x + w, y+ h), (255, 0, 0), 1)
                    cv2.rectangle(frame, (x - w_portion, y_ini), (x + w + w_portion, y_fin), (0, 255, 0), 1)

                    # Visualizar el resultado en la imagen de predicción
                    cv2.putText(image_prediction, 
                                f"Prediction ({prediction[0][predicted_class]*100:.1f}%):", 
                                (5, 20), 
                                1, 
                                1.2, 
                                (0, 255, 255), 
                                1)
                    cv2.putText(image_prediction, 
                                str(predicted_class), 
                                (5, 240), 
                                1, 
                                20,
                                (0, 255, 255),
                                3)

                    # Visualizar las ventanas de recorte
                    cv2.imshow("crop_image", crop_image)
                    cv2.imshow("crop_resize_image", crop_resize_image)

    cv2.imshow("Frame", frame)
    cv2.imshow("image_prediction", image_prediction)
    cv2.imshow("binary", binary)

    # Salir con la tecla ESC (código ASCII 27)
    if cv2.waitKey(1) & 0xFF == 27:
        break

# Liberar la cámara y cerrar todas las ventanas
cap.release()
cv2.destroyAllWindows()

🤔 ¿Qué pudo fallar con las predicciones del modelo (Mnist)?

Durante las pruebas, nuestro modelo tuvo algunos errores de predicción, esto pudo haberse ocasionado por lo siguiente:

Todo esto demuestra que para obtener buenos resultados, es fundamental que el tipo de datos que usamos durante la predicción sea lo más parecido posible a los datos de entrenamiento. Y que la arquitectura del modelo esté bien alineada con el tipo de problema que queremos resolver. Así que cuando abordemos las redes neuronales convolucionales, volveremos a desplegar el modelo y comprobar si obtenemos mejores resultados. 

¡Y listo, Omesitos! Hemos creado un sistema capaz de reconocer números escritos a mano con una webcam y un modelo de TensorFlow. Nos vemos en el siguiente post. Cuídate mucho, chao chao.

Salir de la versión móvil