3. 深度相机与点云

深度相机这东西,说白了就是给机器人装上一双能「看穿距离」的眼睛。普通相机只能拍颜色,深度相机却能告诉你每个像素离你有多远。我在做仓储机器人项目时,就吃过纯视觉的亏——货架上的箱子颜色一样,普通相机根本分不清前后,换上深度相机才搞定。

深度相机的工作原理

目前主流的深度相机有三种技术路线。我列个表,你一看就明白区别在哪。

技术类型 原理 典型距离 优缺点
结构光 投射红外点阵,通过变形计算深度 0.1-5m 精度高,但室外易受干扰
双目立体 两个相机视差三角测量 0.3-20m 成本低,但弱纹理区域失效
ToF 发射光脉冲,测量飞行时间 0.1-10m 帧率高,但分辨率偏低

Isaac Sim 里默认用的是 双目立体 + 结构光混合仿真模型。它模拟的是 Intel RealSense D435 这类传感器。你想想看,仿真里为什么要模拟这些?因为真实传感器的噪声、畸变、盲区,都会直接影响下游的点云处理算法。

核心概念:深度图是 2D 的,每个像素存的是距离值(单位米)。点云是 3D 的,每个点存的是 (x, y, z) 坐标。深度图转点云,本质就是做了一次投影逆变换。

深度图与点云生成

在 Isaac Sim 里生成深度图和点云,其实就几步配置。我个人习惯先配好相机参数,再开渲染管线。

# 通过 Python API 创建深度相机
from omni.isaac.sensor import Camera

camera = Camera(
    prim_path="/World/Camera",
    resolution=(640, 480),
    orientation=[0.5, 0.5, -0.5, 0.5],
    position=[0.5, 0.0, 0.5]
)

# 开启深度和点云输出
camera.enable_depth()
camera.enable_pointcloud()

嗯,这里要注意:深度图默认单位是米,但 Isaac Sim 内部存储时用的是 float32。你拿到深度图后,直接乘以相机内参矩阵的逆,就能得到点云。

我记得第一次做这个转换时,踩了个坑——深度图里有些像素值是 0。那不是距离为 0,而是「无效像素」。真实传感器在边缘、反光表面、黑色物体上都会产生无效深度。仿真里也一样,你得先做一步掩膜处理。

避坑指南:我曾经在项目里直接拿原始深度图转点云,结果点云里全是噪点。后来加了一步深度图滤波——用中值滤波去掉孤立噪点,再用双边滤波保边缘。效果立竿见影。

深度相机参数调优

参数调优这件事,很多人觉得仿真里随便设设就行。其实不然。你调的参数,最终要映射到真实传感器上。

关键参数就这几个:

  • 焦距 (focal length):影响视场角和深度精度。焦距越长,视场角越小,但深度分辨率越高。
  • 基线 (baseline):双目相机两个镜头之间的距离。基线越长,深度精度越高,但盲区也越大。
  • 最小/最大深度:直接决定了传感器的有效工作范围。
  • 噪声标准差:模拟真实传感器的测量噪声。

我建议你按这个流程来调:

  1. 先确定应用场景的工作距离(比如 0.5m-3m)
  2. 根据距离选基线(近距离用小基线,远距离用大基线)
  3. 调焦距,让目标物体在图像中占合理比例
  4. 最后加噪声,让仿真数据尽量贴近真实传感器

警告:不要把仿真里的噪声参数设得太理想。我见过有人把噪声标准差设成 0.001,结果算法在仿真里跑得飞起,一上真机就崩。建议至少设到 0.01-0.05 级别,才能模拟真实传感器的抖动。

噪声模型与仿真

真实深度相机的噪声来源很复杂。Isaac Sim 提供了几种噪声模型:

噪声类型 来源 仿真参数
高斯噪声 传感器电子噪声 mean, stddev
量化噪声 ADC 转换精度限制 bit_depth
散粒噪声 光子到达的随机性 gain, exposure
运动模糊 相机或物体运动 motion_blur_amount

我个人习惯的做法是:先关掉所有噪声,调好基础参数,再一层层往上加。这样你能清楚知道每个噪声对最终点云的影响有多大。

为什么会这样?因为噪声之间会互相耦合。比如高斯噪声和量化噪声叠加后,点云边缘会出现「锯齿状」的伪影。如果你一开始就把所有噪声都打开,出了问题根本不知道是哪一层导致的。

点云数据保存与加载

仿真跑出来的点云,最终要保存下来做离线算法开发。Isaac Sim 支持几种格式:

  • .ply:最常用,支持颜色和法线
  • .pcd:PCL 库的标准格式
  • .npy:NumPy 原生格式,适合 Python 快速读写
# 保存点云为 .ply 文件
import numpy as np
from omni.isaac.core.utils.numpy.rotations import quats_to_rot_matrices

# 获取点云数据
points = camera.get_pointcloud()
colors = camera.get_rgba()[:, :, :3]

# 保存为 PLY
def save_ply(filename, points, colors):
    with open(filename, 'w') as f:
        f.write("ply\nformat ascii 1.0\n")
        f.write(f"element vertex {len(points)}\n")
        f.write("property float x\nproperty float y\nproperty float z\n")
        f.write("property uchar red\nproperty uchar green\nproperty uchar blue\n")
        f.write("end_header\n")
        for p, c in zip(points, colors):
            f.write(f"{p[0]} {p[1]} {p[2]} {int(c[0]*255)} {int(c[1]*255)} {int(c[2]*255)}\n")

save_ply("output.ply", points.reshape(-1, 3), colors.reshape(-1, 3))

加载技巧:加载点云时,我建议先做一次体素滤波降采样。仿真出来的点云动辄几十万个点,直接加载会卡死。用 Open3D 的 voxel_down_sample,设体素大小 0.01m,能降到几万点,处理速度提升 10 倍以上。

知识体系总览

下面这张图,把本章的核心逻辑串起来了。你跟着箭头走一遍,就能理解深度相机仿真的全链路。

深度相机仿真知识体系 深度相机原理 深度图生成 点云生成 参数调优 噪声模型 数据保存 数据加载 关键流程说明 1. 从深度相机原理出发,理解深度图如何生成 2. 深度图通过内参逆变换转为点云 3. 参数调优和噪声模型让仿真更贴近真实传感器 4. 最终点云数据保存为 .ply/.pcd 格式,供后续算法使用

实战建议

最后给你几条实在的建议:

  • 先跑通默认参数:别一上来就调参。Isaac Sim 的默认参数已经能跑出可用的深度图和点云。
  • 对比仿真 vs 真实数据:如果你手头有真实深度相机的数据,把仿真数据调成和真实数据一样的噪声水平。这一步很关键。
  • 点云可视化用 Open3D:Isaac Sim 自带的 viewer 够用,但 Open3D 的交互式可视化更适合调试。

一句话总结:深度相机仿真的核心,不是把图做得多漂亮,而是让仿真数据的行为和真实传感器一致。噪声、畸变、盲区,一个都不能少。