一覧に戻る

Android ネイティブAPIの画像認識

2018.07.08AndroidネイティブAPI

画像認識

このトピックではカメラビュー内の画像を認識し、それらの画像の上に拡張オブジェクトを表示する方法を示します。 まずは、より良く理解できるように、このドキュメントで使用する画像認識型ARに関する用語の定義を以下に示します。

  • ターゲット: 認識対象の2D画像と認識に必要とされる抽出データ。
  • ターゲットコレクション: 2Dターゲットの集合体。1つのターゲットコレクションには最大1,000のターゲットを格納できます。
  • 画像トラッカー: トラッカーはカメラビューの画像を分析し、ターゲットコレクションに格納されたターゲットを検出します。複数のトラッカーを作成できますが、同時に使用できるトラッカーは1つだけです。

シンプルな画像トラッキング

このセクションでは、com.wikitude.samples.recognition.clientパッケージのサンプルにあるSimpleImageTrackingActivityのコードを解説します。ここではWikitude SDK ネイティブAPIの使い方に関する一般的な概念についても説明します。

Wikitude SDKは、標準的なAndroidアクティビティで使用するために設計されており、アクティビティのライフサイクルイベントを利用します。開発者はインタフェースを使用してWikitude SDKの内部または外部レンダリングメソッドとやり取りし、ClientTrackerおよびCloudTrackerに必要なコールバックを提供します。

まず、アクティビティクラスの宣言を見てみましょう。

public class SimpleImageTrackingActivity extends Activity implements ImageTrackerListener, ExternalRendering {
...
}

Androidアクティビティのサブクラスを作成し、ImageTrackerListenerおよびExternalRenderingの各インタフェースを実装します。後でWikitudeSDKのインスタンスを作成するときにこのアクティビティのthisポインタをWikitudeSDKのコンストラクターに渡して、どのレンダリングタイプを使用するかを指定します。このサンプルでは外部レンダリングを使用します。

次のステップは、WikitudeSDKクラスのインスタンスを作成することです。開発者が手動で作成するオブジェクトは、このクラスのオブジェクトとStartupConfigurationだけです。その他のオブジェクトはすべて、WikitudeSDKインスタンスのファクトリメソッドによって作成されます。
 

次にSimpleImageTrackingActivityクラスの各メソッドを見ていきます。まずはonCreateです。onCreateメソッドでは、WikitudeSDKのインスタンスと、ライセンスキーを保持するNativeStartupConfigurationのインスタンスを作成します。ライセンスキーをまだ取得していない場合は、トライアルキーを入手してください。これでonCreateライフサイクルイベントをWikitudeSDKに伝達できます。onCreate、onPause、およびonResumeを伝達することが非常に重要です。そうしなければ、Wikitude SDKのリソースが適切に管理されず、予期しない動作につながります。

WikitudeSDKのonCreateメソッドを呼び出すと、SDKが初期化され、TargetCollectionResourceおよびImageTrackerを作成できるようになります。そのためには、WikitudeSDKインスタンスからTrackerManagerを取得し、createTargetCollectionResourceを呼び出してターゲットコレクション(.wtc)ファイルのURLを渡します。このサンプルではデバイス上に存在するターゲットコレクションをロードするので、file:///android_asset/の後にアセットルートディレクトリから始まるファイルへのパスを付加したURLを指定します。

createTargetCollectionResourceの後、TrackerManagerからcreateImageTrackerメソッドを呼び出して、この新しく作成したTargetCollectionResourceを使用するImageTrackerを作成できます。

トラッカーが読み込みを終了したときに通知を受け取るには、ターゲットを認識した後、ImageTrackerListenerをリスナーとして実装するActivityを、関数の2番目のパラメータとして渡して登録します。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    wikitudeSDK = new WikitudeSDK(this);
    NativeStartupConfiguration startupConfiguration = new NativeStartupConfiguration();
    startupConfiguration.setLicenseKey(WikitudeSDKConstants.WIKITUDE_SDK_KEY);
    startupConfiguration.setCameraPosition(CameraSettings.CameraPosition.BACK);
    startupConfiguration.setCameraResolution(CameraSettings.CameraResolution.AUTO);

    wikitudeSDK.onCreate(getApplicationContext(), this, startupConfiguration);

    final TargetCollectionResource targetCollectionResource = wikitudeSDK.getTrackerManager().createTargetCollectionResource("file:///android_asset/magazine.wtc");
    wikitudeSDK.getTrackerManager().createImageTracker(targetCollectionResource, this, null);
}

次にonRenderExtensionCreatedメソッドについて確認します。ExternalRenderingを実装することで外部レンダリング機能の使用を宣言したため、WikitudeSDKからRenderExtensionが提供されます。RenderExtensionインタフェースは、Android標準のGLSurfaceView.Rendererと同じメソッドを公開しています。カスタムのGLSurfaceView.Rendererでは、どのメソッドにおいても最初に必ず、提供されたRenderExtensionの同じメソッドを呼び出します。それを可能とするため、RendererのコンストラクターにRenderExtensionを渡し、SurfaceViewを作成し、Driverを初期化して、SurfaceViewをコンテンツビューとして設定します。

@Override
public void onRenderExtensionCreated(final RenderExtension renderExtension) {
    glRenderer = new GLRenderer(renderExtension);
    view = new CustomSurfaceView(getApplicationContext(), glRenderer);
    driver = new Driver(view, 30);
    setContentView(view);
}

次に、ImageTrackerListenerインタフェースを確認します。onErrorLoadingTargetsメソッドは、その名前が示すとおり、ターゲットをWikitude SDKがTarget Collection Resourceから画像トラッカーに読み込みできなかったときに呼び出されます。たとえば、ファイルの読み込みに成功してもコンテンツが破損していたり、画像トラッカーと互換性がない場合に呼び出されます。onTargetsLoadedメソッドは、ターゲットがトラッカーに正常に読み込まれた場合に1回呼び出されます。画像トラッカーが最初にターゲットを認識すると、認識されたターゲット名を提供するonImageRecognizedを呼び出します。画像トラッカーがターゲットのトラッキングを開始するとonImageTrackedが連続的に呼び出され、ターゲットを見失うとonImageLostが呼び出されてトラッキングが終了します。

onImageTrackedメソッドで提供されるImageTargetオブジェクトには、トラッキング中のターゲットの名前、ターゲットまでの距離、ターゲットがカメラビュー上のどこで検出されたかを示すマトリックスなどの情報が含まれます。イメージが認識されるとStrokedRectangleインスタンスが作成され、トラッキング時にマトリックスが更新され、ターゲットを見失ったときに削除されます。スケール値を設定すると、StrokedRectangleをターゲットのアスペクト比に合わせることができます。

@Override
public void onTargetsLoaded(ImageTracker tracker) {
    Log.v(TAG, "Image tracker loaded");
}

@Override
public void onErrorLoadingTargets(ImageTracker tracker, WikitudeError error) {
    Log.v(TAG, "Unable to load image tracker. Reason: " + error.getMessage());
}

@Override
public void onImageRecognized(ImageTracker tracker, final ImageTarget target) {
    Log.v(TAG, "Recognized target " + target.getName());

    StrokedRectangle strokedRectangle = new StrokedRectangle(StrokedRectangle.Type.STANDARD);
    glRenderer.setRenderablesForKey(target.getName() + target.getUniqueId(), strokedRectangle, null);
}

@Override
public void onImageTracked(ImageTracker tracker, final ImageTarget target) {
    StrokedRectangle strokedRectangle = (StrokedRectangle)glRenderer.getRenderableForKey(target.getName() + target.getUniqueId());

    if (strokedRectangle != null) {
        strokedRectangle.projectionMatrix = target.getProjectionMatrix();
        strokedRectangle.viewMatrix = target.getViewMatrix();

        strokedRectangle.setXScale(target.getTargetScale().getX());
        strokedRectangle.setYScale(target.getTargetScale().getY());
    }
}

@Override
public void onImageLost(ImageTracker tracker, final ImageTarget target) {
    Log.v(TAG, "Lost target " + target.getName());
    glRenderer.removeRenderablesForKey(target.getName() + target.getUniqueId());
}

複数ターゲットの画像認識

ここでは、単純な画像認識サンプルを拡張して、複数のターゲットを同時に認識してトラッキングする方法を紹介します。

最初に、onCreateでImageTrackerを作成するときにTrackerManagerに渡される設定を変更します。具体的には、同時にトラッキングできるターゲットの最大数を5に設定し、距離の変更を10ミリメートルに登録するためのしきい値を設定し、使用される2つのターゲットの物理的なターゲットの高さを252ミリメートル(252mmはA4用紙での100%のスケーリング)に設定します。同時に最大5つのターゲットをトラッキングしたときの制限はパフォーマンスの最適化で、ターゲットを追加で検索した場合は検索が中断される可能性があります。ユースケースでこのパラメータを設定できる場合は、この方法をお勧めします。

HashMap<String, Integer> physicalTargetImageHeights = new HashMap<>();
physicalTargetImageHeights.put("pageOne", 252);
physicalTargetImageHeights.put("pageTwo", 252);

ImageTrackerConfiguration trackerConfiguration = new ImageTrackerConfiguration();
trackerConfiguration.setMaximumNumberOfConcurrentlyTrackableTargets(5);
trackerConfiguration.setDistanceChangedThreshold(10);
trackerConfiguration.setPhysicalTargetImageHeights(physicalTargetImageHeights);

final TargetCollectionResource targetCollectionResource = wikitudeSDK.getTrackerManager().createTargetCollectionResource("file:///android_asset/magazine.wtc");
wikitudeSDK.getTrackerManager().createImageTracker(targetCollectionResource, this, trackerConfiguration);

さらに、現在トラッキングしているターゲットごとに複数の矩形を描画できるようにコードを変更します。
onImageRecognizedは、新しいStrokedRectangleインスタンスを割り当て、それをGLRendererインスタンスに登録します。登録に使用されるキーは、画像ターゲット名とユニークIDを組み合わせて同一のターゲットが互いに識別されることを可能にします。onImageTracked関数は、ImageTargetの入力からのマトリックスとスケールで事前登録された正方形を更新します。 onImageLost関数は、見失った画像ターゲットに関する四角形のインスタンスを削除します。

@Override
public void onImageRecognized(ImageTracker tracker, final ImageTarget target) {
    [...]

    StrokedRectangle strokedRectangle = new StrokedRectangle(StrokedRectangle.Type.STANDARD);
    glRenderer.setRenderablesForKey(target.getName() + target.getUniqueId(), strokedRectangle, null);
}
@Override
public void onImageTracked(ImageTracker tracker, final ImageTarget target) {
    StrokedRectangle strokedRectangle = (StrokedRectangle)glRenderer.getRenderableForKey(target.getName() + target.getUniqueId());

    if (strokedRectangle != null) {
        strokedRectangle.projectionMatrix = target.getProjectionMatrix();
        strokedRectangle.viewMatrix = target.getViewMatrix();

        strokedRectangle.setXScale(target.getTargetScale().getX());
        strokedRectangle.setYScale(target.getTargetScale().getY());
    }
}
@Override
public void onImageLost(ImageTracker tracker, final ImageTarget target) {
    [...]

    glRenderer.removeRenderablesForKey(target.getName() + target.getUniqueId());
}

最後に、トラッキング対象の距離の変化を認識できるようにアクティビティを拡張します。これを行うために、ImageTarget.OnDistanceBetweenTargetsListener型のプライベートメンバー変数を追加し、その型の新しく割り当てられたインスタンスで初期化し、onDistanceBetweenTargetsChanged関数をオーバーライドします。その関数では、2つのターゲット間の距離に基づいて色が選択され、300ミリメートルより近いターゲットはオレンジ以外の色になります。具体的には、2つの同一ターゲットの場合は青、2つの異なるターゲットの場合は赤になります。特定の画像ターゲット間の対話が必要な場合は、渡された画像ターゲットパラメータに含まれる名前を使用してそのようなケースを識別する必要があります。

private final ImageTarget.OnDistanceBetweenTargetsListener mDistanceListener = new ImageTarget.OnDistanceBetweenTargetsListener() {
    @Override
    public void onDistanceBetweenTargetsChanged(int distance, ImageTarget firstTarget, ImageTarget secondTarget) {
        float r = 1.0f;
        float g = 0.58f;
        float b = 0.16f;

        if (distance < 300.0f) {
            if (firstTarget.getName().equals(secondTarget.getName())) {
                r = 0.0f;
                g = 0.0f;
                b = 1.0f;
            } else {
                r = 1.0f;
                g = 0.0f;
                b = 0.0f;
            }
        }

        StrokedRectangle firstStrokedRectangle = (StrokedRectangle)glRenderer.getRenderableForKey(firstTarget.getName() + firstTarget.getUniqueId());
        if (firstStrokedRectangle != null) {
            firstStrokedRectangle.setColor(r, g, b);
        }

        StrokedRectangle secondStrokedRectangle = (StrokedRectangle)glRenderer.getRenderableForKey(secondTarget.getName() + secondTarget.getUniqueId());
        if (secondStrokedRectangle != null) {
            secondStrokedRectangle.setColor(r, g, b);
        }
    }
};

onDistanceBetweenTargetsChanged関数を呼び出すには、リスナーオブジェクトはonImageRecognizedおよびonImageLostのImageTargetから登録および登録解除されます。

@Override
public void onImageRecognized(ImageTracker tracker, final ImageTarget target) {
    [...]

    target.setOnDistanceBetweenTargetsListener(mDistanceListener);
}
@Override
public void onImageLost(ImageTracker tracker, final ImageTarget target) {
    [...]

    target.setOnDistanceBetweenTargetsListener(null);
}

画像トラッキングの拡張

トラッキングの拡張は、ターゲットごとに個別に設定できるオプションモードです。このモードを有効にすると、ターゲットが視認されなくなっても、ユーザーの環境のスキャンが続行されます。そのため、ターゲットの限界を超えてトラッキングを続けることができます。この機能のパフォーマンスは、デバイスの計算能力、背景テクスチャ、オブジェクトなどのさまざまな要因に左右されます。

上記のサンプルでトラッキングの拡張を有効にするには、トラッキングの拡張の対象を定義するStringの配列を提供する必要があります。下記では、ワイルドカードの*を設定して、このトラッカーのすべてのターゲットをトラッキングの拡張の対象としています。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    ImageTrackerConfiguration trackerConfiguration = new ImageTrackerConfiguration();
    trackerConfiguration.setExtendedTargets(new String[]{"*"});

    final TargetCollectionResource targetCollectionResource = wikitudeSDK.getTrackerManager().createTargetCollectionResource("file:///android_asset/iot_tracker.wtc");
    imageTracker = wikitudeSDK.getTrackerManager().createImageTracker(targetCollectionResource, this, trackerConfiguration);
}