プラグイン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コードを実行できます。

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

iOSでは、Objective-C++のおかげでC++プラグインを簡単にロードできます。単に新しいC++ファイルを作成し、インクルードしてwikitude::sdk::Pluginから派生したクラスを追加し、そのプラグインクラスからstd::shared_ptrを作成するだけです。これにより、作成した共有ポインタをWikitude SDK固有のプラグイン登録/解除用のAPIに渡すことができます。

Objective-CファイルをObjective-C++としてマークするには、ファイル拡張子を.mから.mmに変更するか、XcodeのIdentityおよびTypeインスペクターを使用してファイルタイプを手動でObjective-C++に変更します。

プラグインの登録

C++プラグインの登録

iOS用のWikitude SDK ネイティブAPIにはC++プラグインを登録/解除する方法が3通りあります。これらはすべて、WTArchitectView+Plugins.hに実装されたWTArchitectViewクラスのカテゴリーWTArchitectView+Pluginsからアクセスできます。このヘッダファイルはWikitude SDKアンブレラヘッダの一部ではないため、明示的にインポートする必要があります。

C++プラグインの登録

C++プラグインを登録するには、-registerPlugin:メソッドを呼び出して、C++プラグインのポインタをラップするstd::shared_ptrを渡します。共有ポインタ用のプロパティの定義とその初期化および登録呼び出しの方法を示すサンプルコードを以下に示します。

@property (nonatomic, assign) std::shared_ptr<BarcodePlugin> barcodePlugin;
// ...
_barcodePlugin = std::make_shared<BarcodePlugin>(640, 480, self); // arguments are passed to the C++ class constructor
// ...
[self.wikitudeSDK registerPlugin:_barcodePlugin];

C++プラグインの解除

登録済みのC++プラグインを解除するには、-removePlugin:または-removeNamedPlugin:メソッドを呼び出します。前者は共有ポインタを引数として受け取り、このプラグイン識別子と一致するプラグインを検索します。後者は文字列パラメーターに基づいてC++プラグインを解除します。後者を使用する場合は、共有ポインタを保持するプロパティを定義せずに、-registerPlugin:の呼び出し時に直接std::make_sharedを呼び出すことができます。この場合、ホスティングアプリケーションはプラグインポインタへの参照を持ちませんが、それでもその固有の識別子を使用してプラグインを解除できます(ホスティングアプリケーションまたは開発者がプラグインの識別子を知っている必要があります)。

[self.architectView removePlugin:_faceDetectionPlugin];
//...
[self.architectView removeNamedPlugin:@"com.wikitude.plugin.face_detection"];

バーコードおよび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と、Objective-CクラスのWTFaceDetectionPluginWrapperおよびWTAugmentedRealityViewController+PluginLoadingで構成されています。OpenCVを使用して現在のカメラフレーム内で顔を検出し、OpenGLを使用して検出された顔の周りに矩形をレンダリングします。

このサンプルは、WTArchitectViewオブジェクトを管理する1つのビューコントローラーに基づいています。サンプルの中には追加のObjective-Cコードを必要とするものがあり、その固有のサンプルコードを実装するさまざまなクラス拡張が存在します。このサンプルの場合、それはWTAugmentedRealityViewController+PluginLoadingクラス拡張です。この拡張は文字列の識別子に基づいてWikitude SDKプラグインをロードします。また、このサンプルは前述のバーコードサンプルよりも必要なObjective-Cコードが多いので、オブジェクト(プラグインなど)の作成と管理を処理するラッパーオブジェクトを作成し、そのクラス(WTFaceDetectionPluginWrapper)のインスタンスをObjective-CのAssociated ObjectとしてWTAugmentedRealityViewControllerに設定しています。

FaceDetectionPluginConnectorは、FaceDetectionPluginクラスをWTFaceDetectionPluginWrapperクラス拡張に接続するために使用されます。このクラスが存在する主な理由はプラットフォームを越えたプラグインの実装を容易にすることなので、ここでは実装の詳細について説明しません。また、OpenCVとOpenGLの詳細についても触れません。これらに関心がある場合は、Wikitude SDKサンプルアプリケーションに含まれるソースコードを見てください。

プラグインラッパーは、前のバーコードサンプルで説明したように顔検出プラグインを扱います。このサンプルの新しい点は、顔が常に正しい向きになるように、UIDeviceOrientationsを顔検出プラグインが使用するintに変換してカメラフレームを回転させるクラスメソッドを備えている点です。デバイスの向きが変更されるたびにこのクラスメソッドが使用されます。

__weak typeof(self) weakSelf = self;
[[NSNotificationCenter defaultCenter] addObserverForName:UIDeviceOrientationDidChangeNotification object:[UIDevice currentDevice] queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
        weakSelf.faceDetectionPlugin->setFlipFlag( [WTFaceDetectionPluginViewController flipFlagForDeviceOrientation:[[UIDevice currentDevice] orientation]] );
}];

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


... ctor/dtor ...
void FaceDetectionPlugin::cameraFrameAvailable(const wikitude::sdk::Frame& cameraFrame_) {
    wikitude::sdk::Size frameSize = cameraFrame_.getSize();
    _scaledCameraFrameWidth = cameraFrame_.getScaledWidth();
    _scaledCameraFrameHeight = cameraFrame_.getScaledHeight();
    std::memcpy(_grayFrame.data,cameraFrame_.getLuminanceData(), frameSize.height*frameSize.width*sizeof(unsigned char));
    //... 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_) {
    /* Intentionally Left Blank */
}
//... other internally used methods ...

検出された顔の周りに枠をレンダリングするため、顔の周りの矩形のレンダリングを処理するStrokedRectangleクラスのインスタンスを作成しています。プラグインが顔を検出するか、見失うか、または射影行列が再計算されると、適切なプラグインラッパーメソッドが呼び出されてStrokedRectangleインスタンスが更新されます。検出された顔の周りに枠をレンダリングする場合、プラグインはrenderDetectedFaceAugmentationを呼び出します。このメソッドはstartRenderメソッド内で呼び出されるだけなので、現在のスレッドはOpenGLスレッドであり、OpenGL呼び出しをディスパッチできます。

void FaceDetectionPluginConnector::faceDetected(const float *modelViewMatrix) {
    [_faceDetectionPluginWrapper setFaceDetected:YES];
    [_faceDetectionPluginWrapper.faceAugmentation setModelViewMatrix:modelViewMatrix];
}
void FaceDetectionPluginConnector::faceLost() {
    [_faceDetectionPluginWrapper setFaceDetected:NO];
}
void FaceDetectionPluginConnector::projectionMatrixChanged(const float *projectionMatrix) {
    [_faceDetectionPluginWrapper.faceAugmentation setProjectionMatrix:projectionMatrix];
}
void FaceDetectionPluginConnector::renderDetectedFaceAugmentation() {
    if ( [_faceDetectionPluginWrapper faceDetected] ) {
        [_faceDetectionPluginWrapper.faceAugmentation drawInContext:[EAGLContext currentContext]];
    }
}