Cómo Superponer un Laberinto en Tiempo Real en tu Cámara Web con OpenCV

En este artículo exploraremos cómo superponer un laberinto en tiempo real en la vista de tu cámara web utilizando la poderosa biblioteca OpenCV de Python. Esta emocionante aplicación combina la visión por computadora y la programación creativa para crear una experiencia interactiva. Te explicaré paso a paso a construir este proyecto, como también a cargar tus propias imágenes y figuras, y superponerlas en tu cámara web. ¡Vamos a empezar!

Contenido del post

1. Preparación del entorno de desarrollo

Instalación de librerías

Para iniciar, vamos a instalar la librería que nos permite controlar y dominar los graficos en pantalla desde Python. Esta librería se llama OpenCV, y para poder utilizarla vamos a ejecutar desde nuestra terminal de comandos la siguiente instrucción:

				
					pip install pygame opencv-python
				
			

En caso de que estés trabajando en MacOS, te sugiero seguir este tutorial oficial de OpenCV, dado que algunas veces se puede presentar problemas de compatibilidad con algunas versiones del sistema operativo. También usaremos la librería de NumPy para crear el mapa del laberinto que vamos a dibujar en la interfaz. Para instalar la librería puedes usar el siguiente comando:

				
					pip install numpy
				
			

Una vez instaladas las bibliotecas, vamos a importarlas en la cabecera de nuestro código:

				
					import cv2
import numpy as np
				
			

2. Captura de Video en Tiempo Real

Inicialización de la captura de video desde la cámara web

Para iniciar vamos a utilizar un función de OpenCV que nos permite acceder a la cámara integrada de nuestro computador (en el caso de portátiles), o también a cámaras externas conectadas por USB:

				
					# Crea la captura de video
cap = cv2.VideoCapture(0)
				
			

En caso de que tengas más de una cámara conectada en tu computador, lo único que debes hacer es cambiar el número 0 entre paréntesis, por el número 1 o 2, de acuerdo al número de cámaras que tengas conectadas. Así podrás acceder a la imagen de la cámara que desees.

Configuración del tamaño de la ventana

Después de acceder a la imagen de nuestra cámara, vamos a definir el tamaño de la ventana que deseamos. Lo haremos creando dos variables: width y height que corresponden al ancho y alto respectivamente. Para este caso, vamos a definir una tamaño de 800 x 650 píxeles, y posteriormente vamos a asignarle estos valores a nuestra imagen de video usando la instrución set().

				
					# Ancho y alto de la ventana
width, height = 800, 650

# Configurar el tamaño de la ventana
cap.set(3, width)  # 3 corresponde al ancho
cap.set(4, height)  # 4 corresponde a la altura
				
			
Test de captura de video
Ahora vamos a hacer un test de captura de video para comprobar si estamos accediendo correctamente a la imagen de nuestra cámara web. Para ello vamos a crear un ciclo while en el que extraemos los frames (fotogramas) de nuestro video y los mostramos con la función imshow() de OpenCV.
				
					# Ciclo principal de lectura
while True:
    # Lee los fotogramas
    ret, frame = cap.read()
    # Muestra los fotogramas en la ventana
    cv2.imshow('Laberinto Real Time con OpenCV', frame)
				
			

Con la función cap.read(), capturamos los fotogramas desde la fuente de video, teniendo en cuenta que:

  • ret es una variable booleana que indica si la captura del fotograma fue exitosa (True) o no (False).
  • frame es una variable que almacena el fotograma capturado como una matriz NumPy. Cada elemento de la matriz representa un píxel de la imagen, lo que te permite realizar procesamiento de imagen en el fotograma.

Con la función imshow(), mostramos los fotogramas almacenados en la variable frame, e igualmente le asignamos un nombre a nuestra ventana, que en nuestro caso es ‘Laberinto Real Time con OpenCV’Finalmente, para realizar correctamente nuestro test de captura, agregaremos una condición para identificar si la tecla ESC es presionada, y si es así, nos salimos del ciclo principal y después pausamos la captura de video y cerramos la ventana creada. De modo que si hemos realizado los pasos anteriores y ejecutamos el código, deberíamos ver la imagen de nuestra cámara en tiempo real en una ventana de 800 x 650 píxeles.

				
					# Ciclo principal de lectura
while True:
    
    # Leer los fotogramas
    ret, frame = cap.read()

    # Mostrar los fotogramas en la ventana
    cv2.imshow('Laberinto Real Time con OpenCV', frame)

    # Si la tecla ESC es presionada, se sale del ciclo
    if cv2.waitKey(1) & 0xFF == 27:
        break
    
# Libera la captura de video
cap.release()

# Cierra la ventana
cv2.destroyAllWindows()
				
			

Si pudiste acceder con éxito a la imagen de tu cámara, puedes ver que la imagen está invertida horizontalmente. Si deseas ajustarla en modo espejo, solo basta con agregar una línea inmediatamente después de la instrucción cap.read():

				
					# Voltear horizontalmente el fotograma
frame = cv2.flip(frame, 1)
				
			
Figura 1. Test de visualización cámara web SIN flip.
Figura 2. Test de visualización de cámara web CON flip.
Redimensión adicional de la imagen

A pesar de que logramos acceder perfectamente a la imagen de nuestra cámara web, las dimensiones de la ventana no son exactamente las mismas que definimos anteriormente. Aunque se establezca el tamaño deseado de la ventana, no garantiza que el fotograma capturado coincida exactamente con esas dimensiones. Por lo tanto, para garantizar que el tamaño del fotograma coincida exactamente con el tamaño de la ventana, en el ciclo principal vamos a redimensionar el fotograma usando la función cv2.resize.

				
					# Voltea horizontalmente el fotograma
frame = cv2.flip(frame, 1)

# Cambia el tamaño del fotograma para que coincida con el tamaño de la ventana
frame = cv2.resize(frame, (width, height), interpolation=cv2.INTER_CUBIC)

# Muestra los fotogramas en la ventana
cv2.imshow('Laberinto Real Time con OpenCV', frame)
				
			

El parámetro cv2.INTER_CUBIC es opcional y se utiliza para especificar el método de interpolación que se utilizará al cambiar el tamaño de la imagen. En nuestro caso, empleamos un método de interpolación bicúbica que proporciona una buena calidad en la redimensión de la imagen, aunque puede ser un poco más lento que otros métodos. El método calcula los valores de píxeles en la imagen redimensionada basándose en los valores de píxeles en la imagen original.

3. Dibujar el Laberinto en la Imagen de Video

Creación de una matriz que representa el laberinto

Una vez hemos accedido a la imagen de nuestra cámara, el siguiente paso es crear nuestro laberinto. Como ya sabes, enPython existen muchas formas para hacerlo. Para esta ocasión haremos uso de una matriz de NumPy, dada la facilidad de acceso y procesamiento de los datos. Para declarar una matriz con NumPy podemos usar la instrucción np.array(), y declarar la matriz teniendo en cuenta la estructura de la figura 3. Es importante que te asegures de declarar la matriz (laberinto) antes del ciclo principal.

				
					# Matriz del laberinto
laberinto = np.array([
  [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
  [1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
  [1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1],
  [1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1],
  [1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1],
  [1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
  [1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1],
  [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1],
  [1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1],
  [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1],
  [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1],
  [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
  [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
])
				
			
Figura 3. Estructura Matrices en NumPy.

Como puedes ver, la matriz que hemos declarado tiene 16 columnas y 13 filas. Hemos definido estos valores con el fin de que nuestro laberinto abarque el total de la ventana creada anteriormente (donde mostramos la imagen de la cámara), considerando un tamaño fijo para cada celda de 50×50 px, lo que significa que: 800 px = 16 columnas, y 650 px = 13 filas.

Dibujo de figuras en la imagen de video
Ahora que ya declaramos el laberinto, le asignaremos el tamaño de la celda (50×50 px) a la variable square_size, y posteriormente usarla para dibujar en la ventana. Y para lograr un efecto de transparencia en las celdas donde dibujemos las paredes del laberinto, vamos a usar la variable alpha para definir el nivel de transparencia entre 0 y 1.
				
					# Tamaño de la celda
square_size = 50

# Nivel de transparencia
alpha = 0.7
				
			

Dentro del bucle principal, vamos a realizar una copia de los frames que leemos de nuestra imagen de video y la guardaremos en la variable overlay. Con esta variable vamos a dibujar las paredes del laberinto, y al final superponerla con la imagen principal.

				
					# Crear una copia del fotograma para superponer el laberinto
overlay = frame.copy()
				
			
Dibujo laberinto en la imagen de video

Lo primero que haremos para dibujar las celdas, será ubicar todas las posiciones del laberinto donde haya ‘1’, dado que son puntos donde debemos mostrar las paredes de nuestro laberinto. Para lograrlo, usaremos la función np.where() que nos permite realizar una búsqueda rápida y efectiva de las posiciones deseadas. Dado que la función np.where() nos devuelve un par de arrays con los índices donde se encuentran las paredes, vamos a unificarlas en una lista usando la función list(zip()), teniendo en cuenta que debemos multiplicar los valores de cada coordenada por el tamaño de la celda, es decir, 50 píxeles para que se pueda dibujar de forma correcta en la interfaz.

				
					# Obtiene las coordenadas de las paredes
y_indices, x_indices = np.where(laberinto == 1)

# Lista que contiene todas las coordenadas (x, y) correspondientes a las paredes
list_celdas = list(zip(x_indices * square_size, y_indices * square_size))

# Dibuja las celdas en la imagen de superposición de color rojo
for x, y in list_celdas:
    cv2.rectangle(overlay, (x, y), (x + square_size, y + square_size), (0, 0, 255), -1)
    
# Combina la imagen de superposición con el fotograma original
frame = cv2.addWeighted(overlay, alpha, frame, 1 - alpha, 0)
				
			
Figura 3. Valores de la variable "list_celdas".

Como puedes ver en el código, después de obtener la lista de los índices de las paredes, la recorremos con un ciclo for y dibujamos las todas celdas (paredes) de color rojo usando la función cv2.rectangle. Finalmente, con cv2.addWeighted() unimos los frames donde dibujamos el laberinto, con los frames de la imagen original, considerando el nivel de transparencia definido anteriormente. Si todos los pasos anteriores los hemos realizado correctamente, deberíamos de tener el resultado que puedes ver en la figura 4.

Figura 4. Laberinto con paredes rojas superpuesto en imagen de video en tiempo real.

Para mejor entendimiento de la forma en cómo pintamos las celdas de color rojo, puedes ver a continuación cómo funciones las funciones empleadas en esta sección del código:

4. Personalización del laberinto

Carga de imagen personalizada

Ahora que sabemos cómo superponer recuadros sobre la imagen de video original, llegó la hora de personalizar nuestro laberinto con nuestras propias imágenes de paredes. En Kenney podrás encontrar una biblioteca gigante de imágenes ya listas para emplear en tu proyecto, solo debes explorar y descargar las que desees usar. Para nuestro caso, he preparado 4 diferentes imágenes para probar en nuestro proyecto, y las he guardado en una carpeta llamada «Images». Para cargar la imagen, lo único que debemos hacer es indicar la ruta donde se encuentra la imagen y guardarla en una variable. Y dado que queremos que la imagen se ajuste al tamaño de la celda (50×50 px), vamos a redimensionar la imagen usando la función cv2.resize():

				
					# Cargar una imagen para reemplazar los cuadros rojos
image = cv2.imread("Images/pared1.png")
# Redimensionar el tamaño de la imagen
image = cv2.resize(image, (square_size, square_size))
				
			
Dibujar la imagen cargada sobre la imagen de video

Dentro del bucle principal solo debemos hacer un pequeño ajuste para dibujar nuestra imagen. Eliminaremos la línea list_celdas, y modificaremos la declaración del ciclo for de la siguiente forma:

				
					# Dibuja la imagen en las coordenadas de las paredes
for x, y in zip(x_indices * square_size, y_indices * square_size):
    overlay[y:y + square_size, x:x + square_size] = image
				
			

Ten en cuenta que overlay[y:y + square_size, x:x + square_size] = image,  toma un fragmento de la imagen overlay (la copia del fotograma original) y se reemplaza con image (la imagen que hemos cargado). Las coordenadas (x, y) se utilizan para especificar la región en la que se reemplaza la imagen.

  • y:y + square_size, representa el rango vertical de píxeles, y
  • x:x + square_size, representa el rango horizontal de píxeles.
Resultado final con cada una de las imágenes

Código Fuente

Puedes encontrar todo el material en mi repo de GitHub.

Desafío: Crea tu Propio Laberinto y Compártelo

En este tutorial, hemos explorado cómo crear un emocionante proyecto de laberinto superpuesto sobre la imagen de video de nuestra cámara web, totalmente en tiempo real utilizando Python y OpenCV. Hemos aprendido a representar el laberinto en una matriz, superponerlo sobre nuestro video y reemplazar las paredes del laberinto con imágenes personalizadas. Ahora es tu turno de ser creativo y poner a prueba tus habilidades. ¿Por qué no intentar desarrollar tu propio laberinto? Puedes personalizarlo, cambiar las imágenes, o incluso añadir características adicionales para hacerlo aún más desafiante y entretenido. Me encantaría ver lo que puedes crear, así que no dudes en compartir tus resultados, preguntas o ideas en la sección de comentarios. Espero que este proyecto haya sido inspirador y que te diviertas experimentando con tus propios diseños y creaciones.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *