Augmented Images avec ARCore

1. Présentation

ARCore est une plate-forme qui permet de créer des applications de réalité augmentée sur Android. Augmented Images vous offre la possibilité de concevoir des applications de RA qui peuvent reconnaître des images 2D préenregistrées dans le monde réel et d'y associer des contenus virtuels.

Cet atelier de programmation vous aidera à modifier un exemple d'application ARCore existant afin d'y intégrer des éléments Augmented Images en mouvement ou fixes.

Ce que vous allez faire

Dans cet atelier de programmation, vous allez vous appuyer sur un exemple d'application ARCore existant. Voici les tâches que votre application pourra effectuer quand vous aurez terminé :

  • Détecter une image cible et y associer un labyrinthe virtuel
  • Suivre une cible en mouvement tant qu'elle reste visible dans le champ de l'appareil photo

6bc6605df89de525.gif

Est-ce la première fois que vous créez une application ARCore ?

Oui Non

Avez-vous l'intention d'écrire un exemple de code dans cet atelier de programmation ou souhaitez-vous simplement lire ces pages ?

Écrire du code Lire ces pages

Ce que vous allez apprendre

  • Utiliser Augmented Images dans ARCore en Java
  • Évaluer la capacité d'une image à être reconnue par ARCore
  • Associer un contenu virtuel à une image et suivre ses déplacements

Prérequis

Vous aurez besoin de matériel et de logiciels spécifiques pour cet atelier de programmation.

Matériel requis

Logiciels requis

  • APK ARCore 1.9.0 ou version ultérieure (cet APK est normalement installé automatiquement sur l'appareil via le Play Store)
  • Un ordinateur de développement sur lequel est installé Android Studio (version 3.1 ou ultérieure)
  • Un accès à Internet (car vous devrez télécharger des bibliothèques pendant le développement)

Maintenant que tout est prêt, nous pouvons nous lancer.

2. Configurer l'environnement de développement

Télécharger le SDK

Nous allons commencer par télécharger la dernière version du SDK Android ARCore depuis GitHub. Décompressez-le à l'emplacement de votre choix. La plus ancienne version du SDK qui est compatible avec cet atelier de programmation est la version 1.18.1. Le dossier s'intitule arcore-android-sdk-x.xx.x ("x.xx.x" correspondra à la version du SDK que vous utilisez).

Lancez Android Studio, puis cliquez sur Open an existing Android Studio project (Ouvrir un projet Android Studio existant).

5fbf2b21609187cc.png

Accédez à ce dossier décompressé :

arcore-android-sdk-x.xx.x/samples/augmented_image_java

Cliquez sur Open (Ouvrir).

Attendez qu'Android Studio synchronise le projet. Si des composants requis manquent à Android Studio, le message d'erreur Install missing platform and sync project peut s'afficher. Suivez les instructions pour résoudre le problème.

Exécuter l'application exemple

Maintenant que vous disposez d'un projet d'application ARCore opérationnel, nous allons pouvoir essayer de l'exécuter.

Connectez votre appareil ARCore à l'ordinateur de développement, puis utilisez le menu Run > Run 'app' (Exécuter > Exécuter "application") pour exécuter la version de débogage sur l'appareil. Dans la boîte de dialogue qui vous invite à choisir l'appareil sur lequel exécuter l'application, sélectionnez l'appareil connecté, puis cliquez sur OK.

1aa2c6faa7ecdbd0.png

92e4c144a632b4ca.png

Cet exemple de projet utilise targetSdkVersion 28. Si vous rencontrez une erreur de build telle que Failed to find Build Tools revision 28.0.3, suivez les instructions fournies dans Android Studio pour télécharger et installer la version requise d'Android Build Tools.

Si l'opération réussit, l'application exemple s'ouvre sur l'appareil et vous invite à autoriser Augmented Images à prendre des photos et à enregistrer des vidéos. Appuyez sur ALLOW (AUTORISER) pour accorder cette autorisation.

Tester l'application avec un exemple d'image

Maintenant que vous avez configuré votre environnement de développement, vous pouvez ajouter une image afin que l'application l'examine.

Dans Android Studio, dans la fenêtre Project (Projet), accédez à app > assets (Application > Éléments), puis double-cliquez sur le fichier default.jpg pour l'ouvrir.

9b333680e7b9f247.jpeg

Orientez l'appareil photo de votre appareil vers l'image de la Terre affichée à l'écran, puis suivez les instructions pour adapter l'image que vous scannez au cadre.

Une image de cadre se superpose sur la photo, comme ci-dessous :

999e05ed35964f6e.png

Apportons quelques petites améliorations à notre application.

3. Afficher un modèle de labyrinthe sur l'image 2D

Vous pouvez commencer à tester votre image Augmented Images en y superposant un modèle 3D.

Télécharger un modèle 3D

Pour cet atelier de programmation, nous allons utiliser Circle Maze - Green par Evol, sous licence CC-BY 3.0. Une copie de ce modèle 3D est disponible dans le dépôt GitHub de cet atelier de programmation. Pour y accéder, cliquez ici.

Procédez comme suit pour télécharger le modèle et l'importer dans Android Studio.

  1. Accédez au répertoire "third_party" du dépôt GitHub de cet atelier de programmation.
  2. Cliquez sur GreenMaze_obj.zip, puis sur le bouton Download (Télécharger).

Un fichier nommé GreenMaze_obj.zip est alors téléchargé.

  1. Dans Android Studio, créez le répertoire green-maze sous app > assets > models (Application > Éléments > Modèles).
  2. Décompressez GreenMaze_obj.zip, puis copiez son contenu à l'emplacement suivant : arcore-android-sdk-x.xx.x/samples/augmented_image_java/app/src/main/assets/models/green-maze
  3. Dans Android Studio, accédez à app > assets > models > green-maze (Application > Éléments > Modèles > green-maze).

Ce dossier doit contenir deux fichiers : GreenMaze.obj et GreenMaze.mtl.

a1f33a2d2d407e03.png

Afficher le modèle de labyrinthe

Procédez comme suit pour superposer le modèle 3D GreenMaze.obj à l'image 2D existante.

Dans AugmentedImageRenderer.java, ajoutez une variable de membre appelée mazeRenderer pour afficher le modèle de labyrinthe. Comme le labyrinthe doit se superposer à l'image, il est logique de placer mazeRenderer dans la classe AugmentedImageRenderer.

AugmentedImageRenderer.java

  // Add a member variable to hold the maze model.
  private final ObjectRenderer mazeRenderer = new ObjectRenderer();

Dans la fonction createOnGlThread(), chargez GreenMaze.obj. Pour plus de simplicité, utilisez la même texture pour l'élément et son cadre.

AugmentedImageRenderer.java

  // Replace the definition of the createOnGlThread() function with the
  // following code, which loads GreenMaze.obj.
  public void createOnGlThread(Context context) throws IOException {

    mazeRenderer.createOnGlThread(
        context, "models/green-maze/GreenMaze.obj", "models/frame_base.png");
    mazeRenderer.setMaterialProperties(0.0f, 3.5f, 1.0f, 6.0f);

  }

Remplacez la définition de la fonction draw() par ce qui suit. Cela permet d'ajuster la taille du labyrinthe à celle de l'image détectée et de l'afficher à l'écran.

AugmentedImageRenderer.java

  // Adjust size of detected image and render it on-screen
  public void draw(
      float[] viewMatrix,
      float[] projectionMatrix,
      AugmentedImage augmentedImage,
      Anchor centerAnchor,
      float[] colorCorrectionRgba) {
    float[] tintColor =
        convertHexToColor(TINT_COLORS_HEX[augmentedImage.getIndex() % TINT_COLORS_HEX.length]);

    final float mazeEdgeSize = 492.65f; // Magic number of maze size
    final float maxImageEdgeSize = Math.max(augmentedImage.getExtentX(), augmentedImage.getExtentZ()); // Get largest detected image edge size

    Pose anchorPose = centerAnchor.getPose();

    float mazeScaleFactor = maxImageEdgeSize / mazeEdgeSize; // scale to set Maze to image size
    float[] modelMatrix = new float[16];

    // OpenGL Matrix operation is in the order: Scale, rotation and Translation
    // So the manual adjustment is after scale
    // The 251.3f and 129.0f is magic number from the maze obj file
    // You mustWe need to do this adjustment because the maze obj file
    // is not centered around origin. Normally when you
    // work with your own model, you don't have this problem.
    Pose mazeModelLocalOffset = Pose.makeTranslation(
                                -251.3f * mazeScaleFactor,
                                0.0f,
                                129.0f * mazeScaleFactor);
    anchorPose.compose(mazeModelLocalOffset).toMatrix(modelMatrix, 0);
    mazeRenderer.updateModelMatrix(modelMatrix, mazeScaleFactor, mazeScaleFactor/10.0f, mazeScaleFactor); // This line relies on a change in ObjectRenderer.updateModelMatrix later in this codelab.
    mazeRenderer.draw(viewMatrix, projectionMatrix, colorCorrectionRgba, tintColor);
  }

Le labyrinthe doit maintenant s'afficher au-dessus de l'image default.jpg de la Terre.

Remarque : Comme vous ne contrôlez pas entièrement cet exemple de modèle 3D, le code ci-dessus contient des nombres qui peuvent donner l'impression de sortir de nulle part. Les dimensions du modèle de labyrinthe sont de 492,65 × 120 × 492,65, et les coordonnées de son centre sont : (251,3 ; 60 ; -129). Les coordonnées x, y et z minimales et maximales de ses sommets sont : [5,02 ; 497,67], [0 ; 120], [-375,17 ; 117,25]. Ainsi, l'échelle du modèle de labyrinthe doit être image_size / 492.65. Un décalage mazeModelLocalOffset est ajouté, car le modèle 3D du labyrinthe n'est pas centré sur l'origine (0, 0, 0).

Le mur du labyrinthe est encore un peu trop élevé pour être superposé à l'image. Créez une fonction d'assistance updateModelMatrix() capable de mettre à l'échelle les coordonnées X, Y et Z de façon différenciée, afin de redimensionner la hauteur du labyrinthe à l'échelle 0,1. Notez que vous devez conserver la fonction updateModelMatrix(float[] modelMatrix, float scaleFactor) existante et ajouter la surcharge de fonction updateModelMatrix(float[] modelMatrix, float scaleFactorX, float scaleFactorY, float scaleFactorZ) en tant que nouvelle fonction.

common/rendering/ObjectRenderer.java

// Scale X, Y, Z coordinates unevenly
public void updateModelMatrix(float[] modelMatrix, float scaleFactorX, float scaleFactorY, float scaleFactorZ) {
    float[] scaleMatrix = new float[16];
    Matrix.setIdentityM(scaleMatrix, 0);
    scaleMatrix[0] = scaleFactorX;
    scaleMatrix[5] = scaleFactorY;
    scaleMatrix[10] = scaleFactorZ;
    Matrix.multiplyMM(this.modelMatrix, 0, modelMatrix, 0, scaleMatrix, 0);
}

Exécutez le code. Le labyrinthe doit maintenant se superposer parfaitement à l'image.

772cbe2a8baef3ba.png

4. Ajouter le personnage Andy au labyrinthe

Ajoutons maintenant un personnage qui se déplace à l'intérieur du labyrinthe. Utilisez le fichier andy.obj inclus dans le SDK Android ARCore. Utilisez la même texture pour le cadre et l'image, car elle est différente du labyrinthe vert superposé à l'image.

Dans AugmentedImageRenderer.java, ajoutez un ObjectRenderer privé pour afficher Andy.

AugmentedImageRenderer.java

// Render for Andy
  private final ObjectRenderer andyRenderer = new ObjectRenderer();

Ensuite, initialisez andyRenderer à la fin de createOnGlThread().

AugmentedImageRenderer.java

public void createOnGlThread(Context context) throws IOException {

    // Initialize andyRenderer
    andyRenderer.createOnGlThread(
        context, "models/andy.obj", "models/andy.png");
    andyRenderer.setMaterialProperties(0.0f, 3.5f, 1.0f, 6.0f);
  }

Pour terminer, affichez Andy sur le labyrinthe à la fin de la fonction draw().

AugmentedImageRenderer.java

public void draw(
      float[] viewMatrix,
      float[] projectionMatrix,
      AugmentedImage augmentedImage,
      Anchor centerAnchor,
      float[] colorCorrectionRgba) {

    // Render Andy, standing on top of the maze
    Pose andyModelLocalOffset = Pose.makeTranslation(
        0.0f,
        0.1f,
        0.0f);
    anchorPose.compose(andyModelLocalOffset).toMatrix(modelMatrix, 0);
    andyRenderer.updateModelMatrix(modelMatrix, 0.05f); // 0.05f is a Magic number to scale
    andyRenderer.draw(viewMatrix, projectionMatrix, colorCorrectionRgba, tintColor);

  }

Exécutez votre code. Vous devriez voir Andy apparaître sur le labyrinthe.

cb1e74569d7ace69.png

Déterminer la qualité de l'image cible

ARCore se base sur certaines caractéristiques visuelles pour reconnaître des images. Comme toutes les images n'ont pas la même qualité, certaines sont moins faciles à identifier.

L'outil de ligne de commande arcoreimg vous permet de déterminer si une image sera facilement reconnaissable par ARCore. Cet outil génère un nombre compris entre 0 et 100, où 100 correspond à la plus grande facilité de reconnaissance.

Voici un exemple.

arcore-android-sdk-x.xx.x/tools/arcoreimg/macos$
$ ./arcoreimg  eval-img --input_image_path=/Users/username/maze.jpg
100

L'image maze.jpg présente 100 comme valeur. ARCore peut donc facilement la reconnaître.

5. Facultatif : Déplacer Andy dans le labyrinthe

Enfin, vous pouvez ajouter du code pour qu'Andy se déplace dans le labyrinthe. Par exemple, utilisez le moteur physique Open Source jBullet pour gérer la simulation physique. N'hésitez pas à sauter cette étape si elle ne vous intéresse pas.

Téléchargez PhysicsController.java, puis ajoutez-le à votre projet dans le répertoire.

arcore-android-sdk-x.xx.x/samples/augmented_image_java/app/src/main/java/com/google/ar/core/examples/java/augmentedimage/

Dans Android Studio, ajoutez GreenMaze.obj au répertoire des éléments du projet, afin qu'il puisse être chargé au moment de l'exécution. Copiez GreenMaze.obj depuis app > assets > models > green-maze (Application > Éléments > Modèles > green-maze), puis collez-le dans app > assets (Application > Éléments).

Ajoutez les dépendances suivantes au fichier build.gradle de l'application.

app/build.gradle

    // jbullet library
    implementation 'cz.advel.jbullet:jbullet:20101010-1'

    // Obj - a simple Wavefront OBJ file loader
    // https://github.com/javagl/Obj
    implementation 'de.javagl:obj:0.2.1'

Définissez une variable andyPose pour enregistrer la position actuelle d'Andy.

AugmentedImageRenderer.java

  // Create a new pose for the Andy
  private Pose andyPose = Pose.IDENTITY;

Modifiez AugmentedImageRenderer.java pour afficher Andy en fonction de la nouvelle variable andyPose.

AugmentedImageRenderer.java

public void draw(
      float[] viewMatrix,
      float[] projectionMatrix,
      AugmentedImage augmentedImage,
      Anchor centerAnchor,
      float[] colorCorrectionRgba) {

    // Use these code to replace previous code for rendering the Andy object
    //
    // Adjust the Andy's rendering position
    // The Andy's pose is at the maze's vertex's coordinate
    Pose andyPoseInImageSpace = Pose.makeTranslation(
        andyPose.tx() * mazeScaleFactor,
        andyPose.ty() * mazeScaleFactor,
        andyPose.tz() * mazeScaleFactor);

    anchorPose.compose(andyPoseInImageSpace).toMatrix(modelMatrix, 0);
    andyRenderer.updateModelMatrix(modelMatrix, 0.05f);
    andyRenderer.draw(viewMatrix, projectionMatrix, colorCorrectionRgba, tintColor);
  }

Ajoutez une fonction utilitaire updateAndyPose() pour recevoir des informations sur la position d'Andy.

AugmentedImageRenderer.java

  // Receive Andy pose updates
  public void updateAndyPose(Pose pose) {
    andyPose = pose;
  }

Dans AugmentedImageActivity.java, créez un objet PhysicsController qui utilise le moteur physique JBullet pour gérer toutes les fonctions physiques.

AugmentedImageActivity.java

import com.google.ar.core.Pose;

  // Declare the PhysicsController object
  private PhysicsController physicsController;

Dans le moteur physique, nous représentons Andy avec une bille rigide et nous mettons à jour sa position avec celle de la bille. Appelez PhysicsController pour mettre à jour les mouvements chaque fois que l'application reconnaît une image. Pour déplacer la bille dans le labyrinthe comme dans la vraie vie, appliquez la gravité réelle.

AugmentedImageActivity.java

// Update the case clause for TRACKING to call PhysicsController
// whenever the app recognizes an image
  private void drawAugmentedImages(

    ...

        case TRACKING:
          // Switch to UI Thread to update View
          this.runOnUiThread(
              new Runnable() {
                @Override
                public void run() {
                  fitToScanView.setVisibility(View.GONE);
                }
              });

          // Create a new anchor for newly found images
          if (!augmentedImageMap.containsKey(augmentedImage.getIndex())) {
            Anchor centerPoseAnchor = augmentedImage.createAnchor(augmentedImage.getCenterPose());
            augmentedImageMap.put(
                augmentedImage.getIndex(), Pair.create(augmentedImage, centerPoseAnchor));

            physicsController = new PhysicsController(this);
          } else {
            Pose ballPose = physicsController.getBallPose();
            augmentedImageRenderer.updateAndyPose(ballPose);

            // Use real world gravity, (0, -10, 0), as gravity
            // Convert to Physics world coordinate(maze mesh has to be static)
            // Use the converted coordinate as a force to move the ball
            Pose worldGravityPose = Pose.makeTranslation(0, -10f, 0);
            Pose mazeGravityPose = augmentedImage.getCenterPose().inverse().compose(worldGravityPose);
            float mazeGravity[] = mazeGravityPose.getTranslation();
            physicsController.applyGravityToBall(mazeGravity);

            physicsController.updatePhysics();
          }
          break;

Exécutez l'application. Andy devrait désormais se déplacer de manière réaliste lorsque vous inclinez l'image.

Dans l'exemple ci-dessous, un autre téléphone est utilisé pour afficher l'image. Vous pouvez vous servir du support qui vous convient, par exemple d'une tablette, de la couverture d'un livre ou simplement d'une feuille fixée sur un objet plat.

2f0df284705d3704.gif

Et voilà ! Vous pouvez désormais vous amuser à déplacer Andy dans le labyrinthe. Conseil : Pour trouver la sortie plus facilement, il vous suffit de positionner l'image cible à l'envers.

6. Félicitations

Félicitations, vous avez terminé cet atelier de programmation ! Voici ce que vous y avez appris :

  • Créer et exécuter un exemple AugmentedImage Java
  • Mettre à jour le code pour afficher un modèle de labyrinthe sur l'image, à l'échelle appropriée
  • Créer un jeu basé sur le positionnement de l'image

Si vous souhaitez accéder au code complet, vous pouvez le télécharger sur cette page.

Est-ce que cet atelier de programmation vous a plu ?

Oui Non

Avez-vous appris des choses utiles dans cet atelier de programmation ?

Oui Non

Avez-vous terminé la création de l'application dans cet atelier de programmation ?

Oui Non

Avez-vous l'intention de créer une application ARCore au cours des six prochains mois ?

Oui Peut-être Non