Detección de objetos con Mediapipe | Python – OpenCV

Por Administrador

¡Hola, hola Omesitos!. ¿Cómo han estado?.

Mediapipe nos ha traído nuevas tareas y es hora de explorarlas. En este primer blog vamos a tratar la detección de objetos. Vamos a ver como funciona y como podemos aplicarlos a imágenes, videos y videostreamings. Así que… ¡Vamos a por ello!.

CONTENIDO

  • Detección de objetos usando Mediapipe
  • ¿Cómo usar la detección de objetos en imágenes con Mediapipe – Python?
  • ¿Cómo usar la detección de objetos en video con Mediapipe – Python?
  • ¿Cómo usar la detección de objetos en video streaming con Mediapipe – Python?

Detección de objetos usando Mediapipe

Para empezar, vamos a explorar un poco la información que nos provee Google sobre Mediapipe. Para ello tendremos que buscar este framework en el buscador o in directamente a este link: https://ai.google.dev/edge/mediapipe/solutions/guide. Aquí podemos encontrar información de lo que es y que hace Mediapipe junto a sus soluciones.

Vamos a ver que en la sección izquierda tenemos distintas tareas a las que podemos acceder para información. Tenemos Vision tasks, Text tasks, Audio tasks y Generative AI tasks.

En nuestro caso iremos a las tareas de Visión, luego a Object detection. Y vamos a Overview. Aquí podremos encontrar más información sobre la Detección de objetos – Object detection.

Con esta tarea MediaPipe nos permitirá detectar y rastrear o trackear múltiples objetos dentro de imágenes o videos.

Para iniciar con el proceso de detección, además de tener nuestros datos de entrada (imágenes, videos, videostreaming), necesitaremos de modelos entrenados para la detección de objetos. ¡Pero tranquilidad!. Mediapipe nos permitirá descargar varios modelos, los cuales podrás evaluar su funcionamiento.

Para obtenerlos tendremos que ir a la sección Models:

Aquí veremos que Mediapipe nos ofrece: EfficientDet-Lite0 model, EfficientDet-Lite2 model y SSD MobileNetV2 model. Cada uno de ellos con diferentes versiones.

Veamos por ejemplo al EfficientDet-Lite0 model (Recommended), que usa EfficientNet-Lite0 y que necesita como entrada una imagen de 320×320. En su descripción tenemos que el modelo ha sido entrenado con el dataset COCO, que contiene 1.5 millones de instancias de objetos y que podrá identificar entre 80 etiquetas de objetos, de hecho nos ofrecen un link para ver todos los objetos que puede detectar.

En cuanto a EfficientDet-Lite0, tenemos 3 versiones de modelos: int8, float 16 y float 32. Pero, ¿qué diferencia a cada una de estas versiones?. Estas hacen referencia a la precisión numérica utilizada para representar los valores de los parámetros del modelo. Por ejemplo, un modelo representado con 8 bits tiene menos precisión, pero es mucho más rápido que uno de 16 o 32 bits.

Entonces, lo que tendremos que hacer es descargar el o los modelos que deseemos probar. En lo personal recomendaría probarlos todos y ver cual se adapta mejor al problema a resolver. ?

Y por cierto, si aún no has instalado Mediapipe puedes hacerlo a través de:

pip install mediapipe

Una vez que tenemos el o los modelos, es hora de pasar a la programación…

¿Cómo usar la detección de objetos en imágenes con Mediapipe – Python?

Vamos a crear un nuevo archivo de python. Y vamos a empezar por la importación de las librerías.

import mediapipe as mp
from mediapipe.tasks.python import vision
from mediapipe.tasks.python import BaseOptions
import cv2

Lo primero que haremos es importar mediapipe, luego de mediapipe.task.python importaremos vision, ya que estaremos empleado una tarea de visión por computador, la cual nos permitirá manejar imágenes o videos. Luego importaremos BaseOptions que permitirá básicamente leer el modelo que se usará y finalmente importamos OpenCV.

# Especificar la configuración del detector de objetos
options = vision.ObjectDetectorOptions(
    base_options=BaseOptions(model_asset_path="path/efficientdet_lite0_32.tflite"),
    max_results=5,
    score_threshold=0.2,
    running_mode=vision.RunningMode.IMAGE)
detector = vision.ObjectDetector.create_from_options(options)

Empezamos llamando a la tarea de detección de objetos con vision.ObjectDetectorOptions, para especificar las opciones de configuración. Con la ayuda de BaseOptions especificaremos model_asset_path igual al path del modelo que vayamos a usar.

Luego podemos establecer max_results en 5 por ejemplo, que corresponderá al número máximo de resultados con los puntajes más altos (si colocamos -1 nos devolverá todos los resultados, aunque esto puede no ser lo mejor, puesto que se pueden presentar predicciones muy bajas y más bien harían ruido). Tenemos también score_threshold, el cual indica que se tomarán en cuenta a aquellas predicciones sean superiores a este umbral. Y por último estaremos usando running_mode para especificar nuestros datos de entrada, en este caso la entrada es una imagen, por lo que tendremos que usar VisionRunningMode.IMAGE.

Nota: Para más información sobre las opciones de configuración, no olvides echarle un vistazo a la sección Configuration Options de Mediapipe.

# Leer la imagen de entrada
image = cv2.imread("/path/image.jpg")
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image_rgb = mp.Image(image_format=mp.ImageFormat.SRGB, data=image_rgb)

Ahora vamos a leer la imagen de entrada, para ello usaremos cv2.imread. Tendremos que especificar su ubicación o simplemente su nombre junto con su extensión si se encuentra en el mismo directorio que el archivo de Python en el que estamos trabajando.

Vamos a proceder a cambiar el orden de los canales de la imagen de entrada, transformaremos de BGR a RGB. Y además tendremos que convertir la imagen a un objeto mediapipe.Image. Para ello especificamos mp.ImageFormat.SRGB y data=image_rgb.

# Detectar objetos sobre la imagen
detection_result = detector.detect(image_rgb)

Para empezar con la detección tendremos que usar detector.detect, y especificar la imagen bajo la cual queremos hacer la detección.

for detection in detection_result.detections:
    # Bounding box
    bbox = detection.bounding_box
    bbox_x, bbox_y, bbox_w, bbox_h = bbox.origin_x, bbox.origin_y, bbox.width, bbox.height

    # Score y Category name
    category = detection.categories[0]
    score = category.score * 100
    category_name = category.category_name

    cv2.rectangle(image, (bbox_x, bbox_y), (bbox_x + bbox_w, bbox_y - 30), (100, 255, 0), -1)
    cv2.rectangle(image, (bbox_x, bbox_y), (bbox_x + bbox_w, bbox_y + bbox_h), (100, 255, 0), 2)
    cv2.putText(image, f"{category_name}: {score:.2f}%", (bbox_x + 5, bbox_y - 5), cv2.FONT_HERSHEY_SIMPLEX,
                0.6, (255, 255, 255), 2)

Vamos a recorrer cada una de las detecciones obtenidas, estos valores se encontrarán en detection_result.detections. Lo primero que haremos es extraer los puntos x, y, ancho y alto de cada cuadro delimitador, con ayuda de detection.bounding_box. Estos datos se almacenarán en: bbox_x, bbox_y, bbox_w, bbox_h.

También podemos extraer el nombre de la categoría del objeto detectado y el valor de su predicción. Estos valores se encontrarán en detection.categories[0]. Para extraer el valor de la predicción, que será un valor entre 0 a 1 usaremos category.score * 100 (lo multiplicamos por 100 para obtener valores de entre 0 y 100, es decir en porcentaje). En cuanto al nombre de la categoría, la podemos extraer a través de category.category_name.

De la línea 33 a la 36, tenemos el uso de OpenCV, para visualizar los resultados en la imagen.

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

Finalmente visualizamos la imagen. Esperamos a que se presione una tecla para finalmente cerrar la ventana de visualización

Una vez terminado el programa, probemos el modelo con un par de imágenes:

En la imagen de la izquierda tenemos que se han detectado objetos tales como: mesa de comedor, taza, una planta en maceta, teclado y un libro. Aunque este último parce ser una agenda, no estaría del todo mal, debido a la similitud entre los objetos.

En la imagen de la derecha se han detectado correctamente: la persona, la cama, un gato, mientras que para el gato más grande presente en la imagen, se detecta como perro. Este último sería un resultado erróneo.

A continuación tenemos el programa completo sobre la detección de objetos sobre una imagen:

import mediapipe as mp
from mediapipe.tasks.python import vision
from mediapipe.tasks.python import BaseOptions
import cv2

# Especificar la configuración del detector de objetos
options = vision.ObjectDetectorOptions(
    base_options=BaseOptions(model_asset_path="path/efficientdet_lite0_32.tflite"),
    max_results=5,
    score_threshold=0.2,
    running_mode=vision.RunningMode.IMAGE)
detector = vision.ObjectDetector.create_from_options(options)

# Leer la imagen de entrada
image = cv2.imread("/path/image.jpg")
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image_rgb = mp.Image(image_format=mp.ImageFormat.SRGB, data=image_rgb)

# Detectar objetos sobre la imagen
detection_result = detector.detect(image_rgb)
#print(detection_result)

for detection in detection_result.detections:
    # Bounding box
    bbox = detection.bounding_box
    bbox_x, bbox_y, bbox_w, bbox_h = bbox.origin_x, bbox.origin_y, bbox.width, bbox.height

    # Score y Category name
    category = detection.categories[0]
    score = category.score * 100
    category_name = category.category_name

    cv2.rectangle(image, (bbox_x, bbox_y), (bbox_x + bbox_w, bbox_y - 30), (100, 255, 0), -1)
    cv2.rectangle(image, (bbox_x, bbox_y), (bbox_x + bbox_w, bbox_y + bbox_h), (100, 255, 0), 2)
    cv2.putText(image, f"{category_name}: {score:.2f}%", (bbox_x + 5, bbox_y - 5), cv2.FONT_HERSHEY_SIMPLEX,
                0.6, (255, 255, 255), 2)

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

¿Cómo usar la detección de objetos en video con Mediapipe – Python?

Debido a que el programa para la detección de objetos en video presenta similitudes con la detección de objetos en imágenes, me centraré en explicar las diferencias entre ambos, así que vamos a por ello.

import mediapipe as mp
from mediapipe.tasks.python import vision
from mediapipe.tasks.python import BaseOptions
import cv2

# Especificar la configuración del detector de objetos
options = vision.ObjectDetectorOptions(
     base_options=BaseOptions(model_asset_path="path/efficientdet_lite0_32.tflite"),
     max_results=5,
     score_threshold=0.15,
     running_mode=vision.RunningMode.VIDEO)
detector = vision.ObjectDetector.create_from_options(options)

La principal diferencia con el código anterior está presente en la línea 11, ya que en running_mode debemos especificar vision.RunningMode.VIDEO, puesto a que el análisis se llevará a cabo en un video de entrada.

# Leer el video de entrada
cap = cv2.VideoCapture("path/video.mp4")
frame_count = cap.get(cv2.CAP_PROP_FRAME_COUNT)
fps = cap.get(cv2.CAP_PROP_FPS)

Con cap.get(cv2.CAP_PROP_FRAME_COUNT), obtendremos la cantidad total de fotogramas que posee el video de entrada. Mientras que con cap.get(cv2.CAP_PROP_FPS) obtendremos los fps del video. Estos datos los usaremos más adelante.

for frame_index in range(int(frame_count)):
     ret, frame = cap.read()
     if ret == False:
          break
     
     frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
     frame_rgb = mp.Image(image_format=mp.ImageFormat.SRGB, data=frame_rgb)

     # Calcular la marca temporal del frame actual (en milisegundos)
     frame_timestamp_ms = int(1000 * frame_index / fps)

     # Detectar objetos sobre el frame
     detection_result = detector.detect_for_video(frame_rgb, frame_timestamp_ms)
     for detection in detection_result.detections:
          #print(detection)
          bbox = detection.bounding_box
          bbox_x, bbox_y, bbox_w, bbox_h = bbox.origin_x, bbox.origin_y, bbox.width, bbox.height
          category = detection.categories[0]
          score = category.score*100
          category_name = category.category_name

          cv2.rectangle(frame, (bbox_x, bbox_y), (bbox_x + bbox_w, bbox_y - 30), (100, 255, 0), -1)
          cv2.rectangle(frame, (bbox_x, bbox_y), (bbox_x + bbox_w, bbox_y + bbox_h), (100, 255, 0), 2)
          cv2.putText(frame, f"{category_name} {score:.2f}%", (bbox_x + 5, bbox_y - 5), cv2.FONT_HERSHEY_SIMPLEX,
                         0.6, (255, 255, 255), 2)
     
     cv2.imshow('Video', frame)
     if cv2.waitKey(1) & 0xFF == 27:
          break
cap.release()
cv2.destroyAllWindows()

Vamos a recorrer cada uno de los fotogramas del video, y como lo podemos ver en la línea 24 y 25 también tendremos que transformar cada frame de BGR a RGB y lo convertimos en un objeto mediapipe.Image.

Lo nuevo viene en la línea 28, con el cálculo de frame_timestamp_ms, que es una marca temporal en milisegundos. Esta irá incrementando conforme pase cada fotograma, esto le ayudará a mediapipe a manejar correctamente el flujo de video, de tal forma que los datos estén correctamente ordenados. (Puedes echarle un vistazo a este link para más información).

Entonces en la línea 31, tendemos que pasar al detector no solo la imagen, sino también la marca de tiempo calculada.

Una vez terminado el programa, probemos el modelo con un video:

Tenemos que se han detectado correctamente todos los objetos que aparecen encerrados en el cuadro delimitador verde. Desde el horno, hasta la persona y los pasteles que están sobre la mesa.

A continuación tenemos el programa completo sobre la detección de objetos sobre un video:

import mediapipe as mp
from mediapipe.tasks.python import vision
from mediapipe.tasks.python import BaseOptions
import cv2

# Especificar la configuración del detector de objetos
options = vision.ObjectDetectorOptions(
     base_options=BaseOptions(model_asset_path="path/efficientdet_lite0_32.tflite"),
     max_results=5,
     score_threshold=0.2,
     running_mode=vision.RunningMode.VIDEO)
detector = vision.ObjectDetector.create_from_options(options)

# Leer el video de entrada
cap = cv2.VideoCapture("path/video.mp4")
frame_count = cap.get(cv2.CAP_PROP_FRAME_COUNT)
fps = cap.get(cv2.CAP_PROP_FPS)

for frame_index in range(int(frame_count)):
     ret, frame = cap.read()
     if ret == False:
          break
     
     frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
     frame_rgb = mp.Image(image_format=mp.ImageFormat.SRGB, data=frame_rgb)

     # Calcular la marca temporal del frame actual (en milisegundos)
     frame_timestamp_ms = int(1000 * frame_index / fps)

     # Detectar objetos sobre el frame
     detection_result = detector.detect_for_video(frame_rgb, frame_timestamp_ms)
     for detection in detection_result.detections:
          #print(detection)
          bbox = detection.bounding_box
          bbox_x, bbox_y, bbox_w, bbox_h = bbox.origin_x, bbox.origin_y, bbox.width, bbox.height
          category = detection.categories[0]
          score = category.score*100
          category_name = category.category_name

          cv2.rectangle(frame, (bbox_x, bbox_y), (bbox_x + bbox_w, bbox_y - 30), (100, 255, 0), -1)
          cv2.rectangle(frame, (bbox_x, bbox_y), (bbox_x + bbox_w, bbox_y + bbox_h), (100, 255, 0), 2)
          cv2.putText(frame, f"{category_name} {score:.2f}%", (bbox_x + 5, bbox_y - 5), cv2.FONT_HERSHEY_SIMPLEX,
                         0.6, (255, 255, 255), 2)
     
     cv2.imshow('Video', frame)
     if cv2.waitKey(1) & 0xFF == 27:
          break
cap.release()
cv2.destroyAllWindows()

¿Cómo usar la detección de objetos en video streaming con Mediapipe – Python?

Finalmente veremos como podemos aplicar la detección de objetos sobre un video streaming, o video en directo. Para ello tendremos que hacerle unas cuantas modificaciones al programa anterior, veamos:

import mediapipe as mp
from mediapipe.tasks.python import vision
from mediapipe.tasks.python import BaseOptions
import cv2
import time

detection_result_list = []

# Función callback para procesar los resultados de detección
def detection_callback(result, output_image, timestamp_ms):
     detection_result_list.append(result)

En la línea 7 definiremos una lista vacía que nos ayudará más adelante a almacenar los resultados de la detección de objetos. Mientras que en la línea 10, tenemos la definicion de una función callback, la cual nos ayudará a procesar los resultados de las detecciones de objetos. Esta se llama automáticamente cada vez que el detector de objetos produce un nuevo resultado.

# Especificar la configuración del detector de objetos
options = vision.ObjectDetectorOptions(
     base_options=BaseOptions(model_asset_path="/path/efficientdet_lite0_32.tflite"),
     max_results=5,
     score_threshold=0.15,
     running_mode=vision.RunningMode.LIVE_STREAM, 
     result_callback=detection_callback)
detector = vision.ObjectDetector.create_from_options(options)

En el apartado de opciones de configuración, tendremos que definir vision.RunningMode.LIVE_STREAM para el running_mode, y en result_callback especificamos la función que habíamos creado recientemente, esta nos permitirá manejar los resultados de las predicciones.

# Leer el video de entrada
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)

while True:
     ret, frame = cap.read()
     if ret == False:
          break
     
     frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
     frame_rgb = mp.Image(image_format=mp.ImageFormat.SRGB, data=frame_rgb)

     # Detectar objetos sobre el frame
     detection_result = detector.detect_async(frame_rgb, time.time_ns() // 1_000_000)

     if detection_result_list:
          for detection in detection_result_list[0].detections:   
               bbox = detection.bounding_box
               bbox_x, bbox_y, bbox_w, bbox_h = bbox.origin_x, bbox.origin_y, bbox.width, bbox.height
               category = detection.categories[0]
               score = category.score * 100
               category_name = category.category_name
               #print("------")
               #print(score)
               #print(category_name)
               cv2.rectangle(frame, (bbox_x, bbox_y), (bbox_x + bbox_w, bbox_y - 30), (100, 255, 0), -1)
               cv2.rectangle(frame, (bbox_x, bbox_y), (bbox_x + bbox_w, bbox_y + bbox_h), (100, 255, 0), 2)
               cv2.putText(frame, f"{category_name} {score:.2f}%", (bbox_x + 5, bbox_y - 5), cv2.FONT_HERSHEY_SIMPLEX,
                         0.6, (255, 255, 255), 2)
          cv2.imshow('Video', frame)

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

En la línea 34 vamos a llamar al modelos para realizar las predicciones. Para ello usaremos detector.detect_async, al cual tendremos que darle el frame que va a analizar junto con la marca de tiempo en milisegundos.

IMPORTANTE: La descripción en cuanto a la detección de objetos en un videostreming nos dice lo siguiente: «El método detect_async está diseñado para procesar datos de transmisión en vivo, como la entrada de una cámara. Para reducir la latencia general, el detector de objetos puede omitir las imágenes de entrada si es necesario. En otras palabras, no se garantiza que haya una salida por cada imagen de entrada». (Fuente)

import mediapipe as mp
from mediapipe.tasks.python import vision
from mediapipe.tasks.python import BaseOptions
import cv2
import time

detection_result_list = []

# Función callback para procesar los resultados de detección
def detection_callback(result, output_image, timestamp_ms):
     detection_result_list.append(result)

# Especificar la configuración del detector de objetos
options = vision.ObjectDetectorOptions(
     base_options=BaseOptions(model_asset_path="C:/Users/Gaby/Desktop/Object_Detection/Models/efficientdet_lite0_32.tflite"),
     max_results=5,
     score_threshold=0.15,
     running_mode=vision.RunningMode.LIVE_STREAM, 
     result_callback=detection_callback)
detector = vision.ObjectDetector.create_from_options(options)

# Leer el video de entrada
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)

while True:
     ret, frame = cap.read()
     if ret == False:
          break
     
     frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
     frame_rgb = mp.Image(image_format=mp.ImageFormat.SRGB, data=frame_rgb)

     # Detectar objetos sobre el frame
     detection_result = detector.detect_async(frame_rgb, time.time_ns() // 1_000_000)

     if detection_result_list:
          for detection in detection_result_list[0].detections:   
               bbox = detection.bounding_box
               bbox_x, bbox_y, bbox_w, bbox_h = bbox.origin_x, bbox.origin_y, bbox.width, bbox.height
               category = detection.categories[0]
               score = category.score * 100
               category_name = category.category_name
               #print("------")
               #print(score)
               #print(category_name)
               cv2.rectangle(frame, (bbox_x, bbox_y), (bbox_x + bbox_w, bbox_y - 30), (100, 255, 0), -1)
               cv2.rectangle(frame, (bbox_x, bbox_y), (bbox_x + bbox_w, bbox_y + bbox_h), (100, 255, 0), 2)
               cv2.putText(frame, f"{category_name} {score:.2f}%", (bbox_x + 5, bbox_y - 5), cv2.FONT_HERSHEY_SIMPLEX,
                         0.6, (255, 255, 255), 2)
          cv2.imshow('Video', frame)

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

Una vez concluido el programa, podremos hacer pruebas sobre este:

Y bien, hemos llegado al final. Nos vemos en un siguiente post. ? ¡Espero que les haya gustado!.

Referencias