文章目录
综述
第一代 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 方




最低0.47元/天 解锁文章
2967

被折叠的 条评论
为什么被折叠?



