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.

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:
- Localizar a face na imagem
- 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:

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

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


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