Rerun DepthImage 原语详解:深度图像的记录、单位换算与 3D 点云反投影
2026/9/16 17:45:03 网站建设 项目流程

Rerun DepthImage 原语详解:深度图像的记录、单位换算与 3D 点云反投影

【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun

DepthImage 是 Rerun 中用于记录深度相机数据的核心原语(archetype):每个像素对应一个由DepthMeter指定单位的深度值。本文以 depth_image.md 参考文档为骨架,结合仓库内 SDK 生成源码、Spatial 视图可视化器实现以及 Python / C++ / Rust 三端示例,系统讲解 DepthImage 的字段语义、默认行为、2D 着色与 3D 点云反投影原理。读完本文,你将能够正确构造 DepthImage、理解meterdepth_range对渲染结果的影响,并把带针孔相机标定的深度图一键变成 3D 点云。

DepthImage 是什么

DepthImage 表示由深度相机(depth camera)捕获的深度图像。其官方定义位于 depth_image.def.rs,文档分类为 "Image & tensor"(图像与张量),状态为stable(稳定),并由DepthImage可视化器(visualizer)负责渲染:

A depth image, i.e. as captured by a depth camera. Each pixel corresponds to a depth value in units specified byDepthMeter.

这段话说明了 DepthImage 的两个关键特征:

  1. 语义上不是普通图像:像素值是标量深度(距离),而不是颜色。因此渲染时必须先经过 colormap(颜色映射)才能看到可视化结果。
  2. 单位是可配置的:像素值所代表的物理长度由DepthMeter组件决定(例如 uint16 像素值 1000 表示 1 米,即毫米精度)。

在 SDK 中,DepthImage是一个包含 8 个组件的 archetype(2 个必填、0 个推荐、6 个可选),Rust 侧的完整定义见 depth_image.rs,其类型声明为"rerun.archetypes.DepthImage",显示名为 "Depth image"。

字段详解:必填与可选组件

必填字段(2 个)

字段组件类型说明
bufferImageBuffer原始深度图像数据,如 uint8 / uint16 / float32 的连续数组
formatImageFormat图像的格式(宽、高、像素类型、色彩模型等)

这两个字段均标记为#[rerun(no_ui_edit)](定义见 depth_image.def.rs),即它们属于结构性数据,不允许在 Viewer 的 UI 中直接编辑,只能通过重新记录数据来更新。Rust SDK 中DepthImage::new(buffer, format)的构造器签名也印证了这一点(depth_image.rs)。

从 C++ 使用角度,由于ImageBuffer底层使用rerun::Collection,原始指针、std::vectorstd::array或 C 数组可以直接零拷贝传入,无需先拷贝到中间容器;如需扩展,还可以自定义CollectionAdapter(见 depth_image.def.rs)。

可选字段(6 个)

字段组件类型类型默认行为
meterDepthMeterFloat32浮点深度格式默认1.0;整数格式默认1000.0(毫米)
colormapColormap枚举未设置时使用 Turbo 颜色映射
depth_rangeValueRangeRange1D未设置时由 Viewer 根据数据自动估计
point_fill_ratioFillRatioFloat32默认1.0,仅影响 3D 视图
draw_orderDrawOrderFloat32默认-20.0,仅影响 2D 视图
magnification_filterMagnificationFilter枚举2D 放大时的纹理过滤方式

对应组件在源码中的类型包装可参见 depth_meter.rs、fill_ratio.rs 与 value_range.rs。

meter:深度单位换算

meter是一个浮点值,表示「1 米等于多少个原生深度单位」。定义中的注释给出了一个直观的例子(depth_image.def.rs):

with uint16, perhapsmeter=1000which would mean you have millimeter precision and a range of up to ~65 meters (2^16 / 1000).

即:uint16 最大像素值 65535,若meter=1000(每米 1000 个原生单位,毫米精度),则最大可表示的深度约 65 米。meter在 2D 与 3D 视图中的作用不同:

  • 2D 视图:只影响鼠标悬停时显示的物理深度值;
  • 3D 视图:直接决定反投影点云中各点沿相机光轴方向的坐标位置。

若省略该字段,Viewer 对浮点深度格式默认1.0,对整数格式默认1000.0(即毫米)。

colormap:着色方式

深度值是标量,必须映射为颜色才能显示。默认使用Turbo颜色映射;可选值包括GrayscaleInfernoMagmaPlasmaTurboViridis等(示例中即使用viridis)。该逻辑在可视化器中体现为:所有深度图像都必须有一个 colormap,未设置时通过 fallback 机制补齐(depth_images.rs)。

depth_range:颜色映射范围

depth_range指定期望的深度值范围,通常对应有效值范围:

  • 超出该范围的值在颜色映射时会被 clamp 到范围内;
  • 但由该图生成的点云仍会显示全部点,不受范围裁剪影响。

若未指定,Viewer 会从数据自动估计,且可能估计出比数据实际最小/最大值更宽的范围。例如:若所有值均为正、部分大于 1.0 且全部小于 255.0,Viewer 会猜测数据来自 8bit 图像,从而假设范围为 0–255(depth_image.def.rs)。在可视化器实现中,未设置value_range时通过ImageStatsCache计算图像统计量,再调用ColormapWithRange::default_range_for_depth_images得到默认范围(depth_images.rs)。

point_fill_ratio:点云点半径

该值缩放由深度图生成的 3D 点云中每个点的半径:

  • 1.0(默认):每个点大到与同深度相邻点的中心相接触,不留空隙;
  • 0.5:每个点恰好与同深度相邻点的边缘相接触。

注意源码中的TODO(#6744)标注:该参数当前仅作用于 3D 视图(depth_image.def.rs)。

draw_order:2D 绘制顺序

仅当深度图以 2D 图像形式展示时生效,值越大越靠上层绘制,默认-20.0

magnification_filter:2D 放大过滤

2D 视图下当纹素被放大(texel 大于屏幕像素)时使用的过滤方式。过滤作用于标量值在经 colormap 映射为颜色之前,因此过滤的是深度数值而非颜色;对 3D 视图无影响(depth_image.def.rs)。

可在哪些视图中展示

参考文档明确列出 DepthImage 支持的三类视图:

  • Spatial2DView:以经过 colormap 着色的 2D 图像(textured rect)展示;
  • Spatial3DView:在实体位于针孔相机(pinhole)投影之下时,自动反投影为 3D 深度点云(depth cloud);
  • DataframeView:以表格形式查看组件数据。

从源码看,SpatialView3D下深度图不再显示为纹理矩形,而是需要变换树中存在 pinhole 相机才生成点云;而即使最终只展示点云,纹理矩形仍会被构建用于 UI 交互(悬停取色等),见 depth_images.rs。

示例一:记录一张简单的深度图像

参考文档的 "Simple example" 在仓库中对应三份代码:depth_image_simple.py、depth_image_simple.cpp、depth_image_simple.rs。它们生成一张 200×300 的 uint16 合成深度图,背景 65535,两个矩形区域分别填 20000 与 45000。

Python:

import numpy as np import rerun as rr depth_image = 65535 * np.ones((200, 300), dtype=np.uint16) depth_image[50:150, 50:150] = 20000 depth_image[130:180, 100:280] = 45000 rr.init("rerun_example_depth_image_simple", spawn=True) # Log the tensor, assigning names to each dimension rr.log("depth", rr.DepthImage(depth_image, meter=10_000.0))

C++:

#include <rerun.hpp> #include <algorithm> // fill_n #include <vector> int main(int argc, char* argv[]) { const auto rec = rerun::RecordingStream("rerun_example_depth_image_simple"); rec.spawn().exit_on_failure(); // create a synthetic depth image. const uint32_t HEIGHT = 200; const uint32_t WIDTH = 300; std::vector<uint16_t> pixels(WIDTH * HEIGHT, 65535); for (uint32_t y = 50; y < 150; ++y) { std::fill_n( pixels.begin() + y * WIDTH + 50, 100, static_cast<uint16_t>(20000) ); } for (uint32_t y = 130; y < 180; ++y) { std::fill_n( pixels.begin() + y * WIDTH + 100, 180, static_cast<uint16_t>(45000) ); } rec.log( "depth", rerun::DepthImage(pixels.data(), {WIDTH, HEIGHT}).with_meter(10000.0) ); }

Rust:

//! Create and log a depth image. use ndarray::{Array, ShapeBuilder as _, s}; fn main() -> Result<(), Box<dyn std::error::Error>> { let rec = rerun::RecordingStreamBuilder::new("rerun_example_depth_image_simple") .spawn()?; let mut image = Array::<u16, _>::from_elem((200, 300).f(), 65535); image.slice_mut(s![50..150, 50..150]).fill(20000); image.slice_mut(s![130..180, 100..280]).fill(45000); let depth_image = rerun::DepthImage::try_from(image)?.with_meter(10_000.0); rec.log("depth", &depth_image)?; Ok(()) }

几个值得注意的实操要点:

  1. 维度顺序:NumPy 中数组形状为(height, width)(行优先,200 行 × 300 列);Rust 侧使用 Fortran 顺序(200, 300).f()与之对应。
  2. meter=10_000.0的含义:这里是 1 米 = 10000 个原生单位,即 0.1 毫米精度(参考文档示例即采用此值)。
  3. DepthImage::try_from:Rust SDK 支持直接从ndarray数组构造,自动推导出ImageBufferImageFormat两个必填字段,无需手动指定尺寸。

示例二:深度图反投影为 3D 点云

参考文档的 "Depth to 3D example"(depth_image_3d.py、depth_image_3d.cpp、depth_image_3d.rs)展示了 DepthImage 最有价值的场景:在实体路径上先记录一个 Pinhole(针孔相机)模型,深度图会被自动反投影为 3D 点云

Python:

import numpy as np import rerun as rr depth_image = 65535 * np.ones((200, 300), dtype=np.uint16) depth_image[50:150, 50:150] = 20000 depth_image[130:180, 100:280] = 45000 rr.init("rerun_example_depth_image_3d", spawn=True) # If we log a pinhole camera model, the depth gets automatically # back-projected to 3D rr.log( "world/camera", rr.Pinhole( width=depth_image.shape[1], height=depth_image.shape[0], focal_length=200, ), ) # Log the tensor. rr.log( "world/camera/depth", rr.DepthImage(depth_image, meter=10_000.0, colormap="viridis"), )

C++:

#include <rerun.hpp> #include <algorithm> // fill_n #include <vector> int main(int argc, char* argv[]) { const auto rec = rerun::RecordingStream("rerun_example_depth_image_3d"); rec.spawn().exit_on_failure(); // Create a synthetic depth image. const int HEIGHT = 200; const int WIDTH = 300; std::vector<uint16_t> data(WIDTH * HEIGHT, 65535); for (auto y = 50; y < 150; ++y) { std::fill_n( data.begin() + y * WIDTH + 50, 100, static_cast<uint16_t>(20000) ); } for (auto y = 130; y < 180; ++y) { std::fill_n( data.begin() + y * WIDTH + 100, 180, static_cast<uint16_t>(45000) ); } // If we log a pinhole camera model, the depth gets automatically back-projected to 3D rec.log( "world/camera", rerun::Pinhole::from_focal_length_and_resolution( 200.0f, {static_cast<float>(WIDTH), static_cast<float>(HEIGHT)} ) ); rec.log( "world/camera/depth", rerun::DepthImage(data.data(), {WIDTH, HEIGHT}) .with_meter(10000.0) .with_colormap(rerun::Colormap::Viridis) ); }

Rust:

use ndarray::{Array, ShapeBuilder as _, s}; fn main() -> Result<(), Box<dyn std::error::Error>> { let rec = rerun::RecordingStreamBuilder::new("rerun_example_depth_image_3d") .spawn()?; let width = 300; let height = 200; let mut image = Array::<u16, _>::from_elem((height, width).f(), 65535); image.slice_mut(s![50..150, 50..150]).fill(20000); image.slice_mut(s![130..180, 100..280]).fill(45000); let depth_image = rerun::DepthImage::try_from(image)? .with_meter(10000.0) .with_colormap(rerun::components::Colormap::Viridis); // If we log a pinhole camera model, the depth gets automatically back-projected to 3D rec.log( "world/camera", &rerun::Pinhole::from_focal_length_and_resolution( [200.0, 200.0], [width as f32, height as f32], ), )?; rec.log("world/camera/depth", &depth_image)?; Ok(()) }

该示例的关键结构是层级实体路径

  • world/camera:记录Pinhole相机模型(焦距 200,分辨率与深度图一致 300×200);
  • world/camera/depth:记录DepthImage

在 3D 视图中,可视化器会沿实体路径向上查找 pinhole 变换,一旦找到,就依据针孔模型把每个像素反投影为 3D 点,形成深度点云;metercolormap在此场景下分别决定点云在光轴方向的物理位置与点云着色。这正是机器人多模态数据可视化(如 RGB-D 相机、LiDAR 投影、SLAM 回放)中最常用的套路:相机内参 + 深度帧 = 即时的 3D 观测。

渲染与可视化的底层原理

深度图在 Spatial 视图中的处理链路集中在 depth_images.rs,核心流程如下:

  1. 组装组件数据:从数据存储中取出ImageInfoDepthMeterFillRatioColormapValueRangeMagnificationFilter,其中缺失的可选字段通过 fallback 机制补齐(depth_images.rs)。
  2. 生成着色纹理:用ColormapWithRange{colormap, value_range}将深度标量映射为颜色,构建textured_rect(depth_images.rs)。
  3. 按视图分流
    • 2D 视图:直接展示着色后的纹理矩形;
    • 3D 视图:先判断变换树中是否存在 pinhole,存在则生成DepthCloud点云并丢弃纹理矩形(depth_images.rs 之后的部分)。

这段实现从侧面印证了参考文档的三条要点:colormap 缺省为 Turbo(fallback 逻辑兜底)、depth_range 缺省时由统计缓存自动估计、3D 点云生成以针孔投影为前提。同时,即使只显示点云,纹理矩形仍被保留用于 UI 交互(如悬停时查看对应像素的深度值),这也是文档中"2D 视图中 meter 影响悬停显示的物理深度值"这一行为的实现基础。

使用建议与注意事项

综合参考文档与源码实现,实际使用 DepthImage 时有几点值得留意:

  • 必须同时提供 buffer 与 format:二者是必填组件;Rust 的try_from与 Python 的rr.DepthImage(array, ...)会自动从数组推断格式,C++ 则通过{WIDTH, HEIGHT}显式给出尺寸。
  • 整数深度务必设置meter:整数格式默认按毫米(meter=1000.0)解释,若传感器原始单位不同(如 0.1mm 精度),不设置会导致 3D 点云距离与真实物理尺寸不符。
  • depth_range影响的是着色而非点云:clamp 只作用于 colormap 映射;若希望剔除无效深度点,需要在记录前自行过滤数据。
  • point_fill_ratio当前仅对 3D 点云生效(源码中存在 TODO(#6744)),2D 显示不受影响。
  • 向 3D 反投影的前提是存在 pinhole:记得在深度图实体路径的祖先位置记录Pinhole,且其分辨率(width/height)与深度图一致。
  • DataframeView 同样支持该原语:可用表格方式逐行检查各组件值,便于调试数据问题。

参考文档本身还附有指向 C++ / Python / Rust 三端 API 文档的链接(ref.rerun.iodocs.rs),需要查阅完整构造器与重载方法时可访问对应语言的 API 参考。

【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询