位置決めプラグインAPI

Wikitude SDKでは、プラグインAPIを使用すると、組み込みのトラッキング機能を使用せずにJavaScript APIで定義されたレンダリングオブジェクトを直接配置することができます。したがって、カスタムのトラッキングアルゴリズムを提供しながらWikitude SDKのレンダリング機能を使用することが可能です。ここでは、カスタムアルゴリズムの実装の手順を説明します。具体的には、OpenCVのとArUcoライブラリを使用してMarker Trackingプラグインを実装します。

概要

このサンプルとAR.Positionableオブジェクトを使用する前に、Wikitude SDKでの実装方法を理解してください。このセクションでは、その実装方法を示します。

AR.PositionableはJavaScript APIで定義することができます。この定義は、wikitude::sdk::PluginupdatePositionables関数への参照を持つ補完的なC++オブジェクトをインスタンス化し、JavaScript APIで処理することを可能にします。Positionableクラスを使用したカスタムプラグインは、あるクラスから派生して、updatePositionablesメンバ関数をオーバーライドすることによって実現できます。 updatePositionables関数によって修正を行った後、AR.Positionableオブジェクトは各フレームをレンダリングするために提出されます。したがって、PositionableクラスはWikitude SDKにレンダリングするためのプラグイン変更可能なラッパーオブジェクトです。これにより、プラグインAPIを使用して簡単にJavaScript APIの拡張機能が提供されます。

前提条件

このサンプルに対しては次のリソースを推奨します。

Pluginサンプル

詳細については、「プラグインAPI」のセクションを参照してください。

ArUcoマーカー

独自のArUcoマーカーを作成したいのなら、ArUcoライブラリパッケージに付属しているユーティリティを参照してください。ArUcoはSourceForgeページからダウンロードできます。

ID#303を持つArUco拡張現実ライブラリに特有のマーカー

ArUcoとOpenCVドキュメント

トラッキングアルゴリズムの詳細については、「ArUco」ページと「OpenCV」ページのカメラキャリブレーションと3D再構築を参照してください。

JavaScript実装

JavaScriptで実装する場合はAR.Trackable2DObjectAR.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オブジェクトを描画するサンプルを参照してください。