E4:相机、图像与渲染的原生数据路径#
Learning home · Drake contents
Course text is primarily Chinese, with English terminology and summaries. The original content is preserved.
Sim Atlas 学习首页 · 课程 · 采样与延迟 · 惯性与力传感
先修 E1 坐标与对象和状态与时间。本篇完成 A6 的几何、RGB-D、label、点云及显示部分;固定阅读 Drake v1.57.0 提交 1e1466ba466e7ce8fa9fcca4e086ce1383e5427d。全部结论来自原生声明、实现和绑定,未启动渲染或浏览器,也不补充 DexLab 运行资格。
1. 一张图像经过哪些对象#
MultibodyPlant 提供身体运动,SceneGraph 保存几何、各 role 的属性和 Context 中的位姿;QueryObject 是访问当前场景的查询入口;RgbdSensor 把相机参数和 QueryObject 变成系统输出;被命名的 RenderEngine 真正生成像素。相机可以观察只有锚定几何的 SceneGraph,不要求场景一定包含动力学 plant。
flowchart LR
P[Plant geometry_pose] --> S[SceneGraph Context]
S --> Q[QueryObject]
Q --> C[RgbdSensor]
C --> R[Named RenderEngine]
R --> I[Image output]
Q --> M[MeshcatVisualizer publish]
M --> B[Browser scene]
RgbdSensor::CalcColorImage/CalcDepthImage32F/CalcLabelImage 调用 QueryObject 的三个 Render 方法;QueryObject 先完成 pose/configuration 更新,GeometryState 再按 renderer_name 找 renderer,计算相机世界位姿、UpdateViewpoint 后渲染。这条链没有调用物理推进器。Eval 可能触发渲染工作,但不表示前进了一步仿真。传感器实现、QueryObject、渲染分派。
原生对象/API |
输入或配置 |
结果与边界 |
|---|---|---|
|
唯一名字、具体 renderer |
Python 对应复制/clone 重载; |
|
几何的感知材质和 label |
相机渲染使用 perception;proximity 与 illustration 不能自动替代 |
|
宽高、fx/fy/cx/cy 或垂直 FOV |
针孔内参,不包含世界位姿 |
|
renderer 名、内参、clipping、X_BS |
S 是该 imager 的光学坐标,RGB 对应 C,深度对应 D |
|
core、show_window |
同时供 RGB 和 label 使用 |
|
core、DepthRange |
测量范围与 clipping 有不同职责 |
|
parent FrameId、X_PB、两种 camera |
输出图像、X_WB、图像时标;本体是 |
Renderer 的模型/Context 重载、perception role、RenderCameraCore、RgbdSensor 构造。添加 renderer 后仍需给目标几何正确的 perception 属性;几何可通过 (renderer, accepting) 属性限制接收它的 renderer,不能仅凭“物体在 Meshcat 里可见”判定相机也能看到。
2. 光学坐标、内参和投影#
约定 X_AB 将 B 中坐标映到 A。P 是父 SceneGraph frame,B 是相机外壳,C/D 分别是颜色和深度 imager:
C 和 D 都是 x 向图像右、y 向图像下、z 沿视线向前。body_pose_in_world 是 X_WB,只有 X_BD=I 时才可直接用作深度点云的 X_WD。RGB 与 label 共用 C 的配置以保证配准;RGB 与 depth 可以有不同外参、内参和尺寸。frame 定义、位姿输出、光学位姿组合。
忽略畸变,对 z>0 的 p_CQ=(x,y,z),
fx、fy、cx、cy 的单位都是像素;x/y/z 为 m。图像左上第一个像素中心是 (u,v)=(0,0)。宽 W、高 H、垂直视场 θ 的构造器实际取 fx=fy=(H/2)/tan(θ/2),cx=(W−1)/2,cy=(H−1)/2,不是 W/2、H/2。投影约定、像素中心、构造实现。
这种模型没有自动加入镜头畸变、曝光、运动模糊或 rolling shutter。要研究这些现象,先明确新增成像模型与时间合同;不能把普通 pinhole 深度图当作真实硬件全部误差的实现。
3. 图像形状、特殊值和所有权#
构造图像时写 (width,height),Python .data 却是 (height,width,channels);像素 image.at(u,v) 对应 .data[v,u,:]。底层存储偏移是 (u+v*width)*channels。不要把单通道图像直接当作二维矩阵,显式选择 [...,0]。C++ 图像索引、NumPy 绑定。
输出端口 |
原生 Image / NumPy dtype |
Python shape |
物理含义 |
|---|---|---|---|
|
|
Hc×Wc×4 |
RGBA,不是 BGR;颜色和透明度通道无长度单位 |
|
|
Hd×Wd×1 |
D 中的 z,m;0 太近,+Inf 太远 |
|
|
Hd×Wd×1 |
D 中的 z,mm;0 太近,65535 太远/超表示范围,最大有效 65534 mm |
|
|
Hc×Wc×1 |
几何所带 RenderLabel,无单位;不是 GeometryId 的原始整数 |
|
|
非图像 |
X_WB:旋转无量纲、平移 m |
|
vector size 1 |
(1,) |
秒;不同传感器类的采样语义见下一篇 |
格式合同、traits 常量。32F→16U 实现乘 1000、饱和到 [0,65535] 后向零截断;NaN 和 +Inf 映成 65535,−Inf 映成 0,不能保持所有无效值类别。16U→32F 把 65535 恢复为 +Inf,其余乘 0.001。转换实现。
.data 是引用 Image 存储的 NumPy 视图,不是每次分配的独立快照。输出 Eval 的 Image 也通常由系统缓存拥有。长期记录应在明确时刻取 .data.copy(),同时保存内外参、捕获时间、输出可用时间及单位;不要跨后续 Eval、resize 或 Context 修改保存一个视图并声称它是旧帧。初始零尺寸图像先检查 width/height,再访问像素。绑定引用策略、resize。
4. z-depth、射线距离和点云#
深度 z 不是沿像素射线的欧氏距离 r。无畸变、合法有限 z 时:
例如 fx=fy=100,主点 (50,40),像素 (150,40) 的 z=2 m,对应 (2,0,2) m,射程为 2√2 m。该解析例子只检验单位和投影,未生成任何图像。
DepthImageToPointCloud 实现正是逐像素按上式反投影,并可乘输入的 X_PD 输出 P 中坐标。默认 depth 类型 32F、scale=1;16U 的毫米输入必须显式 scale=0.001 才能得到米。它不自动做 RGB/depth 配准,传入 color 必须已经在同一 frame/像素网格;点云列序为 v*width+u,XYZ 输出 float32。太近/太远转换为三维 +Inf;NaN 转为 NaN,并未删除这些点。接口与 scale、反投影实现。
固定官方 tree 中未发现以 lidar、depth_sensor、raycast 或 ray_cast 命名的实现文件,也未将历史 DepthSensor 注释当作当前可用 API。当前这条原生深度路径是 renderer 生成的相机 z-depth;距离/碰撞查询属于另一类几何 API。本课没有宣称实现扫描式 LiDAR、扫描时序、射线多回波、强度或飞行时间噪声。需要这些时应明确适配器/外部传感器模型与原生功能边界,不能只改图像名称。
5. clipping 与有效测距范围#
ClippingRange(near,far) 决定 renderer 的可见视体,DepthRange(min,max) 决定测距输出的有效区间;后者必须包含在前者内。若物体位于 near 与 min 之间,它仍应遮挡远方物体并产生“太近”。把 near=min 会直接裁掉它,可能让相机看穿近处遮挡。深度与 clipping 的区别。
例如 clipping=[0.01,10] m,depth=[0.1,5] m;0.05 m 的物体是太近遮挡,而不是透明。也不应把 far 设得任意巨大:固定精度 z-buffer 在过大范围内会损失深度区分能力,产生 z-fighting。裁剪精度。这不是接触 penetration 容差,也不是 plant 的碰撞距离阈值。
6. label 是应用定义的语义#
每个 perception geometry 可带 (label,id): RenderLabel(n)。多个几何可以共享 n 表示同一类别,也可以逐几何分配做 instance mask;Drake 不会推断“这个 id 是杯子”。必须保存 label→类别/实例的映射,不能假设稳定跨资产导入顺序。RenderLabel 合同。
保留 label |
含义 |
易混淆点 |
|---|---|---|
|
没有几何渲染到该像素 |
不应当作普通物体 label 分配 |
|
该几何不参与 label 图像 |
后方物体可出现在 label 中,不等于把 RGB 几何删除 |
|
仍参与 label 渲染,但类别不关心 |
仍可遮挡后方物体 |
|
未明确指定 |
renderer 默认策略可能拒绝注册,不是自动背景类别 |
保留值、renderer 默认 label 与注册规则。显示 label 的伪彩色图仅供人查看,原始监督信号应保留 int16 id;不能对伪彩色 RGB 值自行反推统一语义类别。
7. Meshcat 与 headless#
Meshcat 是 HTTP/WebSocket 服务加浏览器场景;MeshcatVisualizer 是订阅 QueryObject 的 publish 系统,不输出 RgbdSensor 的 depth/label 图像。默认 role=illustration、发布周期 1/64 s、prefix=visualizer,初始化可删除该 prefix。显示碰撞几何需选择 proximity;显示 hydroelastic 网格还需 show_hydroelastic=True。默认参数、周期/强制/初始化事件。
MeshcatVisualizer.AddToBuilder 连接查询端口;未来运行中 diagram.ForcedPublish(root_context) 可以发布当前场景,但不会推进 plant 或更新相机 ZOH。StartRecording/StopRecording/PublishRecording 记录并发送可视化动画,不是物理状态快照,更不是 RGB-D 传感器数据集。录制实现。StaticHtml() 可保存无需服务器的场景快照,但不包含依赖服务器的控制按钮/滑块。HTML 导出。
两个显示时钟也要分开:publish_period 是仿真时间事件间隔,浏览器收到消息与画出的帧受网络、队列和浏览器影响。Flush() 只等待缓冲消息发给已连接客户端,不承诺屏幕完成绘制。Meshcat 方法应从创建它的线程调用,MeshcatVisualizer 不支持 Context-per-thread 并行;克隆 Context 不会凭空克隆独立浏览器场景。服务与线程、Flush、visualizer 并行限制。
需求 |
原生路径 |
实际环境边界 |
|---|---|---|
取得 RGB-D/label 数组 |
RgbdSensor + 命名 RenderEngine |
不需要 Meshcat 浏览器;仍需要该 renderer 的图形依赖 |
浏览器交互看几何 |
Meshcat + MeshcatVisualizer |
服务和浏览器有独立生命周期;不是相机光学渲染结果 |
无弹出窗口成像 |
|
false 只是窗口请求;不能证明图形驱动、EGL/GLX 或设备可用 |
异步渲染 |
RgbdSensorAsync 的独立 worker |
本固定版主要面向外部 glTF renderer,详见时序篇 |
VTK 参数的 backend 可为 ""/"GLX"/"EGL"/"Cocoa";固定版本默认 Linux EGL、macOS Cocoa,EGL 不支持调试窗口。非法名称报错;平台不支持的选择可能警告后回落默认,不应只看配置字符串宣称实际 backend。参数合同、选择实现。CameraConfig 支持 VTK、GL、glTF client 三类,类名/能力与本机实际 wheel、依赖和渲染设备仍需将来单独核验;本课不提供性能排名。renderer 配置。
8. 阅读练习与答案#
相机 depth 有效而 Meshcat 什么也不显示,首先核对什么? 答:两者的 role、连接和 prefix。相机用 perception,Meshcat 默认 illustration;显示失败不能推导物理几何缺失。
深度图 shape=(480,640,1),如何取 u=20,v=30 的 z? 答:
.data[30,20,0];32F 是米,16U 是毫米并有哨兵值。相机 body 位置是 X_WB,深度 imager 在 B 中偏移 2 cm,点云可直接用 X_WB 吗? 答:不能,应使用 X_WD=X_WB X_BD,且它要与图像同一捕获时刻。
给定上一节投影例子,z=2 m 为什么不代表射程 2 m? 答:该像素离光轴有角度,反投影是 (2,0,2),射程 2√2 m;只有光轴上 r=z。
16U 点云放大了 1000 倍,先查什么? 答:DepthImageToPointCloud 的 scale 默认 1,应对毫米输入设 0.001,并先处理 0/65535。
要忽略玻璃的 label 但看到后方实例,选哪个保留值? 答:kDoNotRender;kDontCare 仍参加 label 成像。
show_window=False、Flush 成功,能证明 headless 图像正确且浏览器已画完吗? 答:两者都不能。前者是窗口选项,后者是消息发送边界;本轮没有实际图像或屏幕验收。
下一篇沿同一组对象讲采样、延迟与 Context 所有权。来源身份、检查结果和未运行范围见 E4 静态证据。