Detección de objetos con OpenCV-Python usando un clasificador Haar-Cascade

¡En esta guía práctica, aprenda cómo realizar la detección de objetos en imágenes, videos en tiempo real y transmisiones de video en Python con OpenCV y Haar-Cascade Classifiers!

Introducción

Python tiene muchas aplicaciones en el campo de Computer Vision, generalmente a través de Deep Learning. ¡Desde realizar OCR en documentos hasta permitir que los robots "ver" - Computer Vision es un campo emocionante y desafiante!

OpenCV es un marco multiplataforma de código abierto, desarrollado como una biblioteca orientada a la visión artificial en tiempo real. Al ser multiplataforma, puede interactuar con él a través de C ++, Python y Java, ¡independientemente de su sistema operativo!

Computer Vision es un campo amplio, y hay muchas tareas/problemas individuales que podría intentar abordar. Uno grande es Detección de objetos.

{.icon aria-hidden=“true”}

Nota: La detección de objetos se refiere a la clasificación (etiquetado), la detección de posición y la detección de contorno (generalmente crudo, como un cuadro delimitador) para un objeto en una imagen, video o transmisión. Estas son tres tareas distintas que podrían ser temas en su propia luz.
La detección de contorno no crudo también puede denominarse segmentación de imágenes, si segmenta la imagen en cada objeto distinto, sin embargo, la segmentación de imágenes no se limita a esta aplicación.

En esta guía, aprenderá cómo realizar la detección de objetos en Python con OpenCV. Cubriremos cómo leer, detectar y mostrar objetos detectados en una imagen, archivo de video y en tiempo real, utilizando el clasificador Haar-Cascade preentrenado.

¡Comencemos con la instalación de OpenCV!

Detección de objetos usando OpenCV

Si aún no ha instalado OpenCV, instalar el controlador de Python es fácil con pip:

1
$ pip install opencv-python

¡Eso es todo! Se instalará OpenCV y todas las dependencias con las que funciona.

{.icon aria-hidden=“true”}

Nota: Si obtiene errores con la instalación, intente instalar opencv-contrib-python en su lugar.

Ahora que tenemos nuestra biblioteca configurada, nuestro primer paso en el reconocimiento de objetos es leer y mostrar una imagen con OpenCV. Puedes usar cualquier imagen que quieras, en esta guía usaremos face_image.jpg, obtenida a través de estapersonanoexiste.com.

image of a person

[El sitio web genera "personas imaginadas" utilizando EstiloGan.]{.small}

El método imread() del módulo cv2 (representa OpenCV) se puede usar para cargar una imagen. Entonces, podemos mostrarlo en una ventana:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import cv2

image_path = "generic-face.webp" # Put an absolute/relative path to your image
window_name = f"Detected Objects in {image_path}" # Set name of window that shows image
original_image = cv2.imread(image_path)  # Read image in memory
cv2.namedWindow(window_name, cv2.WINDOW_KEEPRATIO) # Create window and set title
cv2.imshow(window_name, original_image)  # Load image in window
cv2.resizeWindow(window_name, (400, 400))  # Resize window
cv2.waitKey(0)  # Keep window open indefinitely until any keypress
cv2.destroyAllWindows()  # Destroy all open OpenCV windows

Ejecutar este código abrirá una ventana como esta:

OpenCV window displaying an image of person

{.icon aria-hidden=“true”}

Nota: A veces, es posible que su sistema operativo no muestre la ventana al frente de la pantalla, lo que hace que parezca que el código se está ejecutando indefinidamente. Asegúrese de recorrer las ventanas abiertas si no ve una ventana después de ejecutar el código.

El método imread() carga la imagen y el método imshow() se usa para mostrar la imagen en la ventana. Los métodos namedWindow() y resizeWindow() se utilizan para crear una ventana personalizada para la imagen en caso de discrepancias relacionadas con el tamaño de la ventana y la imagen.

El método waitKey() mantiene una ventana abierta durante una determinada cantidad de milisegundos, o hasta que se presiona una tecla. Un valor de 0 significa que OpenCV mantendrá la ventana abierta indefinidamente hasta que presionemos una tecla para cerrarla. El método destroyAllWindows() le dice a OpenCV que cierre todas las ventanas que abrió.

Con la configuración básica, demos los siguientes pasos para detectar objetos con OpenCV. Necesitamos entender:

  1. Cómo dibujar usando OpenCV (para "localizar"/delinear objetos cuando se detectan)
  2. Clasificadores en cascada de Haar (cómo OpenCV distingue objetos)

¿Cómo dibujar usando OpenCV?

OpenCV puede dibujar varias formas, incluidos rectángulos, círculos y líneas. Incluso podemos usar un método putText() para poner una etiqueta con la forma. Dibujemos una forma rectangular simple en la imagen usando el método rectangle() que toma argumentos posicionales, color y el grosor de la forma.

Agregue una nueva línea para crear un rectángulo después de leer la imagen y antes de nombrar la ventana:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Reading the image
...

original_image = cv2.imread(image_path)
rectangle = cv2.rectangle(original_image, 
                          (200, 100), # X-Y start
                          (900, 800), # X-Y end
                          (0, 255, 0), 
                          2)
cv2.namedWindow(window_name, cv2.WINDOW_KEEPRATIO)

# Naming the window
...

Ahora, vuelva a ejecutar su código para ver un rectángulo dibujado sobre la imagen:

OpenCV window displaying an image of a person with a rectangle drawn to identify their face

Aquí, arreglamos la ubicación del rectángulo con la llamada cv2.rectangle(). Estas ubicaciones son algo que debe inferirse de la imagen, no adivinarse. ¡Ahí es donde OpenCV puede hacer el trabajo pesado! Una vez que lo haga, podemos usar este método exacto para dibujar un rectángulo alrededor del objeto detectado.

Dibujar rectángulos (o círculos) como este es un paso importante en la detección de objetos, ya que nos permite anotar (etiquetar) los objetos que detectamos de manera clara.

Ahora que hemos terminado con el dibujo con OpenCV, echemos un vistazo al concepto de Haar Cascade Classifier, cómo funciona y cómo nos permite identificar objetos en una imagen.

Clasificador de cascada de cabello {#clasificador de cascada de cabello}

Un clasificador en cascada de Haar es un clasificador de aprendizaje automático que funciona con características de Haar. Está incorporado en la clase cv2.CascadeClassifier. Varios archivos XML vienen preempaquetados con OpenCV, cada uno de los cuales tiene las características de Haar para diferentes objetos.

Las características de Haar funcionan de manera similar a los mapas de características de las redes neuronales convolucionales (CNN) normales.

Las características se calculan para muchas regiones de una imagen, donde se suman las intensidades de los píxeles, antes de calcular la diferencia entre estas sumas. Esta reducción de la resolución de la imagen conduce a un mapa de características simplificado que se puede utilizar para detectar patrones en las imágenes.

{.icon aria-hidden=“true”}

Nota: Existen muchas opciones de reconocimiento de patrones, incluidas redes extremadamente potentes que ofrecen una mayor precisión y más flexibilidad que los clasificadores en cascada de Haar. El principal atractivo de las características de Haar y el clasificador en cascada de Haar es lo rápido que es. Es muy adecuado para la detección de objetos en tiempo real, donde ve la mayor parte de su uso.

Cuando instala OpenCV, obtiene acceso a archivos XML con las características de Haar para:

  1. ojos
  2. Cara frontal
  3. Cuerpo completo
  4. Parte superior del cuerpo
  5. Parte inferior del cuerpo
  6. Gatos
  7. Señales de alto
  8. Matrículas, etc.

Puede encontrar sus nombres de archivo en el [repositorio de GitHub]{noreferrer=“https://github.com/opencv/opencv/tree/master/data/haarcascades" rel=“nofollow noopener” target="_blank”}.

¡Cubren un espectro de uso bastante amplio! Por ejemplo, carguemos el clasificador para ojos e intentemos detectar ojos en la imagen que hemos cargado, dibujando un rectángulo alrededor del objeto detectado:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import cv2

image_path = "face_image.jpg"
window_name = f"Detected Objects in {image_path}"
original_image = cv2.imread(image_path)

# Convert the image to grayscale for easier computation
image_grey = cv2.cvtColor(original_image, cv2.COLOR_RGB2GRAY)

cascade_classifier = cv2.CascadeClassifier(
    f"{cv2.data.haarcascades}haarcascade_eye.xml")
detected_objects = cascade_classifier.detectMultiScale(image_grey, minSize=(50, 50))

# Draw rectangles on the detected objects
if len(detected_objects) != 0:
    for (x, y, width, height) in detected_objects:
        cv2.rectangle(original_image, (x, y),
                      (x + height, y + width),
                      (0, 255, 0), 2)

cv2.namedWindow(window_name, cv2.WINDOW_KEEPRATIO)
cv2.imshow(window_name, original_image)
cv2.resizeWindow(window_name, 400, 400)
cv2.waitKey(0)
cv2.destroyAllWindows()

Ejecutar este código debería mostrar algo similar a esto:

OpenCV window displaying an image of a person with their eyes detected by code

Aquí, estamos escalando en gris la imagen para que el clasificador reduzca el costo computacional (más información significa más cómputo). Los colores no importan demasiado para esta detección, ya que los patrones que definen los ojos se ven prácticamente iguales ya sea que estén coloreados o no.

El cascade_classifier es una instancia de CascadeClassifier, con características de Haar cargadas para los ojos. ¡Estamos localizando dinámicamente el archivo a través de Cuerdas f!

El método detectMultiScale() es lo que hace la detección real y puede detectar el mismo objeto en una imagen, independientemente de la escala. Devuelve una lista de las coordenadas de los objetos detectados, en forma de rectángulos (tuplas). ¡Esto hace que sea natural delinearlos con, bueno, rectángulos! Para cada tupla de (x, y, ancho, alto) ubicada en detected_objects, podemos dibujar un rectángulo.

El argumento minSize define el tamaño mínimo de un objeto a considerar. Si configura el tamaño para que sea realmente pequeño, es probable que el clasificador detecte muchos de falsos positivos en la imagen. Esto generalmente depende de la resolución de las imágenes con las que está trabajando y del tamaño promedio del objeto. En la práctica, se reduce a tamaños de prueba razonables hasta que funciona bien.

Establezcamos el tamaño mínimo en (0, 0) para ver qué se recoge:

changing the minsize argument of multiScaleDetect()

En esta imagen, no hay otra pelusa que pueda clasificarse erróneamente como un ojo, por lo que solo tenemos dos clasificaciones erróneas. ¡Uno en el ojo y otro en la barbilla! Dependiendo de la resolución de la imagen y del contenido, establecer un tamaño bajo podría terminar resaltando una buena parte de la imagen de forma incorrecta.

El proceso de detección de objetos para todas las demás imágenes es el mismo. Cargas el clasificador entrenado correctamente, ejecutas detectMultiScale() y dibujas encima de detected_objects.

¡Vale la pena señalar que puede combinar múltiples clasificadores! Por ejemplo, podría detectar la cara frontal, los ojos y la boca de un individuo por separado y dibujar sobre ellos. Carguemos estos clasificadores y usemos la misma imagen con diferentes colores para cada tipo de objeto:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import cv2

image_path = "face_image.jpg"
window_name = f"Detected Objects in {image_path}"
original_image = cv2.imread(image_path)

# Convert the image to grayscale for easier computation
image_grey = cv2.cvtColor(original_image, cv2.COLOR_RGB2GRAY)

eye_classifier = cv2.CascadeClassifier(
    f"{cv2.data.haarcascades}haarcascade_eye.xml")

face_classifier = cv2.CascadeClassifier(
    f"{cv2.data.haarcascades}haarcascade_frontalface_alt.xml")

smile_classifier = cv2.CascadeClassifier(
    f"{cv2.data.haarcascades}haarcascade_smile.xml")


detected_eyes = eye_classifier.detectMultiScale(image_grey, minSize=(50, 50))
detected_face = face_classifier.detectMultiScale(image_grey, minSize=(50, 50))
detected_smile = smile_classifier.detectMultiScale(image_grey, minSize=(200, 200))

# Draw rectangles on eyes
if len(detected_eyes) != 0:
    for (x, y, width, height) in detected_eyes:
        cv2.rectangle(original_image, (x, y),
                      (x + height, y + width),
                      (0, 255, 0), 2)
# Draw rectangles on eyes
if len(detected_face) != 0:
    for (x, y, width, height) in detected_face:
        cv2.rectangle(original_image, (x, y),
                      (x + height, y + width),
                      (255, 0, 0), 2)
        
# Draw rectangles on eyes
if len(detected_smile) != 0:
    for (x, y, width, height) in detected_smile:
        cv2.rectangle(original_image, (x, y),
                      (x + height, y + width),
                      (0, 0, 255), 2)

cv2.namedWindow(window_name, cv2.WINDOW_KEEPRATIO)
cv2.imshow(window_name, original_image)
cv2.resizeWindow(window_name, 400, 400)
cv2.waitKey(0)
cv2.destroyAllWindows()

Aquí, hemos cargado tres clasificadores: uno para sonrisas, uno para ojos y otro para rostros. Cada uno de ellos se ejecuta en la imagen y dibujamos rectángulos alrededor de todos los objetos detectados, coloreando los rectángulos según la clase del objeto:

Running multiple haar-cascade classifiers on the same image

La sonrisa no se captó tan bien, tal vez porque la sonrisa en la imagen es bastante neutral. No es una gran sonrisa, lo que podría haber desviado el clasificador.

Detección de objetos en un video usando OpenCV

Con la detección de objetos en imágenes fuera del camino, cambiemos a videos. Los videos son, de todos modos, solo imágenes en breve sucesión, por lo que se aplica el mismo proceso. Esta vez, sin embargo, se aplican en cada cuadro.

Para detectar objetos en un video, el paso principal es cargar el archivo de video en el programa. Después de cargar el archivo de video, tenemos que segregar los datos de video cuadro por cuadro y realizar la detección de objetos como antes.

Cargar un video usando OpenCV

Para esta guía, usaremos un video disponible gratuitamente de [un gato en un árbol](https://www.pexels.com/video/cat-on-the-top-of-a-tree-4768520 /), guardado como cat-on-tree.mp4 localmente. El archivo es de uso gratuito, según el creador del video, ¡así que estamos listos para comenzar!

Primero carguemos el video y mostrémoslo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import cv2
import time

video_path = "cat-on-tree.mp4"
window_name = f"Detected Objects in {video_path}"
video = cv2.VideoCapture(video_path)

while True:
    # read() returns a boolean alongside the image data if it was successful
    ret, frame = video.read()
    # Quit if no image can be read from the video
    if not ret:
        break
    # Resize window to fit screen, since it's vertical and long
    cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
    cv2.imshow(window_name, frame)
    if cv2.waitKey(1) == 27:
        break
    # Sleep for 1/30 seconds to get 30 frames per second in the output
    time.sleep(1/30)

video.release()
cv2.destroyAllWindows()

Este código leerá el archivo de video y mostrará su contenido hasta que se presione la tecla Esc. El VideoCapture() se usa para leer el archivo de video desde la ruta, y si le damos el valor 0 en el método, abrirá la cámara web y leerá los cuadros de la entrada. Haremos esto más tarde y por ahora trataremos con un archivo de video local.

Ahora, podemos aplicar un clasificador de Haar-Cascade como antes en cada imagen del video:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import cv2
import time

video_path = "cat-on-tree.mp4"
window_name = f"Detected Objects in {video_path}"
video = cv2.VideoCapture(video_path)

while True:
    # read() returns a boolean alongside the image data if it was successful
    ret, frame = video.read()
    # Quit if no image can be read from the video
    if not ret:
        break
    cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
    # Greyscale image for classification
    image = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    # Define classifier
    cascade_classifier = cv2.CascadeClassifier(
        f"{cv2.data.haarcascades}haarcascade_frontalcatface.xml")
    # Detect objects
    detected_objects = cascade_classifier.detectMultiScale(
        image, minSize=(50, 50))
    # Draw rectangles
    if len(detected_objects) != 0:
        for (x, y, height, width) in detected_objects:
            cv2.rectangle(
                frame, (x, y), ((x + height), (y + width)), (0, 255, 0), 15)
    #Show image
    cv2.imshow(window_name, frame)
    
    if cv2.waitKey(1) == 27:
        break

video.release()
cv2.destroyAllWindows()

El clasificador está entrenado en imágenes frontales de gatos, lo que significa que realmente no puede detectar perfiles. Durante una buena parte del video, el gato se posiciona desde un perfil, por lo que hasta que mueva su rostro hacia la cámara, seguramente habrá muchos errores de clasificación.

Da la casualidad de que el fondo borroso tiene algunas características que el clasificador recoge como posibles caras de gatos. Sin embargo, una vez que mueve la cabeza, claramente se fija en su cara.

Esto es lo que clasifica cuando el gato mira hacia un lado:

Haar Cascade Classifier no detecta gato al mirar hacia un lado

Y cómo atrapa correctamente al gato cuando está frente a la cámara:

Haar Cascade Classifier detecta correctamente un gato cuando mira a la cámara

En realidad, estamos detectando estos cuadros en tiempo real en el video. También podríamos guardar estos objetos detectados (nuevamente, solo una lista de números) y dibujarlos "fuera de línea" para cada cuadro y volver a procesar el video para ahorrar energía de la CPU mientras se lleva a cabo la detección.

Detección de objetos en tiempo real mediante OpenCV

Detectar objetos en un video en tiempo real, nuevamente, no es nada diferente de detectar desde videos o imágenes. Sin embargo, detectamos la cara del gato en tiempo real en el video, el video era local.

¡Obtengamos una transmisión de video desde una cámara web! Para tomar la entrada de la cámara web, tenemos que hacer un ligero cambio en la llamada VideoCapture(). Como se mencionó anteriormente, en lugar de darle una ruta de archivo, le damos un número (en la mayoría de los casos, 0, cuando tiene una cámara web):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import cv2

window_name = "Detected Objects in webcam"
video = cv2.VideoCapture(0)

while video.isOpened():
    ret, frame = video.read()
    if not ret:
        break
    cv2.imshow(window_name, frame)
    if cv2.waitKey(1) == 27:
        break

video.release()
cv2.destroyAllWindows()

{.icon aria-hidden=“true”}

Nota: en macOS, es posible que deba otorgar a la Terminal o al programa que ejecuta la Terminal permisos para usar la cámara web antes de que esto funcione.

Ahora, para realizar la detección de objetos en tiempo real, podemos seguir el mismo enfoque que hicimos con el archivo de video, es decir, segregar cada cuadro y detectar objetos cuadro por cuadro y mostrarlos al unísono:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import cv2

window_name = "Detected Objects in webcam"
video = cv2.VideoCapture(0)

while video.isOpened():
    ret, frame = video.read()

    if not ret:
        break

    image = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    cascade_classifier = cv2.CascadeClassifier(
        f"{cv2.data.haarcascades}haarcascade_frontalface_default.xml")
    detected_objects = cascade_classifier.detectMultiScale(
        image, minSize=(20, 20))

    if len(detected_objects) != 0:
        for (x, y, height, width) in detected_objects:
            cv2.rectangle(
                frame, (x, y), ((x + height), (y + width)), (0, 255, 0), 5)
    cv2.imshow(window_name, frame)

    if cv2.waitKey(1) == 27:
        break

video.release()
cv2.destroyAllWindows()

Cuando ejecute el código anterior, aparecerá una ventana emergente que transmite desde su cámara web y verá un rectángulo que resalta su rostro. Lo más probable es que este código se ejecute más rápido que el anterior, ya que las cámaras web generalmente no tienen una resolución realmente alta, por lo que estas imágenes son mucho menos costosas desde el punto de vista computacional.

{.icon aria-hidden=“true”}

Ayuda si estás sentado en una habitación bien iluminada, o si al menos tienes una fuente de luz dirigida hacia tu cara.

Conclusión

En esta guía, hemos usado OpenCV para realizar la detección de objetos en Python, usando el clasificador Haar-Cascade.

Nos presentaron el clasificador, las características de Haar y realizamos la detección de objetos en imágenes, videos en tiempo real, así como una transmisión de video desde una cámara web.

El siguiente paso en la detección de objetos usando OpenCV es explorar otros clasificadores como Yolo y mobilenetv3 porque la precisión que obtiene de Haar Cascades es mediocre en comparación con las alternativas de redes neuronales profundas. fundas.