Detección facial en Python con OpenCV

En este artículo, realizaremos detección facial en imágenes y una cámara web en vivo usando Python y OpenCV.

Introducción

La detección facial es un caso de uso poderoso y común de Machine Learning. Se puede utilizar para automatizar tareas manuales como la asistencia a la escuela y la aplicación de la ley. Por otro lado, se puede utilizar para la autorización biométrica.

En este artículo, realizaremos la detección facial en Python, usando OpenCV.

CV abierto

OpenCV es una de las bibliotecas de visión artificial más populares. Fue escrito en C y C++ y también brinda soporte para Python, además de Java y MATLAB. Si bien no es la biblioteca más rápida que existe, es fácil trabajar con ella y proporciona una interfaz de alto nivel, lo que permite a los desarrolladores escribir código estable.

Instalemos OpenCV para que podamos usarlo en nuestro código Python:

1
$ pip install opencv-contrib-python

Alternativamente, puede instalar opencv-python solo para los módulos principales de OpenCV. opencv-contrib-python contiene los módulos principales, así como los módulos contrib que brindan una funcionalidad extendida.

Detección de rostros en una imagen mediante OpenCV

Con OpenCV instalado, podemos importarlo como cv2 en nuestro código.

Para leer una imagen, usaremos la función imread(), junto con la ruta a la imagen que queremos procesar. La función imread() simplemente carga la imagen del archivo especificado en un ndarray. Si la imagen no se puede leer, por ejemplo, en caso de que falte un archivo o un formato no compatible, la función devolverá Ninguno.

Usaremos una imagen de Conjunto de datos Kaggle:

1
2
3
4
import cv2

path_to_image = 'Parade_12.jpg'
original_image = cv2.imread(path_to_image)

La información RGB completa no es necesaria para la detección facial. El color contiene mucha información irrelevante en la imagen, por lo que es más eficiente eliminarlo y trabajar con una imagen en escala de grises. Además, el algoritmo Viola-Jones, que funciona bajo el capó con OpenCV, comprueba la diferencia de intensidad del área de una imagen. Las imágenes en escala de grises señalan esta diferencia de manera más dramática.

Nota: En el caso de imágenes en color, las imágenes decodificadas tendrán los canales almacenados en orden BGR, por lo que al cambiarlos a escala de grises, debemos usar el indicador cv2.COLOR_BGR2GRAY:

1
image = cv2.cvtColor(original_image, cv2.COLOR_BGR2GRAY)

Esto podría haberse hecho directamente al usar imread(), configurando el indicador cv2.IMREAD_GRAYSCALE:

1
original_image = cv2.imread(path_to_image, cv2.IMREAD_GRAYSCALE)

La biblioteca OpenCV viene con varios clasificadores pre-entrenados que están entrenados para encontrar diferentes cosas, como caras, ojos, sonrisas, parte superior del cuerpo, etc.

Las funciones Haar para detectar estos objetos se almacenan como XML y, dependiendo de cómo haya instalado OpenCV, se pueden encontrar con mayor frecuencia en Lib\site- paquetes\cv2\datos. También se pueden encontrar en el Repositorio OpenCV GitHub.

Para acceder a ellos desde el código, puede usar cv2.data.haarcascades y agregar el nombre del archivo XML que le gustaría usar.

Podemos elegir qué características de Haar queremos usar para nuestra detección de objetos, agregando la ruta del archivo al constructor CascadeClassifier(), que usa modelos previamente entrenados para la detección de objetos:

1
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")

Ahora, podemos usar este objeto face_cascade para detectar rostros en la Imagen:

1
detected_faces = face_cascade.detectMultiScale(image=image, scaleFactor=1.3, minNeighbors=4)

Cuando se entrenan los modelos de detección de objetos, se entrenan para detectar rostros de cierto tamaño y es posible que pasen por alto rostros que son más grandes o más pequeños de lo esperado. Con esto en mente, la imagen se redimensiona varias veces con la esperanza de que una cara termine siendo de un tamaño "detectable". El scaleFactor le permite a OpenCV saber cuánto escalar las imágenes. En nuestro caso, 1.3 significa que puede escalar 30% hacia abajo para tratar de hacer coincidir mejor las caras.

En cuanto al parámetro minNeighbors, se usa para controlar el número de falsos positivos y falsos negativos. Define el número mínimo de rectángulos positivos (detectar rasgos faciales) que deben estar adyacentes a un rectángulo positivo para que se considere realmente positivo. Si minNeighbors se establece en 0, el más mínimo indicio de una cara se contará como una cara definitiva, incluso si no se detectan otras características faciales cerca de ella.

Los parámetros scaleFactor y minNeighbors son algo arbitrarios y se establecen de forma experimental. Hemos elegido valores que funcionaron bien para nosotros y no dieron falsos positivos, con la contrapartida de más falsos negativos (caras no detectadas).

El método detectMultiScale() devuelve una lista de rectángulos de todos los objetos detectados (caras en nuestro primer caso). Cada elemento de la lista representa una cara única. Esta lista contiene tuplas, (x, y, w, h), donde los valores x, y representan las coordenadas superiores izquierdas del rectángulo, mientras que los valores w, h representan el ancho y el alto del rectángulo. rectángulo, respectivamente.

Podemos usar la lista de rectángulos devuelta y usar la función cv2.rectangle() para dibujar fácilmente los rectángulos donde se detectó una cara. Tenga en cuenta que el color proporcionado debe ser una tupla en orden RGB:

1
2
3
4
5
6
7
8
for (x, y, width, height) in detected_faces:
    cv2.rectangle(
        image,
        (x, y),
        (x + width, y + height),
        color,
        thickness=2
    )

Ahora, pongámoslo todo junto:

 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
import cv2

def draw_found_faces(detected, image, color: tuple):
    for (x, y, width, height) in detected:
        cv2.rectangle(
            image,
            (x, y),
            (x + width, y + height),
            color,
            thickness=2
        )

path_to_image = 'Parade_12.jpg'
original_image = cv2.imread(path_to_image)

if original_image is not None:
    # Convert image to grayscale
    image = cv2.cvtColor(original_image, cv2.COLOR_BGR2GRAY)

    # Create Cascade Classifiers
    face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")
    profile_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_profileface.xml")
    
    # Detect faces using the classifiers
    detected_faces = face_cascade.detectMultiScale(image=image, scaleFactor=1.3, minNeighbors=4)
    detected_profiles = profile_cascade.detectMultiScale(image=image, scaleFactor=1.3, minNeighbors=4)

    # Filter out profiles
    profiles_not_faces = [x for x in detected_profiles if x not in detected_faces]

    # Draw rectangles around faces on the original, colored image
    draw_found_faces(detected_faces, original_image, (0, 255, 0)) # RGB - green
    draw_found_faces(detected_profiles, original_image, (0, 0, 255)) # RGB - red

    # Open a window to display the results
    cv2.imshow(f'Detected Faces in {path_to_image}', original_image)
    # The window will close as soon as any key is pressed (not a mouse click)
    cv2.waitKey(0) 
    cv2.destroyAllWindows()
else:
    print(f'En error occurred while trying to load {path_to_image}')

Usamos dos modelos diferentes en esta foto. El modelo predeterminado para detectar rostros que miran hacia el frente y un modelo creado para detectar mejor los rostros que miran hacia un lado.

Los rostros detectados con el modelo frontalface se delinean en verde y los rostros detectados con el modelo profileface se delinean en rojo. La mayoría de las caras que encontró el primer modelo también habrían sido encontradas por el segundo, por lo que solo dibujamos rectángulos rojos donde el modelo profileface detectó una cara pero frontalface no:

1
profiles_not_faces = [x for x in detected_profiles if x not in detected_faces]

El método imshow() simplemente muestra la imagen pasada en una ventana con el título proporcionado. Con la imagen que seleccionamos, esto proporcionaría el siguiente resultado:

detección de cara frontal y de perfil

Usar diferentes valores para scaleFactor y minNeighbors nos dará resultados diferentes. Por ejemplo, usar scaleFactor = 1.1 y minNeighbors = 4 nos da más falsos positivos y verdaderos positivos con ambos modelos:

factor de escala inferior de detección de rostros

Podemos ver que el algoritmo no es perfecto, pero es muy eficiente. Esto es más notable cuando se trabaja con datos en tiempo real, como una transmisión de video de una cámara web.

Detección de rostros en tiempo real usando una cámara web

Los flujos de video son simplemente flujos de imágenes. Con la eficiencia del algoritmo Viola-Jones, podemos detectar rostros en tiempo real.

Los pasos que debemos seguir son muy similares a los del ejemplo anterior con una sola imagen: lo realizaremos en cada imagen de la secuencia.

Para obtener la transmisión de video, usaremos la clase cv2.VideoCapture. El constructor de esta clase toma un parámetro entero que representa la transmisión de video. En la mayoría de las máquinas, se puede acceder a la cámara web pasando 0, pero en máquinas con varias transmisiones de video, es posible que deba probar diferentes valores.

A continuación, necesitamos leer imágenes individuales del flujo de entrada. Esto se hace con la función read(), que devuelve retval e image. La imagen es simplemente el marco recuperado. El valor de retorno retval se usa para detectar si un marco ha sido recuperado o no, y será Falso si no lo ha sido.

Sin embargo, tiende a ser inconsistente con los flujos de entrada de video (no detecta que la cámara web se ha desconectado, por ejemplo), por lo que ignoraremos este valor.

Avancemos y modifiquemos el código anterior para manejar una transmisión de 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
36
37
38
39
40
41
42
43
44
import cv2

def draw_found_faces(detected, image, color: tuple):
    for (x, y, width, height) in detected:
        cv2.rectangle(
            image,
            (x, y),
            (x + width, y + height),
            color,
            thickness=2
        )

# Capturing the Video Stream
video_capture = cv2.VideoCapture(0)

# Creating the cascade objects
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")
eye_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_eye_tree_eyeglasses.xml")

while True:
    # Get individual frame
    _, frame = video_capture.read()
    # Covert the frame to grayscale
    grayscale_image = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    
    # Detect all the faces in that frame
    detected_faces = face_cascade.detectMultiScale(image=grayscale_image, scaleFactor=1.3, minNeighbors=4)
    detected_eyes = eye_cascade.detectMultiScale(image=grayscale_image, scaleFactor=1.3, minNeighbors=4)
    draw_found_faces(detected_faces, frame, (0, 0, 255))
    draw_found_faces(detected_eyes, frame, (0, 255, 0))

    # Display the updated frame as a video stream
    cv2.imshow('Webcam Face Detection', frame)

    # Press the ESC key to exit the loop
    # 27 is the code for the ESC key
    if cv2.waitKey(1) == 27:
        break

# Releasing the webcam resource
video_capture.release()

# Destroy the window that was showing the video stream
cv2.destroyAllWindows()

Conclusión

En este artículo, hemos creado una aplicación de detección facial usando Python y OpenCV.

El uso de la biblioteca OpenCV es muy sencillo para los programas básicos de detección de objetos. Ajustar experimentalmente los parámetros scaleFactor y minNeighbors para los tipos de imágenes que desea procesar puede brindar resultados bastante precisos de manera muy eficiente. te.