定位相機

開始之前,建議您先查看 我們的 Locatable 相機概觀 文章,其中包含概觀資訊和具有 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. 取得收集多個相機影像的迴圈
  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);
 }

定位模型化場景

假設有兩個以上的追蹤標籤位置,您可以放置模型化場景,以符合使用者目前的案例。 如果您無法假設重力,則需要三個標籤位置。 在許多情況下,我們使用色彩配置,其中白色球體代表即時追蹤標記位置,而藍色球體代表模型化標記位置。 這可讓使用者以視覺化方式測量對齊品質。 我們假設所有應用程式中都有下列設定:

  • 兩個或多個模型化標記位置
  • 其中一個「校正空間」,在場景中是標記的父系
  • 相機功能識別碼
  • 行為,移動校正空間以將模型化標籤與即時標籤對齊, (我們小心移動父空間,而不是模型化標記本身,因為其他連接相對於它們的位置) 。
// 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 代碼,可讓移動物件變慢)
  • 識別和辨識會議室中的物件
  • 識別及辨識會議室中的人員,例如將全像攝影連絡人卡片放在臉部上

另請參閱