Como usar MEDIAPIPE HANDS ?️ | Python – MediaPipe – OpenCV

Por Administrador

MediaPipe me ha sorprendido mucho con las soluciones que nos ofrece, MediaPipe Hands es una de ellas. Esta nos permite detectar manos, identificándolas como izquierda o derecha. Pero además nos proporciona 21 hand landmarks o puntos de referencia con la ubicación de cada uno de los dedos así como la palma de la mano.  Estoy segura de que cuando pruebes esta solución, se te vendrán un montón de aplicaciones a la mente. ¿Quieres saber como usarla?

CONTENIDO:

  • MediaPipe hands
    • Modelos empleados en MediaPipe Hands
      • Modelo para la detección de la palma de la mano
      • Modelo para la detección de los 21 puntos de referencia en la mano
    • Opciones de configuración
      • STATIC_IMAGE_MODE (POR DEFECTO FALSE) 
      • MAX_NUM_HANDS (POR DEFECTO 2) 
      • MIN_DETECTION_CONFIDENCE (POR DEFECTO 0.5) 
      • MIN_TRACKING_CONFIDENCE (POR DEFECTO 0.5) 
    • ¡Ahora si, vamos con el código! 
      • Detección de manos y puntos claves con MediaPipe, en imágenes
        • Salida: multi_handedness
        • Salida: multi_hand_landmarks 
        • Dibujando los 21 puntos de la mano y sus conexiones con MediaPipe
        • Accediendo a los puntos claves mediante su nombre asociados
        • Accediendo a los puntos claves mediante su índice
        • Probando nuestro programa con una imagen que no contiene manos
      •  Detección de manos y puntos claves con MediaPipe, en videos
  • Referencias

NOTA: Antes de pasar al tutorial sobre el uso de MediaPipe Hands, te recuerdo que necesitas instalar este framework en Python, por lo que si aún no lo has hecho, puede seguir este post: ? Como instalar MEDIAPIPE | Python.

MediaPipe Hands

Ahora si, vamos con MediaPipe Hands. Esta es una solución que permite la detección de manos, con 21 puntos de referencias 3D. Permitiendo identificar cada uno de los dedos y palma de la mano. Para ello MediaPipe emplea Machine Learning, de donde han obtenido múltiples modelos que trabajan juntos, para obtener los resultados que podemos apreciar a continuación:

Figura 1: Ejemplo del uso de MediaPipe para manos.

Modelos empleados en MediaPipe Hands

En la documentación de Mediapipe, se nos describe el proceso que realizaron para llegar a obtener estos resultados, así que vamos a hablar un poquito sobre ello. 

Modelo para la detección de la palma de la mano

Figura 2: Ilustración del proceso que realiza Palm Detection Model.

En un principio tenemos un modelo para la detección de la palma de la mano. Este es el que se aplica en toda la imagen, intentando obtener alguna detección. En cuanto este detector haya encontrado una palma, devolverá un cuadro delimitador orientado de la mano. 

Modelo para la detección de los 21 puntos referencia en la mano

Una vez obtenida el área en donde se encuentra la mano, la imagen pasara por un hand landmark model, que es el modelo que permitirá ubicar los 21 puntos de referencia en la imagen dada por la detección de palma. 

Figura 3: Ilustración del proceso que realiza Hand Landmark Model.

Estos 21 hands landmark tienen asociados un nombre cada uno. Ya veremos más adelante como emplear sus nombres e índices para acceder las coordenadas de estos puntos en la imagen. 

Figura 4: 21 puntos de referencia y sus nombres asociados. Su documentación la puedes encontrar aquí: https://google.github.io/mediapipe/solutions/hands#hand-landmark-model

Este proceso de por si ya es muy interesante de analizar, y se puede aplicar a imágenes de entrada. Pero ¿qué pasa con los videos?. Pues bien, en ese caso han desarrollado un método en el cual se aplique la detección de palmas y el modelo hand landmark, en las primeros fotogramas.  

Una vez obtenidos los 21 puntos de referencia, estos son rastreados para calcular la nueva ubicación de la mano. 

De este modo no se está aplicando la detección de palmas a cada momento, sino que se realiza seguimiento o tracking del área ya encontrada en primer lugar. Invocando al detector de palmas únicamente cuando los puntos no se puedan identificar. Reduciendo así la latencia. 

En si este es un resumen del proceso que nos describen en mediapipe, por lo que si quieres profundizar un poquito más estos aspectos, te recomiendo que visites su web y su repositorio.

Opciones de configuración 

Antes de pasar al resto del programa, necesitaremos explorar las opciones de configuración. 

STATIC_IMAGE_MODE (POR DEFECTO FALSE) 

En primer lugar tenemos a static_image_mode, que puede tener valores de True o False. Cuando se le asigna False, entonces trata a las imágeness de entrada como un videostream, de tal manera que aplica el modelo de detección de palma y el modelo hand landmarks en un principio, pero luego realiza tracking para obtener la nueva ubicación de la mano, basándose en los puntos de referencia. De este modo, solo se invocará nuevamente al detector de palmas cuando no se hayan identificado los 21 puntos. 

Cuando se le asigna True, entonces los detectores estarán aplicádose en cada imagen, por lo que es mejor usarla en caso de que se trate de imágenes que no tengan que ver entre sí. 

MAX_NUM_HANDS (POR DEFECTO 2) 

Número máximo de manos por detectar. 

MIN_DETECTION_CONFIDENCE (POR DEFECTO 0.5) 

Valor mínimo de confianza del modelo de detección de manos, para que la detección sea considerada como exitosa. Sus valores comprenden de 0 a 1. 

MIN_TRACKING_CONFIDENCE (POR DEFECTO 0.5) 

Valor mínimo de confianza del modelo de rastreo de los landmark, para que el rastreo de los 21 puntos sea considerado como exitoso. En caso de no serlo, se invocará al detector de manos en la siguiente imagen. 

Este es ignorado si static_image_mode está en True. 

¡Ahora si, vamos con el código! 

Detección de manos y puntos claves con MediaPipe, en imágenes

Como imagen de entrada, tendremos la siguiente:

Figura 5: Imagen de entrada que usaremos a lo largo de este tutorial.

También probaremos más adelante con una imagen que no posea manos, simplemente para probar que nuestro programa actúe correctamente.

Ahora vamos a crear nuevo script llamado manos_imagen_mediapipe.py

import cv2
import mediapipe as mp

mp_drawing = mp.solutions.drawing_utils
mp_hands = mp.solutions.hands

Línea 1 y 2: Importamos OpenCV y MediaPipe con un alias mp. Recuerda que la instalación de MediaPipe la vimos en el post: ? Como instalar MEDIAPIPE | Python. Y una vez que lo instalábamos, este instalaba automáticamente OpenCV. 

Línea 4: Ahora vamos con mp.solutions.drawing_utils, y lo igualamos a mp_drawing. Este nos ayudará más adelante a dibujar los resultados de las detecciones, es decir los 21 puntos y sus conexiones. 

Línea 5: Ya que vamos a emplear la solución hands, vamos a igualar mp.solutions.hands a mp_hands. 

with mp_hands.Hands(
    static_image_mode=True,
    max_num_hands=1,
    min_detection_confidence=0.5) as hands:

Línea 7 a 10: Vamos con las opciones de configuración. En nuestro caso static_image_mode será True ya que estamos tomando como entrada una imagen. max_num_hands 1 aunque podríamos modificarlo a cualquier número entero dependiendo del número de manos que queramos que se detecte, y min_detection_confidence 0.5.

image = cv2.imread("imagen_0001.jpg")
height, width, _ = image.shape

image = cv2.flip(image, 1)
image_rgb =cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

Línea 12 a 16: Ahora procederemos a leer la imagen de entrada con OpenCV, y a obtener el alto y ancho de ella. Además vamos a voltear la imagen horizontalmente. Necesitamos voltearla horizontalmente, ya que para que una mano sea determinada como izquierda o derecha, se asume que la imagen de entrada está reflejada. Necesitaremos también, pasar nuestra imagen de entrada de BGR a RGB, ya que las detecciones se realizan con imágenes en RGB.  

results = hands.process(image_rgb)

Línea 18: Una vez que tenemos nuestra imagen lista, la pasaremos por process, de donde podremos obtener las detecciones, mediante las salidas: multi_handedness y multi_hand_landmarks.

Salida: multi_handedness 

# HANDEDNESS
print('Handedness:', results.multi_handedness)

Línea 21: Imprimamos lo que tenemos en multi_handedness. 

Figura 6: Impresión de la salida handedness, que obtuvimos al realizar el proceso de detección sobre la imagen de entrada, estableciendo max_num_hands=1.

Como puedes ver en la figura 6, obtenemos una colección de datos de la mano detectada. Tenemos el label o la etiqueta en donde podremos identificar si se trata de una mano derecha o izquierda, en este caso Rigth o Left. Además tenemos score, que es la probabilidad estimada de la predicción.  

Recuerda que hemos obtenido los datos de una sola mano, ya que en max_num_hands lo habíamos establecido en 1. 

Si cambiamos el valor de  max_num_hands por 2, y ejecutamos nuevamente el programa, entonces como puedes ver, obtenemos los datos de ambas manos.  

Figura 7: Impresión de la salida handedness, que obtuvimos al realizar el proceso de detección sobre la imagen de entrada, estableciendo max_num_hands=2.

Hasta ahora sabemos que existe una mano derecha y una izquierda en la imagen.

Salida: multi_hand_landmarks 

# HAND LANDMARKS
print('Hand landmarks:', results.multi_hand_landmarks)

Línea 23: Ahora veremos multi_hand_landmarks, que es una colección de manos rastreadas o detectadas, donde cada mano está representado como una lista de 21 puntos, en donde cada punto está compuesto por las coordenadas x, y, z.

Figura 8: Impresión de una porción de la salida multi_hand_landmarks, que obtuvimos al realizar el proceso de detección sobre la imagen de entrada.

Y como podemos en la figura 8, tenemos cada uno de los 21 puntos con sus coordenadas, que están en decimales, y no enteros, como los que hemos trabajado antes, en otros tutoriales para determinar las coordenadas en la imagen. Pero ya veremos más adelante como acceder a estos puntos como coordenadas de la imagen. 

Dibujando los 21 puntos de la mano y sus conexiones con MediaPipe

Ahora vamos a ver como podemos dibujar los 21 puntos de los hand landmarks con ayuda de mediapipe, y luego veremos como acceder a las coordenadas x e y de estos puntos, ya sea por su nombre o por su índice. 

if results.multi_hand_landmarks is not None:	
    # Dibujando los puntos y las conexiones mediante mp_drawing 
    for hand_landmarks in results.multi_hand_landmarks:
        mp_drawing.draw_landmarks(
            image, hand_landmarks, mp_hands.HAND_CONNECTIONS,
            mp_drawing.DrawingSpec(color=(255,255,0), thickness=4, circle_radius=5),
            mp_drawing.DrawingSpec(color=(255,0,255), thickness=4))

Línea 25: Para evitar cualquier problema en la ejecución del programa, cuando no hayan manos presentes en la imagen. Voy a añadir la condición de que se realice a visualización de los puntos, siempre  y cuando results.multi_hand_landmarks no sea None. 

Línea 27:  Vamos a usar un for para obtener cada grupo de 21 puntos por cada mano detectada.

Línea 28 a 31: Comenzaremos dibujando los puntos y conexiones con ayuda del propio mediaPipe, para ello usaremos mp_drawing.draw_landmarks. Allí tendremos que especificar la imagen en donde queremos que se dibujen los puntos, los 21 puntos detectados y especificamos mp_hands.HAND_CONNECTIONS para que se dibujen las conexiones entre los puntos.

En la línea 30, podremos modificar le color, grosor de línea y radio de cada punto detectado, mientras que en la línea 31 podremos modificar el color y grosor de línea de las conexiones.

NOTA: Si no especificamos las líneas 30 y 31, MediaPie de igual forma mostrará las detecciones con los colores por defecto establecidos para esta solución, es decir con rojo y verde.

    image = cv2.flip(image, 1)
cv2.imshow("Image",image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Línea 33: Luego de que realicemos todo el proceso de detección, necesitaremos voltear nuevamente la imagen, para dejarla con la orientación original. Y procedemos a visualizar la imagen hasta que una tecla sea presionada. 

Figura 9: Visualizaciones de los puntos detectados y sus conexiones. Izq. Para esta imagen solo he establecido que se detecte una mano ya que he usado max_num_hands=1, y no he especificado colores para los puntos ni conexiones (no he usado las líneas 30 y 31). Der. Tenemos dos manos detectadas ya que he establecido max_num_hands=2, y los puntos y conexiones se muestran de otros colores ya que aquí si he usado las líneas 30 y 31.

Como has visto dibujamos todos los puntos y conexiones, pero esto lo hacía directamente la función de MediaPipe. Si tomaramos las coordenadas de los landmarks así como están, en decimales, no podríamos usarlos ya que necesitamos coordenadas en enteros y además que estén en la escala de la imagen. 

Por ello ahora veremos como obtener las coordenadas, para usarlas en la imagen empleando los nombres de cada uno de los hand landmarks. 

 NOTA: Voy a usar la misma estructura del programa hasta la línea 25 para los siguientes temas, así que voy solo a describir los procesos que vienen luego de esta línea.

Accediendo a los puntos claves mediante su nombre asociado

Voy tratar de acceder a solo las puntas de cada dedo, para ello voy a estar usando los nombres establecidos por MediaPipe, es decir: THUMB_TIP, INDEX_FINGER_TIP, MIDDLE_FINGER_TIP, RING_FINGER_TIP y  PINKY_TIP. Como lo tenemos en la figura 4.

if results.multi_hand_landmarks is not None:	
    # Accediendo a los puntos de referencia, de acuerdo a su nombre
    for hand_landmarks in results.multi_hand_landmarks:
        x1 = int(hand_landmarks.landmark[mp_hands.HandLandmark.THUMB_TIP].x * width)
        y1 = int(hand_landmarks.landmark[mp_hands.HandLandmark.THUMB_TIP].y * height)

Línea 28: Comencemos entonces con THUMB_TIP que corresponde al pulgar. Para ello vamos a especificar hand_landmarks.landmark y entre corchetes digitamos mp_hands.HandLandmark. Luego el nombre establecido para la punta del dedo pulgar, es decir THUMB_TIP. Y para acceder a la coordenada x, tendremos que digitar .x. Ya que estamos en la coordenada x, lo multiplicaremos por el ancho de la imagen, y todo esto debe estar dentro de int para obtener el valor entero.

Línea 29: Para obtener la coordenada y del pulgar, tendremos que seguir el mismo procedimiento, lo único que cambiará es que debemos pedir y y multiplicarlo por el alto de la imagen. 

x2 = int(hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP].x * width)
y2 = int(hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP].y * height)

x3 = int(hand_landmarks.landmark[mp_hands.HandLandmark.MIDDLE_FINGER_TIP].x * width)
y3 = int(hand_landmarks.landmark[mp_hands.HandLandmark.MIDDLE_FINGER_TIP].y * height)

x4 = int(hand_landmarks.landmark[mp_hands.HandLandmark.RING_FINGER_TIP].x * width)
y4 = int(hand_landmarks.landmark[mp_hands.HandLandmark.RING_FINGER_TIP].y * height)

x5 = int(hand_landmarks.landmark[mp_hands.HandLandmark.PINKY_TIP].x * width)
y5 = int(hand_landmarks.landmark[mp_hands.HandLandmark.PINKY_TIP].y * height)

cv2.circle(image, (x1, y1), 3,(255,0,0),3)
cv2.circle(image, (x2, y2), 3,(255,0,0),3)
cv2.circle(image, (x3, y3), 3,(255,0,0),3)
cv2.circle(image, (x4, y4), 3,(255,0,0),3)
cv2.circle(image, (x5, y5), 3,(255,0,0),3)

Línea 31 a 41: Ahora para los otros 4 dedos, seguiremos con el mismo procedimiento, solo tendríamos que cambiar el nombre del punto al cual deseamos acceder a sus coordenadas x e y. 

Línea 43 a 47: Y una vez que tengamos cada una de las coordenadas, podemos dibujar un círculo por cada dedo con la ayuda de cv2.circle. 

    image = cv2.flip(image, 1)
cv2.imshow("Image",image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Ahora vamos a probar el programa que hemos realizado para obtener los puntos correspondientes a las puntas de los dedos:

Figura 10: Visualizaciones de los puntos a los que accedimos mediante sus nombres. En este caso se detectan los puntos en ambas manos, ya que se ha establecido max_num_hands=2.

Accediendo a los puntos claves mediante su índice

Veremos otra forma a la que podemos acceder a los puntos correspondientes a las puntas de los dedos. Para ello nos ayudaremos de los índices que ocupa cada hand landmark de nuestro interés.

if results.multi_hand_landmarks is not None:	
    # Accediendo al valor de los puntos por su índice
    index = [4, 8, 12, 16, 20]
    for hand_landmarks in results.multi_hand_landmarks:
        for (i, points) in enumerate(hand_landmarks.landmark):
            if i in index:
                x = int(points.x * width)
                y = int(points.y * height)
                cv2.circle(image, (x, y), 3,(255, 0, 255), 3)

Línea 27: Creamos una lista que contendrá los índices a los que deseamos acceder es decir: 4, 8, 12, 16, 20. Entos los puedes ver en la figura 4.

Línea 28 a 33: Dentro del ciclo for voy a insertar un nuevo ciclo for, esta vez para que recorra cada uno de los 21 puntos de cada mano, y además en i se irá incrementando en uno de acuerdo a las iteraciones.  Entonces añadimos una condición. De tal forma que si i (que irá tomando valores de de 0 a 20), está presente en la lista index, se proceda a obtener las coordenadas x e y, y que además se dibuje un círculo con estas coordenadas. 

Recuerda que para obtener las coordenadas x e y, seguimos un procedimiento bastante similar a lo que hicimos antes, cuando accedíamos por los nombres. Esto se realiza en las líneas 31 y 32.

    image = cv2.flip(image, 1)
cv2.imshow("Image",image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Ejecutamos este programa para visualizar los resultados.

Figura 11: Visualización de los puntos que accedimos mediante su índice. Se visualizan los resultados en una sola mano ya que max_num_hands lo he establecido en 1.

Probando nuestro programa con una imagen que no contiene manos

Veamos los resultados al probar nuestros programas en una imagen que no contiene manos:

Figura 12: Prueba de nuestro programa con una imagen que no contiene manos. Podemos apreciar en la parte derecha tenemos None en Handedness, debido a que no existen manos en la imagen.

Detección de manos y puntos claves con MediaPipe, en videos

Finalmente, vamos a ver un ejemplo de como emplear mediapipe hands en un video stream. Para ello seguiremos un proceso similar. Solo que esta vez tendremos que especificar cv2.VideoCapture.

import cv2
import mediapipe as mp

mp_drawing = mp.solutions.drawing_utils
mp_hands = mp.solutions.hands

cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)

with mp_hands.Hands(
    static_image_mode=False,
    max_num_hands=2,
    min_detection_confidence=0.5) as hands:

Línea 10: En este caso, ya que se trata de un video, en la opción de configuración correspondiente a static_image_mode la especifico como False.

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

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

        results = hands.process(frame_rgb)

        if results.multi_hand_landmarks is not None:
            for hand_landmarks in results.multi_hand_landmarks:
                mp_drawing.draw_landmarks(
                    frame, hand_landmarks, mp_hands.HAND_CONNECTIONS,
                    mp_drawing.DrawingSpec(color=(0,255,255), thickness=3, circle_radius=5),
                    mp_drawing.DrawingSpec(color=(255,0,255), thickness=4, circle_radius=5))

        cv2.imshow('Frame',frame)
        if cv2.waitKey(1) & 0xFF == 27:
            break
cap.release()
cv2.destroyAllWindows()

Como puedes apreciar, el procedimiento es bastante parecido a lo que habíamos realizado para las imágenes. La diferencia es que ahora tendremos que leer un video stream.

Veamos los resultados de este programa:

Figura 13: Visualización de los 21 puntos por cada mano y sus conexiones.

Como vez en la figura 13, pudimos realizar la detección usando MediaPipe Hands en un video stream o video en vivo. Y estos se adaptan muy bien, incluso cuando muevo mis dedos para realizar una especie de corazón en la figura 13 en la sección derecha.

Sin duda alguna, esta solución que nos ofrece MediaPipe es impresionante, no solo se desempeña muy bien detectando cada uno de los puntos, sino que también la podemos usar con el CPU, y sigue trabajando muy bien. Espero puedas experimentar con MediaPipe Hands, y realices muchos proyectos aplicándolo.

Y hemos llegado al final de este post. Espero que te haya gustado y sido útil a la vez. Nos vemos en un siguiente video/tutorial, chao, chao. 😀

Referencias