一覧に戻る

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

2018.07.08iOSネイティブ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;
}; 

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

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] initWithRenderingConfiguration:selectedConfiguration 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/ManagedCameraFrame.hpp>
#import <WikitudeNativeSDK/RecognizedTargetsBucket.h>
#import <WikitudeNativeSDK/InterfaceOrientation.h>

@class WTBarcodePluginViewController;

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

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

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

protected:
    int                             _currentlyRecognizedBarcodes;

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

    __weak WTBarcodePluginViewController   *_presentingViewController;
}; 

コンストラクタでは、_worldNeedsUpdateを更新が不要であることを示すゼロに設定します。さらに、zBar::Imageメンバ変数を初期化するため、そのコンストラクタにカメラフレームの幅と高さ、Y800の画像形式、nullのデータポインタ、ゼロのデータ長を渡しています。
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です。このメソッドでは、以前に初期化したzbar::Imageメンバー変数のデータを受け取ったばかりのフレームデータに設定し、set_dataを呼び出してデータの長さを対応するサイズに設定します。次に、zBar::ImageScannerのscanメソッドを呼び出して、zBar::Imageメンバーインスタンスを渡してスキャン処理を開始します。zBar::ImageScanner::scanメソッドは、画像フレーム内で検出されたバーコードの数を返すので、その数をローカル変数nに保存します。nが_worldNeedsUpdateメンバー変数に保存された最後のフレームの結果と等しくない場合は、新しいバーコードが検出されたことが分かります(最後のフレームにバーコードがないことを意味します)。または前回のフレームにバーコードがあって今回はなかったことを示します。この場合、バーコードが実際にこのフレームで検出されたかどうかをもう一度チェックし、検出された場合はコードコンテンツを渡してpresentBarcodeResultメソッドを呼び出します。

void BarcodePlugin::cameraFrameAvailable(wikitude::common_code::ManagedCameraFrame& cameraFrame_) {
    const wikitude::sdk::CameraFramePlane& luminancePlane = cameraFrame_.get()[0];
    _image.set_data(luminancePlane.getData(), luminancePlane.getDataSize());

    int numberOfRecognizedCodes = _imageScanner.scan(_image);
    if ( numberOfRecognizedCodes != _currentlyRecognizedBarcodes ) {
        if ( numberOfRecognizedCodes ) {
            zbar::Image::SymbolIterator symbol = _image.symbol_begin();
            [_presentingViewController presentBarCodeResult:[NSString stringWithUTF8String:std::string(symbol->get_data()).c_str()]];
        } else {
            [_presentingViewController presentBarCodeResult:nil];
        }
    }
    _currentlyRecognizedBarcodes = numberOfRecognizedCodes;
}

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

- (void)presentBarCodeResult:(NSString *)barcodeScanResult
{
    NSString *uiBarcodePrompt = nil;
    if ( barcodeScanResult )
    {
        uiBarcodePrompt = [NSString stringWithFormat:@"Found '%@'", barcodeScanResult];
    }
    else
    {
        uiBarcodePrompt = @"Scanning for barcodes...";
    }

    dispatch_async(dispatch_get_main_queue(), ^{
        [self.navigationItem setPrompt:uiBarcodePrompt];
    });
}

-presentBarcodeResult:メソッドはメインスレッドから呼び出されないことに注意してください。つまり、UI関連の呼び出しをメインスレッドで制御する必要があります。そのために、ディスパッチメインキューでブロックを非同期に実行しています。
検出されたバーコードを可視化するため、NSLabelを使用し、そのテキストプロパティを検出されたバーコードの文字列に設定します。

顔検出

このサンプルは、OpenCVを使用して顔検出をWikitudeのAR体験に追加する方法を示します。
顔検出プラグインサンプルは、C++クラスのFaceDetectionPluginおよびFaceDetectionPluginConnectorと、Objective-CクラスのWTFaceDetectionPluginViewControllerで構成されています。OpenCVを使用して現在のカメラフレーム内で顔を検出し、OpenGLを使用して検出された顔の周りに矩形をレンダリングします。 FaceDetectionPluginConnectorは、FaceDetectionPluginクラスをWTFaceDetectionPluginViewControllerに接続するために使用されます。このクラスが存在する主な理由はプラットフォームを越えたプラグインの実装を容易にすることなので、ここでは実装の詳細について説明しません。また、OpenCVとOpenGLの詳細についても触れません。これらに関心がある場合は、サンプルに含まれるソースコードを見てください。
WTFaceDetectionPluginViewControllerは、前のバーコードサンプルとまったく同じように顔検出プラグインの作成と登録を処理します。
この例の新しいところは、顔が直立していることを前提にする顔検出アルゴリズムに起因するカメラフレームの向きへの依存です。したがって、カメラフレームをそれに応じて回転させるために、Wikitude SDKからの入力としてカメラ対面角度を受け入れます。

void FaceDetectionPlugin::initialize(const std::string& temporaryDirectory_, wikitude::sdk::PluginParameterCollection& pluginParameterCollection_) {
    wikitude::sdk::RuntimeParameters& runtimeParameters = pluginParameterCollection_.getRuntimeParameters();
    runtimeParameters.addCameraToSurfaceAngleChangedHandler(reinterpret_cast<std::uintptr_t>(this), std::bind(&FaceDetectionPlugin::cameraToSurfaceAngleChanged, this, std::placeholders::_1));

    _runtimeParameters = &runtimeParameters;
}
void FaceDetectionPlugin::cameraToSurfaceAngleChanged(float cameraToSurfaceAngle_) {
    { // auto release scope
        std::unique_lock<std::mutex>(_cameraToSurfaceAngleMutex);

        _cameraToSurfaceAngle = cameraToSurfaceAngle_;
    }

    calculateProjection(_cameraToSurfaceAngle, -1.f, 1.f, -1.f, 1.f, 0.f, 500.f);
    _observer.projectionMatrixChanged(_projectionMatrix);
}

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

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

// only one plane that contains luminance and chrominance
const unsigned luminanceDataSize = cameraFrame_.get()[0].getDataSize() * 2 / 3;

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

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::transpose(smallImg, smallImg);
    cv::flip(smallImg, smallImg, 0);
} else if (currentCameraToSurfaceAngle == 270) {
    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();
}

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

- (void)setFaceIsRecognized:(BOOL)recognized atPosition:(const float*)modelViewMatrix
{
    self.faceDetected = recognized;
    NSString *faceDetectionInstruction = nil;
    if (recognized) {
        [self.recognizedFaceRectangle setModelViewMatrix:modelViewMatrix];
    }
    else
    {
        faceDetectionInstruction = @"Scanning for faces...";
    }
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.navigationItem setPrompt:faceDetectionInstruction];
    });
}

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