Interfaz
Gesture.java
En esta interfaz encontramos 3 métodos abstractos:
- public int getDistance(Gesture g): nos indicará si un gesto es válido o no.
- public double getDistance(Data[] d): calcula la distancia entre un array en el que se almacenan coordenadas de aceleración y un gesto patrón.
- public int getId(): devuelve el número que identifica a un gesto patrón.
Esta interfaz es
implementada por la clase AccelerometerGesture.
Clase
AccelerometerGesture.java
Un objeto de esta clase, que implementa la interfaz Gesture,
representará un gesto patrón que ya
ha sido enseñado en la fase de entrenamiento.
- private AccelerometerData[][] data: Es un array bidimensional donde tenemos almacenados todos los valores medidos de coordenadas de aceleración en las 3 iteraciones de un gesto patrón, con la estructura que ya hemos explicado al hablar de la clase GestureFactory.
- private int id: Es el número que identifica a ese gesto en concreto.
Como ya hemos visto, al terminar la fase de
entrenamiento, el método stopLearning()
de la clase GestureFactory nos
devolverá un objeto de esta clase AccelerometerData.
Por lo tanto, se crearán tantos objetos AccelerometerData
como gestos se hayan enseñado.
- En el array de coordenadas “d” del gesto que se ha realizado, se busca el último elemento no nulo, pues a partir de la primera tripla de coordenadas nulas el gesto ya habrá terminado.
- Se crea un nuevo array “nd” de objetos Data, que contendrá únicamente estas coordenadas no nulas.
- Se realiza esta misma operación para los array de coordenadas de cada iteración del gesto patrón, creando nuevos arrays “nd2” que sólo contengan coordenadas no nulas.
- Mediante el algoritmo de alineamiento temporal dinámico (DTW) se calcula la distancia entre los arrays “nd” y “nd2”, haciendo la comparación con cada una de las 3 iteraciones almacenadas del gesto patrón. Con esto, obtendremos 3 distancias distintas.
- De las 3 distancias obtenidas, al final nos quedará almacenada en la variable “xMinDist” la menor de ellas, que será la que este método devolverá.
Interfaz
Device.java
- public void addEventListener(EventListener e)
- public void enableDevice()
- public void disableDevice()
Entenderemos cuál es la función de cada uno de estos
métodos abstractos cuando hablemos de la clase AndroidDevice, que implementa
esta interfaz.
Clase
AndroidDevice.java
El principal objetivo de esta clase, que implementa
la interfaz Device, es eliminar el efecto
de la gravedad en la lectura del acelerómetro en la fase de realización de gestos.
Hay que tener en cuenta la importancia de que las
coordenadas de aceleración que almacenemos tengan ya aislado el efecto de la
gravedad para que sean coherentes
con las coordenadas de los gestos en la fase de entrenamiento. Si aislamos este
efecto en una fase y no en la otra, no podremos comparar los patrones.
- Objetos de las clases Sensor, SensorEventListener y SensorManager, proporcionadas por las librerías de Android.
- Un objeto de la propia clase AndroidDevice.
- Un EventListener.
Además
del método que se encarga de aislar la contribución de la gravedad en nuestras
medidas (funciona exactamente igual que el ya explicado para la fase de
entrenamiento), esta clase implementa los 3 métodos abstractos de la interfaz Device:
- public void addEventListener(EventListener e): permite asociarle a este objeto AndroidDevice un listener de eventos.
- public void enableDevice(): a través del método public boolean registerListener (SensorEventListener listener, Sensor sensor, int rate) que nos proporciona Android en su clase SensorManager, asocia un SensorEventListener al acelerómetro y se especifica que la obtención de las lecturas del sensor sea lo más rápida posible (SENSOR_DELAY_FASTEST).
- public void disableDevice(): de manera análoga, mediante el método public void unregisterListener (SensorEventListener listener) de SensorManager, elimina la asociación entre el SensorEventListener y el acelerómetro.
Clase
GestureManager.java
En esta clase encontramos toda la inteligencia que
interviene tanto en la fase de realización
de gestos como en la de comparación
del gesto realizado con los patrones.
Aunque el algoritmo que utilizamos tiene cierta complejidad, podemos resumirlo en el siguiente diagrama de estados (hacer click sobre la imagen para verla en grande):
Antes de pasar a
explicar cada uno de los estados, es importante entender el significado de 4
constantes que utilizamos en el algoritmo. El valor que adquiere cada una de
ellas ha sido determinado de forma empírica.
- UMBRAL1=0,8: nivel de “energía” por debajo del cual consideramos que
estamos en reposo. La “energía” de
la aceleración la calculamos como el módulo
de la aceleración sufrida:
Aunque continuamente hablemos de este módulo como una energía, sabemos que en realidad no lo es, pero nos ayuda a entender lo que está ocurriendo de forma intuitiva. - UMBRAL2=1,2: nivel de energía por encima del cual consideramos que se está realizando un gesto. Valores intermedios entre los umbrales 1 y 2 se utilizan para detectar que es posible que se esté iniciando un movimiento.
- TIME1=15: tiempo mínimo durante el cual el nivel de energía tiene que superar el umbral 2 para considerar que realmente se está realizando un gesto.
- TIME2=15: tiempo mínimo durante el cual el nivel de energía tiene que estar por debajo del umbral 2 para establecer que se ha dejado de realizar un gesto.
Conociendo ya estos 4
umbrales, podremos entender mejor lo que ocurre en cada estado:
- REPOSO
En este estado no se está realizando ningún gesto ni estamos detectando movimiento. Las variables booleanas “makingGesture” y “someDetected” toman el valor “false”.
Saldremos de este estado cuando se detecte una aceleración (almacenada en la variable “tot”) que supere el nivel de energía del UMBRAL1. Pasaremos al estado “algo detectado”, cambiando a “true” el valor de “someDetected”. - ALGO DETECTADO
Entramos en este estado cuando se detecta cierto movimiento, aunque todavía no consideramos que se está haciendo un gesto, pues puede ser una falsa alarma. Al entrar en él tenemos “makingGesture=false” y “someDetected=true”.
En este estado empezamos a guardar las coordenadas de aceleración medidas por el acelerómetro, aunque luego sólo las conservaremos si verdaderamente se estaba realizando un gesto.
En el momento en el que el nivel de energía supera el UMBRAL2, empezamos a incrementar el contador “valuesDetected”. Si se detecta movimiento con energía por encima del “UMBRAL2” durante un tiempo superior a “TIME1” (“valuesDetected>TIME1”), consideramos que ya estamos haciendo gesto y ponemos a “true” la variable “makingGesture”. Con esto pasamos al estado “haciendo gesto”.
Si se supera “UMBRAL1” pero no llega a superarse “UMBRAL2”, era una falsa alarma y en realidad no se estaba haciendo ningún gesto, por lo que volvemos al estado de reposo inicial. Se eliminan todos los datos de coordenadas que se habían empezado a almacenar y se pone de nuevo “someDetected” a “false”. - HACIENDO GESTO:
Mientras “makingGesture” tome el valor “true”, será porque la energía está por encima del “UMBRAL2” y se almacenarán todas las coordenadas de aceleración medidas por el acelerómetro (siempre que tengamos menos de 512 triplas de coordenadas, que es el tamaño máximo que hemos establecido para los arrays de Data).
En el momento en el que detectemos que la energía ya está por debajo del “UMBRAL2”, empezaremos a incrementar el contador “valuesDetected”. Si el valor de este contador supera el umbral de tiempo “TIME2”, consideraremos que ya se ha parado de hacer un gesto. Es en ese momento cuando comienza la fase de comparación con los patrones. - COMPARANDO GESTOS:
Al comenzar la comparación de gestos, suponemos que el gesto que se está realizando no coincide con ninguno de los patrones almacenados y lo identificamos con “gestureid=-1”.
Se hace una llamada al método public double getDistance(Data[] d) de cada uno de los gestos patrón, para buscar aquel que se parezca más al que acabamos de realizar. Le asignaremos a este gesto el id de aquel gesto patrón de distancia mínima a él.
Una vez que ya tenemos identificado el gesto, llamamos al método onGestureDetected() de la interfaz GestureListener, pasándole como parámetro el entero que lo identifica. A partir de este momento ya es la aplicación Android quien se encarga de enviar al robot el comando que se corresponde con este movimiento.
Al terminar la comparación, se vuelven a poner todas las variables booleanas de estado a “false”, el contador “valuesDetected” a 0, se borran los valores de todas las coordenadas de aceleración del gesto que se acaba de realizar y se vuelve al estado inicial de reposo.
Clase
DTW.java
De todas las clases que forman el software de
reconocimiento de gestos, esta es la única que no ha sido desarrollada por
nosotros, sino que hemos cogido el código de otro autor y la hemos adaptado.
En esta clase encontramos el algoritmo de alineamiento temporal dinámico o DTW (Dynamic Time Warping), que nos permite medir la similitud entre dos secuencias que
pueden variar en el tiempo o en el espacio. Gracias a él podremos calcular la distancia entre un gesto que realicemos
y cada uno de los gestos patrón.
No hay comentarios:
Publicar un comentario