プラグインAPI

このガイドは複数のセクションで構成されています。Wikitude SDKプラグインの概念、プラットフォーム固有の事項とプラグインをWikitude SDKに登録する方法について説明してから、サンプルに付属するサードパーティ製プラグインの実装例を紹介します。

Wikitude SDK プラグインAPIについて

技術的に見ると、プラグインは基底クラスであるPluginクラスから派生したC++またはJavaクラスです。Pluginクラスには、ライフサイクル処理と、プラグインを有効/無効にするオプションに加えて、cameraFrameAvailableupdatestartRenderendRenderの4つのオーバーライド可能なメソッドがあります。cameraFrameAvailableは、カメラが新しいフレームを作成するたびに呼び出されます。updateは、ターゲット認識の後に呼び出されます。startRenderendRenderはそれぞれ、Wikitude SDKがレンダリングを実行する前および実行した後に呼び出されます。

プラグインを操作するときに覚えておくべき最も重要なことは、プラグインには一意の識別子が必要であるということです。Wikitude SDKにとって既知の識別子を持つプラグインを登録しようとした場合、登録メソッドはfalseを返します。

Pluginクラス

class Plugin {
   public:
      Plugin(std::string identifier_);
      ~Plugin();
      string getIdentifier() const; // returns a unique plugin identifier
      bool processesColorCameraFrames(); // returns true if the plugins wants to process color frames instead of bw
      void setEnabled(bool enabled_);
      bool isEnabled();
      string callJavaScript(string javaScriptSnippet); // evaluates the given JavaScript snippet in the currently loaded ARchitect World context.
   protected:
      void initialize(); // called when the plugin is initially added to the Wikitude SDK
      void pause(); // called when the Wikitude SDK is paused e.g. the application state changes from active to background
      void resume(uint pausedTime_); // called when the Wikitude SDK resumes e.g. from background to active state. pausedTime represents the time in milliseconds that the plugin was not updated.
      void destroy(); // called when the plugin is removed from the Wikitude SDK
      void cameraFrameAvailable(const Frame&; cameraFrame_); // called each time the camera has a new frame
      void update(const vector<RecognizedTarget> recognizedTargets_); // called each time the Wikitude SDK renders a new frame
      void startRender(); // called before any Wikitude SDK internal rendering is done
      void endRender(); // called right after any Wikitude SDK internal rendering is done
   protected:
      string      _identifier;
      bool        _enabled;
};

これらのメソッドを適切に実装することで、プラグインから任意の目的のためにカメラフレームを読み取ることができ、そのYUV画像はWikitude SDKのコンピュータビジョンエンジンでも処理されます。

ターゲットに関する情報

Wikitude SDKで画像認識を実行している場合、ターゲットが認識されると、認識されたターゲットがupdateメソッドのRecognizedTargetに格納されます。これにより、プラグインからRecognizedTargetクラス(カメラビュー内のターゲットの詳細をラップするクラス)を操作し、ターゲットの情報を読み出して任意の目的に使用できます。また、ターゲットまでの距離も取得できます。

class RecognizedTarget {
   public:
      const string&    getIdentifier() const; // the identifier of the target. The identifier is defined when the target is added to a target collection
      const Mat4&      getModelViewMatrix() const; // the model view matrix that defines the transformation of the target in the camera frame (translation, rotation, scale)
      const Mat4&      getProjectionMatrix() const;
      const float      getDistanceToCamera() const; // represents the distance from the target to the camera in millimeter
};

プラグイン内からARchitect WorldのJavaScript部分に値を渡すには、PluginクラスのaddToJavaScriptQueue()メソッドを使用します。この関数を使用すると、ARchitect Worldのコンテキストで任意のJavaScriptコードを実行できます。

プラットフォーム固有の事項

C++で記述したプラグインをAndroidで使用できるようにするには、C++コードから、サポートされているCPUアーキテクチャごとにバイナリを作成する必要があります。このプロセスをできるだけ簡単にするため、Android NDKメイクファイルと、プラグインをWikitude SDKに渡すテンプレートコードが用意されています。次のセクションで、これらのテンプレートを対象のプラグインに合わせて変更する方法を説明します。

複数のC++プラグインをアプリケーションで使用する場合は、すべてのプラグインを1つの共有ライブラリにパッケージ化する必要があります。その理由は、C++プラグインをWikitude SDKに登録する際にJNIが使用されており、これを実行するためのシンボルを一意にしなければならないためです。

Android C++ プラグインライブラリの作成

必要なすべてのファイルは、Wikitude SDK AndroidパッケージのSDKPluginTemplateフォルダにあります。 Android NDKをまだセットアップしていない場合は、公式ガイドに従ってください。

SDKPackageRoot/SDKPluginTemplate/jniフォルダにあるAndroid.mkファイルを見てみましょう。まず、メイクファイルの場所を基準としたソースファイルの相対パスを含む変数を宣言し、このパスをLOCAL_PATHに設定します。すべてのインクルードファイルが存在する場所と、コンパイルするファイルを定義します。samplePluginではAndroidログを使用しているので、Androidネイティブのログをリンクします。

LOCAL_PATH := $(call my-dir)/..
SRC_DIR := $(LOCAL_PATH)/src
include $(CLEAR_VARS)
LOCAL_PATH := $(SRC_DIR)
include $(CLEAR_VARS)
LOCAL_MODULE := samplePlugin
LOCAL_C_INCLUDES := $(SRC_DIR)
LOCAL_SRC_FILES := __YOUR_PLUGIN__.cpp JniRegistration.cpp Plugin.cpp
LOCAL_LDLIBS += -llog
include $(BUILD_SHARED_LIBRARY)

PluginLoader/srcフォルダに、プラグインを正しくコンパイルしてリンクするために必要な各種ソースファイルがあります。これらのうち、__YOUR_PLUGIN__.h__YOUR_PLUGIN__.cpp以外は変更しないでください。ただし、JniRegistration.cppは少し変更を加える必要があります。その例を以下に示します。includeディレクティブとコンストラクター呼び出しを対象のプラグイン名に変更します。複数のC++プラグインを使用する場合は、必要なプラグインをcPluginsArray配列に追加し、numberOfPluginsの値をプラグインの数と同じにします。

複数のプラグインを1つの共有ライブラリにパッケージ化し、それらのプラグインのサブセットのみをインスタンス化する場合は、Javaからライブラリをロードするときにこのメソッドに識別子を渡すことができます。これにより、jPluginNameの値によってどのプラグインを作成するかを指定できます。

#include <jni.h>
#include "Plugin.h"
#include "__YOUR_PLUGIN__.h"
extern "C" JNIEXPORT jlongArray JNICALL Java_com_wikitude_architect_PluginManager_createNativePlugins(JNIEnv *env, jobject thisObj, jstring jPluginName) {
    int numberOfPlugins = 1;
    jlong cPluginsArray[numberOfPlugins];
    cPluginsArray[0] = (jlong) new __YOUR_PLUGIN__("com.example.plugin");
    jlongArray jPluginsArray = env->NewLongArray(numberOfPlugins);
    if (jPluginsArray != nullptr) {
        env->SetLongArrayRegion(jPluginsArray, 0, numberOfPlugins, cPluginsArray);
    }
    return jPluginsArray;
}

プラグインのバイナリファイルをビルドするには、jniフォルダに移動してndk-buildを呼び出します。arm7、arm64、およびintel用のバイナリを含むlibsフォルダが作成されます。このlibsフォルダの内容をYourProjectRoot/app/src/main/jniLibsにコピーします。

Wikitudeのサンプルアプリケーションで提供されたプラグインをリビルドするにはプロジェクトにOpenCVを追加する必要があります。OpenCVはMarker Trackingプラグインと顔検出プラグインで使用されます。OpenCVを追加するにはOpenCV公式ページからAndroid版OpenCV3.0.0をダウンロードして、src、lib、jni、includeフォルダ以下のSDKExamplePluginsフォルダに展開したフォルダの内容を配置します。

プラグインの登録

Androidでは、プラグインをC++またはJavaで記述できます。C++とJavaの登録プロセスは少し異なります。以降のセクションで、まずC++プラグインの登録方法を示し、次にJavaプラグインの登録方法を示します。

C++プラグインの登録

C++プラグインをWikitude SDKに登録するには、ArchitectViewインスタンスのregisterNativePluginsメソッドを呼び出して、プラグインライブラリの名前を渡します。名前の前にlibは付けず、.so拡張子も付けないでください。ArchitectViewonCreateメソッドがすでに呼び出されていることを確認してください。

@Override
protected void onPostCreate(final Bundle savedInstanceState) {
    super.onPostCreate(savedInstanceState);
    this.architectView.registerNativePlugins("pluginLibraryName");
}

複数のプラグインをライブラリにパッケージ化していて、ロードするプラグインを実行時に指定する場合は、ネイティブコードに組み込んだ識別子を渡します。

@Override
protected void onPostCreate(final Bundle savedInstanceState) {
    super.onPostCreate(savedInstanceState);
    this.architectView.registerNativePlugins("pluginLibraryName", "plugin_1");
}

Javaプラグインの登録

JavaプラグインをWikitude SDKに登録するには、ArchitectViewインスタンスのregisterPluginメソッドを呼び出して、プラグインのインスタンスを渡します。

@Override
protected void onPostCreate(final Bundle savedInstanceState) {
    super.onPostCreate(savedInstanceState);
    this.architectView.registerPlugin(new MyPlugin());
}

バーコードおよびQRコードリーダー

このサンプルは、人気のあるバーコードライブラリのZBarをWikitude SDKに実装する例を示します。ZBarはLGPL2.1の下でライセンスされているため、このサンプルは他のプロジェクトにも使用できます。

ZBarは、ビデオストリーム、画像ファイル、強度センサーなどのさまざまなソースからバーコードを読み取るオープンソースのソフトウェアスイートです。EAN-13/UPC-A、UPC-E、EAN-8、Code 128、Code 39、Interleaved 2 of 5、QRコードといった数多くの一般的なシンボル体系(バーコードの種類)をサポートしています。

まず、BarcodePlugin.hファイルを見てみましょう。バーコードプラグインを作成するため、wikitude::sdk::PluginクラスからBarcodePluginクラスを派生させ、initializedestroycameraFrameAvailableupdateをオーバーライドします。また、メンバ変数として、_worldNeedsUpdate_image_imageScannerを宣言しています。_worldNeedsUpdate変数は、ArchitectViewの更新が必要かどうかを示すインジケーターとして使用されます。_image_imageScannerは、バーコードのスキャンに使用するzBarのクラスです。

class BarcodePlugin : public wikitude::sdk::Plugin {
public:
    BarcodePlugin(int cameraFrameWidth, int cameraFrameHeight);
    virtual ~BarcodePlugin();
    virtual void initialize();
    virtual void destroy();
    virtual void cameraFrameAvailable(const wikitude::sdk::Frame& cameraFrame_);
    virtual void update(const std::list<wikitude::sdk::RecognizedTarget>& recognizedTargets_);
protected:
    int                             _worldNeedsUpdate;
    zbar::Image                     _image;
    zbar::ImageScanner              _imageScanner;
};

次にBarcodePluginクラスの各メソッドを見ていきます。まずはコンストラクターとデストラクターです。コンストラクターでは、_worldNeedsUpdateをゼロ(更新が不要であることを示す)に設定します。さらに、zBar::Imageメンバ変数を初期化するため、そのコンストラクターにカメラフレームの幅と高さ、Y800の画像形式、nullのデータポインタ、ゼロのデータ長を渡しています。データの動的な割り当ては行わないため、デストラクターでは何もしません。

BarcodePlugin::BarcodePlugin(int cameraFrameWidth, int cameraFrameHeight) :
Plugin("com.wikitude.ios.barcodePluign"),
_worldNeedsUpdate(0),
_image(cameraFrameWidth, cameraFrameHeight, "Y800", nullptr, 0)
{
}
BarcodePlugin::~BarcodePlugin()
{
}

initializeメソッドでは、zbar::ImageScannersetConfigを呼び出して、サポートされているすべてのバーコードを有効にします。特定の種類のバーコードのみを読み取る場合は、まずすべてのバーコードの種類を無効にしてから、読み取るバーコードの種類を手動で1つずつ有効にするのが得策です。こうすることで、パフォーマンスが大幅に向上します。

void BarcodePlugin::initialize() {    
    _imageScanner.set_config(zbar::ZBAR_NONE, zbar::ZBAR_CFG_ENABLE, 1);
}

destroyイベントが発生したときは、zbar::Imageメンバの現在のデータポインタをnullに設定し、長さをゼロにします。

void BarcodePlugin::destroy() {
    _image.set_data(nullptr, 0);
}

最も興味深いメソッドはcameraFrameAvailableupdateです。cameraFrameAvailableメソッドでは、set_dataを呼び出して、すでに初期化したzbar::Imageメンバ変数のデータを今受け取ったフレームデータに設定し、データの長さをフレームの幅×フレームの高さにします。次に、zBar::ImageScannerscanメソッドを呼び出してzBar::Imageメンバインスタンスを渡し、スキャンプロセスを開始します。zBar::ImageScanner::scanメソッドはフレームで検出されたバーコードの数を返すので、その数をローカル変数のnに格納します。nが前回のフレームの結果(_worldNeedsUpdateメンバ変数に格納されている)と等しくない場合は、新しいバーコードが検出されたか(前回のフレームにバーコードがなかった場合)、または前回のフレームにバーコードがあって今回はなかったことを示します。この場合はさらに、今回のフレームでバーコードが検出されたかどうかをチェックし、検出された場合はsdk::Plugin基底クラスのaddToJavaScriptQueueメソッドに渡すJavaScriptコードフラグメントを作成します。このJavaScriptコードフラグメントには、ビューの上部に配置されたloadingMessage div要素のHTMLコンテンツを設定するコードが含まれます。zbar::Image::SymbolIteratorを使用して最初に検出されたシンボルを取得し、そのデータを取り出します。これは、最後のフレームで複数のバーコードが検出された場合でも、最初に検出されたバーコードのみを使用することを意味します。

このサンプルではWikitude SDK画像認識の結果は使用せず、OpenGLで何もレンダリングしないので、updateメソッドは空にします。

void BarcodePlugin::cameraFrameAvailable(const wikitude::sdk::Frame& cameraFrame_) {
    int frameWidth = cameraFrame_.getSize().width;
    int frameHeight = cameraFrame_.getSize().height;
    _image.set_data(cameraFrame_.getLuminanceData(), frameWidth * frameHeight);
    int n = _imageScanner.scan(_image);
    if ( n != _worldNeedsUpdate ) {
        if ( n ) {
            std::ostringstream javaScript;
            javaScript << "document.getElementById('loadingMessage').innerHTML = 'Code Content: ";
            zbar::Image::SymbolIterator symbol = _image.symbol_begin();
            javaScript << symbol->get_data();
            javaScript << "';";
            addToJavaScriptQueue(javaScript.str());
        }
    }
    _worldNeedsUpdate = n;
}
void BarcodePlugin::update(const std::list<wikitude::sdk::RecognizedTarget>& recognizedTargets_) {
}

顔検出

このサンプルは、OpenCVを使用して顔検出をWikitudeのAR体験に追加する方法を示します。

顔検出プラグインサンプルは、C++クラスのFaceDetectionPluginおよびFaceDetectionPluginConnectorと、JavaクラスのFaceDetectionPluginActivityで構成されています。OpenCVを使用して現在のカメラフレーム内で顔を検出し、JavaでOpenGLを呼び出して検出された顔の周りに矩形をレンダリングします。

FaceDetectionPluginConnectorはネイティブコードとJavaの間のインタフェースとして機能し、JNIコードが含まれます。JNIはこのサンプルの焦点ではないので、その実装については詳しく説明しません。完全なコードが見たい場合は、Wikitude SDK Androidパッケージに含まれるソースコードを自由に閲覧してください。

initNativesetFlipFlagの2つのJavaネイティブメソッドを実装しています。前者はプラグインをOpenCVデータベースのパスで初期化し、後者はデバイスの向きの変更をプラグインに知らせます。その他のfaceDetectedfaceLostprojectionMatrixChangedrenderDetectedFaceAugmentationの各メソッドは、レンダリングを制御するJava Androidアクティビティを更新するためにプラグインによって呼び出されます。

extern "C" JNIEXPORT void JNICALL
Java_com_wikitude_samples_FaceDetectionPluginActivity_initNative(JNIEnv* env, jobject obj, jstring databasePath_)
{
    ...
}
extern "C" JNIEXPORT void JNICALL
Java_com_wikitude_samples_FaceDetectionPluginActivity_setFlipFlag(JNIEnv* env, jobject obj, jint flag)
{
    ...
}
... ctor / dtor ...
void FaceDetectionPluginConnector::faceDetected(const float *modelViewMatrix)
{
...
}
void FaceDetectionPluginConnector::faceLost()
{
...
}
void FaceDetectionPluginConnector::projectionMatrixChanged(const float *projectionMatrix)
{
...
}
void FaceDetectionPluginConnector::renderDetectedFaceAugmentation() {
...
}

次に、FaceDetectionPluginクラスを見てみましょう。今度も実装の詳細は省略し、プラグイン自体の使用方法に焦点を合わせます。cameraFrameAvailableメソッドでOpenCVを使用して、Wikitude SDKからプラグインに渡された現在のカメラフレームで顔を検出します。FaceDetectionPluginConnectorのインスタンスであるオブザーバーを呼び出して、結果をJavaアクティビティに通知します。基底クラスであるPluginクラスにはstartRenderendRenderが定義されており、Wikitude SDKが実行するすべてのレンダリングの上にレンダリングするか下にレンダリングするかに応じて、どちらか一方または両方を選んでオーバーライドします。このサンプルではWikitudeのすべてのレンダリングの下にレンダリングするためstartRenderを選び、再びFaceDetectionPluginConnectorインスタンスからAndroidアクティビティを呼び出します。このサンプルではWikitude SDK画像認識の結果は使用しないので、updateは空のままにします。


... ctor/dtor ...
void FaceDetectionPlugin::cameraFrameAvailable(const wikitude::sdk::Frame& cameraFrame_) {
    ... Control Open CV ...
    if ( _result.size() ) {
        convertFaceRectToModelViewMatrix(croppedImg, _result.at(0));
        _observer->faceDetected(_modelViewMatrix);
    } else {
        _observer->faceLost();
    }
}
void FaceDetectionPlugin::startRender() {
    _observer->renderDetectedFaceAugmentation();
}
void FaceDetectionPlugin::update(const std::list<wikitude::sdk::RecognizedTarget> &recognizedTargets_) {
}
... other internally used methods ...

FaceDetectionPluginActivity JavaクラスでonPostCreateをオーバーライドし、initNativeネイティブメソッドにデータベースファイルのパスを渡してプラグインを初期化します。また、onConfigurationChangedをオーバーライドしてデバイスの向きが変更されたときに通知を受け、setFlipFlagネイティブメソッドを呼び出して向きの変更をプラグインに知らせます。検出された顔の周りに矩形をレンダリングするため、OpenGL呼び出しを含むStrokedRectangleというクラスを作成しています。プラグインが顔を検出するか、見失うか、または射影行列が再計算されると、適切なJavaメソッドが呼び出されてStrokedRectangleインスタンスが更新されます。検出された顔の周りに矩形をレンダリングする場合、プラグインはrenderDetectedFaceAugmentationを呼び出します。このメソッドはstartRenderメソッド内で呼び出されるだけなので、現在のスレッドはOpenGLスレッドであり、OpenGL呼び出しをディスパッチできます。


... imports ...
public class FaceDetectionPluginActivity extends SampleCamActivity {
    private File _cascadeFile;
    private StrokedRectangle rectangle = new StrokedRectangle(StrokedRectangle.Type.FACE);
    @Override
    protected void onPostCreate(final Bundle savedInstanceState) {
        ...
        initNative(_cascadeFile.getAbsolutePath());
        ...
    }
    private void setInterfaceOrientationInPlugin() {
        ...
        setFlipFlag(...);
        ...
    }
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        setInterfaceOrientationInPlugin();
    }
    public void onFaceDetected(float[] modelViewMatrix) {
        rectangle.setViewMatrix(modelViewMatrix);
    }
    public void onFaceLost() {
       rectangle.onFaceLost();
    }
    public void onProjectionMatrixChanged(float[] projectionMatrix) {
        rectangle.setProjectionMatrix(projectionMatrix);
    }
    public void renderDetectedFaceAugmentation() {
        rectangle.onDrawFrame();
    }
    private native void initNative(String casecadeFilePath);
    private native void setFlipFlag(int flag);
}

FaceDetectionPluginActivityクラスまたはStrokedRectangleクラスの実装の詳細については、Wikitude SDKのサンプルを確認してください。