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
¿Es la primera vez que creas una app de ARCore?
¿Planeas escribir un código de muestra en este codelab o solo quieres 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
- Un dispositivo compatible con ARCore conectado mediante un cable USB a la máquina de desarrollo
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.
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.
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.
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:
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:
- Navega al directorio third_party del repositorio de GitHub de este codelab.
- Haz clic en GreenMaze_obj.zip y, luego, en el botón Download.
Esta acción descarga un archivo llamado GreenMaze_obj.zip
.
- En Android Studio, crea el directorio
green-maze
en app > assets > models. - 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
. - En Android Studio, navega a app > Assets > models > green-maze.
Debe haber dos archivos en esta carpeta: GreenMaze.obj
y GreenMaze.mtl
.
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.
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.
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.
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í.