一覧に戻る

Android ネイティブAPIのプラグインAPI

2018.07.08AndroidネイティブAPI

プラグインAPI

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

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

プラグインは、Wikitude SDKの機能を拡張することができるC++で書かれたクラスまたはクラスのセットです。いくつかの主な機能を提供するPlugin基本クラスがありますが、プラグイン自体には、より複雑な概念を実装できる複数のオプションモジュールがあります。次の表は、プラグイン関連クラスの分布の概要を示しています。

クラス 説明
Plugin プラグイン実装のメインクラス。このクラスから派生してプラグインを作成します。アプリケーションライフサイクルとメインプラグインの機能を処理します。Wikitude SDKのさまざまなパラメータへのアクセスを提供し、カメラフレームと認識されるターゲットへのアクセスを提供します。さらに、すべてのオプションのプラグインモジュールを所有して管理します。
ImageTrackingPluginModule カスタム画像トラッキングの実装を可能にするオプションのモジュールです。このクラスからWikitude SDKアルゴリズムと連携して独自の画像トラッキングアルゴリズムを実装します。
InstantTrackingPluginModule カスタムインスタントトラッキングの実装を可能にするオプションのモジュールです。このクラスから派生してWikitude SDKアルゴリズムと連携してインスタントトラッキングアルゴリズムを実装します。
ObjectTrackingPluginModule カスタムオブジェクトトラッキングの実装を可能にするオプションのモジュールです。このクラスから派生して独自のオブジェクトトラッキングアルゴリズムを実装し、Wikitude SDKアルゴリズムと連携して動作させます。
CameraFrameInputPluginModule フレームデータをWikitude SDKに入力できるようにするオプションのモジュールです。カメラフレーム取得を実装するために、このクラスから派生します。供給されたフレームデータは、Wikitude SDKで処理してレンダリングすることができます。
DeviceIMUInputPluginModule センサーデータをWikitude SDKに入力できるオプションのモジュールです。センサーデータ取得を実装するために、このクラスから派生します。提供されたセンサーデータは、Wikitude SDKによってトラッキングアルゴリズム用に使用されます(該当する場合)。
OpenGLESRenderingPluginModule カスタムOpenGL ESレンダリングを可能にするオプションモジュールです。iOSとAndroidで利用できます。
MetalRenderingPluginModule カスタムメタルレンダリングを可能にするオプションモジュールです。iOSでのみ利用可能です。
DirectXRenderingPluginModule カスタムDirectXレンダリングを可能にするオプションモジュールです。Windowsでのみ使用できます。
 
各オプションモジュールは、Pluginクラスの対応する関数を呼び出すことで登録できます。
プラグインを操作するときに重要なことは、プラグインが一意の識別子を持つ必要があることです。Wikitude SDKに既に知られている識別子でプラグインを登録しようとすると、registerメソッド呼び出しはfalseを返します。

Pluginクラス

class Plugin {
public:
    Plugin(std::string identifier_);
    virtual ~Plugin();

    virtual void initialize(const std::string& temporaryDirectory_, PluginParameterCollection& pluginParameterCollection_);
    virtual void pause();
    virtual void resume(unsigned int pausedTime_);
    virtual void destroy();

    virtual void cameraFrameAvailable(common_code::ManagedCameraFrame& managedCameraFrame_) = 0;
    virtual void update(const RecognizedTargetsBucket& recognizedTargetsBucket_) = 0;

    virtual const std::string& getIdentifier() const;

    virtual void setEnabled(bool enabled_);
    virtual bool isEnabled() const;

    virtual bool canPerformTrackingOperationsAlongOtherPlugins();
    virtual bool canUpdateMultipleTrackingInterfacesSimultaneously();

    ImageTrackingPluginModule* getImageTrackingPluginModule() const;
    InstantTrackingPluginModule* getInstantTrackingPluginModule() const;
    ObjectTrackingPluginModule* getObjectTrackingPluginModule() const;

    CameraFrameInputPluginModule* getCameraFrameInputPluginModule() const;
    DeviceIMUInputPluginModule* getDeviceIMUInpputPluginModule() const;

    OpenGLESRenderingPluginModule* getOpenGLESRenderingPluginModule() const;
    MetalRenderingPluginModule* getMetalRenderingPluginModule() const;
    DirectXRenderingPluginModule* getDirectXRenderingPluginModule() const;

protected:
    void setImageTrackingPluginModule(std::unique_ptr<ImageTrackingPluginModule> imageTrackingPluginModule_);
    void setObjectTrackingPluginModule(std::unique_ptr<ObjectTrackingPluginModule> objectTrackingPluginModule_);
    void setInstantTrackingPluginModule(std::unique_ptr<InstantTrackingPluginModule> instantTrackingPluginModule_);

    void setCameraFrameInputPluginModule(std::unique_ptr<CameraFrameInputPluginModule> cameraFrameInputPluginModule_);
    void setDeviceIMUInputPluginModule(std::unique_ptr<DeviceIMUInputPluginModule> deviceIMUInputPluginModule_);

    void setOpenGLESRenderingPluginModule(std::unique_ptr<OpenGLESRenderingPluginModule> openGLESRenderingPluginModule_);
    void setMetalRenderingPluginModule(std::unique_ptr<MetalRenderingPluginModule> metalRenderingPluginModule_);
    void setDirectXRenderingPluginModule(std::unique_ptr<DirectXRenderingPluginModule> directXRenderingPluginModule_);

    void iterateEnabledPluginModules(std::function<void(PluginModule& activePluginModule_)> activePluginModuleIteratorHandle_);

protected:
    std::string     _identifier;
    bool            _enabled;

private:
    std::unique_ptr<ImageTrackingPluginModule> _imageTrackingModule;
    std::unique_ptr<InstantTrackingPluginModule> _instantTrackingModule;
    std::unique_ptr<ObjectTrackingPluginModule> _objectTrackingModule;

    std::unique_ptr<CameraFrameInputPluginModule>   _cameraFrameInputModule;
    std::unique_ptr<DeviceIMUInputPluginModule>     _deviceIMUInputPluginModule;

    std::unique_ptr<OpenGLESRenderingPluginModule> _openGlesRenderingModule;
    std::unique_ptr<MetalRenderingPluginModule> _metalRenderingModule;
    std::unique_ptr<DirectXRenderingPluginModule> _directXRenderingModule;

    mutable std::mutex          _pluginModuleAccessMutex;
    std::set<PluginModule*>     _availablePluginModules;
};

Pluginクラスとすべてのオプションのモジュールクラスのすべての機能については説明しませんが、次のセクションでは独自のプラグインを作成する際のコンセプトとメソッドを示すサンプルプラグインを紹介します。

ターゲットに関する情報

Wikitude SDKがアクティブな画像認識、インスタントトラッキング、または物体認識で実行されている場合、プラグインAPIは、updateメソッドのRecognizedTargetsBucketに現在認識されているターゲットを設定します。プラグインは、対応するターゲットオブジェクトを使用して、データ、最も重要なポーズを取得して処理に使用することができます。

class RecognizedTargetsBucket {
public:
    RecognizedTargetsBucket(const RecognizedTargetsBucketConnector& recognizedTargetsBucketConnector_);
    ~RecognizedTargetsBucket() = default;

    const std::list<ImageTarget>& getImageTargets() const;
    const std::list<ObjectTarget>& getObjectTargets() const;
    const std::list<InstantTarget>& getInstantTargets() const;

private:
    RecognizedTargetsBucketConnector _recognizedTargetsBucketConnector;
};

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

C++で記述したプラグインをAndroidで使用できるようにするには、C++コードから、サポートされているCPUアーキテクチャごとにバイナリを作成する必要があります。このプロセスをできるだけ簡単にするため、CMakeファイルが用意されています。
複数のC++プラグインをアプリケーションで使用する場合は、すべてのプラグインを1つの共有ライブラリにパッケージ化する必要があります。その理由は、C++プラグインをWikitude SDKに登録する際にJNIが使用されており、これを実行するためのシンボルを一意にしなければならないためです。

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

次のCMakeファイルは、プラグインの追加とビルド方法を示しています。Pluginとして使用されるクラスは、wikitude::sdk::Pluginから派生します。libnativesdk.soライブラリはSDKパッケージには直接付属していませんが、手動または自動で.aarファイルから抽出する必要があります。

cmake_minimum_required(VERSION 3.6)

add_library(lib_nativeSDK SHARED IMPORTED)
set_target_properties(lib_nativeSDK PROPERTIES IMPORTED_LOCATION ${WIKITUDE_NATIVE_PATH}/${ANDROID_ABI}/libnativesdk.so)

add_library(yourPlugins SHARED
    src/jniHelper.cpp
    src/JniRegistration.cpp
    src/__YOUR_PLUGIN__.cpp
)

target_include_directories(yourPlugins
    PRIVATE
    include/wikitude
    src
)

target_link_libraries(yourPlugins
    lib_nativeSDK
)

このgradleスニペットは、.aarファイルからnativeSDK共有ライブラリを自動的に抽出できるようにする方法を示しています。このコードが追加されたgradleスクリプトは、Java Native SDK(Android ネイティブAPIのセットアップで説明されているように)でビルドして実行できるものとします。

/* Defines the temporary path for the NativeSDK shared library and Plugins shared library.*/
def wikitudeTmpPath = "${buildDir}/wikitude"

/* Creates a new configuration to extract the shared library from the NativeSDK for the Plugins to link against. */
configurations { libraryExtraction }

android {
    ...

    defaultConfig {
        ...

        externalNativeBuild {
            cmake {
                /* Only build for supported architectures. */
                abiFilters 'x86', 'armeabi-v7a', 'arm64-v8a'

                arguments "-DANDROID_TOOLCHAIN=clang",
                        "-DANDROID_STL=c++_shared", /* Can also be c++_static. */
                        "-DANDROID_NATIVE_API_LEVEL=19",
                        /* Provides the path to the extracted nativesdk.so to CMake */
                        "-DWIKITUDE_NATIVE_PATH=${wikitudeTmpPath}/jni"

                cppFlags "-std=c++14"
            }
        }
    }

    ...

    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
        }
    }
}

dependencies {
    ...

    /* Extract the native sdk shared library from the aar. */
    libraryExtraction (name:'wikitude-native-sdk', ext:'aar')
}

/* Task to extract the nativesdk shared library from the aar. */
task extractNativeLibraries() {
    doFirst {
        configurations.libraryExtraction.files.each { file ->
            copy {
                from zipTree(file) into wikitudeTmpPath include "jni/**/*" } } } } tasks.whenTaskAdded {
    task ->
        if (task.name.contains("external") && !task.name.contains("Clean")) {
            /* The externalNativeBuild depends on the extraction task to be able to link properly. */
            task.dependsOn(extractNativeLibraries)
        }
}

C++プラグインをSDKに接続するには、Java_com_wikitude_common_plugins_internal_PluginManagerInternal_createNativePlugins関数を実装する必要があります。この関数は3つのパラメータ(JNIEnv*、jobjectおよびjstring)を持つ必要があり、jlongArrayを返さなければなりません。3番目のパラメータは、複数のプラグインがある場合に登録されるプラグインです。return配列には、jlongとして登録されるべきPluginsへのポインタが含まれています。サンプルのアプリケーションコードのJNIRegistration.cppファイルを参照してください。

JavaVM* pluginJavaVM;

extern "C" JNIEXPORT jlongArray JNICALL Java_com_wikitude_common_plugins_internal_PluginManagerInternal_createNativePlugins(JNIEnv *env, jobject thisObj, jstring jPluginName) {

    env->GetJavaVM(&pluginJavaVM);

    int numberOfPlugins = 1;

    jlong cPluginsArray[numberOfPlugins];

    JavaStringResource pluginName(env, jPluginName);

    if (pluginName.str == "face_detection") {
        FaceDetectionPluginConnector* connector = new FaceDetectionPluginConnector();
        cPluginsArray[0] = (jlong) new FaceDetectionPlugin(640, 480, connector);
    } else if (pluginName.str == "barcode") {
        cPluginsArray[0] = (jlong) new BarcodePlugin(640, 480);
    } else if ( pluginName.str == "customcamera" ) {
        cPluginsArray[0] = (jlong) new YUVFrameInputPlugin();
    } else if ( pluginName.str == "simple_input_plugin" ) {
        cPluginsArray[0] = (jlong) new SimpleInputPlugin();
    }

    jlongArray jPluginsArray = env->NewLongArray(numberOfPlugins);
    if (jPluginsArray != nullptr) {
        env->SetLongArrayRegion(jPluginsArray, 0, numberOfPlugins, cPluginsArray);
    }

    return jPluginsArray;
}

プラグインの登録

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

C++プラグインの登録

C++プラグインをWikitude SDK ネイティブAPIに登録するには、WikitudeSDKインスタンスからPluginManagerを取得し、registerNativePluginを呼び出してライブラリの名前を渡します。名前の前にlibは付けず、.so拡張子も付けないでください。アクティビティのonCreateメソッドでPluginを登録する場合は、必ず先にWikitudeSDKのonCreateメソッドを呼び出してください。以下のサンプルコードは、バンドルされているサンプルのBarcodePluginActivityから抜粋したものです。

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    mWikitudeSDK.onCreate(getApplicationContext(), startupConfiguration);
    ...
    wikitudeSDK.getPluginManager().registerNativePlugins("wikitudePlugins", "barcode", new ErrorCallback() {
        @Override
        public void onError(@NonNull WikitudeError error) {
            Toast.makeText(BarcodePluginActivity.this, "Could not load plugin. Reason: " + error.getMessage(), Toast.LENGTH_LONG).show();
            Log.v(TAG, "Plugin failed to load. Reason: " + error.getMessage());
        }
    });
}

バーコードおよび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コードといった数多くの一般的なシンボル体系(バーコードの種類)をサポートしています。
BarcodePluginActivity.onCreateメソッドで、バーコードC++プラグインを登録します。そのために、WikitudeSDKからPluginManagerを取得し、registerNativePluginを呼び出してC++プラグインを含むネイティブライブラリの名前を渡します。その直後にinitNative()を呼び出し(これはネイティブメソッドとして宣言し、C++プラグインで実装しています)、ネイティブプラグインが保持するJavaVMポインタを初期化します。また、スキャンしたバーコードの内容を表示するonBarcodeDetectedメソッドも実装しています。このメソッドは、後でバーコードプラグインから呼び出します。

public class BarcodePluginActivity extends Activity implements ImageTrackerListener, ExternalRendering {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        [...]

        wikitudeSDK.getPluginManager().registerNativePlugins("wikitudePlugins", "barcode", new ErrorCallback() {
            @Override
            public void onError(@NonNull WikitudeError error) {
                Toast.makeText(BarcodePluginActivity.this, "Could not load plugin. Reason: " + error.getMessage(), Toast.LENGTH_LONG).show();
                Log.v(TAG, "Plugin failed to load. Reason: " + error.getMessage());
            }
        });

        initNative();
    }

    ...

    public void onBarcodeDetected(final String codeContent) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                dropDownAlert.removeAllImages();
                dropDownAlert.setTextWeight(1);
                dropDownAlert.setText("Scan result: " + codeContent);
            }
        });
    }

    private native void initNative();
}

次に、プラグインのC++コードに進みます。まず、BarcodePlugin.hファイルを見てみましょう。バーコードプラグインを作成するため、wikitude::sdk::PluginからBarcodePluginクラスを派生させ、initialize、destroy、cameraFrameAvailable、updateをオーバーライドします。また、メンバ変数として、_worldNeedsUpdate、_image、_imageScanner、_methodIdを宣言しています。_worldNeedsUpdate変数は、ArchitectViewの更新が必要かどうかを示すインジケーターとして使用されます。_imageと_imageScannerは、バーコードのスキャンに使用するzBarのクラスです。_methodIdは、onBarcodeDetected JavaメソッドのメソッドIDを保持します。

extern JavaVM* pluginJavaVM;

class BarcodePlugin : public wikitude::sdk::Plugin {
public:
    BarcodePlugin(unsigned int cameraFrameWidth, unsigned int cameraFrameHeight);
    virtual ~BarcodePlugin();

    void initialize(const std::string& temporaryDirectory_, wikitude::sdk::PluginParameterCollection& pluginParameterCollection_) override;
    void destroy() override;

    void cameraFrameAvailable(wikitude::common_code::ManagedCameraFrame& managedCameraFrame_) override;
    void update(const wikitude::sdk::RecognizedTargetsBucket& recognizedTargetsBucket_) override;

protected:
    int _worldNeedsUpdate;

    zbar::Image         _image;
    zbar::ImageScanner  _imageScanner;

private:
    jmethodID   _methodId;
    bool        _jniInitialized;
};

グローバルnamespaceで2つの変数を宣言します。一方はJavaVMへのポインタを保持し、もう一方はアクティビティへの参照を保持します。これら2つの変数を初期化するため、BarcodeActivityでinitNativeネイティブメソッドを宣言し、これを以下のように実装しました。ここでは、JNIEnvからJavaVMへのポインタを取得し、呼び出し元アクティビティインスタンスへの新しいグローバル参照を作成しています。

jobject barcodeActivityObj;

extern "C" JNIEXPORT void JNICALL Java_com_wikitude_samples_plugins_BarcodePluginActivity_initNative(JNIEnv* env, jobject obj) {
    env->GetJavaVM(&pluginJavaVM);
    barcodeActivityObj = env->NewGlobalRef(obj);
}

コンストラクターでは、_worldNeedsUpdateを更新が不要であることを示すゼロに設定します。さらに、zBar::Imageメンバ変数を初期化するため、そのコンストラクターにカメラフレームの幅と高さ、Y800の画像形式、nullのデータポインタ、ゼロのデータ長を渡しています。また、JavaVMを使用して、JavaVMResource(JavaVMを管理するヘルパークラス)のインスタンスを作成します。このクラスはjniHelper.cppファイルに含まれています。次に、JavaVMResourceからJava環境を取得し、_methodIdメンバを初期化します。デストラクターでは、アクティビティオブジェクトへのグローバル参照を削除します。

BarcodePlugin::BarcodePlugin(unsigned int cameraFrameWidth, unsigned int cameraFrameHeight) :
        Plugin("com.wikitude.android.barcodePlugin"),
        _worldNeedsUpdate(0),
        _image(cameraFrameWidth, cameraFrameHeight, "Y800", nullptr, 0),
        _jniInitialized(false) {
}

BarcodePlugin::~BarcodePlugin() {
    JavaVMResource vm(pluginJavaVM);
    vm.env->DeleteGlobalRef(barcodeActivityObj);
}

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

void BarcodePlugin::initialize(const std::string& temporaryDirectory_, wikitude::sdk::PluginParameterCollection& pluginParameterCollection_) {
    _imageScanner.set_config(zbar::ZBAR_NONE, zbar::ZBAR_CFG_ENABLE, 1);
}

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

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

最後に、最も興味深いメソッドはcameraFrameAvailableです。cameraFrameAvailableメソッドでは、set_dataを呼び出して、すでに初期化したzbar :: Imageメンバ変数のデータを受信したばかりのフレームデータに設定し、フレーム幅*フレームの高さまでのデータの長さを設定します。次にzBar :: ImageScannerのscanメソッドを呼び出して、zBar :: Imageメンバーインスタンスを渡してスキャン処理を開始します。zBar :: ImageScanner :: scanメソッドは、画像フレーム内で検出されたバーコードの数を返すので、その数をローカル変数nに保存します。nが_worldNeedsUpdateメンバー変数に保存された最後のフレームの結果と等しくない場合は、新しいバーコードが検出されたことがわかります(最後のフレームにバーコードがないことを意味します)。または前回のフレームにバーコードがあって今回はなかったことを示します。この場合、バーコードが実際にこのフレームで検出され、コードコンテンツを渡すonBarcodeDetected Javaメソッドを呼び出した場合、もう一度チェックを行います。

void BarcodePlugin::cameraFrameAvailable(wikitude::common_code::ManagedCameraFrame& cameraFrame_) {
    if (!_jniInitialized) {
        JavaVMResource vm(pluginJavaVM);
        jclass clazz = vm.env->FindClass("com/wikitude/samples/plugins/BarcodePluginActivity");
        _methodId = vm.env->GetMethodID(clazz, "onBarcodeDetected", "(Ljava/lang/String;)V");
        _jniInitialized = true;
    }

    const wikitude::sdk::CameraFramePlane& luminancePlane = cameraFrame_.get()[0];
    _image.set_data(luminancePlane.getData(), luminancePlane.getDataSize());

    int n = _imageScanner.scan(_image);

    if (n != _worldNeedsUpdate) {
        if (n) {
            JavaVMResource vm(pluginJavaVM);
            zbar::Image::SymbolIterator symbol = _image.symbol_begin();
            jstring codeContent = vm.env->NewStringUTF(symbol->get_data().c_str());
            vm.env->CallVoidMethod(barcodeActivityObj, _methodId, codeContent);

        }
    }

    _worldNeedsUpdate = n;
}

顔検出

このサンプルは、OpenCVを使用して顔検出をWikitudeのAR体験に追加する方法を示します。
顔検出プラグインサンプルは、C++クラスのFaceDetectionPluginおよびFaceDetectionPluginConnectorと、JavaクラスのFaceDetectionPluginActivityで構成されています。OpenCVを使用して現在のカメラフレーム内で顔を検出し、JavaでOpenGLを呼び出して検出された顔の周りに枠をレンダリングします。
FaceDetectionPluginConnectorはネイティブコードとJavaの間のインタフェースとして機能し、JNIコードが含まれます。JNIはこのサンプルの焦点ではないので、その実装については詳しく説明しません。完全なコードが見たい場合は、Wikitude SDKリリースパッケージに含まれるソースコードを自由に閲覧してください。
initNativeのJavaネイティブメソッドを実装しています。OpenCVデータベースへのパスでプラグインを初期化するために使用されます。その他のメソッドfaceDetected、faceLostおよびprojectionMatrixChangedは、レンダリングを制御するJava Android Activityを更新プラグインによって呼び出されます。

extern "C" JNIEXPORT void JNICALL Java_com_wikitude_samples_plugins_FaceDetectionPluginActivity_initNative(JNIEnv* env, jobject obj, jstring databasePath_) {
    env->GetJavaVM(&pluginJavaVM);
    faceDetectionActivityObj = env->NewGlobalRef(obj);
    FaceDetectionPlugin::_databasePath = env->GetStringUTFChars(databasePath_, NULL);
}

void FaceDetectionPluginConnector::faceDetected(const float* modelViewMatrix) {
    JavaVMResource vm(pluginJavaVM);
    jclass clazz = vm.env->FindClass("com/wikitude/samples/plugins/FaceDetectionPluginActivity");
    _faceDetectedId = vm.env->GetMethodID(clazz, "onFaceDetected", "([F)V");
    jfloatArray jModelViewMatrix = vm.env->NewFloatArray(16);
    vm.env->SetFloatArrayRegion(jModelViewMatrix, 0, 16, modelViewMatrix);
    vm.env->CallVoidMethod(faceDetectionActivityObj, _faceDetectedId, jModelViewMatrix);
}

void FaceDetectionPluginConnector::faceLost() {
    JavaVMResource vm(pluginJavaVM);
    jclass clazz = vm.env->FindClass("com/wikitude/samples/plugins/FaceDetectionPluginActivity");
    _faceLostId = vm.env->GetMethodID(clazz, "onFaceLost", "()V");
    vm.env->CallVoidMethod(faceDetectionActivityObj, _faceLostId);
}

void FaceDetectionPluginConnector::projectionMatrixChanged(const float* projectionMatrix) {
    if (faceDetectionActivityObj) {
        JavaVMResource vm(pluginJavaVM);
        jclass clazz = vm.env->FindClass("com/wikitude/samples/plugins/FaceDetectionPluginActivity");
        _projectionMatrixChangedId = vm.env->GetMethodID(clazz, "onProjectionMatrixChanged", "([F)V");
        jfloatArray jProjectionMatrix = vm.env->NewFloatArray(16);
        vm.env->SetFloatArrayRegion(jProjectionMatrix, 0, 16, projectionMatrix);
        vm.env->CallVoidMethod(faceDetectionActivityObj, _projectionMatrixChangedId, jProjectionMatrix);
    }
}

次に、FaceDetectionPluginクラスを確認します。今度も実装の詳細は省略し、プラグイン自体の使用方法に焦点を合わせます。cameraFrameAvailableメソッドでOpenCVを使用して、Wikitude SDKからプラグインに渡された現在のカメラフレームで顔を検出します。FaceDetectionPluginConnectorのインスタンスであるオブザーバーを呼び出して、結果をJavaアクティビティに通知します。

void FaceDetectionPlugin::cameraFrameAvailable(wikitude::common_code::ManagedCameraFrame& cameraFrame_) {
    if (!_isDatabaseLoaded) {
        _isDatabaseLoaded = _cascadeDetector.load(_databasePath);
        if (!_isDatabaseLoaded) {
            return;
        }
    }
    wikitude::sdk::Size<int> frameSize = cameraFrame_.getColorMetadata().getPixelSize();

    std::memcpy(_grayFrame.data, cameraFrame_.get()[0].getData(), cameraFrame_.get()[0].getDataSize());

    cv::Mat smallImg = cv::Mat(frameSize.height * 0.5f, frameSize.width * 0.5f, CV_8UC1);


    cv::resize(_grayFrame, smallImg, smallImg.size(), CV_INTER_AREA);

    /* Depending on the device orientation, the camera frame needs to be rotated in order to detect faces in it */

    float currentCameraToSurfaceAngle;
    { // auto release scope
        std::unique_lock<std::mutex>(_cameraToSurfaceAngleMutex);

        currentCameraToSurfaceAngle = _cameraToSurfaceAngle;
    }
    if (currentCameraToSurfaceAngle == 90) {
        cv::transpose(smallImg, smallImg);
        cv::flip(smallImg, smallImg, 1);
    } else if (currentCameraToSurfaceAngle == 180) {
        cv::flip(smallImg, smallImg, 0);
    } else if (currentCameraToSurfaceAngle == 270) {
        cv::transpose(smallImg, smallImg);
        cv::flip(smallImg, smallImg, -1);
    } else if (currentCameraToSurfaceAngle == 0) {
        // nop for landscape right
    }

    cv::Rect crop = cv::Rect(smallImg.cols / 4, smallImg.rows / 4, smallImg.cols / 2, smallImg.rows / 2);

    cv::Mat croppedImg = smallImg(crop);


    _result.clear();
    _cascadeDetector.detectMultiScale(croppedImg, _result, 1.1, 2, 0, cv::Size(20, 20));

    if (_result.size()) {
        convertFaceRectToModelViewMatrix(croppedImg, _result.at(0), currentCameraToSurfaceAngle);
        _observer->faceDetected(_modelViewMatrix);
    } else {
        _observer->faceLost();
    }
}

FaceDetectionPluginActivityでonCreateをオーバーライドし、initNativeネイティブメソッドにデータベースファイルのパスを渡してPluginを初期化します。
検出された顔の周りにフレームをレンダリングするため、アクティブなImageTrackerの面およびすべてのターゲットの周囲に矩形のレンダリングを処理するGLRendererFaceDetectionPluginクラスのインスタンスを作成します。プラグインが投影行列を検出、緩和、または再計算すると、Rendererインスタンスを更新するために使用する適切なJavaメソッドが呼び出されます。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        [...]

        wikitudeSDK.getPluginManager().registerNativePlugins("wikitudePlugins", "face_detection", new ErrorCallback() {
            @Override
            public void onError(WikitudeError error) {
                Log.v(TAG, "Plugin failed to load. Reason: " + error.getMessage());
            }
        });

        try {
            // load cascade file from application resources
            InputStream is = getResources().openRawResource(R.raw.high_database);
            File cascadeDir = getDir("cascade", Context.MODE_PRIVATE);
            final File cascadeFile = new File(cascadeDir, "lbpcascade_frontalface.xml");
            FileOutputStream os = new FileOutputStream(cascadeFile);

            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = is.read(buffer)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            is.close();
            os.close();

            initNative(cascadeFile.getAbsolutePath());

            cascadeDir.delete();

        } catch (IOException e) {
            e.printStackTrace();
            Log.e(TAG, "Failed to load cascade. Exception thrown: " + e);
        }

        [...]
    }

    [...]

    public void onFaceDetected(float[] modelViewMatrix) {
        faceTarget.setViewMatrix(modelViewMatrix);
        glRenderer.setCurrentlyRecognizedFace(faceTarget);
        [...]
    }

    public void onFaceLost() {
        glRenderer.setCurrentlyRecognizedFace(null);
    }

    public void onProjectionMatrixChanged(float[] projectionMatrix) {
        faceTarget.setProjectionMatrix(projectionMatrix);
        glRenderer.setCurrentlyRecognizedFace(faceTarget);
    }

    private native void initNative(String cascadeFilePath);
}

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