Detecção de Fadiga em tempo real com Python, OpenCV e dlib

Neste artigo iremos construir um detector de fadiga utilizando Python, OpenCV e a biblioteca de detecção de landmarks dlib. Projeto baseado no artigo de Adrian Rosebrock.

Detecção de Fadiga em tempo real com Python, OpenCV e dlib

Neste artigo iremos construir um detector de fadiga utilizando Python, OpenCV e a biblioteca de detecção de landmarks dlib. Esse projeto foi baseado neste artigo do mestre da visão computacional Adrian Rosebrock.

O que iremos utilizar?

Para esse projeto iremos utilizar Python e algumas bibliotecas, dentre elas: OpenCV, dlib e imutils. Utilizaremos alguns recursos adicionais, porém essas três bibliotecas são as principais para atingirmos nosso objetivo central: Detectar Fadiga.

OpenCV

Em nosso projeto o OpenCV será utilizado, principalmente, para realizar o pré-processamento das imagens que serão utilizadas para a detecção. Além disso, iremos utilizá-la para exibir as imagem (frames), aplicar o contorno aos pontos de interesse (em nosso caso, os olhos), adicionar texto sobre a imagem, etc.

Imutils

A biblioteca imutils irá nos auxiliar na captura do vídeo na forma de streaming, para que possamos realizar a detecção em tempo real.

Dlib

Será com a biblioteca dlib, em conjunto com um modelo pré-treinado de deep learning (presente na mesma) - e capaz de detectar 68 landmarks -, que iremos detectar as landmarks faciais.

Mas o que são landmarks faciais?

Em nosso contexto, as landmarks faciais são pontos de interesse ao longo de uma área, ou seja, o objetivo de um detector de landmarks faciais é identificar estruturas importantes de um rosto utilizando métodos previsão de formas (shape prediction). Detectar essas marcações, em geral utiliza dois processos:

  1. Localizar a face na imagem
  2. Detectar as estruturas faciais chave do objeto de interesse

A localização da face pode ser realizada de diversas maneiras com diversos algoritmos, mas, para que nossa detecção funcione, precisamos extrair dessa imagem a bounding box (uma espécie de caixa) que representa a face.

Com a área do rosto, podemos aplicar a segunda etapa: detectar as estruturas faciais chave. Existem diversos algoritmos para tais tarefas, mas em essência, a maioria tenta detectar e rotular as regiões:

  • boca;
  • nariz;
  • sobrancelhas (direita e esquerda)
  • olhos (direito e esquero);
  • mandíbula;

Detectar Fadiga

Até agora conseguimos carregar o vídeo e detectar os pontos de interesse dos rostos nas imagens, mas como detectamos a fadiga?

Nesse projeto iremos considerar a proporção de abertura dos olhos, assim, se a proporção for menor que nosso limiar, temos a fadiga.

Como estamos utilizando a biblioteca dlib com o detector de 68 landmarks, temos, para cada olho, 6 pontos, conforme imagem abaixo:

Fonte: https://www.pyimagesearch.com/2017/04/03/facial-landmarks-dlib-opencv-python/

Podemos calcular a proporção do aspecto do olho com a seguinte equação:

Fonte: https://towardsdatascience.com/drowsiness-detection-system-in-real-time-using-opencv-and-flask-in-python-b57f4f1fcb9e

Com esse valor, conseguiremos identificar se há ou não sinais de fadiga na imagem/vídeo.

Juntando tudo

Combinaremos essas e outras bibliotecas para criar nosso detector de fadiga. Começaremos instalando e importando todas elas.

pip install opencv-python imutils playsound numpy

NOTA: para a instalação correta da dlib recomendo seguir esse tutorial.

# importar pacotes necessários
from os.path import join, dirname
from scipy.spatial import distance as dist
from imutils.video import VideoStream
from imutils import face_utils
from threading import Thread
import numpy as np
import playsound
import imutils
import time
import dlib
import cv2
import matplotlib.pyplot as plt
import os
  1. Definiremos algumas constantes que serão utilizadas ao longo do algoritmo
# definir constantes
ALARM_SOUND = join(dirname(__file__), "buzina.wav") # local do arquivo do alarme
WEBCAM = os.environ.get('WEBCAM', 1) # indice da câmera que capturará o stream
EYE_THRESHOLD = 0.25 # limiar de "abertura" dos olhos
FRAMES_SEQ = 40 # quantidade de frames seguidos que o EAR médio deve permanecer abaixo do limiar antes de soar o alarme
COUNTER = 0 # contador
ALARM_TRIGGERED = False # indica se o alarme está tocando ou não
SHAPE_PREDICTOR = join(dirname(__file__), 'shape_predictor_68_face_landmarks.dat') # caminho do modelo pré-treinado

2. Carregar o modelo para a dlib e capturar os índices dos olhos do previsor

# carregar o dlib para detectar rostos
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor(SHAPE_PREDICTOR)

# pegar os indices do previsor, para olhos esquero e direito
(lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS['left_eye']
(rStart, rEnd) = face_utils.FACIAL_LANDMARKS_IDXS['right_eye']

3. Capturar o vídeo como stream (imutils)

print("[INFO] inicializando streaming de video")
vs = VideoStream(src=WEBCAM).start()
time.sleep(2.0)

Como nosso objetivo é capturar os frames em stream, os passos a seguir serão colocados dentro de um loop infinito while True

4. Converter a imagem para escala de cinza (opencv)

# ler o frame do stream
frame = vs.read()
# redimensionar imagem para 800px de largura
frame = imutils.resize(frame, width=800)
# converter para escala de cinza
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

5. Detectar faces (dlib)

# detecar faces da imagem em escala de cinza utilizando o detector configurado anteriormente
rects = detector(gray, 0)

6. Extrair olhos (direito e esquerdo) - para cada face detectada

7. Calcular o EAR de cada olho e o EAR médio dos dois olhos - Separamos o calculo do EAR na função:

def calculate_eye_aspect_ratio(eye):
    # calcular a distância euclidiana entre os conjuntos das
    # landmarks verticais do olho coordenadas-(x, y)
    A = dist.euclidean(eye[1], eye[5])
    B = dist.euclidean(eye[2], eye[4])

    # calcular a distância euclidiana entre as
    # landmarks horizontais do olho coordenadas-(x, y)
    C = dist.euclidean(eye[0], eye[3])

    # calcular o EAR
    ear = (A + B) / (2.0 * C)
    return ear

Utilizamos a distancia euclidiana entre as landmarks verticais e a landmark horizontal e aplicamos a fórmula citada anteriormente.

8. Disparar alarme e exibir mensagem na tela caso: EAR ficar abaixo do limiar 40 quadros consecutivos (frames).

    # loop nas faces detectadas
    for rect in rects:
        shape = predictor(gray, rect)
        shape = face_utils.shape_to_np(shape)

        # extrair coordenadas dos olhos e calcular a proporção de abertura
        leftEye = shape[lStart:lEnd]
        rightEye = shape[rStart:rEnd]
        leftEAR = calculate_eye_aspect_ratio(leftEye)
        rightEAR = calculate_eye_aspect_ratio(rightEye)

        # ratio medio para os dois olhos
        ear = (leftEAR + rightEAR) / 2.0

        # convex hull para os olhos
        leftEyeHull = cv2.convexHull(leftEye)
        rightEyeHull = cv2.convexHull(rightEye)
        cv2.drawContours(frame, [leftEyeHull], -1, (0, 255, 0), 1)
        cv2.drawContours(frame, [rightEyeHull], -1, (0, 255, 0), 1)

        # exibe gráfico
        draw_graph(ear)

        # checar ratio do olho x threshold
        if ear < EYE_THRESHOLD:
            COUNTER += 1

            # verificar critério para soar o alarme
            if COUNTER >= FRAMES_SEQ:
                # ligar alarme
                if not ALARM_TRIGGERED:
                    ALARM_TRIGGERED = True
                    t = Thread(target=trigger_alarm)
                    t.daemon = True
                    t.start()

                cv2.putText(frame, "[ALERTA] FADIGA!", (10, 30),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)

            # se acima do threshold, desliga alarme e reseta contador
        else:
            ALARM_TRIGGERED = False
            COUNTER = 0

        # desenhar a proporção de abertura dos olhos
        cv2.putText(frame, "EAR {:.2f}".format(ear), (300, 30),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)

9. Mostrar frame e aguardar tecla de saída

# ainda dentro do loop while True
# mostrar frame
cv2.imshow("Frame", frame)
key = cv2.waitKey(1) & 0xFF

# tecla para sair do script "q"
if key == ord("q"):
	break

10. Finalizar tudo

# fora do loop while
# clean
cv2.destroyAllWindows()
vs.stop()

Demonstração:

Conclusão

Os recursos de detecção de landmarks da dlib são bastante poderosos, e as aplicaçõs são inúmeras, com eles podemos, por exemplo:

  • aplicar filtros semelhantes aos do Instagram (bigode, óculos, modificar a boca, etc.);
  • construir um detector de fadiga semelhante aos encontrados em veículos mais modernos (como nosso exemplo);
  • ou até mesmo  extrair emoções de expressões faciais.

Referências

Facial landmarks with dlib, OpenCV, and Python - PyImageSearch
Learn how to detect and extract facial landmarks from images using dlib, OpenCV, and Python.
Drowsiness Detection System in Real-Time using OpenCV and Flask in Python
This article provides an overview of a system that detects whether a person is drowsy while driving and if so, alerts him by using voice…

http://vision.fe.uni-lj.si/cvww2016/proceedings/papers/05.pdf

Drowsiness detection with OpenCV - PyImageSearch
In this tutorial, I’ll demonstrate how to build a driver drowsiness detector using OpenCV, Python, and computer vision techniques.