プラグインAPI
このガイドは複数のセクションで構成されています。Wikitude SDKプラグインの概念、プラットフォーム固有の事項とプラグインをWikitude SDKに登録する方法について説明してから、サンプルに付属するサードパーティ製プラグインの実装例を紹介します。
Wikitude SDK プラグインAPIについて
技術的に見ると、プラグインは基底クラスであるPluginクラスから派生したC++またはJavaクラスです。Pluginクラスには、ライフサイクル処理と、プラグインを有効/無効にするオプションに加えて、cameraFrameAvailable
、update
、startRender
、endRender
の4つのオーバーライド可能なメソッドがあります。cameraFrameAvailableは、カメラが新しいフレームを作成するたびに呼び出されます。updateは、ターゲット認識の後に呼び出されます。startRender
とendRender
はそれぞれ、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クラスを派生させ、initialize
、destroy
、cameraFrameAvailable
、update
をオーバーライドします。また、メンバ変数として、_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::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
メンバ変数に格納されている)と等しくない場合は、新しいバーコードが検出されたか(前回のフレームにバーコードがなかった場合)、または前回のフレームにバーコードがあって今回はなかったことを示します。この場合はさらに、今回のフレームでバーコードが検出されたかどうかをチェックし、検出された場合は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
のインスタンスであるオブザーバーを呼び出して、結果をビューコントローラーに通知します。プラグイン基本クラスには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 ...
検出された顔の周りに枠をレンダリングするため、顔の周りの矩形のレンダリングを処理する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]];
}
}