場所を特定できるカメラ

これらの手順を開始する前に、概要情報と HoloLens 1 および 2 カメラの詳細情報を示すテーブルを含む、「場所を特定できるカメラの概要」の記事を参照することをお勧めします。

MediaFrameReference の使用

これらの手順は、MediaFrameReference クラスを使用してカメラから画像フレームを読み取る場合に適用されます。

各画像フレーム (写真とビデオの両方) には、キャプチャ時にカメラでルート化される SpatialCoordinateSystem が含まれます。これには、MediaFrameReferenceCoordinateSystem プロパティを使用してアクセスできます。 各フレームには、CameraIntrinsics プロパティにあるカメラ レンズ モデルの説明が含まれています。 これらの変換が組み合わされて、ピクセルを生成した光子によるパスを表す、3D 空間内の光線がピクセルごとに定義されます。 これらの光線は、フレームの座標系から他の座標系への変換を取得することで (たとえば、基準の固定フレームから)、アプリ内の他のコンテンツに関連付けることができます。

各画像フレームは以下を備えています。

HolographicFaceTracking サンプルは、カメラの座標系と独自のアプリケーション座標系の間の変換に対してクエリを実行する非常に簡単な方法を示しています。

メディア ファンデーションの使用

メディア ファンデーションを直接使用してカメラから画像フレームを読み取る場合は、次のサンプル コードに示すように、各フレームの MFSampleExtension_CameraExtrinsics 属性MFSampleExtension_PinholeCameraIntrinsics 属性を使用して、アプリケーションの他の座標系を基準にカメラ フレームの場所を特定できます。

#include <winrt/windows.perception.spatial.preview.h>
#include <mfapi.h>
#include <mfidl.h>
 
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Foundation::Numerics;
using namespace winrt::Windows::Perception;
using namespace winrt::Windows::Perception::Spatial;
using namespace winrt::Windows::Perception::Spatial::Preview;
 
class CameraFrameLocator
{
public:
    struct CameraFrameLocation
    {
        SpatialCoordinateSystem CoordinateSystem;
        float4x4 CameraViewToCoordinateSystemTransform;
        MFPinholeCameraIntrinsics Intrinsics;
    };
 
    std::optional<CameraFrameLocation> TryLocateCameraFrame(IMFSample* pSample)
    {
        MFCameraExtrinsics cameraExtrinsics;
        MFPinholeCameraIntrinsics cameraIntrinsics;
        UINT32 sizeCameraExtrinsics = 0;
        UINT32 sizeCameraIntrinsics = 0;
        UINT64 sampleTimeHns = 0;
 
        // query sample for calibration and validate
        if (FAILED(pSample->GetUINT64(MFSampleExtension_DeviceTimestamp, &sampleTimeHns)) ||
            FAILED(pSample->GetBlob(MFSampleExtension_CameraExtrinsics, (UINT8*)& cameraExtrinsics, sizeof(cameraExtrinsics), &sizeCameraExtrinsics)) ||
            FAILED(pSample->GetBlob(MFSampleExtension_PinholeCameraIntrinsics, (UINT8*)& cameraIntrinsics, sizeof(cameraIntrinsics), &sizeCameraIntrinsics)) ||
            (sizeCameraExtrinsics != sizeof(cameraExtrinsics)) ||
            (sizeCameraIntrinsics != sizeof(cameraIntrinsics)) ||
            (cameraExtrinsics.TransformCount == 0))
        {
            return std::nullopt;
        }
 
        // compute extrinsic transform
        const auto& calibratedTransform = cameraExtrinsics.CalibratedTransforms[0];
        const GUID& dynamicNodeId = calibratedTransform.CalibrationId;
        const float4x4 cameraToDynamicNode =
            make_float4x4_from_quaternion(quaternion{ calibratedTransform.Orientation.x, calibratedTransform.Orientation.y, calibratedTransform.Orientation.z, calibratedTransform.Orientation.w }) *
            make_float4x4_translation(calibratedTransform.Position.x, calibratedTransform.Position.y, calibratedTransform.Position.z);
 
        // update locator cache for dynamic node
        if (dynamicNodeId != m_currentDynamicNodeId || !m_locator)
        {
            m_locator = SpatialGraphInteropPreview::CreateLocatorForNode(dynamicNodeId);
            if (!m_locator)
            {
                return std::nullopt;
            }
 
            m_frameOfReference = m_locator.CreateAttachedFrameOfReferenceAtCurrentHeading();
            m_currentDynamicNodeId = dynamicNodeId;
        }
 
        // locate dynamic node
        auto timestamp = PerceptionTimestampHelper::FromSystemRelativeTargetTime(TimeSpan{ sampleTimeHns });
        auto coordinateSystem = m_frameOfReference.GetStationaryCoordinateSystemAtTimestamp(timestamp);
        auto location = m_locator.TryLocateAtTimestamp(timestamp, coordinateSystem);
        if (!location)
        {
            return std::nullopt;
        }
 
        const float4x4 dynamicNodeToCoordinateSystem = make_float4x4_from_quaternion(location.Orientation()) * make_float4x4_translation(location.Position());
 
        return CameraFrameLocation{ coordinateSystem, cameraToDynamicNode * dynamicNodeToCoordinateSystem, cameraIntrinsics };
    }

private:
    GUID m_currentDynamicNodeId{ GUID_NULL };
    SpatialLocator m_locator{ nullptr };
    SpatialLocatorAttachedFrameOfReference m_frameOfReference{ nullptr };
};

場所を特定できるカメラの使用例

キャプチャされた世界の写真またはビデオを表示する

デバイス カメラ フレームには、「カメラから世界へ」変換が備わっており、これを使用して、画像が取得されたときのデバイスの位置を正確に示すことができます。 たとえば、この位置に小さなホログラフィック アイコンを配置したり (CameraToWorld.MultiplyPoint(Vector3.zero))、カメラが向いていた方向に小さな矢印を描画したりすることもできます (CameraToWorld.MultiplyVector(Vector3.forward))。

フレーム レート

対話型アプリケーションのフレームレートを維持することは、特に長時間動作する画像認識アルゴリズムを処理する場合には非常に重要です。 このため、一般的に次のパターンを使用します。

  1. メイン スレッド: カメラ オブジェクトを管理します。
  2. メイン スレッド: 新しいフレームを要求します (非同期)。
  3. メイン スレッド: 新しいフレームを追跡スレッドに渡します。
  4. 追跡スレッド: 画像を処理して主要な点を収集します。
  5. メイン スレッド: 検出された主要な点と一致するように仮想モデルを移動します。
  6. メイン スレッド: 手順 2 から繰り返します。

一部の画像マーカー システムでは、1 ピクセルの位置のみが指定されます (完全な変換を指定するものもあります。その場合、このセクションは不要です)。これは、可能な位置の光線に対応します。 複数の光線を計算し、その近似交差によって最終的な結果を求めることで、1 つの第 3 の位置に到達することができます。 そのために必要な作業を次に示します。

  1. ループによって複数のカメラ画像を収集します。
  2. 関連付けられている特徴点とそれらの世界の光線を見つけます。
  3. 特徴の辞書 (それぞれ複数の世界の光線を含む) がある場合は、次のコードを使用して、それらの光線の交差部分を解決できます。
public static Vector3 ClosestPointBetweenRays(
   Vector3 point1, Vector3 normalizedDirection1,
   Vector3 point2, Vector3 normalizedDirection2) {
   float directionProjection = Vector3.Dot(normalizedDirection1, normalizedDirection2);
   if (directionProjection == 1) {
     return point1; // parallel lines
   }
   float projection1 = Vector3.Dot(point2 - point1, normalizedDirection1);
   float projection2 = Vector3.Dot(point2 - point1, normalizedDirection2);
   float distanceAlongLine1 = (projection1 - directionProjection * projection2) / (1 - directionProjection * directionProjection);
   float distanceAlongLine2 = (projection2 - directionProjection * projection1) / (directionProjection * directionProjection - 1);
   Vector3 pointOnLine1 = point1 + distanceAlongLine1 * normalizedDirection1;
   Vector3 pointOnLine2 = point2 + distanceAlongLine2 * normalizedDirection2;
   return Vector3.Lerp(pointOnLine2, pointOnLine1, 0.5f);
 }

モデル化されたシーンの配置

追跡されたタグの位置が 2 つ以上あれば、ユーザーの現在のシナリオに合わせてモデル化されたシーンを配置することができます。 重力を想定できない場合は、タグの位置が 3 つ必要です。 多くの場合、配色が用いられ、白い球体はリアルタイムに追跡されたタグの位置を表し、青い球体はモデル化されたタグの位置を表します。 これにより、ユーザーは配置の質を視覚的に測定できます。 すべてのアプリケーションで次のものがセットアップされることを想定しています。

  • 2 つ以上のモデル化されたタグの位置。
  • 1 つの '調整空間'。シーン内のものをタグの親とする。
  • カメラ機能識別子。
  • モデル化されたタグをリアルタイムのタグに合わせるために、調整空間を移動させる動作 (他の接続はマーカーに対する相対位置のため、モデル化されたマーカー自体ではなく、親空間を移動させるように注意します)。
// In the two tags case:
 Vector3 idealDelta = (realTags[1].EstimatedWorldPos - realTags[0].EstimatedWorldPos);
 Vector3 curDelta = (modelledTags[1].transform.position - modelledTags[0].transform.position);
 if (IsAssumeGravity) {
   idealDelta.y = 0;
   curDelta.y = 0;
 }
 Quaternion deltaRot = Quaternion.FromToRotation(curDelta, idealDelta);
 trans.rotation = Quaternion.LookRotation(deltaRot * trans.forward, trans.up);
 trans.position += realTags[0].EstimatedWorldPos - modelledTags[0].transform.position;

LED や他の認識ライブラリを使用して、タグ付けされた静止または移動している実世界のオブジェクト/顔を追跡または識別する

例 :

  • LED 搭載の産業用ロボット (または動きが遅いオブジェクトの QR コード)。
  • 部屋にあるオブジェクトを識別して認識する。
  • たとえばホログラフィックの連絡先カードを顔にかざすなどして、部屋にいる人を識別して認識する。

関連項目