• No results found

Contributions, limitations and future studies

CHAPTER 5: CONCLUSION

5.2 Contributions, limitations and future studies

T4300 a 2.10 GHz, 3.00 GB de memoria RAM y Windows 7 Ultimate a 32 bits. Utilizar software de uso libre para desarrollar la prueba, en este caso FittsTaskTwo. [35] Procedimiento Llevar a cabo tareas simples de movimiento multidireccional del cur- sor y selección de objetos, mencionadas en la norma ISO 9241-411 y basada en los conceptos propuestos por Fitts . Cada participante debe realizar la tarea con todos los dispositivos en orden aleatorio. Antes de la prueba se debe informar a todos los parti- cipantes del propósito del experimento, se debe llevar a cabo una demostración inicial de la tarea a desarrollar y permitir que los participantes se familiaricen con el manejo de cada dispositivo.

Para cada sesión el software genera cuatro pruebas, cada prueba presenta 3 círculos de igual diámetro dispuestos en un arreglo circular. En cada prueba varía el diámetro de los círculos (entre 30 y 60 pixeles) y el diámetro del arreglo circular (entre 300 y 500 pixeles), lo que genera condiciones de trabajo que presentan dicultades nominales variables (ID). La secuencia se inicia seleccionando el círculo superior, a continuación

se selecciona el círculo en el lado opuesto, continuando la selección en el sentido de las agujas del reloj para el siguiente círculo del lado opuesto hasta seleccionarlos todos. El círculo a seleccionar se distingue por estar resaltado hasta cuando se detecta un clic del dispositivo. Es necesario informar que los círculos se deben seleccionar de la forma más rápida y precisa posible, cometiendo máximo un error por secuencia (cada error genera una alarma sonora). Para cada dispositivo, un participante debe realizar cuatro secuencias de selección. Finalmente, se obtiene el rendimiento (throughput) de cada persona con cada dispositivo en particular, permitiendo análisis comparativo con los dispositivos apuntadores de uso mas común.

4.4. Ejecución Financiera

En el cuadro 4.1 se detalla la ejecución de los recursos asignados a este proyecto. En el rubro Bibliografía no se presentó ninguna ejecución ya que se tuvo acceso a libros especializados en OpenCV, Python y Visión Articial, en formato digital y de acceso libre por internet. Los recursos asignados a Servicios Técnicos no se ejecutaron porque no se requirió fabricación externa de la carcaza.

Cuadro 4.1: Ejecución nanciera. Fuente: Autores Fuente: Autores

Capitulo 5

Resultados, Conclusiones y Trabajo

Futuro

5.1. Resultados

Inicialmente, se realizaron pruebas con 4 personas de distinto género, edad y expe- riencia con el entorno informático. El software FittsTaskTwo [35], arroja, entre otros datos, el valor del rendimiento o throughput (TP) para cada dispositivo utilizado en las pruebas. Este parámetro permite conocer cuanto se demora el usuario en realizar una tarea con un grado especíco de complejidad, utilizando una dispositivo apuntador. Los resultados para las pruebas realizadas con el ratón óptico, el touchpad y el dispositivo apuntador desarrollado, se presentan en la gura 5.1.

Figura 5.1: Gráco del rendimiento del dispositivo apuntador en comparación con dos dispositivos de uso común

Fuente: Autores

tador desarrollado es inferior al que se obtiene utilizando el ratón óptico o el touchpad, mas especícamente, el ratón óptico tiene casi el doble de rendimiento y el touchpad tiene un rendimiento 2.5 veces mayor. Sin embargo, es importante hacer notar que, a pesar de tomar el doble del tiempo, el dispositivo apuntador desarrollado permite realizar las mismas tareas de desplazamiento y selección que se realizan con los dispo- sitivos apuntadores comunes. Además, el uso habitual permitiría mejorar el parámetro de rendimiento.

Consultando los resultados de otros estudios mas detallados para diferentes disposi- tivos apuntadores, se evidencia que el rendimiento del dispositivo desarrollado durante este proyecto (TP=1.7) es similar al rendimiento de una palanca de mando o Joystick (TP=1.8). [36]

5.2. Conclusiones

Como resultado de todo el proceso de aprendizaje que genera un trabajo de grado, queda la sensibilidad de identicar una necesidad en un grupo poblacional con disca- pacidad en miembros superiores, la adquisición de conocimientos durante la consulta de bibliografía y la vigilancia tecnológica, la satisfacción de aplicar los conocimientos adquiridos durante el desarrollo de nuestra carrera para satisfacer la necesidad identi- cada y la templanza que brinda culminar un proyecto, salvando todas las dicultades encontradas durante su desarrollo.

En cuanto a los objetivos y actividades planteadas inicialmente, podemos concluir que:

Se logró obtener un dispositivo apuntador, que permite controlar el cursor del computador mediante movimientos de la cabeza utilizando clasicadores de uso libre (Filtros Haar en Cascada) y algoritmos clasicadores adaptados a las nece- sidades de este proyecto (Integrales Proyectivas).

Se seleccionó una plataforma óptima de desarrollo, compuesta por un micro- computador Raspberry Pi 2, una tarjeta Teensy 3.1, una cámara Raspberry Pi NoIR, el lenguaje de programación Python, las librerías OpenCV para visión ar- ticial y la cual se comunica con el computador como dispositivo HID a través de interface USB.

5.3 Trabajo futuro Se desarrolló una aplicación software para la tarjeta principal y un algoritmo de funcionamiento para la tarjeta auxiliar.

Se realizó la integración del hardware y el software en un solo dispositivo que emula un ratón con interface USB-HID. Se realizó la validación del dispositivo obtenido mediante un protocolo de pruebas estandarizado.

Se elaboró el manual de usuario.

5.3. Trabajo futuro

En la fase nal de este proyecto, se generó la espectativa de realizar mejoras al dis- positivo, las cuales se encuentran fuera del alcance planteado inicialmente, pero cons- tituyen propuestas que permiten ampliar y mejorar la funcionalidad del sistema. Estas propuestas son:

Implementar el funcionamiento alternativo como teclado.

Generar la posibilidad de reconocimiento de otros gestos faciales, los cuales per- mitan generar acciones externas pero relacionadas con el funcionamiento de un computador (encendido de estabilizador, iluminación, modem, centro de sonido). Realizar el desarrollo sobre un sistema operativo que aproveche la característica multi-procesador de la Raspberry Pi 2 (actualmente en versiones beta).

Anexo A

Código Python para Raspberry Pi 2

- - - - - - - - - - - - - - - - - - - - - - - -

Trabajo de grado para optar al título de Ingeniero (a) en Control

UNIVERSIDAD DISTRITAL “FRANCISCO JOSÉ DE CALDAS” - FACULTAD TECNOLÓGICA Bogotá D.C. , 2015

CÓDIGO DESARROLLADO EN PYTHON PARA TARJETA RASPBERRY PI 2

#python code

# -*- coding: UTF-8 -*-

#La línea anterior permite usar tildes y la ñ

#=========================================================== #=========================================================== __author__ = "Yesid Rodríguez Lozano"

__email__ = "[email protected]"

__credits__ = ["Yesid Rodríguez Lozano ", "Leidy Marcela Peña Bueno"] __copyright__ = "Copyright 2015" __version__ = "1.0" #=========================================================== #=========================================================== # DESCRIPCIÓN #---

# Realiza detección facial, calcula coordenadas de movimiento # y las comunica a una tarjeta Teensy 3.1 para generar # movimiento del cursor del ratón.

# Detecta cuando se abre la boca e inicia rutina de # reconocimiento de ojos. Cada vez que se cierra un ojo, # genera un clic de ratón.

#--- #=========================================================== # LIBRERÍAS #--- import numpy as np #import matplotlib.pyplot as plt #import pylab as pl import cv2

#Librería para el uso de I2C import smbus

#Librería para usar puertos I/O import RPi.GPIO as GPIO

#La librería siguiente permite calcular tiempos de ejecucion import time

#La librería siguiente permite usar funciones matemáticas import math

#---

def ObtenerFotograma(Cam):

#---

#Esta función obtiene fotograma de la cámara (cam), lo convierte #a escala de grises, lo reduce, lo recorta, lo ecualiza

#para mejorar el contraste y retorna la imágen resultante #en tamaño 120x200.

#---

_, FotogColor = Cam.read(0) # Captura fotograma de la cámara. Se guarda en 'FotogColor' FotogGris_Grande = cv2.cvtColor(FotogColor, cv2.COLOR_BGR2GRAY) # Convierte a gris

FotogGris_Med = cv2.resize(FotogGris_Grande, (320, 240), interpolation=cv2.INTER_LINEAR) # Redimensiona FotogGris_Peque = cv2.getRectSubPix(FotogGris_Med, (120, 200), (160, 120)) # Recorta rectángulo de 120x200 FotogGris = cv2.equalizeHist(FotogGris_Peque) # Normaliza

#return FotogGris_Peque return FotogGris

#--- #---

def CalculaCentroRostro(iX, iY, iW, iH, vCoordX, vCoordY): #---

#Esta función obtiene el valor promedio de las últimas cinco #lecturas de las coordenadas del centro del rostro.

#Lo anterior brinda un punto de referencia (centro) estable. #Recibe las coordenadas X y Y de la esquina superior #izquierda del cuadrado que encierra el rostro detectado, #el ancho W y la altura H de dicho cuadrado y los vectores #con las últimas lecturas.

#Retorna coordenadas X,Y del centro del rostro. #--- iCoordX = int(iX + (iW / 2))

iCoordY = int(iY + (iH / 2)) vCoordX = np.roll(vCoordX, 1) vCoordX[0] = iCoordX iCoordX = int(np.mean(vCoordX)) vCoordY = np.roll(vCoordY, 1) vCoordY[0] = iCoordY iCoordY = int(np.mean(vCoordY)) lCentro = iCoordX, iCoordY return vCoordX, vCoordY, lCentro

#--- #---

def ObtenerProyeccionesHV(arrRegion): #--- #Esta función toma un array, obtiene las integrales #proyectivas horizontal e integral, las normaliza y las #visualiza.

if vProyHorz.var() == 0: # Obtiene proyección normalizada (media/varianza), teniendo en cuenta división por cero vProyHorz_norm = (vProyHorz - vProyHorz.mean())

else:

vProyHorz_norm = (vProyHorz - vProyHorz.mean()) / math.sqrt(vProyHorz.var())

if vProyVert.var() == 0: # Obtiene proyección normalizada (media/varianza), teniendo en cuenta división por cero vProyVert_norm = (vProyVert - vProyVert.mean())

else:

vProyVert_norm = (vProyVert - vProyVert.mean()) / math.sqrt(vProyVert.var()) return vProyHorz_norm, vProyVert_norm

#--- #---

def GrabaModeloBocaAbierta(VectorHorizontal, VectorVertical): #---

#Esta función recibe dos vectores de datos y los guarda #en los archivos 'ModeloBocaAbiertaHor.txt' y

#No verifica la coherencia de los datos, por lo que se

#require estar seguro que los datos de entrada correspondan #a las proyecciones de una boca abierta.

#No devuelve ninguna información.

#---

#Proceso para grabar el vector con la proyección horizontal actual en el archivo. #Como es un archivo de texto, se requiere convertir los datos numéricos del #vector a cadenas de texto.

aArchivo = open('ModeloBocaAbiertaHorz.txt', 'a') # Abre archivo para escritura for iDato in VectorHorizontal: # Cada número de tipo 'float' del vector... aArchivo.write(repr(iDato)) # es convertido a 'string' y escrito en el archivo.. aArchivo.write(' ') # y separado por un espacio en blanco

aArchivo.write('\n') # cuando se ha escrito todo el vector, se agrega un salto de línea aArchivo.close()

#Proceso para grabar el vector con la proyección vertical actual en el archivo. aArchivo = open('ModeloBocaAbiertaVert.txt', 'a') # Abre archivo para escritura for iDato in VectorVertical: # Cada número de tipo 'float' del vector...

aArchivo.write(repr(iDato)) # es convertido a 'string' y escrito en el archivo.. aArchivo.write(' ') # y separado por un espacio en blanco

aArchivo.write('\n') # cuando se ha escrito todo el vector, se agrega un salto de línea aArchivo.close()

#--- #---

def LeeModelo(cNombreArchivo, iTamVector): #---

#Esta función obtiene vector con el promedio de los datos #de las proyecciones. Estos datos se encuentran guardados en #un archivo de texto. Cada línea del archivo corresponde a #una proyección. Cada cadena de texto corresponde a un valor. #Las cadenas de texto están separadas por un espacio.

#---

iCuentaProyecciones = 0 # Inicializa conteo del número de proyecciones (líneas) guardadas en el archivo vVectorPromedio = np.zeros(iTamVector, dtype='float16') # Vector de trabajo

aArchivo = open(cNombreArchivo, 'r') # Abre archivo para lectura linea = aArchivo.readline() # Lee la primer línea...

while linea != "": # y realiza proceso hasta el final del archivo iCuentaProyecciones += 1

lista = linea.split() # Crea lista con cadenas de texto definidos por los espacios entre conjunto de caracteres de la línea lista = [float(i) for i in lista] # Convierte lista de cadenas de texto a lista de números tipo 'float'.

vVectorPromedio = vVectorPromedio + lista # Realiza suma uno a uno de los datos en los dos 'array' linea = aArchivo.readline() # Contínúa con la línea siguiente del archivo

vVectorPromedio = vVectorPromedio / iCuentaProyecciones # Calcula la media de cada conjunto de datos aArchivo.close() return vVectorPromedio #--- #--- def CapturaModelosOjos(lCentro): #--- #Esta función obtiene los modelos para las integrales #proyectivas de los ojos cerrados.

#Recibe las coordenadas del centro del rostro, delimita #la región de los ojos, toma cinco fotografías para cada #ojo (derecho e izquierdo), calcula las proyecciones #verticales y finalmente calcula los modelos con los #promedios de los datos obtenidos.

#Retorna los modelos de integral proyectiva vertical para el ojo derecho y el izquierdo. #---

#Enciende led de la cámara y luego lo apaga para indicar lectura de datos para el modelo ControlarLEDs(1, 1, 1, 1)

ControlarLEDs(1, 1, 0, 1) ControlarLEDs(0, 1, 0, 0.5) ControlarLEDs(0, 1, 1, 0)

vProyVert = np.zeros(30) # Vector de trabajo

for iCont in range(0, 10): # Toma 9 fotogramas consecutivos. El ojo izquierdo debe estar cerrado arrFotograma = ObtenerFotograma(cap)

RoiOjoIzq = arrFotograma[lCentro[1] - 15:lCentro[1] + 15, lCentro[0]:lCentro[0] + 35] # Encuadra zona del ojo izquierdo #_, RoiOjoIzq = cv2.threshold(RoiOjoIzq, 80, 255, cv2.THRESH_TOZERO) # XXXXXXXXXXXXXXXXXXX

#cv2.imshow('Captura Ojos' + str(iCont), RoiOjoIzq) # Muestra las cinco capturas de los ojos if 5 <= iCont <= 9:

vProyVert = vProyVert + RoiOjoIzq.mean(axis=1) # Suma 5 integrales proyectivas horizontales para ojo izquierdo cerrado vProyVert = vProyVert / 5 # Obtiene integral proyectiva promedio

if vProyVert.var() == 0: # Obtiene modelo normalizado (media/varianza), teniendo en cuenta división por cero vOjoIzq_Modelo = (vProyVert - vProyVert.mean())

else:

vOjoIzq_Modelo = (vProyVert - vProyVert.mean()) / math.sqrt(vProyVert.var()) #Enciende led de la cámara y luego lo apaga para indicar lectura de datos para el modelo ControlarLEDs(0, 1, 0, 0.5)

ControlarLEDs(1, 1, 0, 2) ControlarLEDs(1, 0, 0, 0.5) ControlarLEDs(1, 0, 1, 0)

if 2 <= iCont <= 6:

vProyVert = vProyVert + RoiOjoDer.mean(axis=1) # Suma 5 integrales proyectivas horizontales para ojo derecho cerrado vProyVert = vProyVert / 5 # Obtiene integral proyectiva promedio

if vProyVert.var() == 0: # Obtiene modelo normalizado (media/varianza), teniendo en cuenta división por cero vOjoDer_Modelo = (vProyVert - vProyVert.mean())

else:

vOjoDer_Modelo = (vProyVert - vProyVert.mean()) / math.sqrt(vProyVert.var()) ControlarLEDs(1, 0, 0, 0.5)

ControlarLEDs(1, 1, 0, 1) ControlarLEDs(0, 0, 0, 0)

return vOjoIzq_Modelo, vOjoDer_Modelo #--- #---

def ComparaConModelo(vMod, vProy, iRango): #---

#Esta función compara un vector de proyección con el modelo #aplicando la distancia definida por la suma de diferencias #al cuadrado.

#Desplaza la proyección adelante y atrás para tomar la #distancia mínima.

#Retorna indicación de similitud.

#---

vDistancia = np.zeros((3), dtype=np.int) # Vector de trabajo #Desplaza la proyección dos lugares atrás y dos lugares adelante... #y compara con el modelo

for i in range(-1, 2): b = np.roll(vProy, i * 2) if i > 0: b[:i * 2] = b[i * 2] if i < 0: b[i * 2:] = b[i * 2] vDistancia[i + 1] = np.sum((vMod - b) ** 2)

iDistancia = min(vDistancia) # Toma la distancia mínima #De acuerdo al rango establecido decide similitud if iDistancia <= iRango:

bSimilitud = 1 # Si los vectores son similares retorna 1 else:

bSimilitud = 0 return bSimilitud

#--- #---

def GraficaProyeccion(vProy, bHV, sTitulo): #--- #Esta función grafica el vector vProy.

#--- if bHV == 0:

#Crea imágen (FxC) negra para representar proyección horizontal. x4 para visualizar mejor imgPH = np.zeros((100, len(vProy) * 4), dtype='uint8')

for iCol in range(0, len(vProy) - 1): # Dibuja curva a partir de la proyección horizontal. x4 para visualizar mejor cv2.line(imgPH, (iCol * 4, 25 - (int(vProy[iCol] * 10))), ((iCol + 1) * 4, 25 - (int(vProy[iCol + 1] * 10))), (255, 0, 0), 1) #Dibuja líneas limítrofes y guías

cv2.line(imgPH, (0, 25), (len(vProy) * 4, 25), (255, 0, 0), 1) cv2.line(imgPH, (0, 50), (len(vProy) * 4, 50), (255, 0, 0), 1) #Coloca texto informativo

#cv2.putText(imgProyHorz, 'IP Horizontal', (0, 60), cv2.FONT_ITALIC, 0.3, (255, 0, 0), 1) cv2.imshow('IP Horizontal ' + sTitulo, imgPH)

if bHV == 1:

#Crea imágen (FxC) negra para representar proyección vertical. x4 para visualizar mejor imgPV = np.zeros(((len(vProy) * 4) + 50, 100), dtype='uint8')

for iFila in range(0, len(vProy) - 1): # Dibuja curva a partir de la proyección vertical

cv2.line(imgPV, (50 - (int(vProy[iFila] * 10)), iFila * 4), (50 - (int(vProy[iFila + 1] * 10)), ((iFila + 1) * 4)), (255, 0, 0), 1) #Dibuja líneas limítrofes y guías

cv2.line(imgPV, (50, 0), (50, len(vProy) * 4), (255, 0, 0), 1)

cv2.line(imgPV, (0, len(vProy) * 4), (100, len(vProy) * 4), (255, 0, 0), 1) #Coloca texto informativo

#cv2.putText(imgProyVert, 'IP Vertical', (0, (len(vProy) * 4) + 20), cv2.FONT_ITALIC, 0.3, (255, 0, 0), 1) cv2.imshow('IP Vertical ' + sTitulo, imgPV)

#--- #---

def AnguloDesplazamiento(iVectorX, iVectorY): #---

#Esta función recibe las componentes X,Y del desplazamiento #de la cabeza y retorna el ángulo del vector (en grados) #---

iDimension = math.hypot(iVectorX, iVectorY) # Calcula hipotenusa if iDimension == 0:

iDimension = 0.1

iAnguloGrados = math.degrees(math.acos(iVectorX / iDimension)) if iVectorY < 0:

iAnguloGrados = 360 - iAnguloGrados return iAnguloGrados

#--- #---

def RatonVelocidad(iVectorX, iVectorY, iLento, iRapido): #---

#Esta función recibe las componentes del desplazamiento en X,Y y #los porcentajes equivalentes al inicio de las zonas de

#velocidad lenta y rápida del ratón.

#calcula la dimensión del vector de movimiento y de acuerdo #a la zona de velocidad a que corresponda, devuelve el rango y #el retardo con el cual se debe mover el ratón.

#---

iDimension = math.hypot(iVectorX, iVectorY) # Calcula hipotenusa if iDimension <= iLento:

elif iRapido <= iDimension: Rango = 2

Retardo = 15 return Rango, Retardo

#--- #---

def RatonMovimientoXY(iAnguloGrados): #--- #Esta función recibe el ángulo (en grados) del vector #desplazamiento de la cabeza y define la dirección en que #se hará el movimiento del ratón.

#Si el vector de movimiento de la cabeza está entre 330º y 30º #el ratón se mueve hacia X=1,Y=0

#Si el vector de movimiento de la cabeza está entre 30º y 60º #el ratón se mueve hacia X=1,Y=1

#Si el vector de movimiento de la cabeza está entre 60º y 120º #el ratón se mueve hacia X=0,Y=1

#Si el vector de movimiento de la cabeza está entre 120º y 150º #el ratón se mueve hacia X=-1,Y=1 y así sucesivamente

#Devuelve los vectores unitarios X,Y del movimiento del ratón. #---

if iAnguloGrados <= 30 or iAnguloGrados >= 330: # Si está entre -30º y 30º iRatonX = 1 # El ratón se mueve en el eje X

iRatonY = 0

elif 30 < iAnguloGrados < 60: # Entre 30º y 60º iRatonX = 1 # El ratón se mueve en la diagonal X,Y iRatonY = 1

elif 60 <= iAnguloGrados <= 120: # Entre 60º y 120º iRatonX = 0 # El ratón se mueve en el eje Y iRatonY = 1

elif 120 < iAnguloGrados < 150: # Entre 120º y 150º iRatonX = -1 # El ratón se mueve en la diagonal -X,Y iRatonY = 1 elif 150 <= iAnguloGrados <= 210: iRatonX = -1 iRatonY = 0 elif 210 < iAnguloGrados < 240: iRatonX = -1 iRatonY = -1 elif 240 <= iAnguloGrados <= 300: iRatonX = 0 iRatonY = -1 elif 300 < iAnguloGrados < 330: iRatonX = 1 iRatonY = -1

return iRatonX, iRatonY

#--- #---

#---

#Esta función enciende o apaga cada Led durante el tiempo #especificado.

#No devuelve variables.

#---

# Configura número para el Led de la cámara. Usar 5 para modelos A/B y 32 para modelos B+ y 2 LED_Izq = 13

LED_Der = 21 CAMLED = 32

# Establece estado de cada Led GPIO.output(LED_Izq, bLedIzq) GPIO.output(LED_Der, bLedDer) GPIO.output(CAMLED, bLedCam) # Genera retardo en la ejecución if iTiempo != 0: time.sleep(iTiempo) #--- #--- #=========================================================== # PRINCIPAL #--- #Carga algoritmo de reconocimiento del rostro

Rostro_Haar = cv2.CascadeClassifier('haarcascade_frontalface_alt2.xml') #Configura puertos I/O

#---

cap = cv2.VideoCapture(0) # Asigna variable asociada a la RasPiCam bus = smbus.SMBus(1) # Define identificador del bus I2C en Raspberry Pi 2 address = 0x04 # Define dirección de la tarjeta Teensy 3.1

GPIO.setmode(GPIO.BCM) # GPIO numerado en modo Broadcom SOC channel (BCM) GPIO.setwarnings(False) # Desabilita "warnings"

# Configura como salidas y apaga los LED

GPIO.setup(32, GPIO.OUT, initial=False) # Led de la cámara GPIO.setup(21, GPIO.OUT, initial=False) # Led derecho GPIO.setup(13, GPIO.OUT, initial=False) # Led izquierdo #---

#Carga valores iniciales #--- ZonaCero = 0 Rostro_OK = 0 BocaAbierta_Entrenamiento = 0 CentroRostro = (0, 0) CentroRef = (0, 0) ZonasDeVelocidad = 0 ZonaLenta = 0 ZonaRapida = 0 QuincePorCiento = 0 PrimerCiclo = 1 iCiclos = 0

#Crea vectores para promediar coordenadas del centro del rostro vCoordCentroX = np.zeros((5), dtype=np.int)

vCoordCentroY = np.zeros((5), dtype=np.int) #Crea vectores para modelos de los ojos vOjoIzq_Modelo = np.zeros(30)

#Enciende y apaga LEDs indicando inicio

ControlarLEDs(1, 0, 0, 1) # (Led izquierdo, Led Derecho, Led Cámara, Retardo) ControlarLEDs(0, 1, 0, 1)

ControlarLEDs(0, 0, 1, 1) ControlarLEDs(0, 0, 0, 0) while(1):

TiempoInicial = time.time() # Comienza cronómetro

#Llama función que toma fotograma, realiza pre-procesado y devuelve imágen en escala de grises, tamaño 120x200 FotogRostro = ObtenerFotograma(cap)

#Detecta rostro (imagen, factor de escala, rostros vecinos, tamaño mínimo de rostro detectado) RostroCoordenadas = Rostro_Haar.detectMultiScale(FotogRostro, 1.2, 1, minSize=(70, 70)) if len(RostroCoordenadas) != 0: # Si se detecta un rostro

ControlarLEDs(0, 0, 1, 0) # Enciende LED de la cámara indicando que se ha reconocido un rostro Rostro_OK = 1

#x,y son coodenadas de esquina superior izquierda del cuadrado que encierra el rostro detectado #w,h son longitudes en x,y (respectivamente) del cuadrado

x = RostroCoordenadas[0, 0] y = RostroCoordenadas[0, 1] w = RostroCoordenadas[0, 2] h = RostroCoordenadas[0, 3]

#Si es el primer ciclo de detección, habilita zonas de velocidad con parámetros del #primer rostro que detecta, para evitar comportamiento errático del puntero del ratón al #iniciar la aplicación.

if PrimerCiclo == 1:

vCoordCentroX[:] = int(x + (w / 2)) # Durante el primer ciclo establece referencia en... vCoordCentroY[:] = int(y + (h / 2)) # la posición actual del rostro

ZonasDeVelocidad = 1 # Activa captura de modelos de ojos y definición de zonas de velocidad PrimerCiclo = 0

#Llama función que calcula centro del rostro.

vCoordCentroX, vCoordCentroY, CentroRostro = CalculaCentroRostro(x, y, w, h, vCoordCentroX, vCoordCentroY) if ZonasDeVelocidad == 1: # Si está habilitada bandera de definición de zonas

#Llama función que captura modelos de ojo derecho cerrado e izquierdo cerrado vOjoIzq_Modelo, vOjoDer_Modelo = CapturaModelosOjos(CentroRostro) #Define tamaño (en porcentajes) de cada zona de velocidad

ZonaLenta = (w * 5 / 100) # % del ancho del rostro ZonaRapida = (w * 11 / 100)

CentroRef = CentroRostro ZonasDeVelocidad = 0

cv2.circle(FotogRostro, CentroRostro, 1, (255, 0, 0)) # Dibuja punto central sobre la imágen #cv2.circle(FotogRostro, CentroRostro, w / 2, (255, 0, 0)) # Dibuja circulo encerrando el rostro #Crea Region Of Interest (ROI) enfocada en cada ojo.

RoiOjo_Izquierdo = FotogRostro[CentroRostro[1] - 15:CentroRostro[1] + 15, CentroRostro[0]:CentroRostro[0] + 35] RoiOjo_Derecho = FotogRostro[CentroRostro[1] - 15:CentroRostro[1] + 15, CentroRostro[0] - 35:CentroRostro[0]] #Llama función que obtiene las integrales proyectivas horizontal y vertical (normalizadas). Interesa la vertical. vOjoIzq_Hor, vOjoIzq_norm = ObtenerProyeccionesHV(RoiOjo_Izquierdo)

RoiBoca_Actual = FotogRostro[CentroRostro[1] + (w / 4):CentroRostro[1] + ((w / 8 * 7)), CentroRostro[0] - (w / 4):CentroRostro[0] + (w / 4)]

RoiBoca_Actual = cv2.resize(RoiBoca_Actual, (40, 30), interpolation=cv2.INTER_LINEAR) # Redimensiona #Llama función que obtiene las integrales proyectivas horizontal y vertical (normalizadas) de la boca vBocaProyHorz_norm, vBocaProyVert_norm = ObtenerProyeccionesHV(RoiBoca_Actual)

#GraficaProyeccion(vBocaProyVert_norm, 1, 'Boca Norm')

#Llama función que compara integral proyectiva normalizada con el modelo

bBocaAbierta = ComparaConModelo(vBocaProyVert_Modelo, vBocaProyVert_norm, 6)

if bBocaAbierta == 1: # Si se confirma apertura de la boca (similar a la gesticulación de la letra 'a')... iCiclos += 1 # se inicia conteo para detectar apertura de la boca por mas de 10 ciclos (aprox. 1 segundo) else:

iCiclos = 0 # En caso contrario se mantiene contador en cero... ControlarLEDs(0, 0, 0, 0) # y se apaga led de la cámara. if iCiclos >= 10: # Si se confirma apertura prolongada de la boca... print 'Boca abierta'

#Activa cálculo de centro de referencia, captura de modelos de ojos y definición de zonas de velocidad PrimerCiclo = 1

iCiclos = 0

if BocaAbierta_Entrenamiento == 1:

#Si se oprime la tecla del número 1, llama función que toma proyecciones normalizadas H y V actuales... #para la boca abierta y las almacena en archivos de texto

GrabaModeloBocaAbierta(vBocaProyHorz_norm, vBocaProyVert_norm)

#Llama a función para obtener modelos de las proyecciones H y V para la boca abierta... #de acuerdo a datos de entrenamiento almacenados en archivo de texto.

vBocaProyHorz_Modelo = LeeModelo('ModeloBocaAbiertaHorz.txt', 40) # Esta integral proyectiva tiene 40 pixeles de tamaño vBocaProyVert_Modelo = LeeModelo('ModeloBocaAbiertaVert.txt', 30) # Esta integral proyectiva tiene 30 pixeles de tamaño BocaAbierta_Entrenamiento = 0

if Rostro_OK == 1:

#Si se ha detectado algún rostro #Calcula desplazamiento X,Y

iDesplazamientoX = CentroRef[0] - CentroRostro[0] iDesplazamientoY = CentroRef[1] - CentroRostro[1]

#Calcula el ángulo del desplazamiento de la cabeza (en grados) #print ("Ángulo (º)="), iAngulo iAngulo = AnguloDesplazamiento(iDesplazamientoX, iDesplazamientoY)

#Calcula la velocidad a la que se moverá el ratón

iVelocidad = RatonVelocidad(iDesplazamientoX, iDesplazamientoY, ZonaLenta, ZonaRapida) if iVelocidad[0] == 0:

bOjoIzq = ComparaConModelo(vOjoIzq_Modelo, vOjoIzq_norm, 4) # Compara proyecciones estableciendo rango de similitud en 4

bOjoDer = ComparaConModelo(vOjoDer_Modelo, vOjoDer_norm, 4) if bOjoIzq == 1:

print "Ojo Izquierdo cerrado" ControlarLEDs(1, 0, 0, 0) if bOjoDer == 1:

print "Ojo Derecho cerrado" ControlarLEDs(0, 1, 0, 0)

#Calcula componentes X,Y del movimiento del ratón iEnviaXY = RatonMovimientoXY(iAngulo)

#Envía comando de velocidad a la tarjeta Teensy

bus.write_i2c_block_data(address, 0, [iVelocidad[0], iVelocidad[1]]) #Envía comando de movimiento a la tarjeta Teensy

#Dibuja zonas de velocidad

cv2.circle(FotogRostro, CentroRef, ZonaLenta, (255, 0, 0))