可定位相机
在开始之前,建议你先阅读可定位相机概述一文,其中包含概述信息及 HoloLens 1 和 2 相机详细信息表格。
使用 MediaFrameReference
如果你使用 MediaFrameReference 类从相机读取图像帧,则这些说明适用。
每个图像帧(无论是照片还是视频)都包含一个在拍摄时根植在相机中的、可以使用 MediaFrameReference 的 CoordinateSystem 属性访问的 SpatialCoordinateSystem。 每一帧都包含相机镜头型号的说明,可在 CameraIntrinsics 属性中找到。 这些变换共同为每个像素定义了 3D 空间中的一条射线,该射线表示生成像素的光子所采用的路径。 通过获取从帧坐标系到其他某个坐标系(例如,从静止参照系)的变换,可将这些射线与应用中的其他内容相关联。
每个图像帧提供以下信息:
- 像素数据(RGB/NV12/JPEG/其他格式)
- 来自捕获位置的 SpatialCoordinateSystem
- 包含相机镜头模式的 CameraIntrinsics 类
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))。
帧速率
保持交互式应用程序的帧速率非常重要,尤其是在处理长时间运行的图像识别算法时。 为此,我们通常使用以下模式:
- 主线程:管理相机对象
- 主线程:请求新帧(异步)
- 主线程:将新帧传递给跟踪线程
- 跟踪线程:处理图像以收集关键点
- 主线程:移动虚拟模型以匹配找到的关键点
- 主线程:重复步骤 2
某些图像标记系统仅提供单个像素位置(其他系统提供完整变换,在这种情况下,请忽略本部分),这等同于可能位置的射线。 若要访问单个第三位置,可利用多条射线,按其近似交点找到最终结果。 为此需要:
- 获取用于收集多个相机图像的循环
- 查找关联的特征点及其世界射线
- 如果你有一个特征字典,其中每个特征具有多条世界射线,则你可以使用以下代码来求解这些射线的交集:
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 码)的工业机器人
- 识别并辨认房间中的对象
- 识别并辨认房间中的人员,例如,在人脸上放置全息联系人卡片