Hololens2开发笔记-研究模式API文档翻译

综述

第一代 HoloLens 引入了研究模式,研究不用于部署访问设备上的关键传感器的应用程序。HoloLens2 的研究模式保留了 HoloLens1 的功能,增加了对额外流的访问。同样,对于第一个版本,可以从以下输入中收集数据:

  • 可见光环境跟踪摄像机-系统用于头部跟踪和地图创建。它们返回每像素 8 位的灰度图像。
  • 两种模式操纵下的深度摄像机
    • 关节式手跟踪方式(AHAT),用于手跟踪的高频(45帧/秒)近深度传感。支持距离设备 1 米之内的手势追踪,HoloLens2 仅通过计算基于相位的飞行时间相机的“混叠深度”来节省电力。这意味着,当距离超过 1 米时,信号只包含到设备的距离的小数部分。
    • 空间映射中使用的长抛、低频(1-5 FPS)的深度传感。
  • 两个版本的红外反射率流- HoloLens 用来计算深度。这些图像由红外线照射,不受周围可见光的影响。

此外,HoloLens2 还支持访问以下内容:

  • 加速度计-系统用来确定沿 X, Y, Z 轴的线性加速度以及重力。
  • 陀螺仪-系统中用来确定旋转的仪器。
  • 磁强计-系统用于绝对方位估计。

大纲

研究模式 API 是基于一种名为 Nano-COM 的轻量级派生。Nano-COM 指的是一种 API 设计模式,它使用 IUnknown 作为对象标识和生存期,但不需要 COM 运行时基础设施,用工厂函数替换 CoCreateInstance 的使用,这些工厂函数返回用参数初始化的对象到这些函数。接口只支持 QueryInterface、AddRef 和 Release。api 返回 HRESULT 错误码。DirectX11 和 12 也是一个 Nano-COM api。

API的结构如下:

  • 首先创建的对象是研究模式设备。这是API工厂对象。它用于:
    • 按类型枚举可用的传感器
    • 创建传感器对象
    • 请求访问权限
    • 每个传感器类型只能创建一个传感器
  • 传感器提供以下功能:
    • 返回传感器的名称和类型
    • 启动和停止流
    • 在流状态下等待和检索帧
    • 返回 extrinsics 矩阵,给出传感器相对于设备连接原点(Rig origin)的相对位置
    • 返回设备坐标帧 GUID,可以用来映射设备坐标帧到其他感知坐标帧
    • 传感器可以是摄像机或 imu,两者都返回帧传感器特定的有效载荷格式
  • 传感器帧提供:
    • 帧时间戳
    • 帧大小
    • 专门针对每个传感器的属性和有效负载格式。

对于所有传感器,初始化调用应该只进行一次,而且传感器不是线程安全的。帧应该从传感器打开的线程读取。传感器可以共享一个线程,或者每个都有一个线程。

主传感器读取循环

主传感器处理循环概述为:

  • 创建研究模式设备
  • 获取所有传感器所在的设备坐标框架。我们称之为 rigNode,它由 GUID 标识,GUID 可与 HoloLens 感知 api 一起用于映射其他HoloLens 感知坐标框架中的传感器特定坐标。下面的 https://docs.microsoft.com/en-us/windows/mixed-reality/coordinate-systems 解释了感知坐标框架。
  • 枚举传感器
  • 获取传感器信息
    • 对于摄像机,该对象在摄像机坐标框架中投影 / 取消投影图像点到 3D 点
    • Extrinsics 用于相对于设备 rigNode 定位传感器。

下面的代码显示了打开研究模式设备,获取传感器描述符和从传感器获取帧。api 返回应该检查错误的结果 HRSULTS。下面的代码省略了错误检查,以便更容易地执行 API 调用。与坐标框架相关的 api 将在后面的章节中描述。

    HRESULT hr = S_OK; 
    IResearchModeSensorDevice *pSensorDevice; 
    IResearchModeSensorDevicePerception *pSensorDevicePerception; 
    std::vector<ResearchModeSensorDescriptor> sensorDescriptors; 
    size_t sensorCount = 0; 
 
    hr = CreateResearchModeSensorDevice(&pSensorDevice); 
 
    // This call makes cameras run at full frame rate. Normaly they are optimized 
    // for headtracker use. For some applications that may be sufficient 
    pSensorDevice->DisableEyeSelection(); 
 
    hr = pSensorDevice->GetSensorCount(&sensorCount);
    sensorDescriptors.resize(sensorCount); 
 
    hr = pSensorDevice->GetSensorDescriptors(sensorDescriptors.data(), 
sensorDescriptors.size(), &sensorCount); 
 
    for (const auto& sensorDescriptor : sensorDescriptors) 
    { 
        // Sensor frame read thread 
 
        IResearchModeSensor *pSensor = nullptr; 
        size_t sampleBufferSize; 
        IResearchModeSensorFrame* pSensorFrame = nullptr; 
 
        hr = pSensorDevice->GetSensor(sensorDescriptor.sensorType, &pSensor); 
 
        swprintf_s(msgBuffer, L"Sensor %ls\n", pSensor->GetFriendlyName()); 
        OutputDebugStringW(msgBuffer); 
 
        hr = pSensor->GetSampleBufferSize(&sampleBufferSize); 
 
        hr = pSensor->OpenStream(); 
 
        for (UINT i = 0; i < 4; i++) 
        { 
            hr = pSensor->GetNextBuffer(&pSensorFrame); 
 
            if (pSensor->GetSensorType() >= IMU_ACCEL) 
            { 
                ProcessFrameImu(pSensor, pSensorFrame, i); 
            } 
            else 
            { 
                ProcessFrameCamera(pSensor, pSensorFrame, i); 
            } 
 
            if (pSensorFrame) 
            { 
                pSensorFrame->Release(); 
            } 
        } 
 
        hr = pSensor->CloseStream(); 
 
        if (pSensor) 
        { 
            pSensor->Release(); 
        } 
    } 
 
    pSensorDevice->EnableEyeSelection(); 
 
    pSensorDevice->Release(); 
 
    return hr; 

上面的代码显示了在同一个线程上读取的所有传感器。由于 GetNextBuffer 调用会引起阻塞,每个传感器帧循环应该在自己的线程上运行。这允许以自己的帧速率处理每个传感器。

OpenStream 和 GetNextBuffer 需要从同一个线程调用。GetNextBuffer 调用会引起阻塞。每个传感器的传感器帧循环应该在它们自己的线程上运行。这允许传感器按照它们自己的帧速率进行处理。推荐使用以下线程模式:

  • 主线程管理研究模式设备和传感器
  • 每个传感器都有一个线程,它打开传感器流,读取缓冲区并处理缓冲区
  • 主线程渲染缓冲区和结果
SensorLoop(IResearchModeSensor *pSensor) 
{ 
    hr = pSensor->OpenStream(); 
 
    while (fRunning) 
    { 
        hr = pSensor->GetNextBuffer(&pSensorFrame); 
 
        ProcessFrame(pSensor, pSensorFrame, i); 
 
        if (pSensorFrame) 
        { 
            pSensorFrame->Release(); 
        } 
    } 
 
    hr = pSensor->CloseStream(); 
}

传感器类型

相机传感器

  • Intrinsics (投影 / 不投影)
  • 在相机坐标空间的一些功能
  • Extrinsics返回设备空间的 R, T 变换
  • 帧被指定为相机帧

惯性传感器

  • Extrinsics返回设备空间的 R, T 变换
  • 帧被指定为惯性传感器帧

传感器坐标帧

每个传感器返回它的变换到 rigNode (Rig origin) 表示为一个外部刚体变换。图 1 显示了相机坐标帧相对于设备坐标帧。注意,在 HoloLens2 上,设备原点对应于左前方可见光相机。因此,该传感器返回的变换对应于恒等变换。

extrinsics变换可检索如下:

IResearchModeCameraSensor *pCameraSensor; 
DirectX::XMFLOAT4X4 cameraPose; 
// … 
// Get matrix of extrinsics wrt the rigNode 
pCameraSensor->GetCameraExtrinsicsMatrix(&cameraPose); 

要将 rigNode(以及设备)映射到其他 HoloLens 感知坐标帧中,可以使用感知api。

using namespace winrt::Windows::Perception::Spatial; 
using namespace winrt::Windows::Perception::Spatial::Preview; 
 
SpatialLocator locator; 
IResearchModeSensorDevicePerception* pSensorDevicePerception; 
GUID guid; 
HRESULT hr = m_pSensorDevice->QueryInterface(IID_PPV_ARGS(&pSensorDevicePerception)); 
if (SUCCEEDED(hr)) 
{ 
    hr = pSensorDevicePerception->GetRigNodeId(&guid); 
    locator = SpatialGraphInteropPreview::CreateLocatorForNode(guid); 
} 
// … 
auto location = locator.TryLocateAtTimestamp(timestamp, anotherCoordSystem); 

相机传感器暴露映射 / 不映射方法,以在相机投影 3D 点。图 3 显示了摄像机参考帧的 3D 坐标与二维图像坐标的关系。

Map / unmap 方法可以使用如下:

IResearchModeCameraSensor *pCameraSensor; 
//… 
float xy[2] = {0}; 
float uv[2] = {0}; 
float uv_mapped[2] = {0}; 
 
for (int i = 0; i <= 10; i++) 
{ 
for (int j = 0; j <= 10; j++) 
       { 
           // VLC images are 640x480 
           uv[0] = i * 64.0f; 
           uv[1] = j * 48.0f; 
  
           pCameraSensor->MapImagePointToCameraUnitPlane(uv, xy); 
           // … 
           pCameraSensor->MapCameraSpaceToImagePoint(xy, uv_mapped); 
           // … 
     } 
}

图片可能因为网络原因掉线了,请刷新或直接点我查看图片~

图 1 相对于 rig node 坐标帧的深度和正面可见光相机坐标帧。Long throw和AHAT是同一相机的不同模式,所以外观是一样的。

图片可能因为网络原因掉线了,请刷新或直接点我查看图片~

图2 Hololens相机。黄色是 VLC 相机,红色是深度相机

图片可能因为网络原因掉线了,请刷新或直接点我查看图片~

图 3 Map Unmap 方

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Apple_Coco

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值