3、双目相机建模:左右相机坐标系定义、基线距离设置、同步触发配置
双目相机,说白了就是给机器人装上一双「立体眼睛」。单目相机只能看到二维画面,而双目能通过左右眼的视差算出物体的深度。这个能力在视觉SLAM、避障、目标抓取中太关键了。
我最早接触双目是在一个移动机器人项目上,当时想把深度图直接喂给导航栈。结果URDF里左右相机坐标系没对齐,基线距离也写错了,点云出来全是扭曲的。嗯,从那以后我养成了一个习惯——先画坐标系拓扑图,再写URDF。
核心要点:双目建模的本质是定义两个独立相机传感器,通过固定关节(fixed joint)将它们连接到同一个基座,并精确设置左右相机坐标系之间的平移量(即基线距离)。
3.1 左右相机坐标系定义
双目相机通常有两种结构:
- 平行光轴结构:左右相机光轴平行,基线沿X轴方向。这是最常见的工业双目设计。
- 汇聚光轴结构:左右相机向内偏转一定角度,用于近距离高精度测量。URDF中需要额外添加旋转关节。
我个人习惯用平行光轴结构,因为后续标定和深度计算都简单。坐标系定义遵循ROS的REP-103规范:
| 坐标系 | 原点位置 | Z轴方向 | X轴方向 |
|---|---|---|---|
left_camera_optical_frame |
左相机光心 | 沿光轴指向场景 | 图像右方向 |
right_camera_optical_frame |
右相机光心 | 沿光轴指向场景 | 图像右方向 |
stereo_camera_frame |
双目模组几何中心 | 与左右相机Z轴平行 | 指向右相机方向 |
你想想看,如果左右相机的Z轴不平行,深度计算时就会引入额外的旋转矩阵,处理起来非常麻烦。所以URDF里我建议用fixed关节,只做平移,不做旋转。
<!-- 左相机坐标系 -->
<joint name="left_camera_joint" type="fixed">
<parent link="stereo_camera_link"/>
<child link="left_camera_link"/>
<origin xyz="-0.06 0 0" rpy="0 0 0"/>
</joint>
<link name="left_camera_link">
<inertial>
<mass value="0.01"/>
<origin xyz="0 0 0"/>
<inertia ixx="1e-6" ixy="0" ixz="0" iyy="1e-6" iyz="0" izz="1e-6"/>
</inertial>
</link>
<!-- 右相机坐标系 -->
<joint name="right_camera_joint" type="fixed">
<parent link="stereo_camera_link"/>
<child link="right_camera_link"/>
<origin xyz="0.06 0 0" rpy="0 0 0"/>
</joint>
<link name="right_camera_link">
<inertial>
<mass value="0.01"/>
<origin xyz="0 0 0"/>
<inertia ixx="1e-6" ixy="0" ixz="0" iyy="1e-6" iyz="0" izz="1e-6"/>
</inertial>
</link>
小技巧:左右相机的惯性参数可以设得很小(比如1e-6),因为相机通常作为传感器不参与动力学计算。但mass不能为0,否则ROS的robot_state_publisher会报错。
3.2 基线距离设置
基线距离就是左右相机光心之间的物理距离。这个值直接影响深度测量精度:
- 基线越大:深度分辨率越高,但近距离盲区增大,模具体积也变大。
- 基线越小:模组紧凑,适合近距离,但远距离深度误差会放大。
我在项目中遇到过选型失误的情况。当时为了追求小体积,选了个20mm基线的双目模组,结果在5米外测距误差直接飙到10%。后来换成60mm基线,同样距离误差降到2%以内。
| 应用场景 | 推荐基线距离 | 有效深度范围 |
|---|---|---|
| 桌面机械臂抓取 | 30 - 50 mm | 0.2 - 2 m |
| 室内移动机器人 | 60 - 120 mm | 0.5 - 8 m |
| 室外无人车 | 150 - 300 mm | 1 - 30 m |
| 无人机避障 | 40 - 80 mm | 0.3 - 5 m |
URDF中基线距离体现在origin xyz的X轴偏移量上。比如基线120mm,左右相机各偏移60mm:
<!-- 左相机偏移 -0.06m,右相机偏移 +0.06m,基线 = 0.12m -->
<origin xyz="-0.06 0 0" rpy="0 0 0"/> <!-- 左 -->
<origin xyz="0.06 0 0" rpy="0 0 0"/> <!-- 右 -->
注意:基线距离必须与相机标定文件(calibration.yaml)中的baseline参数严格一致。如果URDF里写120mm,标定文件里写100mm,深度计算会直接出错。我曾经因为这个bug排查了整整两天。
3.3 同步触发配置
双目相机要正常工作,左右图像必须在同一时刻采集。否则运动物体在左右图中位置不一致,视差计算全乱套。
同步触发有两种主流方式:
- 硬件同步:通过GPIO或触发线,让左右相机同时曝光。延迟通常在微秒级。
- 软件同步:通过ROS的
message_filters做时间戳对齐。延迟在毫秒级,适合静态场景。
我个人强烈推荐硬件同步,尤其是移动机器人场景。软件同步在机器人急停或快速转弯时,左右图时间差可能达到10-20ms,足够让深度图出现大量空洞。
在URDF中,同步触发不直接体现在模型描述里,而是通过<gazebo>标签或插件配置:
<!-- 使用gazebo插件模拟双目同步 -->
<gazebo>
<plugin name="stereo_camera_plugin" filename="libgazebo_ros_multicamera.so">
<baseline>0.12</baseline>
<leftCamera>
<cameraName>left_camera</cameraName>
<imageTopicName>left/image_raw</imageTopicName>
<cameraInfoTopicName>left/camera_info</cameraInfoTopicName>
<frameName>left_camera_optical_frame</frameName>
</leftCamera>
<rightCamera>
<cameraName>right_camera</cameraName>
<imageTopicName>right/image_raw</imageTopicName>
<cameraInfoTopicName>right/camera_info</cameraInfoTopicName>
<frameName>right_camera_optical_frame</frameName>
</rightCamera>
<sync>true</sync> <!-- 开启同步触发 -->
<syncDelay>0.001</syncDelay> <!-- 最大同步延迟1ms -->
</plugin>
</gazebo>
经验之谈:如果你用真实硬件,可以在ROS驱动节点中设置sync_mode: hardware。我习惯在启动脚本里加一个检查项,打印左右图像时间戳差值,超过2ms就报警。这个小脚本帮我抓出过好几次接线松动的问题。
3.4 双目相机URDF完整结构
下面这张图展示了双目相机在URDF中的完整坐标系拓扑:
从图中可以看到,stereo_camera_link是双目模组的物理基座,左右相机通过fixed关节挂载在基座两侧。每个相机下面还有一个光学坐标系,用于图像数据处理。
这里有个细节容易忽略:相机坐标系和光学坐标系是不同的。相机坐标系Z轴朝前(指向场景),Y轴朝下;而光学坐标系Z轴朝前,Y轴朝下,但X轴指向图像右方向。URDF中需要通过rpy旋转来对齐:
<!-- 从相机坐标系到光学坐标系的旋转 -->
<origin xyz="0 0 0" rpy="-1.5708 0 -1.5708"/>
常见错误:很多人忘记添加光学坐标系,直接把图像话题挂载到相机坐标系下。这样会导致camera_info中的frame_id与TF树不一致,RViz中无法正确显示点云。我曾经在项目评审时被指出这个问题,当场改代码,场面一度很尴尬。
好了,双目相机的URDF建模就这些核心内容。坐标系定义要准,基线距离要实测,同步触发要硬连。做到这三点,你的双目模型就能稳定工作了。