👱‍♂️ Alineamiento de rostros 👩 | OpenCV – MediaPipe – Python

Por Administrador

¿Quieres mejorar los resultados del reconocimiento facial 🤔?. Puedes aplicar el alineamiento de rostros en las imágenes con las que vayas a crear tu modelo y con las que hagas pruebas. ¡Anímate a probarlo!

CONTENIDO

Alineamiento de rostros

  • ¡Vamos con la programación!
    • Detección de rostro y ojos
    • Creando un triángulo rectángulo
    • ¡Calculemos el ángulo de inclinación del rostro!
    • Rotando la imagen de entrada para alinear el rostro
    • Recortando el rostro alineado

Alineamiento de rostros

Figura 1: Alineamiento de rostro.

En este tutorial veremos como alinear un rostro presente en una imagen, para ello tomaremos la imagen de entrada, detectaremos el rostro y en este, los ojos de la persona. Con los datos de la ubicación de los ojos podremos calcular la inclinación del rostro, lo que nos permitirá alinear la imagen.

Figura 2: Proceso para realizar la alineación de rostro.

Este procedimiento lo podemos usar por ejemplo en reconocimiento facial, para alinear todos los rostros que usaríamos para entrenar un modelo (como lo vimos en este video), mejorando así los resultados del reconocimiento, ya que todas las imágenes de rostros tendrían una disposición similar. Una vez que tenemos esto en cuenta, podremos seguir adelante.

Figura 3: Ilustración del alineamiento de rostros para el reconocimiento facial.

¡Vamos con la programación!

Para una explicación más detallada del programa que veremos a continuación, por favor dirígete al videotutorial de mi canal.

Para la realización de este programa vamos a ayudarnos de MediaPipe Face Detection, por lo que es importante que conozcamos el procedimiento para aplicar esta solución (no olvides darle un vistazo al tutorial 😉). Además cabe resaltar que usamos esta solución, ya que esta nos entregará un cuadro delimitador y varios puntos claves del rostro. Así que vamos a por ello.

Detección de rostro y ojos

import cv2
import mediapipe as mp
import numpy as np
from math import acos, degrees

mp_face_detection = mp.solutions.face_detection
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)

with mp_face_detection.FaceDetection(
     min_detection_confidence=0.5) as face_detection:

     while True:
          ret, frame = cap.read()
          if ret == False:
               break

          frame = cv2.flip(frame, 1)
          height, width, _ = frame.shape
          frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
          results = face_detection.process(frame_rgb)

          if results.detections is not None:
               for detection in results.detections:
                    # Ojo 1
                    x1 = int(detection.location_data.relative_keypoints[0].x * width)
                    y1 = int(detection.location_data.relative_keypoints[0].y * height)

                    # Ojo 2
                    x2 = int(detection.location_data.relative_keypoints[1].x * width)
                    y2 = int(detection.location_data.relative_keypoints[1].y * height)

En esta sección accedemos a las coordenadas de los ojos izquierdo y derecho del rostro detectado, estos se almacenarán en x1, y1 y x2, y2. Una vez conocidos estos datos podremos graficar un círculo en ellos y una línea que una estos dos puntos, obtendríamos algo como lo siguiente:

Figura 4: Visualización de los puntos obtenidos correspondientes a los ojos, y además hemos dibujado una línea que une dichos puntos.

Creando un triángulo rectángulo

Ya hemos conseguido los puntos correspondientes a los ojos y los hemos unido. Ahora nuestro objetivo será formar un triángulo rectángulo que nos ayudará a encontrar el ángulo de inclinación del rostro para poder corregirlo.  

Figura 5: Ilustración del procedimiento realizado hasta ahora.

Entonces, supongamos que tenemos los rostros de la figura 5, allí cada uno de ellos cuenta con una inclinación hacia la izquierda y derecha. Y lo que hemos hecho hasta ahora es unir las coordenadas de los ojos, el siguiente paso será dibujar una línea horizontal desde las coordenadas del Ojo1 a las coordenadas  x2, y1. Y a este nuevo punto lo uniremos con las coordenadas del Ojo 2. 

Figura 6: Construcción del triángulo rectángulo.

De este modo podremos formar el triángulo rectángulo. Veamos como se vería la visualización del triángulo.

Figura 7: Visualización del triángulo rectángulo.

NOTA: La programación para la visualización de este triángulo rectángulo la veremos más adelante, es importante señalarlo ahora, ya que con ayuda de este triángulo vamos a obtener el ángulo de inclinación del rostro.

¡Calculemos el ángulo de inclinación del rostro!

Una vez que tenemos nuestro triángulo rectángulo podremos calcular el ángulo de inclinación que tiene el rostro. En mi caso voy a ayudarme de la función coseno para calcularlo. 

Figura 8: Procedimiento para encontrar el ángulo que forma el Ojo1 con respecto al Ojo 2 y al otro punto. Para ello usaremos la función coseno.

Entonces necesitaríamos del cateto adyacente que llamaremos l1, y la hipotenusa que llamaremos d_eyes, sin embargo, estos datos aún no los tenemos por lo que debemos calcularlos. Para ello nos ayudaremos del cálculo de la distancia euclidiana o distancia entre dos puntos.

p1 = np.array([x1, y1])
p2 = np.array([x2, y2])
p3 = np.array([x2, y1])

# Obtenemos las distancias de: d_eyes, l1
d_eyes = np.linalg.norm(p1 - p2)
l1 = np.linalg.norm(p1 - p3)

Ya tenemos el cateto adyacente y la hipotenusa de nuestro triángulo rectángulo, ahora podremos aplicar el arco coseno y de este modo obtendremos el ángulo. 

Figura 9: Aplicamos el arco coseno para poder calcular el ángulo.

En programación sería…

# Calcular el ángulo formado por d_eyes y l1
angle = degrees(acos(l1 / d_eyes))

Hasta aquí ya tendríamos el ángulo de inclinación del rostro, podríamos visualizarlo cerca del vértice Ojo1.

Figura 10: Visualización del ángulo obtenido en el Ojo1.

Rotando la imagen de entrada para alinear el rostro

Ahora que hemos encontrado el ángulo de inclinación necesitamos saber cuando este es positivo o negativo, para de este modo rotar de forma inversa la imagen de entrada y conseguir alinear el rostro.

# Determinar si el ángulo es positivo o negativo
if y1 < y2:
     angle = - angle

# Rotar la imagen de entrada, para alinear el rostro
M = cv2.getRotationMatrix2D((width // 2, height // 2), -angle, 1)
aligned_image = cv2.warpAffine(frame, M, (width, height))
cv2.imshow("Aligned_image", aligned_image)

Ahora si veamos las líneas correspondientes a la visualización que nos había quedado pendientes.

# Visualizar datos
cv2.putText(frame, "Ojo1", (x1 - 60, y1), 1, 1.5, (0, 255, 0), 2)
cv2.putText(frame, "Ojo2", (x2 + 10, y2), 1, 1.5, (0, 128, 255), 2)
cv2.putText(frame, str(int(angle)), (x1 - 35, y1 + 15), 1, 1.2, (0, 255, 0), 2)

# Lados del triángulo
cv2.line(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.line(frame, (x1, y1), (x2, y1), (211, 0, 148), 2)
cv2.line(frame, (x2, y2), (x2, y1), (0, 128, 255), 2)

# Círculos en cada uno de los vértices del triángulo
cv2.circle(frame, (x1, y1), 5, (0, 255, 0), -1)
cv2.circle(frame, (x2, y2), 5, (0, 128, 255), -1)
cv2.circle(frame, (x2, y1), 5, (211, 0, 148), -1)

Veamos lo que obtenemos al rotar la imagen:

Figura 11: (Izq) Visualización de la imagen de entrada dibujando el triángulo rectángulo y el ángulo obtenido. (Der) Visualización de la imagen rotada que alinea el rostro.

Recortando el rostro alineado

Ahora para poder recortar el rostro alineado vamos a aplicar nuevamente la detección de rostros con MediaPipe. Puede que el aplicar nuevamente la detección de rostros no sea una muy buena elección, debido a que estamos adicionando más costo computacional, pero por ahora lo dejaremos así.

                    # Detección facial 2
                    results2 = face_detection.process(cv2.cvtColor(aligned_image, cv2.COLOR_BGR2RGB))

                    if results2.detections is not None:
                         for detection in results2.detections:
                              xmin = int(detection.location_data.relative_bounding_box.xmin * width)
                              ymin = int(detection.location_data.relative_bounding_box.ymin * height)
                              w = int(detection.location_data.relative_bounding_box.width * width)
                              h = int(detection.location_data.relative_bounding_box.height * height)

                              if xmin < 0 or ymin < 0:
                                   continue
                              
                              aligned_face = aligned_image[ymin : ymin + h, xmin : xmin + w]
                              cv2.imshow("aligned_face", aligned_face)

          cv2.imshow("Frame", frame)
          k = cv2.waitKey(1) & 0xFF
          if k == 27:
               break

cap.release()
cv2.destroyAllWindows()

Ahora si veamos los resultados. 🤩

Figura 12: Resultados de aplicar el alineamiento de rostros, además hemos recortado el rostro alineado.

Como podemos ver en la figura 12, hemos conseguido alinear el rostro, he incluso lo hemos recortado de la imagen rotada. Espero que te haya gustado el videotutorial y también espero que te animes a probarlo. ¡Ten un lindo día!. 🙂