P.O.C. : Association de la réalité augmentée et du
machine learning sur Android
Julien Bouffard, développeur back et adepte d’Android, se lance un nouveau challenge mobile : afficher un texte contextuel selon une image reconnue.
Google a créé ARCore et Sceneform pour apporter la réalité augmentée sur mobile, et TensorFlow Lite pour y apporter le machine learning.
Le but de ce POC est donc de voir comment les faire communiquer ensemble.
TensorFlow Lite et ARCore sur mobile
TensorFlow Lite propose une boîte à outils pour faire fonctionner des modèles TensorFlow sur des mobiles et des appareils IoT. Il permet l’inférence de machine learning directement sur le téléphone portable grâce à une latence faible et un binaire de petite taille.
L’inférence désigne le processus d’exécution d’un modèle TensorFlow Lite sur mobile afin de réaliser des prédictions basées sur une donnée en entrée. L’inférence de TensorFlow Lite se déroule de la façon suivante :
- Chargement d’un modèle .tflite contenant son graph d’exécution en mémoire
- Transformation de la data en entrée afin qu’elle corresponde à la data d’entrée attendue
- Utilisation de l’API de TensorFlow Lite pour exécuter le modèle
- Interprétation de la data en sortie de l’exécution du modèle.
Par exemple, un modèle pourrait renvoyer une liste de probabilités.
Il nous appartient de faire le mapping pour proposer une expérience aux utilisateurs.
ARCore quant à lui permet d’interpréter notre environnement et Sceneform de construire des assets par-dessus afin de fournir une expérience de réalité augmentée sur mobile.
POC en action
Objectif
Le but de ce POC est de voir comment associer le combo ARCore et Sceneform avec le machine learning pour enrichir l’expérience utilisateur.
L’objectif fixé est d’utiliser le machine learning pour reconnaître des peluches d’animaux, et selon l’animal identifié faire apparaître un texte descriptif à propos de celui-ci en réalité augmentée.
Projet côté ML
Pour ce faire, nous allons nous appuyer sur le codelab tensorFlow for poets 2 tflite qui va nous fournir beaucoup de code boilerplate pour la transformation d’image en bytes et la création de l’interpreter, qui se chargera d’exécuter notre modèle TensorFlow Lite.
Ce codelab a pour but de réentraîner un modèle déjà entraîné avec de nouvelles classes qui nous appartiennent.
Pour en savoir plus sur le transfer learning, n’hésitez pas à consulter ces articles : 1 et 2.
Le modèle en question a été entraîné sur le dataset ImageNet Large Visual Recognition Challenge. Ces modèles peuvent faire la différence entre 1000 classes différentes (par exemple : un chien, une machine à laver, …) mais pas nos peluches.
Construction du dataset
Pour notre POC, il nous faudra créer de nouvelles classes ‘peluche’ et un dataset suffisamment large sur lequel le modèle devra s’entraîner.
Un excellent article détaille la procédure pour créer son propre dataset et l’entraîner sur un modèle.
À noter, qu’il existe plusieurs choix d’architecture afin de trouver le compromis entre la rapidité, la taille et la précision pour chaque cas.
Pour notre cas, nous allons prendre une vidéo de 3 peluches sous toutes leurs coutures.
Grâce au logiciel ffmpeg, chaque vidéo sera découpée en de nombreuses images.
Ensuite dans un dossier ‘peluches’ préalablement créé, on lance cette commande afin de créer un sous-dossier de chaque peluche :
ffmpeg -i flings.mp4 peluches/typeDePeluche_%04d.jpg
Et, comme dit précédemment, nous allons pouvoir récupérer le code du codelab tensorflow for poets 2 :
git clone https://github.com/googlecodelabs/tensorflow-for-poets-2cd tensorflow-for-poets-2
Ensuite, copiez le dossier ‘peluches’ dans le dossier ‘tensorflow-for-poets-2’ à côté des dossiers ‘scripts’ et ‘tflite’.
Réentraîner le modèle avec notre dataset
Google a créé une image docker qui va nous permettre d’entraîner notre modèle et de le convertir au format tensorFlow Lite.
docker run -it — rm -v /home/julolv/linkvalue/tensorflow-for-poets-2:/tf_files -e “ARCHITECTURE=mobilenet_1.0_224” tensorflow/tensorflow
La variable ARCHITECTURE correspond à la configuration de l’architecture MobileNetV1.
MobileNet peut se configurer de deux façons :
- via la résolution d’image en entrée, de 128, 160, 192 ou 224px.
Plus la résolution est haute, plus le temps de calcul est important et la précision améliorée. - Via la taille relative du modèle en fraction du plus grand MobileNet : 1.0, 0.75, 0.50, ou 0.25.
Ici mobilenet_1.0_224 représente une résolution 224px et une taille 1.0.
Libre à vous de tester et de changer ces paramètres.
Il est maintenant temps d’entraîner nos classes peluches.
Pour ce faire, saisissez dans le container docker :
cd tf_files &&python -m scripts.retrain— bottleneck_dir=tf_files/bottlenecks— how_many_training_steps=500— model_dir=tf_files/models/— summaries_dir=tf_files/training_summaries/”${ARCHITECTURE}” — output_graph=tf_files/retrained_graph.pb— output_labels=tf_files/retrained_labels.txt— architecture=”${ARCHITECTURE}”— image_dir=./peluches
Le fichier retrained_labels.txt va contenir tous les labels de nos peluches (loup, tortue et chien dans cet exemple).
Vous pouvez jouer avec le paramètre how_many_training_steps pour augmenter le nombre d’itérations d’entraînement.
Conversion au format TensorFlow Lite
Enfin, il nous faut convertir notre nouveau modèle au format tflite.
Toujours dans le container docker :
tflite_convert \— graph_def_file=tf_files/retrained_graph.pb \— output_file=tf_files/peluches_optimized_graph.tflite \— input_format=TENSORFLOW_GRAPHDEF \— output_format=TFLITE \— input_shape=1,224,224,3 \— input_array=input \— output_array=final_result \— inference_type=FLOAT \— input_data_type=FLOAT
Dans le paramètre input_shape, 224 correspond à la taille de l’image.
Nous pouvons maintenant ouvrir le projet Android. Le projet ne comporte que peu de fichiers.
Copier tout d’abord les fichiers peluches_optimized_graph.tflite et retrained_labels.txt dans le dossier assets.
Ce qui va nous intéresser principalement sera la classe ImageClassifier.
Celle-ci contient tout le code boilerplate pour charger le modèle, l’exécuter et en sortir une liste de probabilités de résultats.
Cette classe va se charger d’instancier l’interpreter et d’exécuter le modèle.
Plusieurs constantes sont alors intéressantes :
static final int DIM_IMG_SIZE_X = 224;
static final int DIM_IMG_SIZE_Y = 224;
Remplacer ensuite les valeurs de MODEL_PATH et LABEL_PATH par celles que vous placez dans le dossier assets :
/** Name of the model file stored in Assets. */
private static final String MODEL_PATH = “peluches_optimized_graph.tflite”;
/** Name of the label file stored in Assets. */
private static final String LABEL_PATH = “retrained_labels.txt”;
Si nous regardons la méthode String classifyFrame(Bitmap bitmap), qui va classifier la donnée d’entrée de notre caméra de téléphone, nous pouvons remarquer qu’elle prend en paramètre un Bitmap.
En fait tout ce dont a besoin TensorFlow Lite pour classifier ce que voit la caméra du mobile est une image.
Or l’API de Sceneform est tout à fait capable de récupérer une image. Nous pouvons donc entrevoir une collaboration aisée entre l’API de Sceneform et celle TensorFlow Lite.
Projet côté AR
L’initialisation d’ARCore et de Sceneform reste classique, en voici un exemple dans cet autre article, et s’articule comme suit :
- Ajout des dépendances dans le gradle
- Ajout des meta dans le manifest
- Ajout de l’ArFragment dans le layout activity_camera.xml
- Création d’un fichier resource card.xml qui va contenir le texte à afficher après que nous ayons classifié une image
<?xml version=”1.0" encoding=”utf-8"?><android.support.constraint.ConstraintLayout
xmlns:android=”http://schemas.android.com/apk/res/android"
xmlns:app=”http://schemas.android.com/apk/res-auto"
xmlns:tools=”http://schemas.android.com/tools"
android:background=”@android:color/background_light”
android:layout_width=”100dp”
android:layout_height=”50dp”>
<TextView
android:id=”@+id/card”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:textColor=”@android:color/black”
android:textSize=”8sp”
app:layout_constraintBottom_toBottomOf=”parent”
app:layout_constraintEnd_toEndOf=”parent”
app:layout_constraintStart_toStartOf=”parent”
app:layout_constraintTop_toTopOf=”parent” />
</android.support.constraint.ConstraintLayout>
Nous pouvons ensuite transformer la CameraActivity pour introduire le code Sceneform qui passera à la classe ImageClassifier le bitmap dont elle a besoin pour identifier le type de peluche.
Puis nous utilisons le fragment AR fourni par Sceneform qui nous expose une API, s’occupe de demander les permissions et de gérer le téléchargement d’ARCore. Nous écoutons lors l’événement onTap quand un plan AR est détecté.
Dans le listener, nous allons créé les ancres et les nodes sur lesquels nous allons peupler nos assets, ici un layout xml.
Association AR et ML
Place maintenant au cœur du sujet : voyons comment faire mapper une image en sortie de l’API Sceneform et en entrée pour TensorFlow Lite.
Image img = arFragment.getArSceneView().getArFrame().acquireCameraImage();
byte[] bytes = imageToByte(img);
img.close();
Nous récupérons une image depuis la Sceneview, puis transformons notre image en array de byte.
Il ne faut pas oublier de close() l’image sous peine de déclencher cette com.google.ar.core.exceptions.ResourceExhaustedException après plusieurs tap pour d’autres peluches, le MaxSizeImageBuffer étant plein.
final Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
Matrix matrix = new Matrix();
matrix.setRotate(-90F);
matrix.postScale(ImageClassifier.DIM_IMG_SIZE_X/(float) bitmap.getWidth(),
ImageClassifier.DIM_IMG_SIZE_Y/(float) (bitmap.getWidth() * 1.1));
final Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
L’image que nous recevons de la SceneView est en mode paysage, il faut donc la pivoter de 90 degrés et de fait la scaler sans qu’elle dépasse en largeur ou hauteur la taille de l’image attendue par ImageClassifier, ici 224px.
String textToShow = “”;
for (int i = 0; i < 7; i++) {
textToShow = classifier.classifyFrame(resizedBitmap);
}
String finalTextToShow = textToShow;
Il faudra ensuite exécuter le modèle pour l’image plusieurs fois afin d’obtenir un minimum de précision, les premières itérations donnant des résultats erronés. Pour ce cas, les résultats étaient bien plus précis au bout de 7 exécutions. Mais on peut pousser encore plus loin le nombre d’itérations pour plus de précision.
À noter qu’il vaut mieux sortir l’exécution du thread UI car le traitement est gourmand en ressources CPU. Cela n’a pas été fait dans la démo, et vous pourrez constater à quel point la classification freeze l’UI.
Après toutes ces étapes, tout est prêt !
Une fois un plan détecté au tap sur celui-ci, nous fournissons à tensorFlow Lite un bitmap qu’il va classifier. Avec le résultat, nous pouvons ajouter le texte correspondant à la peluche sur le renderable et l’ancrer dans l’environnement AR.
Démo
Pour aller plus loin
Si vous avez trouvé ce POC intéressant, je vous invite à découvrir d’autres cas d’usage que j’ai expérimenté avec ARCore et la réalité augmentée :
De plus, nous publions régulièrement des articles sur des sujets de développement produit web et mobile, data et analytics, sécurité, cloud, hyperautomatisation et digital workplace.
Suivez-nous pour être notifié des prochains articles et réaliser votre veille professionnelle.
Retrouvez aussi nos publications et notre actualité via notre newsletter, ainsi que nos différents réseaux sociaux : LinkedIn, Twitter, Youtube, Twitch et Instagram
Vous souhaitez en savoir plus ? Consultez notre site web et nos offres d’emploi.