在 Android 应用中使用“原始深度”

RAW Depth API 为相机图像提供深度数据,该图像的准确性高于完整 Depth API 数据,但并非总是能够覆盖所有像素。还可以进一步处理原始深度图像及其匹配的置信度图像,以便应用仅针对其具体用例使用具有足够准确度的深度数据。

设备兼容性

原始深度适用于所有支持 Depth API 的设备与完整 Depth API 一样,Raw Depth API 不需要支持的硬件深度传感器,例如飞行时间 (ToF) 传感器。不过,Raw Depth API 和完整 Depth API 都会使用设备可能具有的任何受支持的硬件传感器。

Raw Depth API 与 Full Depth API

Raw Depth API 可提供准确度较高的深度估值,但原始深度图像可能不包含相机图像中所有像素的深度估算值。相比之下,完整的 Depth API 可以为每个像素提供估计深度,但由于深度估计的平滑和插值,每像素的深度数据可能不太准确。这两个 API 中的深度图像的格式和大小相同。只是内容有所不同。

下表使用厨房里椅子和桌子的图片说明了 Raw Depth API 和完整 Depth API 之间的区别。

API 返回 相机图片 深度图片 置信度图片
Raw Depth API
  • 原始深度图像,其中包含针对相机图像中部分(但非全部)像素的非常准确的深度估算值。
  • 置信度图像,提供每个原始深度图像像素的置信度。没有深度估算的相机图片像素置信度为零。
全深度 API
  • 单张“平滑”深度图像,其中包含每个像素的深度估算值。
  • 此 API 不提供任何置信度图片。
不适用

置信度图片

在 Raw Depth API 返回的置信度图像中,较浅的像素具有较高的置信度值,其中白色像素表示完全置信度,黑色像素表示无置信度。一般来说,在相机图像中,具有较多纹理的区域(例如树木)比没有纹理的区域(例如空白墙壁)具有更高的原始深度置信度。没有纹理的表面通常产生的置信度为零。

如果目标设备具有受支持的硬件深度传感器,那么即使在无纹理表面上,图像区域足够靠近相机的区域的置信度也可能会较高。

计算费用

Raw Depth API 的计算费用约为完整 Depth API 计算费用的一半。

用例

借助 Raw Depth API,您可以获取深度图像,从而更详细地表示场景中对象的几何形状。在打造 AR 体验时,原始深度数据可能很有用,因为在几何理解任务中,需要提高深度准确性和细节。一些用例包括:

  • 3D 重建
  • 效果衡量
  • 形状检测

前提条件

确保您了解基本 AR 概念以及如何配置 ARCore 会话,然后再继续。

启用深度

新的 ARCore 会话中,检查用户的设备是否支持深度。由于处理能力限制,并非所有与 ARCore 兼容的设备都支持 Depth API。为节省资源,ARCore 上默认停用深度。启用深度模式,以便您的应用使用 Depth API。

Java

Config config = session.getConfig();

// Check whether the user's device supports Depth.
if (session.isDepthModeSupported(Config.DepthMode.AUTOMATIC)) {
  // Enable depth mode.
  config.setDepthMode(Config.DepthMode.AUTOMATIC);
}
session.configure(config);

Kotlin

if (session.isDepthModeSupported(Config.DepthMode.AUTOMATIC)) {
  session.configure(session.config.apply { depthMode = Config.DepthMode.AUTOMATIC })
}

获取最新的原始深度和置信度图像

调用 frame.acquireRawDepthImage16Bits() 以获取最新的原始深度图像。并非通过 Raw Depth API 返回的所有图像像素都包含深度数据,而且不是每个 ARCore 帧都会包含一个新的原始深度图像。如需确定当前帧的原始深度图像是否为新图像,请将其时间戳与上一原始深度图像的时间戳进行比较。如果时间戳不同,原始深度图像将基于新的深度数据。否则,深度图像是之前的深度数据的重新投影。

调用 frame.acquireRawDepthConfidenceImage() 以获取置信度图像。您可以使用置信度图像检查每个原始深度像素的准确度。置信度图片以 Y8 格式返回。每个像素都是一个 8 位无符号整数。0 表示置信度最低,而 255 表示最高置信度。

Java

// Use try-with-resources, so that images are released automatically.
try (
// Depth image is in uint16, at GPU aspect ratio, in native orientation.
Image rawDepth = frame.acquireRawDepthImage16Bits();
    // Confidence image is in uint8, matching the depth image size.
    Image rawDepthConfidence = frame.acquireRawDepthConfidenceImage(); ) {
  // Compare timestamps to determine whether depth is is based on new
  // depth data, or is a reprojection based on device movement.
  boolean thisFrameHasNewDepthData = frame.getTimestamp() == rawDepth.getTimestamp();
  if (thisFrameHasNewDepthData) {
    ByteBuffer depthData = rawDepth.getPlanes()[0].getBuffer();
    ByteBuffer confidenceData = rawDepthConfidence.getPlanes()[0].getBuffer();
    int width = rawDepth.getWidth();
    int height = rawDepth.getHeight();
    someReconstructionPipeline.integrateNewImage(depthData, confidenceData, width, height);
  }
} catch (NotYetAvailableException e) {
  // Depth image is not (yet) available.
}

Kotlin

try {
  // Depth image is in uint16, at GPU aspect ratio, in native orientation.
  frame.acquireRawDepthImage16Bits().use { rawDepth ->
    // Confidence image is in uint8, matching the depth image size.
    frame.acquireRawDepthConfidenceImage().use { rawDepthConfidence ->
      // Compare timestamps to determine whether depth is is based on new
      // depth data, or is a reprojection based on device movement.
      val thisFrameHasNewDepthData = frame.timestamp == rawDepth.timestamp
      if (thisFrameHasNewDepthData) {
        val depthData = rawDepth.planes[0].buffer
        val confidenceData = rawDepthConfidence.planes[0].buffer
        val width = rawDepth.width
        val height = rawDepth.height
        someReconstructionPipeline.integrateNewImage(
          depthData,
          confidenceData,
          width = width,
          height = height
        )
      }
    }
  }
} catch (e: NotYetAvailableException) {
  // Depth image is not (yet) available.
}

后续步骤