位置決めプラグインAPI
Wikitude SDKでは、プラグインAPIを使用すると、組み込みのトラッキング機能を使用せずにJavaScript APIで定義されたレンダリングオブジェクトを直接配置することができます。したがって、カスタムのトラッキングアルゴリズムを提供しながらWikitude SDKのレンダリング機能を使用することが可能です。ここでは、カスタムアルゴリズムの実装の手順を説明します。具体的には、OpenCVのとArUcoライブラリを使用してMarker Trackingプラグインを実装します。
概要
このサンプルとAR.Positionable
オブジェクトを使用する前に、Wikitude SDKでの実装方法を理解してください。このセクションでは、その実装方法を示します。
AR.Positionable
はJavaScript APIで定義することができます。この定義は、wikitude::sdk::Plugin
のupdatePositionables
関数への参照を持つ補完的なC++オブジェクトをインスタンス化し、JavaScript APIで処理することを可能にします。Positionableクラスを使用したカスタムプラグインは、あるクラスから派生して、updatePositionables
メンバ関数をオーバーライドすることによって実現できます。
updatePositionables
関数によって修正を行った後、AR.Positionable
オブジェクトは各フレームをレンダリングするために提出されます。したがって、PositionableクラスはWikitude SDKにレンダリングするためのプラグイン変更可能なラッパーオブジェクトです。これにより、プラグインAPIを使用して簡単にJavaScript APIの拡張機能が提供されます。
前提条件
このサンプルに対しては次のリソースを推奨します。
Pluginサンプル
詳細については、「プラグインAPI」のセクションを参照してください。
ArUcoマーカー
独自のArUcoマーカーを作成したいのなら、ArUcoライブラリパッケージに付属しているユーティリティを参照してください。ArUcoはSourceForgeページからダウンロードできます。
ArUcoとOpenCVドキュメント
トラッキングアルゴリズムの詳細については、「ArUco」ページと「OpenCV」ページのカメラキャリブレーションと3D再構築を参照してください。
JavaScript実装
JavaScriptで実装する場合はAR.Trackable2DObject
とAR.GeoObject
クラスと共にAR.Positionable
クラスも利用可能です。AR.Positionableクラスでは、パラメーターとしてレンダリングオブジェクトとString識別子が必要です。このサンプルでは、AR.Modelが使用されています。トラッキングはプラグインによって提供されるので、トラッカーを指定できません。
var World = {
_myPositionable: null,
init: function initFn() {
this.createOverlays();
},
createOverlays: function createOverlaysFn() {
var myModel = new AR.Model(
"assets/car.wt3", {
onLoaded: this.loadingStep,
scale: {
x: 0.01,
y: 0.01,
z: 0.01
}
});
World._myPositionable = new AR.Positionable("myPositionable", {
drawables: {
cam: myModel
}
});
}
};
World.init();
Plugin実装
カスタムのトラッキングを実装するためにOpenCVライブラリをベースにしたArUcoライブラリのマーカートラッキング機能を使用します。マーカートラッキング機能により、ArUcoマーカーはカメラフレーム内に認識でき、カメラの相対位置を計算してトラッキングしたマーカー上にモデルを配置できます。ArUcoとOpenCVライブラリを正常に動作させるために次のセクションで説明している点に注意してください。
最後に、すべてのプラグインはAR.Positionable
オブジェクトのWorld Matrix、View MatrixとProjection Matrixを設定します。設定するMatrixは3Dまたは2Dモデルのレンダリングによって異なります。
// transformation matrices for a 3D renderable
positionable->setWorldMatrix(identityMatrix.get());
positionable->setViewMatrix(modelViewMatrix.get());
positionable->setProjectionMatrix(projectionMatrix.get());
// transformation matrices for a 2D renderable
positionable->setWorldMatrix((projectionMatrix * modelViewMatrix).get());
positionable->setViewMatrix(identityMatrix.get());
positionable->setProjectionMatrix(identityMatrix.get());
ヘッダーファイル
以下はMarkerTrackingPlugin.hファイルの内容で、wikitude::sdk::Plugin
クラスから派生し、cameraFrameAvailable
関数とupdatePositionables
関数をオーバーライドしたものです。
メンバ変数にはいくつかの追加があります。aruco::MarkerDetector
はarucoライブラリのメインクラスでトラッキングアルゴリズムのすべての手順を実行します。std::vector<aruco::Marker>
メンバーは検出されたマーカーを保持するコンテナです。その他のメンバ変数は自明なので説明せずに、std::mutex
のみを説明します。
class MarkerTrackingPlugin : public wikitude::sdk::Plugin {
public:
MarkerTrackingPlugin();
~MarkerTrackingPlugin();
virtual void surfaceChanged(wikitude::sdk::Size<int> renderSurfaceSize_, wikitude::sdk::Size<float> cameraSurfaceScaling_, wikitude::sdk::InterfaceOrientation interfaceOrientation_);
virtual void cameraFrameAvailable(const wikitude::sdk::impl::Frame& cameraFrame_);
virtual void update(const std::list<wikitude::sdk::impl::RecognizedTarget>& recognizedTargets_);
virtual void updatePositionables(const std::unordered_map<std::string, wikitude::sdk_core::impl::PositionableWrapper*>& positionables_);
private:
aruco::MarkerDetector _detector;
std::vector<aruco::Marker> _markers;
std::vector<aruco::Marker> _markersPrev;
std::vector<aruco::Marker> _markersCurr;
std::vector<aruco::Marker> _markersPrevUpdate;
std::vector<aruco::Marker> _markersCurrUpdate;
bool _projectionInitialized;
float _width;
float _height;
float _scaleWidth;
float _scaleHeight;
std::mutex _markerMutex;
bool _updateDone;
float _viewMatrixData[16];
wikitude::sdk::Matrix4 _projectionMatrix;
std::mutex _interfaceOrientationMutex;
wikitude::sdk::InterfaceOrientation _currentInterfaceOrientation;
};
cameraFrameAvailable関数
cameraFrameAvailable
関数では、入力パラメータを指定して_detector.detect()
を呼び出すことで輝度カメラフレーム上でマーカーのトラッキングを行います。cameraMatrix
以外のほとんどのパラメータは自明です。cameraMatrixパラメータは、カメラからマーカーの相対位置を計算するために必要な情報を含みます。カメラパラメータと歪曲係数は個別のカメラキャリブレーション処理によって予め計算します。このサンプルではiPhone 5での動作を想定したパラメータを設定しているので結果は少し異なりますが、さまざまなデバイス上でアプリケーションを実行できます。もし、お使いのデバイスに対応していない場合は焦点距離やCDDセンサーのサイズを変更してください。
// calculate the focal length in pixels (fx, fy)
const float focalLengthInMillimeter = 4.12f;
const float CCDWidthInMillimeter = 4.536f;
const float CCDHeightInMillimeter = 3.416f;
const float focalLengthInPixelsX = _width * focalLengthInMillimeter / CCDWidthInMillimeter;
const float focalLengthInPixelsY = _height * focalLengthInMillimeter / CCDHeightInMillimeter;
cv::Mat cameraMatrix = cv::Mat::zeros(3, 3, CV_32F);
cameraMatrix.at<float>(0, 0) = focalLengthInPixelsX;
cameraMatrix.at<float>(1, 1) = focalLengthInPixelsY;
// calculate the frame center (cx, cy)
cameraMatrix.at<float>(0, 2) = 0.5f * _width;
cameraMatrix.at<float>(1, 2) = 0.5f * _height;
// always 1
cameraMatrix.at<float>(2, 2) = 1.0f;
const float markerSizeInMeters = 0.1f;
_markers.clear();
_detector.detect(frameLuminance, _markers, cameraMatrix, cv::Mat(), markerSizeInMeters);
マーカーが検出された後、Matrixが計算され、原点をトラッキングしたマーカの中心に変換されます。トラッキングは特定のマーカーのIDに制限されていることに注意してください。
double viewMatrixData[16];
for (auto& marker : _markers) {
// consider only marker 303
if (marker.id == 303) {
marker.calculateExtrinsics(markerSizeInMeters, cameraMatrix, cv::Mat(), false);
marker.glGetModelViewMatrix(viewMatrixData);
}
}
また、updatePositionables
関数によって使用されるためのProjection Matrixが計算されます。入力パラメータはiPhone 5での動作を想定しています。もし、お使いのデバイスに対応していない場合は垂直方向のビューを変更してください。
if (!_projectionInitialized) {
const float fieldOfViewYDegree = 50.0f;
const float nearZ = 0.1f;
const float farZ = 100.0f;
_projectionMatrix.perspective(fieldOfViewYDegree, _width / _height, nearZ, farZ);
_projectionInitialized = true;
}
JavaScript APIで定義されたAR.Positionable
にアクセスするには、updatePositionables
関数内でアルゴリズムを更新する必要があります。ただし、cameraFrameAvailable
関数とupdatePositionables
関数が同時に実行されるので、2つの関数の間でデータの受け渡しをするために同期の設定を行う必要があります。
同期方法としては、スレッド間で使用する共有リソースを排他制御するためのstd::mutex
関数を使用します。また、新しいデータが利用できることをUpdateメソッドに通知するために_updateDone
フラグを使用します。
/* critical section begin */
_markerMutex.lock();
if (_updateDone) {
_markersPrev = _markersCurr;
_markersCurr = _markers;
for (unsigned int i = 0; i < 16; ++i) {
_viewMatrixData[i] = static_cast<float>(viewMatrixData[i]);
}
_updateDone = false;
}
/* critical section end */
_markerMutex.unlock();
updatePositionables関数
updatePositionables
メソッドは、以前のフレームで検出されなかったマーカーの検出、および以前のフレームで検出されたマーカーが失われたかどうかを判定します。また、それに応じて、JavaScript API内でトリガーとして使用できるようにenteredFieldOfVision
またはexitedFieldOfVision
関数を呼び出します。
std::unordered_map<std::string, wikitude::sdk_core::impl::PositionableWrapper*>::const_iterator it = positionables_.find("myPositionable");
if (it == positionables_.end()) {
return;
}
/* critical section start */
_markerMutex.lock();
if (!_updateDone) {
_markersPrevUpdate = _markersPrev;
_markersCurrUpdate = _markersCurr;
for (const auto& marker : _markersCurrUpdate) {
auto itFound = std::find_if(_markersPrevUpdate.begin(), _markersPrevUpdate.end(), [&](const aruco::Marker& other) -> bool { return other.id == marker.id; });
if (itFound != _markersPrevUpdate.end()) {
_markersPrevUpdate.erase(itFound);
}
else {
it->second->enteredFieldOfVision();
}
}
for (const auto& marker : _markersPrevUpdate) {
it->second->exitedFieldOfVision();
}
_updateDone = true;
}
/* critical section end */
_markerMutex.unlock();
また、このメソッドはView Matrixを構成して座標系の原点をマーカーの中心として変換し、モデルを上端に描画します。x軸とy軸はマーカ平面上に,z軸の正の向きがマーカ平面に向かうように調整されます。
View Matrixを構成するためにいくつかの変換が必要となります。ArUcoによって作成したView Matrixは左手座標系を使用し、Wikitudeによって作成したView Matrixは右手座標系を使用します。この矛盾を避けるためにY軸が反転されます。このアプリケーションはモバイルデバイス上で実行するためにさまざまなデバイスの向きを考慮する必要があります。ただし、これに対して現在のインターフェイスの向きに応じて回転を適用する、または縦向きのアスペクト比を補正する必要があります。さらに、モバイルデバイスによって画面やスクリーンショットをキャプチャする方法が異なるので、アスペクト比に対して補正した行列が必要となります。
wikitude::sdk::Matrix4 rotationToLandscapeLeft;
rotationToLandscapeLeft.rotateZ(180.0f);
wikitude::sdk::Matrix4 rotationToPortrait;
rotationToPortrait.rotateZ(270.0f);
wikitude::sdk::Matrix4 rotationToUpsideDown;
rotationToUpsideDown.rotateZ(90.0f);
wikitude::sdk::Matrix4 aspectRatioCorrection;
aspectRatioCorrection.scale(_scaleWidth, _scaleHeight, 1.0f);
wikitude::sdk::Matrix4 portraitAndUpsideDownCorrection;
const float aspectRatio = _width / _height;
portraitAndUpsideDownCorrection.scale(aspectRatio, 1.0f / aspectRatio, 1.0f);
wikitude::sdk::Matrix4 viewMatrix(_viewMatrixData);
// OpenCV left handed coordinate system to OpenGL right handed coordinate system
viewMatrix.scale(1.0f, -1.0f, 1.0f);
wikitude::sdk::Matrix4 modelViewMatrix;
wikitude::sdk::InterfaceOrientation currentInterfaceOrientation;
{
std::lock_guard<std::mutex> lock(_interfaceOrientationMutex);
currentInterfaceOrientation = _currentInterfaceOrientation;
}
if (currentInterfaceOrientation == wikitude::sdk::InterfaceOrientation::InterfaceOrientationPortrait || currentInterfaceOrientation == wikitude::sdk::InterfaceOrientation::InterfaceOrientationPortraitUpsideDown) {
modelViewMatrix *= portraitAndUpsideDownCorrection;
}
modelViewMatrix *= aspectRatioCorrection;
switch (currentInterfaceOrientation) {
case wikitude::sdk::InterfaceOrientation::InterfaceOrientationLandscapeRight:
// nop
// we don't like warnings and not having this case included would cause one
break;
case wikitude::sdk::InterfaceOrientation::InterfaceOrientationLandscapeLeft:
modelViewMatrix *= rotationToLandscapeLeft;
break;
case wikitude::sdk::InterfaceOrientation::InterfaceOrientationPortrait:
modelViewMatrix *= rotationToPortrait;
break;
case wikitude::sdk::InterfaceOrientation::InterfaceOrientationPortraitUpsideDown:
modelViewMatrix *= rotationToUpsideDown;
break;
}
modelViewMatrix *= viewMatrix;
モデルのView MatrixおよびProjection Matrixが生成された後、Positionableクラスに適用できます。
wikitude::sdk::Matrix4 identity;
// 3d trackable
it->second->setWorldMatrix(identity.get());
it->second->setViewMatrix(modelViewMatrix.get());
it->second->setProjectionMatrix(_projectionMatrix.get());
ネイティブ実装
プラグインのインスタンス化と登録は「プラグインAPI」セクションで説明されているので、ここでは説明を省略します。
ArUcoマーカーで実行する例についてはリソースフォルダ内のマーカー上に3Dオブジェクトを描画するサンプルを参照してください。