Detectar objetos en imágenes con el algoritmo YOLO

Detectar objetos en imágenes con el algoritmo YOLO

La detección de objetos es una tarea muy desafiante, ya que implica no sólo clasificar un determinado elemento en una imagen, sino también determinar su posición. A lo largo del tiempo se han creado varios algoritmos para afrontar esta tarea, pero todos ellos siempre tienen el problema común de la lentitud y el esfuerzo computacional necesario para procesar cada imagen.

En este artículo hablaremos de YOLO, que utiliza un enfoque de procesamiento de imágenes diferente que le permite tener buena confiabilidad y ejecutarse en tiempo real.

El algoritmo YOLO (You Only Look Once), que en inglés significa “solo miras una vez”, es un algoritmo de visión por computadora muy utilizado para detectar objetos en imágenes, destaca por su velocidad, pudiendo detectar en tiempo real, lo que lo hace útil para aplicaciones como autos autónomos, seguridad, monitoreo de video y muchos otros. Básicamente funciona dividiendo una imagen en una cuadrícula de celdas y a cada celda se le asigna la responsabilidad de predecir la presencia y la información de los objetos contenidos en ella, como los cuadros delimitadores (bounding boxes) y la probabilidad de que un objeto pertenezca a determinada clase.

A diferencia de otros algoritmos de visión por computadora que procesan una imagen varias veces, YOLO utiliza una única red neuronal convolucional y trata el problema de detección como una tarea de regresión. Las imágenes están formadas por píxeles, cada píxel se compone de 3 canales que van de 0 a 255 cada uno y representan la intensidad del rojo, verde y azul. Combinando diferentes intensidades de estos tres colores se puede obtener cualquier color. Podemos visualizar y manipular estos parámetros usando bibliotecas como Numpy en el lenguaje Python.

Una de las operaciones que se pueden realizar es transformar la imagen mediante un Kernel (Filtro), que consiste en pasar otra imagen pequeña que contenga valores fijos para multiplicar los valores de la imagen original. Existen varios tipos de Kernels y son los encargados de crear los efectos en las imágenes que están presentes en los editores, como desenfocar, mejorar la nitidez, resaltar bordes, etc. Este proceso se llama convolución y lo que hacen las redes neuronales convolucionales, en definitiva, es pasar la imagen por varios de estos Kernels, diseñados específicamente para extraer información. Luego, en base a ellos, es posible clasificar la imagen.

Configurando YOLO

Para comenzar, asegúrese de que Python esté instalado, cree un entorno virtual y luego descargue las bibliotecas necesarias escribiendo el siguiente comando en la terminal:

pip install ultralytics hydra-core matplotlib numpy opencv-python requests scipy scikit-image lap filterpy tqdm torch torchvision PyYAML Pillow


A continuación, crea un archivo llamado main.py, en él importaremos la clase YOLO de la biblioteca ultralytics e importaremos OpenCV:

from ultralytics import YOLO
import cv2


Ahora crearemos una instancia de la clase YOLO, pasando una string con el nombre yolov8n.pt, que será nuestro modelo. La cadena pasada especifica la versión y el tamaño utilizado, en el ejemplo, 'v8' indica que usamos la versión 8 y 'n' el tamaño, que es nano. La primera vez que se ejecute se descargarán los pesos de la red neuronal:

model = YOLO('yolov8n.pt')


Otra opción es usar yolov8l.pt, la letra 'L' significa que queremos usar el modelo 'grande', tiene una mayor capacidad para indicar la confianza de los objetos, además de predecir más objetos en una imagen. Con el modelo cargado simplemente lo llamamos pasándole el nombre de la imagen y estableciendo el parámetro 'show' en true, luego ejecutamos la función waitKey de OpenCV para poder ver la imagen:

results = model("school_bus.png", show=True)
cv2.waitKey(0)


En la imagen de abajo puedes ver el resultado del modelo más grande detectando personas subiendo a un autobús, se detectaron mochilas, patinetas e incluso el propio autobús.


Puedes probar modelos ya entrenados con diferentes clases en diferentes imágenes o videos y analizar el resultado que se generará. A partir de esto, puedes diseñar sistemas para contar personas, automóviles, motocicletas, rastrear objetos, analizar el flujo de algo y mucho más.

Utilizando GPU

Antes de comenzar proyectos más complejos, que implican procesar imágenes en videos (fotogramas) o en tiempo real con una cámara, primero tenemos que configurar el algoritmo para usar la tarjeta de video (GPU) en lugar del procesador (CPU), ya que la GPU tiene mayor capacidad para procesar tareas de este tipo, por lo que dependiendo de la tarjeta que tengas y del tamaño del modelo que intentes utilizar, aumentará significativamente el rendimiento respecto al procesador.

Para comenzar a usar la GPU, si estás utilizando una tarjeta NVIDIA, asegúrate de que los controladores estén actualizados revisando el sitio oficial. Luego accede a la página del kit de herramientas CUDA de NVIDIA, un conjunto de herramientas para crear aplicaciones aceleradas por GPU. El proceso para descargar e instalar es simple: solo descárgalo desde el sitio, ejecuta el instalador y presiona ‘next’ al final. Posteriormente, es necesario registrarse en la plataforma de desarrollador de NVIDIA para bajar cuDNN. Entonces, simplemente copia los archivos de cada carpeta a la carpeta con el mismo nombre ubicada donde se instaló el kit de herramientas CUDA. Hecho eso, solo queda ingresar al sitioe de Pytorch y descargar la versión de la biblioteca que sea compatible con la versión CUDA.

Una vez realizada esta configuración, a partir de ahora se deberá utilizar la GPU en lugar del procesador al ejecutar el código YOLO. Si aún no funciona, verifica si el kit de herramientas CUDA está en las variables de entorno y si no hay otra versión incompatible de Pytorch en el camino. Si es así, desinstálala con el siguiente código e instala la versión correcta nuevamente.

pip uninstall torch

Proyecto de conteo de carros

El proyecto que vamos a desarrollar ahora es simular una cámara de seguridad que necesita contar coches. Para hacer esto, crea un nuevo archivo Python e importa la clase YOLO desde la biblioteca Ultralytics, OpenCV y la biblioteca Math:

from ultralytics import YOLO
import cv2 as cv
import math


Luego, descara un video de autos pasando por una calle en YouTube y colócalo en la misma carpeta del proyecto con el nombre cars.mp4. Luego cárgala con la función VideoCapture de OpenCV, la plantilla grande de YOLO y una fuente OpenCV.

cap = cv.VideoCapture("cars.mp4")
model = YOLO("yolov8l.pt")
font = cv.FONT_HERSHEY_PLAIN


Los modelos ya entrenados de YOLO tienen una cierta cantidad de clases que pueden detectar. Ya las dejé listas en una matriz a continuación que puedes copiar y pegar en tu código:

classNames = [
    "person", "bicycle", "car", "motorbike", "aeroplane", "bus", "train", "truck", "boat", "traffic light", "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow", "elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee", "skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard", "tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple", "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "sofa", "pottedplant", "bed", "diningtable", "toilet", "tvmonitor", "laptop", "mouse", "remote", "keyboard", "cell phone", "microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear", "hair drier", "toothbrush"
]


El vídeo que utilicé fue el de la imagen de abajo. Fíjate que hay varias vías por donde pueden circular los coches. Suponiendo que el sistema necesitara detectar los coches que llegan por el carril central izquierdo, lo que debemos hacer ahora es crear una máscara para que el algoritmo sólo detecte los coches que circulan por la vía que queremos. De lo contrario, detectará todos los coches que estén en la imagen.


Para crear la máscara, puedes ir a una aplicación de edición de imágenes como Canva y pegar el video en sí o una captura de pantalla (que sea del mismo tamaño que el video). Luego, simplemente coloca cuadrados negros sobre las partes donde no quieres la Algoritmo para encontrar los coches. Como en el siguiente ejemplo:


Luego, eliminas el vídeo detrás para que solo tengas la máscara y lo guardas como png en la carpeta del proyecto. Ten en cuenta que la máscara cubre parte de la carretera donde queremos detectar coches, dejando solo la parte que está más cerca de la cámara. La razón es que YOLO tiene dificultades para detectar objetos pequeños, por lo que puede detectar autos que están más lejos con poca confianza o con la clase incorrecta. Cuando termine, debería verse así:


En el código, carga la máscara como una imagen usando la función imread de OpenCV:

mascara = cv.imread("mascara_cars.png")


Ahora, para detectar los autos, necesitaremos hacer un bucle infinito que leerá los fotogramas del video, agregará la máscara y llamará al modelo para detectar los objetos en la imagen generada:

while True:
    success, img = cap.read()
    imgRegion = cv.bitwise_and(img, mascara)
    results = model(imgRegion, stream=True)

La función cap.read() leerá los fotogramas, cv.bitwise_and() los combinará con la máscara y los guardará en la variable imgRegion. Luego los pasamos al modelo y almacenamos el resultado en la variable de resultados. Una vez hecho esto, todavía dentro del ciclo, debemos tomar estos resultados y dibujar el cuadro delimitador, la confiabilidad de la predicción y la clase en la pantalla.

Podemos utilizar un loop ‘for’ para iterar sobre cada resultado, cada uno de ellos puede contener múltiples objetos de la clase ‘Boxes’, usados por Pytorch para almacenar y manipular las bounding boxes. Por esa razón, precisaremos crear otro loop ‘for’ para iterar sobre ellas:

for r in results:
    boxes = r.boxes
    for box in boxes:
        conf = math.ceil((box.conf[0] * 100)) / 100
        current_class = classNames[int(box.cls[0])]
        if current_class == 'car' or current_class == 'bus' or current_class == 'truck' and conf > 0.3:
            x1, y1, x2, y2 = box.xyxy[0]
            x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)

            cv.rectangle(img, (x1, y1), (x2, y2), (200, 200, 120), 3)
            cv.putText(img, f"{current_class} {conf}", (x1, y1), font, 1, (255, 255, 255), 2)


La confianza se obtiene de la propiedad box conf y usamos la función ceil de la biblioteca math para transformar al formato apropiado para visualizar. La clase se obtiene mediante la propiedad cls, la cual devolverá un número, por lo que pasamos este número al array con los nombres de las clases para obtener la string respectiva. Usando if podemos seleccionar las clases que queremos que el modelo detecte y la confianza mínima. Extraemos las coordenadas del objeto en la imagen de la propiedad xyxy, las transformamos en un número entero y las pasamos al rectángulo OpenCV y a las funciones putText para dibujar los cuadros delimitadores, la clase y la confianza en la pantalla.

Ahora, afuera de los loops ‘for’ pero aún dentro del loop infinito, tenemos que diseñar la imagen en la pantalla y llamar la función waitKey para visualizar el algoritmo al ejecutar:

cv.imshow("Image", img)
cv.waitKey(1)


Con el código que hemos escrito hasta ahora, este será el resultado:


Para finalizar el proyecto sólo queda contar los coches. Para ello, necesitamos un rastreador que almacene información que diferencie los coches (por ejemplo, asignando un ID a cada uno). De esta forma, garantizamos que no se cuenten más de una vez. Para hacer esto, es necesario instalar dos bibliotecas más, la scikit-image y la filterpy:

pip install scikit-image filterpy

Éstas serán necesarias para el rastreador, que puedes descargar visitando el GitHub del desarrollador abewley en el proyecto sort. Solo utilizaremos el archivo sort.py que puedes descargar y guardar o copiar el contenido y pegarlo desde de un archivo con el mismo nombre de la carpeta del proyecto. Al comienzo del archivo del detector de carros, importa todo de sort.py y crea un objeto de la clase Sort pasando la max_age como 20 (que será el número de fotogramas que esperará el algoritmo para volver a detectar el mismo coche si desaparece):

from sort import *

tracker = Sort(max_age=20, min_hits=3, iou_threshold=0.3)


Ahora, antes del loop ‘for’, después de la  definición de la variable results, vamos a crear un array numpy vacío al que se añadirán los resultados de la posición y confianza de los resultados. Funciona como si estuviéramos agregando valores a una lista normalmente en Python, con la diferencia que numpy lo llama stack. Una vez hecho esto, simplemente actualiza los resultados del rastreador llamando a la función update y pasando las detecciones del array numpy:

...
results = model(imgRegion, stream=True)

detections = np.empty((0, 5))

for r in results:
    boxes = r.boxes
    for box in boxes:
        conf = math.ceil((box.conf[0] * 100)) / 100
        current_class = classNames[int(box.cls[0])]
        if current_class == 'car' or current_class == 'bus' or current_class == 'truck' and conf > 0.3:
            x1, y1, x2, y2 = box.xyxy[0]
            x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)

            cv.putText(img, f"{current_class} {conf}", (x1, y1), font, 1, (255, 255, 255), 2)
            currentArray = np.array([x1,y1,x2,y2,conf])
            detections = np.vstack((detections, currentArray))

trackerResults = tracker.update(detections)


Dentro de ‘if’ creamos un array con los parámetros de posición y confianza y agregamos los valores para la variable detection utilizando la función vstack de numpy. Cuando el loop termina, los valores son actualizados y guardados en la variable trackerResults. Lo que el código que importamos de GitHub va a hacer es generar un id para cada detección, que se va a mantener a lo largo de los frames del video. Con eso podemos saber la posición y diferenciar los carros.

Una forma de hacer este conteo es dibujar una línea en la pantalla, recorrer los resultados del rastreador y para cada resultado tomar los valores de x2 e y2 y disminuirlos en x1 e y1 para encontrar el tamaño y el ancho respectivamente. Entonces, para encontrar el punto central de la imagen en los ejes xey, disminuye la mitad del ancho en x1 y la mitad del alto en y1, respectivamente. Conociendo el punto central, simplemente verifica si está cerca de la línea que se dibujó; de ser así, verifica si el número de identificación no está presente en un array.

En el código, agregamos una matriz antes del bucle infinito que será donde agregaremos los ID de los autos:

counted_cars = []


Dentro del loop infinito, después de trackerResults, trazamos la línea y hacemos el loop para iterar sobre los resultados del rastreador:

cv.line(img, (350, 470), (650, 470), (0, 0, 255), 2)

for result in trackerResults:
    x1,y1,x2,y2,id = result
    x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
    cv.rectangle(img, (x1, y1), (x2, y2), (200, 200, 120), 3)
    w, h = x2 - x1, y2 - y1
    cx, cy = x1+w//2,y1+h//2
    cv.circle(img, (cx, cy), 5, (200, 200, 120), cv.FILLED)
    if 350 < cx < 650 and 460 < cy < 480:
        if not id in counted_cars:
            counted_cars.append(id)
            cv.line(img, (350, 470), (650, 470), (0, 255, 0), 2)


Para finalizar basta con mostrar en pantalla el tamaño del array, que será el número de coches que se contaron:

cv.putText(img, f"Contagem de carros: {len(counted_cars)}", (10, 240), font, 2, (255, 255, 255), 2)


Conclusión

La Inteligencia Artificial es un tema que ha ganado popularidad en los últimos años, en particular, el área de la visión por computadora destaca por las numerosas aplicaciones prácticas que se pueden desarrollar, como la creación de nuevos métodos de análisis cualitativo de muestras y reacciones para la industria farmacéutica o el desarrollo de coches autónomos. Tecnologías que hace unos años podrían considerarse ciencia ficción, pero que hoy se están haciendo realidad con el desarrollo de nuevos algoritmos para el procesamiento de imágenes en tiempo real.

La detección de objetos en imágenes y videos es un área muy amplia. Existen varios algoritmos para realizar esta tarea y YOLO es uno de ellos. En este artículo, exploramos parte de su potencial utilizando modelos ya entrenados para crear un sistema que cuente automóviles automáticamente. YOLO también permite crear tu propia base de datos y entrenarla para detectar con las clases que tú definas, un tema que podemos cubrir con más detalle en un próximo artículo.

💡
Las opiniones y comentarios emitidos en este artículo son propiedad única de su autor y no necesariamente representan el punto de vista de Listopro.

Listopro Community da la bienvenida a todas las razas, etnias, nacionalidades, credos, géneros, orientaciones, puntos de vista e ideologías, siempre y cuando promuevan la diversidad, la equidad, la inclusión y el crecimiento profesional de los profesionales en tecnología.