1. 概要
ARCore は、Android で拡張現実アプリを作成するためのプラットフォームです。拡張画像を使用すると、現実に事前登録された 2D 画像を認識して、その上に仮想コンテンツを固定することができる AR アプリを作成できます。
この Codelab では、既存の ARCore サンプルアプリを変更して、移動する拡張画像や固定された拡張画像を組み込む方法を説明します。
作成するアプリの概要
この Codelab では、既存の ARCore サンプルアプリに基づいてアプリを作成します。この Codelab を終了すると、アプリで次のことができるようになります。
- 画像ターゲットを検出して、検出したターゲットに仮想迷路を追加する
- 動くターゲットがカメラのビューに存在する限り、対象のターゲットを追跡する
ARCore アプリを作成するのは今回が初めてですか?
この Codelab でサンプルコードを作成しますか、あるいはこれらのページを読むだけでよろしいですか?
学習内容
- Java の ARCore で拡張画像を使用する方法
- ARCore の画像認識機能を評価する方法
- 画像に仮想コンテンツを追加し、コンテンツの動きを追跡する方法
前提条件
この Codelab を完了するには、特定のハードウェアとソフトウェアが必要になります。
ハードウェア要件
- USB ケーブル経由で開発マシンに接続された ARCore 対応デバイス
ソフトウェア要件
- 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] をクリックします。
次の解凍されたフォルダに移動します。
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] をクリックします。
このサンプル プロジェクトでは targetSdkVersion 28
を使用します。Failed to find Build Tools revision 28.0.3
などのビルドエラーが発生した場合は、Android Studio の指示に沿って、必要なバージョンの Android ビルドツールをダウンロードしてインストールします。
正常に完了すると、サンプルアプリがデバイス上で起動し、拡張画像用に写真や動画の撮影を許可するかどうかを確認するメッセージが表示されます。許可するには、[ALLOW] をタップします。
サンプル画像でテストする
開発環境の設定が完了したら、次は確認するための画像を追加してアプリをテストします。
Android Studio に戻り、[Project] ウィンドウで、[app] > [assets] に移動し、ファイル default.jpg
をダブルクリックして開きます。
画面に表示されている地球の画像にデバイスのカメラを向け、指示に従ってスキャン中の画像をフォーカス範囲に合わせます。
以下に示すように、画像フレームが画像の上に重なって表示されます。
次に、サンプルアプリを少し改善します。
3. 2D 画像に迷路のモデルを表示する
画像の上に 3D モデルを表示することで、拡張画像の利用を開始できます。
3D モデルをダウンロードする
この Codelab では、CC-BY 3.0 でライセンスされている Evol 提供の「Circle Maze - Green」を使用します。この 3D モデルのコピーは、この Codelab の GitHub リポジトリに保存されています。GitHub のリポジトリにはこちらからアクセスできます。
このモデルをダウンロードして Android Studio にインポートする手順は次のとおりです。
- この Codelab の GitHub リポジトリの third_party ディレクトリに移動します。
- [GreenMaze_obj.zip] をクリックし、[Download] ボタンをクリックします。
これにより、GreenMaze_obj.zip
という名前のファイルがダウンロードされます。
- Android Studio で、[app] > [assets] > [models] の下に
green-maze
ディレクトリを作成します。 GreenMaze_obj.zip
を解凍して、次の場所にコピーします。arcore-android-sdk-x.xx.x/samples/augmented_image_java/app/src/main/assets/models/green-maze
- Android Studio で、[app] > [assets] > [models] > [green-maze] に移動します。
このフォルダには GreenMaze.obj
と GreenMaze.mtl
の 2 つのファイルがあります。
迷路のモデルをレンダリングする
既存の 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
にする必要があります。迷路の 3D モデルは原点(0, 0, 0)を中心としていないため、mazeModelLocalOffset
が導入されています。
迷路の壁がやや高すぎるため、画像の上には収まりません。X、Y、Z を不均一にスケーリングして迷路の高さを 0.1 ずつスケーリングできるヘルパー関数 updateModelMatrix()
を作成します。既存の 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);
}
コードを実行します。今度は、迷路が画像の上に完全に収まるはずです。
4.迷路にアンディを追加する
迷路ができたので、迷路の中を移動するキャラクターを追加します。ARCore Android SDK に含まれている andy.obj
ファイルを使用します。画像の上にレンダリングされた緑の迷路と視覚的に区別するため、画像フレームのテクスチャをキャラクターのテクスチャとして保持します。
AugmentedImageRenderer.java
に、非公開の ObjectRenderer
を追加してアンディをレンダリングします。
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()
関数の末尾で、迷路の上に立っているアンディをレンダリングします。
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);
}
コードを実行します。迷路の上にアンディが立っているのがわかります。
ターゲット画像の品質を決める
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. 省略可: アンディを迷路の中で移動させる
最後に、迷路内でアンディを移動させるコードを追加します。たとえば、オープンソースの物理エンジンである 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.obj
を [app] > [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
を定義して、アンディの現在のポーズの位置を格納します。
AugmentedImageRenderer.java
// Create a new pose for the Andy
private Pose andyPose = Pose.IDENTITY;
AugmentedImageRenderer.java
を変更して、新しい 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);
}
新しいユーティリティ関数 updateAndyPose()
を追加して、アンディのポーズ更新を受信します。
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;
物理エンジンでは、実際にアンディを表す硬いボールを使用し、ボールのポーズを使ってアンディのポーズを更新します。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;
アプリを実行します。画像を傾けると、アンディが実際に動き回るようになります。
以下の例では、画像の表示に別のスマートフォンを使用しています。タブレット、ハードコピーの本の表紙、平らな物体に添付した印刷された紙など、ユーザーにとって便利な任意のものを使用できます。
これで完了です。アンディが迷路を通り抜けることに挑戦しましょう。ヒント: ターゲット画像を上下逆に配置すると、出口を見つけやすくなります。
6. 完了
この Codelab は以上で終了です。ここでは、以下の内容を行いました。
- ARCore の AugmentedImage Java サンプルを作成して実行しました。
- 迷路のモデルが適切なスケールで画像の上に表示されるようサンプルを更新しました。
- 画像のさまざまな配置を利用して楽しみました。
コード全体を確認するには、こちらからダウンロードできます。