Imágenes aumentadas de ARCore

1. Descripción general

ARCore es una plataforma que permite crear apps de realidad aumentada en Android. Con las imágenes aumentadas, puedes crear apps de RA capaces de reconocer imágenes 2D registradas previamente en el mundo real y anclar contenido virtual sobre ellas.

En este codelab, se te guiará en la modificación de una app de muestra de ARCore existente a fin de incorporar imágenes aumentadas en movimiento o fijas.

Qué compilarás

En este codelab, compilarás una app de ARCore basándote en una aplicación de muestra existente. Al final del codelab, tu app podrá hacer lo siguiente:

  • Detectar una imagen objetivo y superponer un laberinto virtual sobre ella
  • Realizar un seguimiento del objetivo en movimiento siempre que esté en el visor de la cámara

6bc6605df89de525.gif

¿Es la primera vez que creas una app de ARCore?

No

¿Planeas escribir un código de muestra en este codelab o solo quieres leer estas páginas?

Escribir un código de muestra Solo leer estas páginas

Qué aprenderás

  • Cómo usar imágenes aumentadas en ARCore con Java
  • Cómo medir la capacidad de una imagen para que ARCore la reconozca
  • Cómo superponer contenido virtual sobre una imagen y hacer un seguimiento de su movimiento

Requisitos previos

Necesitarás hardware y software específicos para completar este codelab.

Requisitos de hardware

Requisitos de software

  • El archivo APK de ARCore 1.9.0 o una versión posterior (por lo general, se instala automáticamente en el dispositivo a través de Play Store)
  • Una máquina de desarrollo con Android Studio (v3.1 o una versión posterior)
  • Acceso a Internet, ya que deberás descargar bibliotecas durante el desarrollo

Ahora que ya tienes todo listo, comencemos.

2. Configura el entorno de desarrollo

Descarga el SDK

Comenzaremos por descargar la versión más reciente del SDK de ARCore para Android desde GitHub. Descomprímelo en la ubicación que prefieras. Para este codelab, la versión más antigua del SDK es 1.18.1. La carpeta se denominará arcore-android-sdk-x.xx.x, el valor exacto será la versión del SDK que uses.

Inicia Android Studio y haz clic en Open an existing Android Studio project.

5fbf2b21609187cc.png

Navega a esta carpeta descomprimida:

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

Haz clic en Open.

Espera a que Android Studio termine de sincronizar el proyecto. Si Android Studio no tiene los componentes requeridos, es posible que aparezca el mensaje Install missing platform and sync project. Sigue las instrucciones para solucionar el problema.

Ejecuta la app de muestra

Ahora que tienes un proyecto de la app de ARCore que funciona, ejecútalo como prueba.

Conecta tu dispositivo ARCore a la máquina de desarrollo y usa el menú Run > Run 'app' para ejecutar la versión de depuración en el dispositivo. En el cuadro de diálogo en que se te solicita que elijas el dispositivo, selecciona el dispositivo conectado y haz clic en OK.

1aa2c6faa7ecdbd0.png

92e4c144a632b4ca.png

En este proyecto de muestra, se usa targetSdkVersion 28. Si tienes un error de compilación como Failed to find Build Tools revision 28.0.3, sigue las instrucciones descritas en Android Studio para descargar e instalar la versión requerida de las herramientas de compilación de Android.

Si todo funciona correctamente, la app de muestra se iniciará en el dispositivo y te pedirá permiso para que la aplicación de imagen aumentada tome fotos y grabe videos. Presiona PERMITIR para otorgar el permiso.

Haz una prueba con una imagen de muestra

Ahora que ya configuraste el entorno de desarrollo, puedes probar la app indicándole una imagen que debe examinar.

Regresa a Android Studio. En la ventana Project navega a app > assets y haz doble clic en el archivo default.jpg para abrirlo.

9b333680e7b9f247.jpeg

Apunta la cámara de tu dispositivo hacia la imagen de la Tierra en pantalla y sigue las instrucciones para ajustar la imagen que estás analizando en los puntos de mira.

Un marco de imagen se superpone sobre la imagen, como se muestra a continuación:

999e05ed35964f6e.png

A continuación, haremos pequeñas mejoras en la app de muestra.

3. Muestra un modelo de laberinto sobre la imagen 2D

Puedes comenzar a jugar con las imágenes aumentadas mostrando un modelo 3D sobre ellas.

Descarga un modelo 3D

Para este codelab, usaremos “Circle Maze - Green” de Evol, que cuenta con una licencia CC‑BY 3.0. Almacenamos una copia de este modelo 3D en el repositorio de GitHub de este codelab, que puedes encontrar aquí.

Sigue estos pasos para descargar el modelo y, luego, incluirlo en Android Studio:

  1. Navega al directorio third_party del repositorio de GitHub de este codelab.
  2. Haz clic en GreenMaze_obj.zip y, luego, en el botón Download.

Esta acción descarga un archivo llamado GreenMaze_obj.zip.

  1. En Android Studio, crea el directorio green-maze en app > assets > models.
  2. Descomprime GreenMaze_obj.zip y copia el contenido en esta ubicación: arcore-android-sdk-x.xx.x/samples/augmented_image_java/app/src/main/assets/models/green-maze.
  3. En Android Studio, navega a app > Assets > models > green-maze.

Debe haber dos archivos en esta carpeta: GreenMaze.obj y GreenMaze.mtl.

a1f33a2d2d407e03.png

Renderiza el modelo del laberinto

Sigue estos pasos para mostrar el modelo 3D GreenMaze.obj sobre la imagen 2D existente.

En AugmentedImageRenderer.java, agrega una variable de miembro llamada mazeRenderer para procesar el modelo del laberinto. Dado que el laberinto debería superponerse sobre la imagen, tiene sentido colocar mazeRenderer en la clase AugmentedImageRenderer.

AugmentedImageRenderer.java

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

Carga el archivo GreenMaze.obj en la función createOnGlThread(). Para hacerlo más simple, usa la misma textura de marco.

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);

  }

Reemplaza la definición de la función draw() por lo siguiente. Este código ajusta el tamaño del laberinto a la imagen detectada y lo renderiza en pantalla.

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);
  }

Ahora, el laberinto debería mostrarse sobre la imagen default.jpg de la Tierra.

Nota: Como no tienes control total sobre este modelo 3D de muestra, el código anterior utiliza algunos números “mágicos”. La dimensión del modelo del laberinto es de 492.65 × 120 × 492.65, con el centro en (251.3, 60, -129.0). El rango de los vértices de los valores de coordenadas X, Y y Z son [5.02, 497.67], [0, 120] y [-375.17, 117.25], respectivamente. Por lo tanto, la escala del modelo del laberinto debe ser image_size / 492.65. Se agrega mazeModelLocalOffset porque el modelo 3D del laberinto no está centrado en el origen (0, 0, 0).

La pared del laberinto aún es demasiado alta para caber sobre la imagen. Crea una función auxiliar updateModelMatrix() que pueda escalar X, Y y Z de manera desigual para escalar la altura del laberinto en 0.1. Ten en cuenta que debes conservar la función updateModelMatrix(float[] modelMatrix, float scaleFactor) existente y agregar la sobrecarga updateModelMatrix(float[] modelMatrix, float scaleFactorX, float scaleFactorY, float scaleFactorZ) como función nueva.

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);
}

Ejecuta el código. El laberinto ahora debería ajustarse perfectamente a la imagen y aparecer sobre ella.

772cbe2a8baef3ba.png

4. Agrega a Andy al laberinto

Ahora que tienes un laberinto, agrega un personaje para que se mueva dentro de él. Usa el archivo andy.obj incluido en el SDK de ARCore para Android. Mantén la textura del marco de imagen, ya que se ve diferente del laberinto verde que se renderiza sobre la imagen.

En AugmentedImageRenderer.java, agrega un ObjectRenderer privado para renderizar a Andy.

AugmentedImageRenderer.java

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

A continuación, inicializa andyRenderer al final 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);
  }

Por último, renderiza a Andy de pie sobre el laberinto al final de la función 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);

  }

Ejecuta el código. Deberías ver a Andy de pie sobre el laberinto.

cb1e74569d7ace69.png

Determina la calidad de la imagen objetivo

ARCore usa funciones visuales para reconocer imágenes. Debido a las diferencias en la calidad, no todas las imágenes pueden reconocerse con facilidad.

arcoreimg es una herramienta de línea de comandos que permite determinar qué tan reconocible será una imagen para ARCore. El resultado es un número entre 0 y 100, donde 100 representa la máxima facilidad de reconocimiento.

Veamos un ejemplo:

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

maze.jpg tiene un valor de 100, por lo que ARCore puede reconocerla fácilmente.

5. Haz que Andy se mueva en el laberinto (opcional)

Por último, puedes agregar código para hacer que Andy se mueva en el laberinto. Por ejemplo, usa jBullet, un motor de física de código abierto, para controlar la simulación física. Si lo deseas, puedes omitir esta parte.

Descarga PhysicsController.java y agrégalo a tu proyecto en el siguiente directorio:

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

En Android Studio, agrega GreenMaze.obj al directorio de activos del proyecto para que se pueda cargar durante el tiempo de ejecución. Copia GreenMaze.obj de app > assets > models > green-maze en app > assets.

Agrega las siguientes dependencias al archivo build.gradle de tu app.

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'

Define una variable andyPose para almacenar la posición de la pose actual de Andy.

AugmentedImageRenderer.java

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

Modifica AugmentedImageRenderer.java para renderizar a Andy con la nueva 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);
  }

Agrega una nueva función de utilidad, updateAndyPose(), para recibir actualizaciones de la pose de Andy.

AugmentedImageRenderer.java

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

En AugmentedImageActivity.java, crea un objeto PhysicsController que use el motor de física JBullet para administrar todas las funciones relacionadas con la física.

AugmentedImageActivity.java

import com.google.ar.core.Pose;

  // Declare the PhysicsController object
  private PhysicsController physicsController;

En el motor de física, usamos una esfera rígida para representar a Andy y actualizar su pose con la de la esfera. Llama a PhysicsController para actualizar la física cada vez que la app reconozca una imagen. Si quieres mover la esfera como si estuviera en el mundo real, aplica la gravedad real para moverla por el laberinto.

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;

Ejecuta la app. Ahora Andy debería moverse de forma realista cuando inclines la imagen.

En el siguiente ejemplo, se usa otro teléfono para mostrar la imagen. Puedes usar cualquier objeto que te resulte conveniente, como una tablet, la portada de un libro físico o un papel impreso pegado sobre un objeto plano.

2f0df284705d3704.gif

Eso es todo. Diviértete haciendo que Andy llegue al final del laberinto. Pista: Es más fácil encontrar la salida cuando mantienes la imagen objetivo hacia abajo.

6. Felicitaciones

¡Felicitaciones por llegar al final de este codelab! Hicimos lo siguiente:

  • Compilamos y ejecutamos una muestra de Java AugmentedImage de ARCore.
  • Actualizamos la muestra para presentar un modelo de laberinto en la imagen a la escala adecuada.
  • Utilizamos la posición de la imagen para hacer algo divertido.

Si deseas consultar el código completo, puedes descargarlo aquí.

¿Te pareció divertido este codelab?

No

¿Aprendiste algo útil en este codelab?

No

¿Completaste la creación de la app en este codelab?

No

¿Planeas crear una app de ARCore en los próximos 6 meses?

Tal vez No