可定位相机

在开始使用之前,建议先查看可定位相机概述一文,其中包含概述信息以及包含 1 HoloLens 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 CameraViewToCoordinateSytemTransform;
        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 码,用于降低对象移动速度)
  • 识别和识别房间中的对象
  • 识别并识别房间中的人员,例如将全息联系人卡片放在人脸上

请参阅