ARCore Augmented Images

1. 概览

ARCore 是一个用于在 Android 上构建增强现实应用的平台。借助 Augmented Images,您可以创建 AR 应用,该应用可以识别现实中预注册的 2D 图像,并在其上附加虚拟内容。

本 Codelab 将指导您修改现有 ARCore 示例应用,以纳入移动或固定的 Augmented Images 增强图像。

构建内容

在此 Codelab 中,您将在现有 ARCore 示例应用的基础上构建您的应用。在此 Codelab 结束时,应用将可以执行以下操作:

  • 检测图像目标并在目标上附加虚拟迷宫
  • 跟踪移动目标(前提是目标在相机视野中)

6bc6605df89de525.gif

这是您第一次构建 ARCore 应用吗?

您是打算在此 Codelab 中编写示例代码,还是只想阅读这些页面?

编写示例代码 只想阅读这些页面

学习内容

  • 如何通过 Java 在 ARCore 中使用 Augmented Images
  • 如何衡量图像被 ARCore 识别的难易程度
  • 如何在图像上附加虚拟内容并跟踪其移动

前提条件

您需要有特定的软硬件才能完成此 Codelab。

硬件要求

软件要求

  • ARCore APK 1.9.0 或更高版本。此 APK 通常通过 Play 商店自动安装到设备上。
  • 安装了 Android Studio(v3.1 或更高版本)的开发机器。
  • 互联网连接,因为您需要在开发期间下载库

现在,万事俱备,我们开始吧!

2. 设置开发环境

下载 SDK

首先,从 GitHub 下载最新的 ARCore Android SDK,将其解压缩到所需位置。对于本 Codelab,最低的 SDK 版本为 1.18.1。文件夹的名称为 arcore-android-sdk-x.xx.x,其中的具体值就是您要使用的 SDK 的版本。

启动 Android Studio,然后点击 Open an existing Android Studio project

5fbf2b21609187cc.png

转到以下解压缩的文件夹:

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

点击 Open

等待 Android Studio 完成项目同步。如果您的 Android Studio 没有必需的组件,可能会失败,并显示 Install missing platform and sync project 消息。按照相关说明解决该问题。

运行示例应用

现在,您的 ARCore 应用项目已可以正常运行,让我们对它进行一次测试运行。

将 ARCore 设备连接到开发机器,然后使用菜单 Run > Run 'app' 在设备上运行调试版本。在提示您选择从哪个设备运行的对话框中,选择此已连接的设备,然后点击 OK

1aa2c6faa7ecdbd0.png

92e4c144a632b4ca.png

此示例项目使用 targetSdkVersion 28。如果出现构建错误(如 Failed to find Build Tools revision 28.0.3),请按照 Android Studio 中的说明下载并安装所需的 Android 构建工具版本。

如果一切顺利,示例应用便会在设备上启动,并提示您授予相关权限以允许 Augmented Images 拍摄照片和视频。点按 ALLOW 授予权限。

使用示例图像进行测试

现在您已经设置了开发环境,接下来可以通过向其提供图像来测试应用。

回到 Android Studio 中,在 Project 窗口中,转到 app > assets,然后双击文件 default.jpg 将其打开。

9b333680e7b9f247.jpeg

将设备摄像头对准屏幕上显示的地球图像,然后按照说明将正在扫描的图像纳入对焦范围内。

图像边框会在图像之上叠加,如下所示:

999e05ed35964f6e.png

接下来,我们将对这个示例应用稍作改进。

3.在 2D 图像上显示迷宫模型

首先,您可以玩一会儿 Augmented Images,在它上面显示一个 3D 模型。

下载一个 3D 模型

在本 Codelab 中,我们将使用 Evol 提供的 Circle Maze - Green 模型,此模型根据 CC-BY 3.0 许可。我在本 Codelab 的 GitHub 代码库中存储了此 3D 模型的一个副本,您可以在此处找到它。

按以下步骤下载此模型并将其包括在 Android Studio 中:

  1. 转到此 Codelab 的 GitHub 代码库,third_party directory
  2. 点击 GreenMaze_obj.zip,然后点击 Download 按钮。

此操作会下载名为 GreenMaze_obj.zip 的文件。

  1. 在 Android Studio 中,在 app > assets > models 下创建 green-maze 目录
  2. 解压缩 GreenMaze_obj.zip 并将其中的内容复制到以下位置:arcore-android-sdk-x.xx.x/samples/augmented_image_java/app/src/main/assets/models/green-maze
  3. 在 Android Studio 中,导航到 app > assets > models > green-maze

此文件夹中应该有两个文件:GreenMaze.objGreenMaze.mtl

a1f33a2d2d407e03.png

呈现迷宫模型

按照以下步骤在现有 2D 图像之上显示 GreenMaze.obj 3D 模型。

AugmentedImageRenderer.java 中,添加一个名为 mazeRenderer 的成员变量以呈现该迷宫模型。由于迷宫应附加到图像,因此需要将 mazeRenderer 放到 AugmentedImageRenderer 类中。

AugmentedImageRenderer.java

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

createOnGlThread() 函数中,加载 GreenMaze.obj。为简单起见,请使用该帧的纹理作为其纹理。

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

  }

draw() 函数的定义替换为以下代码。这会将迷宫的尺寸调整为检测到的图像的尺寸,并将其显示在屏幕上。

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

现在,该迷宫应显示在地球的 default.jpg 照片上方。

注意:由于您无法完全控制此示例 3D 模型,因此上述代码使用了一些“魔法”数字。迷宫模型的尺寸为 492.65 x 120 x 492.65,中心位置为 (251.3, 60, -129.0)。其顶点的 X、Y 和 Z 坐标值分别为 [5.02, 497.67]、[0, 120] 和 [-375.17, 117.25]。因此,迷宫模型的缩放比例必须为 image_size / 492.65。我们引入了 mazeModelLocalOffset,因为迷宫的 3D 模型并非以原点 (0, 0, 0) 为中心。

迷宫的墙仍然太高,不适合放在照片上。创建一个辅助函数 updateModelMatrix(),它以非平均的方式缩放 X、Y、Z,从而将迷宫高度缩短 0.1。请注意,您应保留现有的 updateModelMatrix(float[] modelMatrix, float scaleFactor),并将函数重载 updateModelMatrix(float[] modelMatrix, float scaleFactorX, float scaleFactorY, float scaleFactorZ) 作为新函数进行添加。

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

运行此代码。现在,该迷宫应完美贴合在图像上方。

772cbe2a8baef3ba.png

4.将 Andy 添加到迷宫中

现在,您已经有了迷宫,可以添加一个人物在迷宫中移动。使用 ARCore Android SDK 中包含的 andy.obj 文件。保留该图像帧纹理作为其纹理,因为它与图像上方显示的绿色迷宫不同。

AugmentedImageRenderer.java 中,添加专用 ObjectRenderer 以呈现 Andy。

AugmentedImageRenderer.java

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

接下来,在 createOnGlThread() 末尾初始化 andyRenderer

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

最后,在 draw() 函数末尾让 Andy 站到迷宫上。

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

  }

运行您的代码。您应该能看到 Andy 站在迷宫上。

cb1e74569d7ace69.png

确定目标图像的画质

ARCore 依靠视觉特征来识别图像。由于画质差异,有的图像可能无法轻易识别。

arcoreimg 是一个命令行工具,可用于确定 ARCore 识别某图像的难易程度。它输出一个 0 到 100 之间的数字,100 表示最容易识别。

示例如下:

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

maze.jpg 的值为 100,因此 ARCore 可以轻松识别该图像。

5. 可选:使 Andy 能够在迷宫中移动

最后,您可以添加一些代码,让小 Andy 能够在迷宫中移动。例如,使用开源物理引擎 jBullet 来处理物理模拟。跳过此部分也无妨。

下载 PhysicsController.java 并将其添加到您目录中的项目中

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

在 Android Studio 中,将 GreenMaze.obj 添加项目资源目录中,以便在运行时加载它。将 GreenMaze.objapp > assets > models > green-maze 复制到 app > assets

将以下依赖项添加到应用的 build.gradle 文件中。

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'

定义一个变量 andyPose 来存储 Andy 当前所处的位置。

AugmentedImageRenderer.java

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

修改 AugmentedImageRenderer.java 以使用新的 andyPose 变量呈现 Andy。

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

添加一个新的实用函数 updateAndyPose(),以接收 Andy 姿势更新。

AugmentedImageRenderer.java

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

AugmentedImageActivity.java 中,创建一个使用 JBullet 物理引擎管理所有物理相关函数的 PhysicsController 对象。

AugmentedImageActivity.java

import com.google.ar.core.Pose;

  // Declare the PhysicsController object
  private PhysicsController physicsController;

在此物理引擎中,我们实际上是使用刚性球来代表 Andy,并根据球的姿势更新 Andy 的姿势。每当应用识别出图像时,都调用 PhysicsController 以更新物理特性。要像在现实世界中一样移动球,请应用真实重力在迷宫中移动球。

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;

运行此应用。现在,当您倾斜图像时,Andy 应该像在现实世界一样移动。

以下示例展示使用另一部手机来显示图像,但您可以使用任何顺手的物品,例如平板电脑、硬质书封面或仅仅是覆盖在平整物品上的纸。

2f0df284705d3704.gif

大功告成!试试能不能帮 Andy 走出迷宫吧。提示:将目标图像上下颠倒可以更容易地找到出口。

6.恭喜

恭喜!您已完成了本 Codelab,因此:

  • 构建并运行了 ARCore AugmentedImage Java 示例。
  • 更新了该示例,以适当的比例在图像上显示迷宫模型。
  • 利用图像的不同摆放方式做了一些有趣的尝试。

如果您想查看完整代码,请点击此处下载代码。

这个 Codelab 学起来是否有趣?

在完成本 Codelab 的过程中,您是否学到了有用的东西?

您是否在本 Codelab 中完成了应用的构建?

您是否打算在未来 6 个月之内制作一款 ARCore 应用?

可能