🤴 Creando Filtros como los de Instagram | OpenCV Python

Por Administrador

Bienvenidos a un nuevo tutorial. En este veremos como realizar un filtro o efecto parecido a los de Instagram en donde trataremos de ubicar algunas imágenes tales como: un gorrito de Navidad, una tiara o un 2021, sobre la frente. ¡No alarguemos más la introducción y vamos con el proceso!.

CONTENIDO

  • ¿Qué necesitamos primero?
    • Imágenes de entrada con las que vamos a trabajar
  • Iniciamos con la programación para crear un filtro como los de Instagram con OpenCV y Python
    • Leer el video de entrada
    • Leer la imagen de entrada
    • Detección de los rostros presentes en el video
    • Redimensionamos la imagen de entrada y la ubicamos aproximadamente en la frente
    • Pero, ¿qué pasa si queremos que se dibuje la imagen a pesar de que no tenga tanto espacio en la parte superior?
    • Incrustar el gorro de Navidad en el video
  • ¡Veamos los resultados del filtro creado con OpenCV y Python!

¿Qué necesitamos primero?

En primer lugar vamos a necesitar de las imágenes con las que desees crear el filtro y un video de entrada, veamos:

Imágenes de entrada con las que vamos a trabajar

Para este tutorial he preparado tres imágenes, de las cuales realizaré el tutorial con una, mientras que las otras las probaremos al final. Estas imágenes poseen formato .png además de cierta transparencia. A continuación tenemos la imagen correspondiente al gorrito de Navidad:

Figura 1: Imagen de entrada.

Para mostrarte la transparencia que poseen las tres imágenes, he hecho algunas capturas mientras estaban siendo leídas por GIMP. Las podrás ver a continuación:

Figura 2: Visualización de las imágenes con su transparencia. Estas han sido tomadas luego de leerlas en GIMP.

Hago hincapié en esto de las transparencias, ya que estas van a ser representadas con una imagen binaria, que nos ayudará más adelante con el proceso de construcción de nuestro filtro. En cuanto al video en donde van a ser ubicadas estas imágenes una vez detectado el rostro, usaremos un video en directo o videostreaming. Esto no quita que puedas probar el filtro con otros videos de entrada. Ahora si, ¡vamos con el código!.

Iniciamos con la programación para crear un filtro como los de Instagram con OpenCV y Python

Para empezar debemos leer el video de entrada, que es un proceso que ya hemos visto en tutoriales anteriores, luego leeremos la imagen que deseamos que se ubique como filtro, después detectaremos los rostros presentes en el video y seguiremos ubicando la imagen del gorrito en el lugar correcto, veamos:

Leer el video de entrada

import cv2
import imutils

# Videostreaming o video de entrada
cap = cv2.VideoCapture(0,cv2.CAP_DSHOW)

Línea 1 y 2: Leemos OpenCV e imutils. Este último lo utilizo para redimensionar más fácilmente las imágenes sin perder su relación de aspecto.

Línea 5: Leemos el video de entrada, en este caso un video en directo o videstreaming.

Leer la imágen de entrada

# Lectura de la imagen a incrustar en el video
image = cv2.imread('gorro_navidad.png', cv2.IMREAD_UNCHANGED)
#image = cv2.imread('tiara.png', cv2.IMREAD_UNCHANGED)
#image = cv2.imread('2021.png', cv2.IMREAD_UNCHANGED)

Línea 8: Leermos la imagen correspondiente al gorrito de navidad, para crear nuestro filtro. Puedes notar que el segundo parámetro en cv2.imread que estoy usando es cv2.IMREAD_UNCHANGED, esto lo hago para que de la imagen lea no solo los tres primeros canales sino también el canal alfa que corresponde a la transparencia de la imagen.

Si quisieras leer el canal alfa de la imagen de entrada image, bastaría con: cv2.imshow('Canal alfa', image[:, :, 3]), en donde se dibujarán todas las filas, todas las columnas, pero solo el canal en la posición 3 (cuarto canal) que corresponde a la transparencia. Veríamos algo como lo siguiente:

Figura 3: Visualización del cuarto canal de la imagen de entrada, que muestra su transparencia.

Como puedes ver en la figura 3, en blanco representará el gorrito mientras que en negro la transparencia.

En cuanto a las líneas 9 y 10, las he puesto para poder probar al final los resultados con estas otras imágenes.

Detección de los rostros presentes en el video

# Detector de rostros
faceClassif = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')

while True:

    ret, frame = cap.read()
    if ret == False: break
    frame = imutils.resize(frame, width=640)

    # Detección de los rostros presentes en el fotograma
    faces = faceClassif.detectMultiScale(frame, 1.3, 5)

    for (x, y, w, h) in faces:
        #cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0),2)

El proceso para la detección de rostros presentes en una imagen o video ya lo habíamos visto en: ? DETECCIÓN DE ROSTROS ? con Haar Cascades Python – OpenCV. Por lo que aquí lo describiré brevemente:

Línea 13: Leemos el detector de rostros con haar cascades.

Línea 17 a 22: Leemos los fotogramas, luego los redimensionamos a 640 pixeles de ancho, para en la línea 22 detectar los rostros que están presentes en los fotogramas.

Línea 24 y 25: Extraemos los datos x, y, ancho y alto de los rostros encontrados, mientras que con cv2.rectangle dibujamos un rectángulo que rodee cada rostro detectado (he comentado la línea 25 ya que no necesitamos la visualización del rectángulo para el resultado final, es para guiarnos por ahora). Veremos algo como lo siguiente:

Figura 4: Visualización de la detección de rostros con OpenCV y Python.

Redimensionamos la imagen de entrada y la ubicamos aproximadamente en la frente

# Redimensionar la imagen de entrada de acuerdo al ancho del
# rostro detectado
resized_image = imutils.resize(image, width=w)
filas_image = resized_image.shape[0]
col_image = w

# Determinar una porción del alto de la imagen de entrada 
# redimensionada
porcion_alto = filas_image // 4

dif = 0

# Si existe suficiente espacio sobre el rostro detectado
# para insertar la imagen de entrada resimensionada
# se visualizará dicha imagen
if y + porcion_alto - filas_image >= 0:

    # Tomamos la sección de frame, en donde se va a ubicar
    # el gorro/tiara
    n_frame = frame[y + porcion_alto - filas_image : y + porcion_alto,
        x : x + col_image]

Línea 29 a 31: Redimensionamos la imagen de entrada al mismo ancho que el ancho del rostro detectado, luego determinamos el número de filas y columnas que tiene la nueva imagen redimensionada.

Línea 35: Tomamos una porción del número de filas de la imagen redimensionada, esto nos ayudará para ubicar la imagen redimensionada aproximadamente en la frente.

Línea 37: Declaramos la variable dif y la igualamos a 0. De esta variable hablaremos más adelante.

Si quisiéramos insertar la imagen de entrada redimensionada en el video, tenemos que tomar en cuenta algo. Y es que obtendremos un error si la imagen que se desea ubicar no tiene suficiente espacio y necesitaría estar más arriba del video, es decir algo como esto:

Figura 5: Representación del gorro cuando este sobresale al video. Si esto pasa podemos obtener errores.

Debido a esto podríamos obtener errores, por lo que será necesario aplicar una condición y deshacernos de este error, veremos esto en la línea 42.

Línea 42 a 47: Vamos a asegurarnos de que exista suficiente espacio para que el gorro pueda ser ubicado sobre la cabeza de la persona, por ello en la línea 42 tomamos como referencia y + porcion_alto - filas_image y este valor debe ser mayor a 0 para no tener problemas. Recuerda que estamos sumando el valor de porcion_alto, para que el gorro se ubique en la frente. 

Luego en la línea 46 estamos tomando la porción de frame donde va a ser incrustada la imagen del gorrito.

Pero antes de continuar podremos ver la diferencia entre no y si sumar porcion_alto, a continuación:

Figura 6: Izq. Visualización del gorrito de Navidad sobre el recuadro que rodea el rostro detectado. Der. Visualización del gorrito de Navidad sobre la frente.

Pero, ¿qué pasa si queremos que se dibuje la imagen a pesar de que no tenga tanto espacio en la parte superior?

La verdad es que puede ser un poco molesto que uno tenga que agacharse para que se pueda dibujar el gorrito sobre la frente, es por eso que vamos a tomar como dato la cantidad de pixeles que exceden al video en cuanto al alto de la imagen. Pero para entenderlo mejor ¡ilustrémoslo!.

Figura 7: Ilustración de como se tomaría el valor de dif. Este valor sería el inicio para dibujar la imagen del gorrito con respecto al alto.

Entonces como podemos apreciar en la figura 7, estamos calculando dif que será el pixel de donde se empezará a dibujar el gorro. De este modo podremos dibujarlo a pesar de que no exista suficiente espacio en la parte superior.

else:
    # Determinamos la sección de la imagen que excede a la del video
    dif = abs(y + porcion_alto - filas_image) 
    # Tomamos la sección de frame, en donde se va a ubicar
    # el gorro/tiara
    n_frame = frame[0 : y + porcion_alto,
        x : x + col_image]

Línea 50: Determinamos dif, en base a y + porcion_alto - filas_image y le aplicamos valor absoluto para evitar cualquier error. Con este valor ya tendremos el pixel desde donde se va a dibujar el gorrito.

Línea 53: Tomamos la porción de frame donde va a ser incrustada la imagen del gorrito.

Figura 8: En el video, el gorro se va a dibujar  de 0 hasta y + porcion_alto, en el alto.

Debido a que no se tiene todo el espacio para ubicar el gorro, se va a dibujar el alto de este desde 0 a y + porcion_alto. Como podemos ver en la figura 8.

Incrustar el gorro de Navidad en el video

Figura 9: Ilustración de las imágenes que necesitamos para sumar y conseguir el filtro.

Para incrustar la imagen redimensionada en el video necesitaremos de dos imágenes:

  • La imagen donde se muestre en negro el fondo y a color el gorro (puedes verlo en la figura 9, en la sección izquierda superior).
  • Parte de frame en donde en negro se muestre el lugar en donde debería estar ubicado el gorro y el resto normal (puedes verlo en la figura 9, en la sección izquierda inferior).

Una vez que tengamos estas dos imágenes bastará con sumarlas y ubicar la imagen resultante en el video.

# Determinamos la máscara que posee la imagen de entrada
# redimensionada y también la invertimos
mask = resized_image[:, :, 3]
mask_inv = cv2.bitwise_not(mask)
    
# Creamos una imagen con fondo negro y el gorro/tiara/2021
# Luego creamos una imagen en donde en el fondo esté frame
# y en negro el gorro/tiara/2021
bg_black = cv2.bitwise_and(resized_image, resized_image, mask=mask)
bg_black = bg_black[dif:, :, 0:3]
bg_frame = cv2.bitwise_and(n_frame, n_frame, mask=mask_inv[dif:,:])

Línea 58 y 59: De la imagen redimensionada vamos a tomar la imagen binaria correspondiente al canal alfa e igualaremos a mask. A esta última le aplicaremos cv2.bitwise_not para invertir dicha imagen. Si visualizamos estas imágenes veríamos lo siguiente:

Figura 10: Visualización de mask y mask_inv.

Línea 64 y 65: Con ayuda de cv2.bitwise_and construimos la imagen del gorrito a color y el fondo en negro. Mientras que en la línea 65 tomamos los primeros 3 canales para posteriormente sumarlos y lograr el filtro. Recuerda que la imagen del gorrito tenía un canal extra que es el correspondiente a la transparencia por ello solo tomamos los 3 primeros.

Línea 66: También nos ayudaremos de cv2.bitwise_and  para constriur la imagen en donde el fondo se ve el video y en negro el gorrito.

Figura 11: Visualización de bg_black y bg_frame.

# Sumamos las dos imágenes obtenidas
result = cv2.add(bg_black, bg_frame)
if y + porcion_alto - filas_image >= 0:
    frame[y + porcion_alto - filas_image : y + porcion_alto, x : x + col_image] = result

else:
    frame[0 : y + porcion_alto, x : x + col_image] = result

Línea 69: Sumamos las dos imángenes obtenidas en las líneas 65 y 66, y su resultante se almacenará en result.

 Figura 12: Visualización de la suma de bg_black y bg_frame.

Línea 70 y 71: Cuando existe suficiente espacio para visualizar toda la imagen de entrada redimensionada se va a aplicar esta condición y se asigna result.

Línea 73 y 74: Si no existiese suficiente espacio ingresaría al else y por lo tanto se dibujaría una imagen recortada. A esta porción se le asigna result

    cv2.imshow('frame',frame)

    k = cv2.waitKey(1) & 0xFF
    if k == 27:
        break
cap.release()
cv2.destroyAllWindows()

Línea 76 a 82: Procedemos a visualizar frame, añadir que el proceso termine hasta que se presione la tecla ESC y de ese modo termine el video y se cierren las ventanas de visualización creadas.

¡Veamos los resultados del filtro creado con OpenCV y Python!

Figura 13: Resultados del filtro similar a los de Instagram con diferentes imágenes de entrada.

Como podemos apreciar en la figura 13, he usado distintas imágenes de entrada para realizar el filtro. En todas ellas esta imagen se redimensiona al ancho del rostro detectado. Además si el rostro está muy cerca de la sección superior no deja de dibujarse sino que se recorta para lograr un efecto más realista.

Y esto ha sido todo por el tutorial de hoy, espero que lo hayas disfrutado 😉 . ¡Nos vemos en el siguiente!.