Para profundizar en el funcionamiento de nuestro
sistema de reconocimiento de gestos
mediante el acelerómetro de un dispositivo Android, en esta entrada
presentaremos de forma detallada cada una de las clases e interfaces que
hemos desarrollado.
La estructura de este software tiene cierta
complejidad y podemos verla resumida en el siguiente diagrama. En él, los
cuadros naranjas representan interfaces y, los azules, clases; las flechas con
línea continua representan una relación de extensión
(AccelerometerData extiende a Data) y las de línea discontinua implementación (AccelerometerGesture implementa a la interfaz Gesture y AndroidDevice a
Device).
Aunque al ver la estructura de las interfaces y
clases pueda parecer que algunas de las interfaces no serían necesarias en
nuestro proyecto, lo hemos diseñado de esta forma pensando en una futura extensión de este sistema de
reconocimiento de gestos a más dispositivos.
De todas ellas, la única que no ha sido desarrollada
por nosotros es la clase DTW.java,
cuyo autor es Cheol-Woo Jung;
nosotros simplemente la hemos adaptado a nuestro proyecto.
A continuación iremos describiéndolas una a una, aunque por la complejidad del software, tendremos que dividir su explicación en 2 entradas distintas:
Clase
Data.java
Únicamente tiene como variable un array de floats llamado “values”, con su
correspondiente getter: el método getValues(). A su constructor tendremos
que pasarle como parámetro un array de floats.
Un objeto de la clase Data.java contendrá coordenadas
de la aceleración medidas con el acelerómetro. Por lo tanto, normalmente la
variable “values” será un array
formado por 3 floats:
- values[0]: coordenada X de la aceleración
- values[1]: coordenada Y de la aceleración
- values[2]: coordenada Z de la aceleración
Clase
AccelerometerData.java
Como esta clase extiende
a Data.java, a su constructor
tendremos que pasarle como parámetro un array de floats para poder hacer la llamada correspondiente al constructor
de la clase padre.
La función de esta subclase es acceder por separado a cada uno de los elementos que
forman el array “values” de un objeto
Data, para poder trabajar con cada
coordenada de la aceleración de manera independiente.
Para ello, consta de los 3 métodos getXAcceleration(),getYAcceleration() y getZAcceleration(), que acceden
respectivamente a la primera, segunda y tercera posición del array “values”.
Interfaz
EventListener.java
En esta interfaz sólo encontramos un método abstracto: public void onEventReceived(float[] values, Device d), que
posteriormente utilizarán otras clases en la fase de realización de gestos para determinar qué hacer al detectar un
evento del acelerómetro.
Interfaz
GestureListener.java
Como ocurría con la interfaz anterior, ésta consta
de un único método abstracto: public void
onGestureDetected(int idGesture).
Este método se usará en la fase de comparación con los patrones y
especificará qué hacer en caso de detectar una coincidencia entre un gesto realizado y un patrón guardado.
Clase
FactorySensorListener.java
Es una clase que implementa la interfaz SensorEventListener
(proporcionada por las liberías Android) y que se encarga de crear una estructura de datos en la que se
almacena un gesto realizado en la fase de entrenamiento.
La estructura de datos elegida para almacenar los
gestos es un array de 512 objetos de la clase AccelerometerData;
a su vez, cada uno de ellos, contiene un array de 3 floats (las coordenadas
x, y, z de la aceleración). En la siguiente figura se ve de manera
sencilla cómo es la estructura:
Esta clase comienza a trabajar cuando nos
encontramos en la fase de entrenamiento,
indicamos que queremos enseñar un nuevo
gesto y el acelerómetro del móvil detecta un evento.
Antes de explicar el funcionamiento interno de la
clase, es importante señalar que en todo momento trabajaremos con aceleraciones lineales, aislando el
efecto producido por la gravedad.
Como ya explicamos en la entrada “El
acelerómetro de un dispositivo Android”, este sensor nos proporciona
medidas de aceleración total,
teniendo en cuenta la gravedad.
Si queremos trabajar únicamente con la aceleración lineal que producen nuestros
movimientos, tendremos que implementar un filtro
paso bajo que aísle la fuerza de la gravedad y, posteriormente, restar
estas aceleraciones filtradas a las medidas por el acelerómetro.
Las librerías de Android
nos indican cómo hay que implementar este filtro:
En él, la constante “alpha” que se utiliza se
calcula como:
En esta expresión, tau es la constante
de tiempo del filtro paso bajo y dT la velocidad
a la que los eventos son detectados. Las librerías Android nos aconsejan
ponerle un valor de 0,8 a la
constante “alpha”; como hemos
comprobado que con este valor funcionaba, es el que estamos utilizando.
Conociendo ya este detalle importante, pasaremos a
explicar el algoritmo que hay detrás
de esta clase:
- Antes de empezar a almacenar información, comprueba que el evento detectado por el acelerómetro del móvil ha sido causado realmente por un movimiento hecho a propósito. Para ello, primero calcula el módulo de la aceleración lineal producida (restando el efecto de la gravedad):Se compara este valor con un umbral de 0,8 (establecido así de forma empírica) y, si no lo supera, consideraremos que seguimos en estado de reposo.
- Cuando se supera el umbral de movimiento, pasamos al estado de almacenamiento de gestos. En él, se van tomando lecturas del acelerómetro, se les resta el efecto de la gravedad y se almacenan en la estructura que hemos creado. Todo esto se realiza mientras tengamos menos de 512 conjuntos de coordenadas (mientras la variable “index” sea menor que 512).
- Cuando la variable “index” toma como valor 512, ya tenemos información suficiente sobre el gesto realizado y se termina el almacenamiento.
- Sólo volveremos al estado de reposo del punto 1 si indicamos a través de la aplicación Android que vamos a realizar de nuevo un entrenamiento del mismo gesto o queremos enseñar otro gesto distinto.
Clase
GestureFactory.java
Esta clase implementa
toda la inteligencia en la que se fundamenta la fase de entrenamiento, ayudándose de la clase FactorySensorListener.
Aquí podemos ver el diagrama de estados en el que se ve el funcionamiento
conjunto de ambas clases (hacer click sobre la imagen para ver en grande):
Para comprender el diagrama, es necesario
conocer los 4 métodos más importantes de esta clase:
- public void startLearning(int type, int iterations, int idGesture):Llamamos a este método cuando queremos empezar el entrenamiento de un gesto determinado. Como parámetro se le pasa un entero asociado al tipo de sensor que vamos a utilizar (en nuestro caso, siempre tomará el valor 0, que identifica al acelerómetro), el número de iteraciones que hay que realizar de un mismo gesto para completar el entrenamiento (nosotros pediremos siempre 3 iteraciones) y un identificador del gesto que se va a enseñar (si será el de avanzar, retroceder, parar…).La principal misión de este método es crear la estructura de datos donde se almacenará la información sobre cada gesto: no será más que un nuevo array formado por las 3 estructuras que crea la clase FactorySensorListener y que ya hemos explicado:Al ser llamado, este método pone la variable “learning” a “true” para indicar que hemos empezado la fase de entrenamiento.
- public void startIteration():Podremos empezar una iteración de un gesto sólo si anteriormente hemos llamado al método startLearning() (se comprueba si “learning=true”) y si hemos realizado menos de 3 iteraciones del gesto que estamos enseñando. Si no se cumplen estas dos condiciones, lanzará una excepción.Una vez que se han satisfecho ambas condiciones, se asocia el FactorySensorListener al acelerómetro para que empiece a almacenar los 512 conjuntos de coordenadas del movimiento realizado, como ya hemos visto anteriormente.
- public void stopIteration()Simplemente incrementa el contador de iteraciones realizadas del gesto actual y elimina la asociación entre el FactorySensorListener y el acelerómetro.
- public Gesture stopLearning()Este método lanzará una excepción si es llamado cuando no estamos en fase de entrenamiento (si “learning=false”) o si el número de iteraciones realizadas no es 3.Si no se da ninguna de estas condiciones de excepción, pone la variable “learning” a “false” para indicar que salimos de la fase de entrenamiento y devuelve un objeto de la clase AccelerometerGesture. A este objeto se le pasan como parámetro el identificador del gesto que hemos entrenado y el array bidimensional donde tenemos almacenados todos los valores medidos de coordenadas de aceleración en las 3 iteraciones.
En la siguiente entrada completaremos la explicación de las clases e interfaces de las que todavía no hemos hablado.
No hay comentarios:
Publicar un comentario