jeudi 24 décembre 2015

Notes sur la Sensor Fusion Demo d'Alexander Pacha

Cette très intéressante démo présente les différences de stabilité obtenues selon la source utilisé pour déterminer la position d'un téléphone.

Tous les capteurs du SDK d'android renvoient leurs données dans le système de coordonnées mondial dit main droite:

Axis globe
Dans ce système de coordonnées une rotation positive:

- selon l'axe X fait pointer le haut du téléphone vers le bas.
- selon l'axe Y fait pencher le téléphone à droite(sens inverse des aiguilles d'une montre)
- selon l'axe Z fait tourner le téléphone vers la droite.

Le code montre l'utilisation de getRotationMatrixFromVector et getQuaternionFromVector
SensorManager.getRotationMatrixFromVector(currentOrientationRotationMatrix.matrix, event.values);
// Calculate angle. Starting with API_18, Android will provide this value as event.values[3], but if not, we have to calculate it manually.
SensorManager.getQuaternionFromVector(q, event.values);
currentOrientationQuaternion.setXYZW(q[1], q[2], q[3], -q[0]);
Déjà comme le commentaire l'indique si l'on code pour une API supérieure à 18 on peut oublier l'appel à getQuaternionFromVector puisque event.values contiendra déjà les quatre réels (x,y,z,w) nécessaires à l'initialisation d'un quaternion.

De plus la matrice de rotation n'est pas utilisée plus tard donc on peut supprimer cet appel également et n'avoir que:
currentOrientationQuaternion.setXYZW(event.values[0], event.values[1], event.values[2], -event.values[3]); PS: je ne comprends pas la nécessité d'utiliser le négatif de event.values[3]
Dans la fonction getEulerAngles qui renvoient la position du téléphone sous la forme de trois angles d'Euler, on peut également supprimer la matrice de rotation et passer directement via le quaternion:
SensorManager.getOrientation(currentOrientationQuaternion.getMatrix4x4().matrix, angles); Lorsque l'on veut réaliser l'affichage du cube en OpenGL on va presque directement passer ces valeurs à la fonction glRotatef que l'on applique sur la matrice MODELVIEW. Quaternion q = orientationProvider.getQuaternion();
gl.glRotatef((float) (2.0f * Math.acos(q.getW()) * 180.0f / Math.PI), q.getX(), q.getY(), q.getZ());
Ceci est possible car glRotatef appliquée à MODELVIEW interprète les rotations à l'inverse du système de coordonnées de l'orientation du téléphone. Ainsi dans ce système:

gl.glRotatef(15, 1, 0, 0) fait pointer le téléphone vers le haut
gl.glRotatef(15, 0, 1, 0) fait pencher le télépone vers la droite (rotation dans le sens des aiguilles)
gl.glRotatef(15, 0, 0, 1) fait tourner le téléphone vers la gauche

La seule adaptation nécessaire au système de coordonnées opengl est la transformation du demi angle de rotation (en radian) stocké par le quaternion en un angle complet en degré: theta_deg = ((2 *half_theta_rad) * 180 / M_PI)
https://play.google.com/store/apps/details?id=org.hitlabnz.sensor_fusion_demo

vendredi 18 décembre 2015

Utiliser le Gesture Recognition Toolkit (GRT) sur Android via JNI

Une fois GRT compilé pour Android la partie la plus complexe consiste à utiliser SWIG afin de générer automatiquement l'interface JNI pour appeler le C++ via Java. Cette page est une traduction et adaptation de ce billet qui m'a fait découvrir SWIG. La procédure à suivre est la suivante:
Commencer par créer le répertoire qui accueillera les fichiers JNI générés par SWIG.
mkdir -p ./GRTApp/app/src/main/java/org/swig/grt/ Ensuite on va avoir besoin du fichier grt.i ci dessous qui définit comment SWIG doit écrire les interfaces C++. C'est la première fois que j'utilise SWIG et trouver la syntaxe pour les opérateurs C++ surchargés n'a pas été simple.
%module grt

%{
#include "GRT.h"
using namespace GRT;
%}

%inline %{
typedef unsigned int UINT;
%}

%rename(assign) GRT::IndexDist::operator=;
%rename(assign) GRT::DTW::operator=;

%include
%include "my_vector.i"
%template(VectorDouble) std::vector;
%include "grt/Util/GRTTypedefs.h"

%include "grt/Util/Matrix.h"
%rename(assign) GRT::MatrixDouble::operator=;
%include "grt/Util/MatrixDouble.h"

%rename(assign) GRT::TimeSeriesClassificationSample::operator=;
%rename(get) GRT::TimeSeriesClassificationSample::operator[];
%include "grt/DataStructures/TimeSeriesClassificationSample.h"

%rename(assign) GRT::TimeSeriesClassificationData::operator=;
%rename(get) GRT::TimeSeriesClassificationData::operator[];
%include "grt/DataStructures/TimeSeriesClassificationData.h"

%rename(assign) GRT::TimeSeriesClassificationDataStream::operator=;
%rename(get) GRT::TimeSeriesClassificationDataStream::operator[];
%include "grt/DataStructures/TimeSeriesClassificationDataStream.h"

%include "grt/CoreModules/Classifier.h"
%include "grt/ClassificationModules/DTW/DTW.h"
Ensuite on lance la commande qui va générer jni/grt_wrap.cpp et tout un tas de fichiers d'interfaces dans org/swig/grt
swig -c++ -java -package org.swig.grt -outdir ../java/org/swig/grt/ -o grt_wrap.cpp grt.i Reste à compiler le tout avec:
Compiler avec ndk-build NDK_LIBS_OUT=../jniLibs On peut ensuite réaliser en java un clone du code d'example de l'algorithme DTW de la page suivante: Voici le code java équivalent.
DTW dtw = new DTW();

TimeSeriesClassificationData trainingData = new TimeSeriesClassificationData();

if(!trainingData.loadDatasetFromFile("/data/data/com.codeflakes.grtapp/files/dtwtrainingdata_grt")) {
    Log.e(TAG, "Failed to load training data!");
}

Log.d(TAG, trainingData.getStatsAsString());

TimeSeriesClassificationData testData = trainingData.partition(80);
dtw.enableTrimTrainingData(true, 0.1, 90);

//Train the classifier
if( !dtw.train_( trainingData ) ){
     Log.e(TAG, "Failed to train classifier!");
}

//Use the test dataset to test the DTW model
double accuracy = 0;
for(int i=0; i<testData.getNumSamples(); i++){
    //Get the i'th test sample - this is a timeseries
    long classLabel = testData.get(i).getClassLabel();
    MatrixDouble timeseries = testData.get(i).getData();
    //Log.d(TAG, "" + classLabel + " " + testData.get(i).getLength() + " " + testData.get(i).getNumDimensions());

    //Perform a prediction using the classifier
    if (!dtw.predict_( timeseries ) ){
        Log.e(TAG, "Failed to perform prediction for test sample: " + i);
    }

    //Get the predicted class label
    long predictedClassLabel = dtw.getPredictedClassLabel();
    double maximumLikelihood = dtw.getMaximumLikelihood();
    VectorDouble classLikelihoods = dtw.getClassLikelihoods();
    VectorDouble classDistances = dtw.getClassDistances();

    //Update the accuracy
    if( classLabel == predictedClassLabel ) accuracy++;

    Log.d(TAG, "TestSample: " + i + "\tClassLabel: " + classLabel + "\tPredictedClassLabel: " + predictedClassLabel + "\tMaximumLikelihood: " + maximumLikelihood);
}

Log.d(TAG, "Test Accuracy: " + (accuracy/(testData.getNumSamples())*100.0) + "%");

mercredi 16 décembre 2015

Compilation du Gesture Recognition Toolkit sur Android

Pour ne pas avoir à développer la partie reconnaissance de mouvement j'ai recherché des bibliothèques existantes. Mon choix s'est porté sur le GRT (Gesture Recognition Toolkit) de Nick Gillian.

Pour le faire fonctionner sur Android je me suis basé sur cet article http://hollyhook.de/wp/grt-for-android

Le but est de compiler le code C++ en une bibliothèque libgrt.so que l'on intégrera dans notre APK et que l'on utilisera via JNI.

Le processus pour compiler le GRT en NDK est le suivant:

- Créer un projet vierge sous Android Studio.
- Spécifier le répertoire dans lequel on va placer le fichier .so et désactiver la compilation du NDK en ajoutant dans gradle:
sourceSets {
     main {
         jniLibs.srcDir 'jniLibs' // spécification du répertoire de libs JNI
         jni.srcDirs = [] // désactivation compilation NDK
     }
}

Copier ensuite (un lien sympbolique marche aussi) le code source du GRT dans GRTApp/app/src/main/jni. La structure de l'application est la suivante:
GRTApp
app
     build
     libs
     src
         main
             java
                 com/codeflakes/grtapp
                 org/swig/grt
             jni
                 grt
             jniLibs
             libs
                 armeabi-v7a
             res
         test
         androidTest
build
gradle
Ajouter les Android.mk et Application.mk suivants dans ./jni pour pouvoir compiler avec ndk-build

Application.mk: NDK_TOOLCHAIN_VERSION := clang
APP_STL := gnustl_static
APP_CPPFLAGS += -frtti
APP_CPPFLAGS += -fexceptions
APP_ABI := armeabi-v7a
Android.mk:
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

$(info $(LOCAL_PATH))

HEADER_FILES_LIST := $(wildcard $(LOCAL_PATH)/grt/*.h)
HEADER_FILES_LIST += $(wildcard $(LOCAL_PATH)/grt/*/*.h)
HEADER_FILES_LIST += $(wildcard $(LOCAL_PATH)/grt/*/*/*.h)
HEADER_FILES_LIST += $(wildcard $(LOCAL_PATH)/grt/*/*/*/*.h)
HEADER_FILES_LIST += $(wildcard $(LOCAL_PATH)/grt/*/*/*/*/*.h)
#$(info $(HEADER_FILES_LIST))
LOCAL_C_INCLUDES := $(HEADER_FILES_LIST:$(LOCAL_PATH)/%=%)
LOCAL_C_INCLUDES += $(LOCAL_PATH)/grt/

LOCAL_MODULE    := grt

SRC_FILES_LIST := $(wildcard $(LOCAL_PATH)/grt/*.cpp)
SRC_FILES_LIST += $(wildcard $(LOCAL_PATH)/grt/*/*.cpp)
SRC_FILES_LIST += $(wildcard $(LOCAL_PATH)/grt/*/*/*.cpp)
SRC_FILES_LIST += $(wildcard $(LOCAL_PATH)/grt/*/*/*/*.cpp)
SRC_FILES_LIST += $(wildcard $(LOCAL_PATH)/grt/*/*/*/*/*.cpp)
#$(info $(SRC_FILES_LIST))
LOCAL_SRC_FILES := $(SRC_FILES_LIST:$(LOCAL_PATH)/%=%)
LOCAL_SRC_FILES += $(LOCAL_PATH)/grt_wrap.cpp

LOCAL_CPPFLAGS += -std=c++11
LOCAL_LDLIBS   += -latomic
LOCAL_LDLIBS   += -llog

include $(BUILD_SHARED_LIBRARY) 
Premier post. Je vais essayer de documenter mes recherches et mon travail sur le développement d'un traqueur sportif. J'y consignerai du code et de la technique, mais aussi des infos sur les concurrents, le marché et le business model d'un tel produit.