ROS2 三相机感知模块接口说明文档
项目名称 :机器人多相机感知模块
ROS 版本 :ROS2 Humble
传输方案 :image_transport + JPEG 压缩图像流;内参、外参采用 ROS 标准消息
相机清单:前视相机、左手臂相机、右手臂相机
相机内外参数说明:
- 相机内参(CameraInfo):使用 Topic 发布
内参在标定完成后固定不变,但遵循 ROS 标准范式,通过/cameraXXX/camera_info持续周期发布;支持订阅节点随时获取,兼容 image_pipeline、calibration 工具链。- 相机外参(相对于 base_link 的 TF / 静态外参):优先 TF2 + static_transform_publisher;动态外参(机械臂末端相机随臂移动)使用 TF 动态广播
- 静态外参(相机固定在机身,位置不变):
static_transform_publisher发布静态 TF,一次性发布,DDS 自动维护;- 动态外参(左右臂末端相机,随机械臂关节运动实时变化):TF2 动态广播;
- 备选方案:如果业务必须以消息数据包形式拿到外参矩阵(4×4 齐次变换矩阵),可以新增自定义 Topic 发布外参矩阵消息;
协议原则:遵循 ROS 视觉生态标准,尽量复用原生消息,最小化自定义消息,保证和 image_pipeline、rviz、标定工具直接兼容
1 通信总览表
表格
| 相机名称 | 原始图像 Topic (raw) | JPEG 压缩图像 Topic (image_transport) | 相机内参 Topic | TF 光学坐标系 | 父坐标系 |
|---|---|---|---|---|---|
| 前视相机 | /camera_front/image_raw |
/camera_front/image_raw/compressed |
/camera_front/camera_info |
camera_front_optical_frame |
base_link |
| 左手臂相机 | /camera_left_arm/image_raw |
/camera_left_arm/image_raw/compressed |
/camera_left_arm/camera_info |
camera_left_arm_optical_frame |
left_arm_end_link |
| 右手臂相机 | /camera_right_arm/image_raw |
/camera_right_arm/image_raw/compressed |
/camera_right_arm/camera_info |
camera_right_arm_optical_frame |
right_arm_end_link |
补充 TF 链路:
前视:
base_link→camera_front_optical_frame(静态,固定外参)左臂:
base_link→left_arm_end_link(由机械臂状态发布动态 TF)→camera_left_arm_optical_frame(手眼标定得到,相对于末端的静态变换)右臂:
base_link→right_arm_end_link(机械臂动态 TF)→camera_right_arm_optical_frame(手眼标定静态变换)
消息 QoS 统一约定:图像 Topic、camera_info 内参 Topic:
rclcpp::SensorDataQoS()(BestEffort,适合传感器数据流)TF 消息:TF2 内置 QoS(默认 RELIABLE,少量数据,无带宽压力)
2 消息结构定义
2.1 图像消息(复用 sensor_msgs)
-
raw 图像:
sensor_msgs/msg/Image -
JPEG 压缩图像:
sensor_msgs/msg/CompressedImagestd_msgs/msg/Header header
string format # "jpeg"
uint8[] data # JPEG二进制字节
2.2 相机内参消息 sensor_msgs/msg/CameraInfo(标准 ROS 消息,内参 Topic 使用)
# 相机标定内参消息,/cameraXXX/camera_info 的消息定义
std_msgs/msg/Header header
uint32 height # 图像高度 px
uint32 width # 图像宽度 px
string distortion_model # 畸变模型:plumb_bob(径向+切向畸变)
double[] D # 畸变系数 D[0~4]
# 内参矩阵 K (3x3 相机内参)
# K = [fx, 0, cx;
# 0, fy, cy;
# 0, 0, 1 ]
double[9] K
# 相机校正矩阵 R(立体相机使用,单目相机填单位矩阵)
double[9] R
# 投影矩阵 P (3x4)
# P = K * [R | t],单目相机R为单位矩阵,t=0
double[12] P
uint32 binning_x
uint32 binning_y
sensor_msgs/msg/RegionOfInterest roi
字段说明:
- header.frame_id:与对应图像 topic 保持一致(
camera_xxx_optical_frame),时间戳和图像帧对齐;- 单目相机 distortion_model 固定为
plumb_bob;- 内参标定后固定不变,节点可按 1Hz~10Hz 周期持续发布(无需和图像帧率完全对齐,推荐 10Hz)
2.3 外参方案 A:TF2(无需自定义 msg)
TF 消息类型:tf2_msgs/msg/TFMessage
包含多个geometry_msgs/msg/TransformStamped
std_msgs/msg/Header header
string child_frame_id
string parent_frame_id
geometry_msgs/msg/Transform transform
# transform包含平移(xyz) + 四元数(xyzw)旋转
- 平移:
transform.translation.x/y/z单位 m - 旋转:
transform.rotation.x/y/z/w四元数
使用方式
- 前视相机:静态外参,launch 中使用
static_transform_publisher一次性发布base_link→camera_front_optical_frame - 左右臂相机:手眼标定得到
left_arm_end_link→camera_left_arm_optical_frame静态 TF;机械臂驱动发布base_link到left_arm_end_link动态 TF;TF 树自动拼接,任意节点可直接查询base_link到相机光学坐标系的完整 4×4 变换矩阵。
优势:RViz 原生支持、ROS2 所有视觉库原生支持,直接调用 tf2_geometry_msgs 完成坐标点变换,不用手动解析矩阵。
2.4 外参方案 B:自定义 Topic(业务必须直接拿到 4×4 齐次外参矩阵时启用,备选方案)
适用场景:业务算法需要直接接收 4×4 齐次变换矩阵,不方便调用 TF2 查询;非必需,优先 TF
新建自定义消息包
robot_calibration_msgs,新建 msg 文件ExtrinsicMatrix.msg文件:
robot_calibration_msgs/msg/ExtrinsicMatrix.msg
# 相机外参齐次4×4变换矩阵,child_frame相对于parent_frame
std_msgs/msg/Header header
string parent_frame_id # 参考坐标系,一般 base_link
string child_frame_id # 相机光学坐标系 camera_xxx_optical_frame
double[16] matrix # 4x4齐次变换矩阵,行优先排列
# T = [R t; 0 0 0 1],16个元素:[m00,m01,m02,m03, m10,m11,m12,m13, m20,m21,m22,m23, m30,m31,m32,m33]
对应 topic 命名(如果启用此方案)
表格
| 相机 | 自定义外参矩阵 Topic | 消息类型 |
|---|---|---|
| 前视 | /camera_front/extrinsic_matrix | robot_calibration_msgs/msg/ExtrinsicMatrix |
| 左手臂 | /camera_left_arm/extrinsic_matrix | robot_calibration_msgs/msg/ExtrinsicMatrix |
| 右手臂 | /camera_right_arm/extrinsic_matrix | robot_calibration_msgs/msg/ExtrinsicMatrix |
发布频率:和机械臂状态同步,推荐 100Hz;静态相机可以低频 1~10Hz 发布。
注意:尽量避免同时开启 TF + 自定义外参 topic,两份数据必须保持一致,避免两套外参冲突。
3 节点发布约定
3.1 相机驱动节点
camera_front_driver_node- 发布:
/camera_front/image_raw(image_transport 自动生成 compressed 压缩图) - 发布:
/camera_front/camera_info(内参) - 外参:static_transform_publisher 发布静态 TF,不属于相机驱动节点
- 发布:
camera_left_arm_driver_node- 发布:
/camera_left_arm/image_raw、compressed 压缩流 - 发布:
/camera_left_arm/camera_info(内参) - 外参:static_transform_publisher 发布静态 TF
left_arm_end_link→camera_left_arm_optical_frame
- 发布:
camera_right_arm_driver_node- 发布:
/camera_right_arm/image_raw、compressed 压缩流 - 发布:
/camera_right_arm/camera_info(内参) - 外参:static_transform_publisher 发布静态 TF
right_arm_end_link→camera_right_arm_optical_frame
- 发布:
3.2 机械臂驱动节点
- 发布机械臂关节状态,广播动态 TF:
base_link→left_arm_end_link、base_link→right_arm_end_link
4 Topic vs Service 选型详细论证
内参:Topic
- 理由:感知节点持续运行,随时可能读取内参;Topic 订阅被动接收,低开销;原生兼容 ROS 标定、image_proc 去畸变节点。
- 缺点:固定参数周期性发送,微小带宽开销(极小,消息体很小)
外参:TF2
- 静态相机:static TF,仅发布一次,几乎无开销
- 机械臂末端动态相机:TF 持续广播末端位姿,全 ROS 生态原生支持坐标变换
内外参不推荐实时业务使用 Service
- Service 工作模式:Client 主动请求,Server 返回一次参数;
- 问题:
- 实时感知流水线,每一帧图像都要请求外参,会引入 RTT 延迟;
- 多订阅节点(检测、SLAM、手眼)会并发调用 service,造成服务阻塞;
- 无法做到图像时间戳和外参时间戳自动对齐;
Service 仅推荐离线标定工具 使用:例如写一个
get_camera_param服务,一次性读取标定文件,程序启动阶段调用一次,不是实时图像流水线使用。
【可选离线服务示例(仅调试)】srv 文件
robot_calibration_msgs/srv/GetCameraParam.srv
# request string camera_name # front / left_arm / right_arm --- # response,一次性返回内参+外参 sensor_msgs/msg/CameraInfo intrinsic robot_calibration_msgs/msg/ExtrinsicMatrix extrinsic
5 时间同步约定
-
image_raw、camera_info消息 header.stamp 必须是同一帧图像采集的硬件时间戳; -
TF 变换查询时,使用图像 header.stamp 去 lookupTF,获取该图像时刻对应的相机外参;
// C++查询某帧图像时刻,base_link到相机光学帧的变换
tf2_ros::Buffer tf_buffer;
auto transform = tf_buffer.lookupTransform(
"base_link",
msg->header.frame_id,
msg->header.stamp,
tf2::durationFromSec(0.1)
);
6 测试命令
# 查看所有相机topic
ros2 topic list | grep -E "image|camera_info|tf"
# 查看内参消息
ros2 topic echo /camera_front/camera_info
# 查看TF树
ros2 run tf2_tools view_frames
# 查询某时刻base_link到前视相机的变换
ros2 run tf2_ros tf2_echo base_link camera_front_optical_frame
# 图像可视化
ros2 run rqt_image_view rqt_image_view
7 性能与约束
- 图像:JPEG 压缩,QoS=SensorData(BestEffort);
- camera_info:消息体很小,带宽可忽略,1~10Hz 周期发布;
- TF:动态 TF(机械臂末端)推荐 100Hz;静态 TF 仅一次发布;
- 跨 IP 多机:ROS_DOMAIN_ID 统一,关闭 localhost_only,FastDDS 锁定网卡
enP1p1s0; - 时间同步:推荐使用 chrony 做机器时钟同步(多机跨 IP 部署必备)。
8 推荐 launch 文件结构简述
<!-- 启动三个相机驱动,发布image + camera_info -->
<node pkg="camera_driver" exec="camera_front_driver_node"/>
<node pkg="camera_driver" exec="camera_left_arm_driver_node"/>
<node pkg="camera_driver" exec="camera_right_arm_driver_node"/>
<!-- 静态TF:手眼标定外参 -->
<node pkg="tf2_ros" exec="static_transform_publisher"
args="x y z qx qy qz qw base_link camera_front_optical_frame"/>
<node pkg="tf2_ros" exec="static_transform_publisher"
args="x y z qx qy qz qw left_arm_end_link camera_left_arm_optical_frame"/>
<node pkg="tf2_ros" exec="static_transform_publisher"
args="x y z qx qy qz qw right_arm_end_link camera_right_arm_optical_frame"/>
9 部署扩展
- 相机驱动节点可封装为 systemctl service,上电自启(前文提到的方案)
- 多机跨 IP:图像流(JPEG compressed)跨 DDS 传输;TF、camera_info 同步跨机共享;
- 去畸变:订阅
camera_info+ 原始图像,使用image_proc节点完成图像去畸变。