一覧に戻る

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

2017.09.22iOSネイティブAPI

プラグインAPI

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

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

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

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
};

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

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++に変更します。

プラグインの登録

iOS用のWikitude SDK ネイティブAPIにはC++プラグインを登録/解除する方法が3通りあります。これらはすべて、WTWikitudeNativeSDKクラスからアクセスできます。

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コードといった数多くの一般的なシンボル体系(バーコードの種類)をサポートしています。

C++バーコードプラグインは、WTBarcodePluginViewControllerのビューオブジェクトがロードされたときに作成されます。

- (void)viewDidLoad
{
    [super viewDidLoad];

    //...   
    self.wikitudeSDK = [[WTWikitudeNativeSDK alloc] initWithRenderingMode:WTRenderingMode_External delegate:self];
    //...

    _barcodePlugin = std::make_shared<BarcodePlugin>(640, 480, self);
}

WTBarcodePluginViewControllerが-start:completion:メソッドを使用してWikitude SDK ネイティブAPIを起動すると、completionブロックの-registerPlugin:メソッドを使用してC++バーコードプラグインが登録されます。

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

     //...

    [self.wikitudeSDK start:^(WTStartupConfiguration *startupConfiguration) {
    //...
    } completion:^(BOOL isRunning, NSError * __nonnull error) {
        if ( !isRunning ) {
            //...
        }
        else
        {
            //...
            [self.wikitudeSDK registerPlugin:_barcodePlugin];
        }
    }];
}

プラグインが登録されるのはWikitude SDK ネイティブAPIが実際に起動したときだけであることに注意してください。

次に、プラグインのC++コードに進みます。まず、BarcodePlugin.hファイルを見てみましょう。バーコードプラグインを作成するため、wikitude::sdk::PluginからBarcodePluginクラスを派生させ、initialize、destroy、cameraFrameAvailable、updateをオーバーライドします。また、メンバ変数として、_worldNeedsUpdate、_image、_imageScanner、_presentingViewControllerを宣言しています。_worldNeedsUpdate変数は、認識されたバーコードを表示する必要があるかどうかを示すインジケーターとして使用されます。_imageと_imageScannerは、バーコードのスキャンに使用するzBarのクラスです。_presentingViewControllerにはWTBarcodePluginViewControllerへのポインタを格納し、新しいバーコードまたはQRコードが認識されるたびにそのポインタを通じてメソッドを呼び出します。

#include <zbar.h>

#import <WikitudeNativeSDK/Plugin.h>
#import <WikitudeNativeSDK/Frame.h>
#import <WikitudeNativeSDK/RecognizedTarget.h>


@class WTBarcodePluginViewController;

class BarcodePlugin : public wikitude::sdk::Plugin {
public:
    BarcodePlugin(int cameraFrameWidth, int cameraFrameHeight, WTBarcodePluginViewController *presentingViewController);
    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;

    WTBarcodePluginViewController   *_presentingViewController;
};

コンストラクタでは、_worldNeedsUpdateを更新が不要であることを示すゼロに設定します。さらに、zBar::Imageメンバ変数を初期化するため、そのコンストラクタにカメラフレームの幅と高さ、Y800の画像形式、nullのデータポインタ、ゼロのデータ長を渡しています。

initializeメソッドでは、zbar::ImageScannerのsetConfigを呼び出して、サポートされているすべてのバーコードを有効にします。特定の種類のバーコードのみを読み取る場合は、まずすべてのバーコードの種類を無効にしてから、読み取るバーコードの種類を手動で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);
}

最も興味深いメソッドはcameraFrameAvailableとupdateです。cameraFrameAvailableメソッドでは、set_dataを呼び出して、すでに初期化したzbar::Imageメンバ変数のデータを今受け取ったフレームデータに設定し、データの長さをフレームの幅×フレームの高さにします。次に、zBar::ImageScannerのscanメソッドを呼び出してzBar::Imageメンバインスタンスを渡し、スキャンプロセスを開始します。zBar::ImageScanner::scanメソッドは画像フレームで検出されたバーコードの数を返すので、その数をローカル変数のnに格納します。nが前回のフレームの結果(_worldNeedsUpdateメンバ変数に格納されている)と等しくない場合は、新しいバーコードが検出されたか、または前回のフレームにバーコードがあって今回はなかったことを示します。この場合はさらに、今回のフレームでバーコードが検出されたかどうかをチェックし、検出された場合はpresentBarcodeResultメソッドを呼び出してコードの内容を渡します。

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 ) {
            zbar::Image::SymbolIterator symbol = _image.symbol_begin();

            [_presentingViewController presentBarCodeResult:[NSString stringWithUTF8String:std::string(symbol->get_data()).c_str()]];
        }
    }

    _worldNeedsUpdate = n;
}

Objective-CクラスのWTBarcodePluginViewControllerを確認すると、-presentBarCodeResult:メソッドは以下のように実装されています。

- (void)presentBarCodeResult:(NSString *)barcodeScanResult
{
    if ( barcodeScanResult ) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.barcodeLabel setText:barcodeScanResult];
        });
    }
}

-presentBarcodeResult:メソッドはメインスレッドから呼び出されないことに注意してください。つまり、UI関連の呼び出しをメインスレッドで制御する必要があります。そのために、ディスパッチメインキューでブロックを非同期に実行しています。

検出されたバーコードを可視化するため、NSLabelを使用し、そのテキストプロパティを検出されたバーコードの文字列に設定します。

顔検出

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

顔検出プラグインサンプルは、C++クラスのFaceDetectionPluginおよびFaceDetectionPluginConnectorと、Objective-CクラスのWTFaceDetectionPluginViewControllerで構成されています。OpenCVを使用して現在のカメラフレーム内で顔を検出し、OpenGLを使用して検出された顔の周りに矩形をレンダリングします。 FaceDetectionPluginConnectorは、FaceDetectionPluginクラスをWTFaceDetectionPluginViewControllerに接続するために使用されます。このクラスが存在する主な理由はプラットフォームを越えたプラグインの実装を容易にすることなので、ここでは実装の詳細について説明しません。また、OpenCVとOpenGLの詳細についても触れません。これらに関心がある場合は、サンプルに含まれるソースコードを見てください。

WTFaceDetectionPluginViewControllerは、前のバーコードサンプルとまったく同じように顔検出プラグインの作成と登録を処理します。このサンプルの新しい点は、顔が常に正しい向きになるように、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のインスタンスであるオブザーバーを呼び出して、結果をビューコントローラーに通知します。プラグイン基本クラスにはstartRenderとendRenderが定義されており、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 ... */

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

- (void)setFaceIsRecognized:(BOOL)recognized atPosition:(const float*)modelViewMatrix
{
    self.faceDetected = recognized;
    if (recognized) {
        [self.recognizedFaceRectangle setModelViewMatrix:modelViewMatrix];
    }
}

- (void)setFaceAugmentationProjectionMatrix:(const float*)projectionMatrix
{
    [self.recognizedFaceRectangle setProjectionMatrix:projectionMatrix];
}