Acoooorda menina! Identificando piscadas com Visão Computacional

Suzana Mota
5 min readApr 20, 2020

--

Demonstração do Projeto

Quem nunca dormiu no ponto, alguma vez na vida não é mesmo? Neste projeto identificamos se os seus olhos ficaram fechados por muito tempo e caso isso aconteça, ativamos um som de “Acoooooorda menina”!

A biblioteca dlib é muito utilizada em Visão Computacional e é capaz de identificar 68 landmarks no rosto, dessa forma, é possível selecionar pontos isolados e observar o comportamento de diferentes parte do rosto, você pode entender este processo com mais detalhes, neste artigo. Neste projeto, vamos analisar o comportamento dos olhos.

Para definir que os olhos estão fechados, vamos precisar de algumas definições:

  1. Área dos olhos: a área de abertura dos olhos, definida pela distância euclidiana entre os pontos chave.
  2. Threshold da área dos olhos: A área dos olhos mínima tolerada pela aplicação.
  3. Fechamento dos olhos: Os olhos serão considerados fechados se a área dos olhos detectada for menor que o threshold de área dos olhos definido.
  4. Threshold de Tempo de Fechamento dos olhos: Definimos a quantidade mínima de frames, que os olhos precisam estar fechados, para que o alarme toque.

Como podemos ver na imagem, cada olho é representado por 6 coordenadas (x, y), começando no canto esquerdo do olho (como se você estivesse olhando para a pessoa) e depois trabalhando no sentido horário em todo o restante da região.

Com base no trabalho no artigo Real-Time Eye Blink Detection using Facial Landmarks, podemos derivar uma equação que reflete essa relação. O numerador dessa equação calcula a distância entre os pontos verticais, enquanto o denominador calcula a distância entre os pontos horizontais.

É interessante observar que a proporção do olho é relativamente constante enquanto o olho está aberto, mas cai para zero quando o olho é fechado.

A partir dos thresholds definidos é possível identificar piscadas longas ou curtas, de acordo com a variação e sensibilidade dos thresholds definidos. Neste exemplo, vamos identificar piscadas longas, que poderiam observar se um motorista está cochilando ao dirigir, por exemplo e emitir um alerta sonoro.

Importante: Este projeto foi baseado neste exemplo, disponível em: https://www.pyimagesearch.com/2017/04/24/eye-blink-detection-opencv-python-dlib/

Para desenvolver este projeto, vamos precisar das seguintes dependências:

import cv2
import dlib
import numpy as np
from scipy.spatial import distance as dist
import pygame

Vamos ao código!

1. Definição de constantes:

Vamos precisar definir algumas constantes referentes aos pontos dos olhos dentro do dlib, a área mínima de abertura dos olhos, a quantidade de frames consecutivos com olhos fechados e os contadores de piscadas. Você pode alterar os valores dos thresholds e observar como a aplicação se comporta.

PREDICTOR_PATH = "landmarks/shape_predictor_68_face_landmarks.dat"# Eyes points
RIGHT_EYE_POINTS = list(range(36, 42))
LEFT_EYE_POINTS = list(range(42, 48))
# Thresholds
# Eyes area
EYE_AR_THRESH = 0.25
# Frames with eyes closed
EYE_AR_CONSEC_FRAMES = 10
# Count blinks
COUNTER_LEFT = 0
TOTAL_LEFT = 0
COUNTER_RIGHT = 0
TOTAL_RIGHT = 0

2. Observando a área dos olhos:

Esta função calcula a distância euclidiana entre os pontos verticais e horizontais definidos ao redor dos olhos. Dessa forma calculamos a área de abertura dos olhos do indivíduo.

def eye_aspect_ratio(eye):
# compute the euclidean distances between the two sets
# vertical eye landmarks (x, y)-coordinates
A = dist.euclidean(eye[1], eye[5])
B = dist.euclidean(eye[2], eye[4])
# compute the euclidean distance between the horizontal
# eye landmark (x, y)-coordinates
C = dist.euclidean(eye[0], eye[3])
# compute the eye aspect ratio
ear = (A + B) / (2.0 * C)
# return the eye aspect ratio
return ear

3. Detectando pontos do rosto:

Neste ponto utilizamos o preditor de pontos de face do dlib e vamos observar apenas os 6 pontos de cada um dos olhos e desenhar um contorno em torno deles.

detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor(PREDICTOR_PATH)
# Start capturing the WebCam
video_capture = cv2.VideoCapture(0)
while True:
ret, frame = video_capture.read()
if ret:
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
rects = detector(gray, 0)
for rect in rects:
x = rect.left()
y = rect.top()
x1 = rect.right()
y1 = rect.bottom()
landmarks = np.matrix([[p.x, p.y]for p in predictor(frame, rect).parts()])
left_eye = landmarks[LEFT_EYE_POINTS]
right_eye = landmarks[RIGHT_EYE_POINTS]
left_eye_hull = cv2.convexHull(left_eye)
right_eye_hull = cv2.convexHull(right_eye)
cv2.drawContours(frame, [left_eye_hull], -1, (0, 255, 0), 1)
cv2.drawContours(frame, [right_eye_hull], -1, (0, 255, 0), 1)
ear_left = eye_aspect_ratio(left_eye)
ear_right = eye_aspect_ratio(right_eye)
cv2.putText(frame, "Abertura olho esquerdo : {:.2f}".format(ear_left), (0, 400), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 0, 0), 2)
cv2.putText(frame, "Abertura olho direito: {:.2f}".format(ear_right), (0, 440), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 0, 0), 2)

4. Validação dos Thresholds:

Aqui iremos realizar as validaçoes e contagens. Verificarmos se a area dos olhos detectada em tempo real é menor que a area definida no threshold e se a quantidade de frames com os olhos fechados é maior que a do threshold definido. Se as duas condições ocorrerem, o som é acionado.

if ear_left < EYE_AR_THRESH:
COUNTER_LEFT += 1
else:
if COUNTER_LEFT >= EYE_AR_CONSEC_FRAMES:
TOTAL_LEFT += 1
cv2.putText(frame, "PISCOU OLHO ESQUERDO : {}".format(TOTAL_LEFT),(10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
COUNTER_LEFT = 0if ear_right < EYE_AR_THRESH:
COUNTER_RIGHT += 1
else:
if COUNTER_RIGHT >= EYE_AR_CONSEC_FRAMES:
TOTAL_RIGHT += 1
cv2.putText(frame, "PISCOU OLHO DIREITO: {}".format(TOTAL_RIGHT),(10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
COUNTER_RIGHT = 0
cv2.putText(frame, "PISCADAS OLHO ESQUERDO : {}".format(TOTAL_LEFT),(10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 0, 0), 2)cv2.putText(frame, "PISCADAS OLHO DIREITO: {}".format(TOTAL_RIGHT),(10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 0, 0), 2)cv2.imshow("Blink Project", frame)
ch = 0xFF & cv2.waitKey(1)
if ch == ord('q'):
break
cv2.destroyAllWindows()

Viu como é simples? Implemente você também este projeto, faça suas alterações e compartilhe suas experiências conosco! :)

--

--