Imágenes aumentadas de ARCore

ARCore es una plataforma que permite crear apps de realidad aumentada en Android. Las imágenes aumentadas te permiten crear apps de RA que puedan reconocer imágenes previamente registradas y contenido virtual anclado en 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, crearás sobre una app de muestra de ARCore preexistente. Al final del codelab, tu app hará lo siguiente:

  • Detectará una imagen objetivo y adjuntará un laberinto virtual del objetivo. A continuación, encontrarás una visualización de ejemplo.
  • Hará un seguimiento del objetivo en movimiento mientras esté en la vista.

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 en Java
  • Cómo medir la capacidad de una imagen para que ARCore la reconozca
  • Cómo adjuntar un contenido virtual en una imagen y hacer un seguimiento de su movimiento

Requisitos

Antes de comenzar este codelab, asegúrate de tener todo lo que necesitas:

  • Un dispositivo ARCore compatible, conectado mediante un cable USB a tu máquina de desarrollo
  • ARCore 1.9 o una versión posterior (por lo general, este APK se instala automáticamente en el dispositivo a través de Play Store. Si algún dispositivo no cuenta con la versión requerida de ARCore, puedes instalarla desde Play Store)
  • Una máquina de desarrollo con Android Studio (v3.1 o versiones posteriores)
  • Acceso a Internet para descargar bibliotecas durante el desarrollo

Ahora que ya tienes todo listo, ¡comencemos!

Comenzaremos por descargar el SDK de Java para ARCore de GitHub arcore-android-sdk-1.18.1.zip. Descomprímelo en la ubicación que prefieras. La carpeta de extracción se llamará arcore-android-sdk-1.18.1.

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

5fbf2b21609187cc.png

Navega a esta carpeta descomprimida:

arcore-android-sdk-1.18.1/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 de error "Install platform miss and sync project" ("Instalar la plataforma que falta y sincronizar el proyecto"). Sigue las instrucciones para solucionar el problema.

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 que te solicita que elijas el dispositivo,

elige 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 y, luego, instalar la versión requerida de las herramientas de compilación de Android.

Si todo funciona correctamente, la app de muestra se inicia en el dispositivo y te solicita permiso para permitir que la imagen aumentada tome fotos y grabe videos. Presiona ALLOW para otorgar el permiso.

Observemos una imagen en la app de muestra.

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.

Como mencionamos al comienzo de este codelab, realizaremos un pequeño juego de laberinto en la imagen. Primero, buscaremos un modelo de laberinto en poly.google.com, que contiene muchos modelos 3D bajo la licencia CC-BY para uso gratuito.

Para este codelab, usaremos "Circle Maze - Green", de Evol, y con licencia CC-BY 3.0.

832fc0f1b09fea1e.png

Sigue estos pasos para descargar el modelo y, luego, incorporarlo a Android Studio:

  1. Navega a la página de Poly del modelo.
  2. Haz clic en Download y selecciona OBJ File.

Esta acción descarga un archivo llamado green-maze.zip.

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

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

a1f33a2d2d407e03.png

Ahora cargaremos este archivo OBJ y lo mostraremos en la imagen detectada.

Ahora que tenemos el modelo 3D, GreenMaze.obj, lo mostraremos en la parte superior de nuestra imagen.

  1. En AugmentedImageRenderer.java, agrega una variable de miembro llamada mazeRenderer para procesar el modelo de laberinto. Dado que el laberinto debería adjuntarse a la imagen, tiene sentido colocar mazeRenderer dentro de la clase de AugmentedImageRenderer.
  2. En la función createOnGlThread, carga el archivo GreenMaze.obj. Para hacerlo más simple, usaremos la misma textura de marco que su textura.
  3. En la función draw, ajusta el tamaño del laberinto al tamaño de la imagen detectada y dibújalo.

En AugmentedImageRenderer.java, realiza estos cambios.

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

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

  }

  // Replace the definition of the draw function with the
  // following code
  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 maze_edge_size = 492.65f; // Magic number of maze size
    final float max_image_edge = Math.max(augmentedImage.getExtentX(), augmentedImage.getExtentZ()); // Get largest detected image edge size

    Pose anchorPose = centerAnchor.getPose();

    float mazsScaleFactor = max_image_edge / maze_edge_size; // 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
    // We 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 mozeModelLocalOffset = Pose.makeTranslation(
                                -251.3f * mazsScaleFactor,
                                0.0f,
                                129.0f * mazsScaleFactor);
    anchorPose.compose(mozeModelLocalOffset).toMatrix(modelMatrix, 0);
    mazeRenderer.updateModelMatrix(modelMatrix, mazsScaleFactor, mazsScaleFactor/10.0f, mazsScaleFactor);
    mazeRenderer.draw(viewMatrix, projectionMatrix, colorCorrectionRgba, tintColor);
  }

Muy bien, parece que tuvimos suficiente cambio de código para mostrar el laberinto sobre nuestra imagen de default.jpg de la Tierra.

En el código anterior, se implementaron algunos números mágicos. No te preocupes, están simplemente allí porque no tenemos control total sobre este modelo 3D. Luego, analizamos manualmente el archivo obj para detectar la posición central (x, y, z) del modelo y su tamaño. Eso no lo haremos en este codelab. Solo revelaremos el valor aquí. La dimensión del modelo de laberinto es de 492.65 x 120 x 492.65, con el centro en (251.3, 60, -129.0). El rango de valores de los vértices de las coordenadas X, Y y Z son [5.02, 497.67], [0, 120] y [-375.17, 117.25], respectivamente. Por lo tanto, debemos configurar la escala del modo de laberinto según image_size / 492.65. Como posiblemente ya hayas notado, el modelo 3D del laberinto no está centrado en el origen (0, 0, 0) por lo que debemos introducir un desplazamiento mozeModelLocalOffset de forma manual.

Además, como la pared del laberinto sigue siendo demasiado alta para nuestro codelab, debemos escalarla 0.1 veces más. Esto reduce la pared de modo que las brechas sean más visibles. Para ello, debemos introducir una función auxiliar que nos permita ajustar de manera uniforme las coordenadas X, Y y Z.

En augmentedimage/rendering/ObjectRenderer.java, realiza estos cambios.

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

Intentemos ejecutarla en tu dispositivo compatible con ARCore. El tamaño del laberinto debe ser igual que el de la imagen.

772cbe2a8baef3ba.png

Ahora, agreguemos un objeto que se mueva dentro del laberinto. En este codelab, lo haremos simple y solo usaremos el archivo andy.obj de la figura de Android que incluye el SDK de ARCore para Android. Además, usaremos la textura del marco de la imagen como su textura, ya que se ve diferente del laberinto verde que vemos sobre la imagen.

Agrega este código en AugmentedImageNode.java.

// Add a private member to render andy
  private final ObjectRenderer andyRenderer = new ObjectRenderer();

  public void createOnGlThread(Context context) throws IOException {

    // Add initialization for andyRenderer at the end of the createOnGlThread function.
    andyRenderer.createOnGlThread(
        context, "models/andy.obj", "models/andy.png");
    andyRenderer.setMaterialProperties(0.0f, 3.5f, 1.0f, 6.0f);
  }

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

    // In draw() function, at the end add code to display the 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);

  }

Ahora intentamos ejecutarlo en el dispositivo. Deberíamos ver algo así.

cb1e74569d7ace69.png

Determina la calidad de la imagen objetivo

Para reconocer una imagen, ARCore se basa en sus características visuales. No todas las imágenes tienen la misma calidad ni se pueden reconocer con facilidad.

La herramienta arcoreimg del SDK de ARCore para Android te permite verificar la calidad de una imagen objetivo. Podemos ejecutar esta herramienta de línea de comandos para determinar qué tan reconocible será una imagen de ARCore. Esta herramienta da como resultado un número entre 0 y 100, donde 100 es el más fácil de reconocer. Por ejemplo:

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

La última sección no es realmente relevante para ARCore, pero es una parte adicional que hace que esta app de muestra sea divertida. Si lo deseas, puedes omitir esta parte.

Utilizaremos un motor físico de código abierto, jBullet, para manejar la simulación física.

Esto es lo que vamos a hacer:

  1. Agregaremos GreenMaze.obj al directorio de activos del proyecto para poder cargarlo durante el tiempo de ejecución.
  2. Crearemos la clase PhysicsController para administrar todas las funciones relacionadas con la física. Internamente, usa el motor físico JBullet.
  3. Llamaremos a PhysicsController cuando se reconozca una imagen y, luego, a updatePhysics.
  4. Usaremos la gravedad real para mover la pelota en el laberinto. Ten en cuenta que necesitamos escalar un poco más el tamaño de la pelota para que pueda atravesar las brechas en el laberinto.

Descarga el código PhysicsController.java y agrégalo a tu proyecto en este directorio arcore-android-sdk-1.18.1/samples/augmented_image_java/app/src/main/java/com/google/ar/core/examples/java/augmentedimage/.

Luego, aplica esos cambios en el código Java existente. Como se muestra a continuación,

en Android Studio, copia GreenMaze.obj de:

app > assets > models > green-maze

a:

app > assets

En app/build.gradle, agrega este código.

    // Add these dependencies.
    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'

En AugmentedImageRenderer.java, agrega este código.

// Add this line at the top with the rest of the imports.
  private Pose andyPose = Pose.IDENTITY;

  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 andy's rendering position
    // Andy's pose is at Maze's vertex's coordinate
    Pose andyPoseInImageSpace = Pose.makeTranslation(
        andyPose.tx() * mazsScaleFactor,
        andyPose.ty() * mazsScaleFactor,
        andyPose.tz() * mazsScaleFactor);

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

  // Add a new utility function to receive Andy pose updates
  public void updateAndyPose(Pose pose) {
    andyPose = pose;
  }

En AugmentedImageActivity.java, agrega este código.

import com.google.ar.core.Pose;

  // Declare the PhysicsController class.
  private PhysicsController physicsController;

  // Update the case clause for TRACKING as below
  private void drawAugmentedImages(

    ...

        case TRACKING:
          // Have to 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 (because Maze mesh has to be static)
            // Use it 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;

Como resultado, podemos hacer que se mueva de esta manera.

2f0df284705d3704.gif

Diviértete.

Felicitaciones, llegaste al final de este codelab. En él, logramos lo siguiente:

  • Compilamos y ejecutamos una muestra de Java AugmentedImage de ARCore.
  • Actualizamos la muestra para enfocar automáticamente las imágenes cercanas y, luego, hicimos que el marco de la imagen se alineara con el tamaño de la imagen.
  • Actualizamos la muestra para usar una imagen especificada por el usuario como objetivo.
  • 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