image_transport 源码详细分析

image_transport 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros-perception/image_common/image_transport
版本:3.1.12,许可证 BSD

image_transport 是 ROS 图像话题的 统一发布/订阅抽象层。应用代码只面对 sensor_msgs/Image 的 base topic,底层通过 pluginlib 插件 按需启用 raw、compressed、theora 等传输方式,从而在带宽受限场景下透明切换压缩格式,而不改业务逻辑。


1. 仓库结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
image_transport/
├── include/image_transport/
│ ├── image_transport.hpp # 主入口:ImageTransport + 自由函数
│ ├── publisher.hpp / subscriber.hpp
│ ├── camera_publisher.hpp / camera_subscriber.hpp
│ ├── publisher_plugin.hpp / subscriber_plugin.hpp # 插件基类
│ ├── simple_publisher_plugin.hpp / simple_subscriber_plugin.hpp # 插件模板
│ ├── raw_publisher.hpp / raw_subscriber.hpp # 内置 raw 插件
│ ├── subscriber_filter.hpp # message_filters 适配
│ ├── transport_hints.hpp # 传输方式参数
│ ├── camera_common.hpp # camera_info 话题推导
│ ├── single_subscriber_publisher.hpp
│ ├── exception.hpp / loader_fwds.hpp
│ └── *.h # ROS 1 遗留头(与 .hpp 并存)
├── src/
│ ├── image_transport.cpp # 全局 plugin loader + ImageTransport
│ ├── publisher.cpp / subscriber.cpp
│ ├── camera_publisher.cpp / camera_subscriber.cpp / camera_common.cpp
│ ├── single_subscriber_publisher.cpp
│ ├── manifest.cpp # raw 插件 PLUGINLIB 导出
│ ├── list_transports.cpp # CLI 工具
│ └── republish.cpp # 传输格式转换节点
├── default_plugins.xml # raw 插件描述
├── test/ # 6 个 gtest
├── CMakeLists.txt
└── package.xml

构建产物:

目标 类型 说明
libimage_transport.so 核心框架
libimage_transport_plugins.so raw 插件实现
list_transports 可执行 列出已声明/可加载 transport
republish 可执行 订阅一种 transport,发布另一种

2. 架构总览

应用 / 驱动 / RVizimage_transport 核心pluginlib传输插件advertise(base_topic)subscribe(base_topic, transport)Publisher\n多插件并行 advertiseSubscriber\n单插件 subscribeCameraPublisher\nimage + camera_infoCameraSubscriber\nTimeSynchronizerPubLoader\nPublisherPluginSubLoader\nSubscriberPluginraw(本包内置)compressed / theora\n(独立包)

设计核心:

  • Publisher 端:一次 advertise() 加载 多个 PublisherPlugin,每个 transport 各建一个 ROS publisher。
  • Subscriber 端:一次 subscribe() 只选 一个 SubscriberPlugin(由 TransportHints 或参数决定)。
  • 按需发布publish() 时只对 有订阅者 的 transport 调用插件,避免无谓压缩/编码开销。

3. 话题命名约定

3.1 图像 base topic

用户指定 base topic,例如 /camera/image

3.2 各 transport 的实际话题

transport 实际话题 实现
raw /camera/image(即 base topic) RawPublisher/Subscriber 覆写 getTopicToAdvertise/Subscribe
compressed /camera/image/compressed SimplePublisherPlugin 默认:base + "/" + transport_name
1
2
3
4
virtual std::string getTopicToAdvertise(const std::string & base_topic) const
{
return base_topic + "/" + getTransportName();
}

3.3 camera_info 话题推导

1
2
3
4
5
6
7
std::string getCameraInfoTopic(const std::string & base_topic)
{
// ...
// 去掉最后一段,追加 /camera_info
info_topic += "/camera_info";
return info_topic;
}

示例:

base image topic camera_info topic
/camera/image /camera/camera_info
/robot/head/rgb /robot/head/camera_info

注意camera_info 始终走 普通 rclcpp Publisher/Subscriber(raw CameraInfo),不经 image_transport 插件。


4. 插件系统

4.1 插件注册

default_plugins.xml 声明 raw 插件:

1
2
3
4
<library path="image_transport_plugins">
<class name="image_transport/raw_pub" type="image_transport::RawPublisher" .../>
<class name="image_transport/raw_sub" type="image_transport::RawSubscriber" .../>
</library>

manifest.cpp 导出:

1
2
PLUGINLIB_EXPORT_CLASS(image_transport::RawPublisher, image_transport::PublisherPlugin)
PLUGINLIB_EXPORT_CLASS(image_transport::RawSubscriber, image_transport::SubscriberPlugin)

4.2 插件 lookup 命名

1
2
3
4
static std::string getLookupName(const std::string & transport_name)
{
return "image_transport/" + transport_name + "_pub";
}

Subscriber 对应 _sub 后缀。对外暴露的 transport 名(如 rawcompressed)由 getTransportName() 返回。

4.3 全局 ClassLoader 单例

1
2
3
4
5
6
7
8
9
10
struct Impl
{
PubLoaderPtr pub_loader_;
SubLoaderPtr sub_loader_;
Impl()
: pub_loader_(std::make_shared<PubLoader>("image_transport", "image_transport::PublisherPlugin")),
sub_loader_(std::make_shared<SubLoader>("image_transport", "image_transport::SubscriberPlugin"))
{}
};
static Impl * kImpl = new Impl();

进程内共享 plugin loader;create_publisher / create_subscription 自由函数直接使用 kImpl

4.4 外部插件包(本工作区未包含)

典型独立包(通过 package.xml export <image_transport plugin=...> 注册):

transport 消息类型
compressed_image_transport compressed sensor_msgs/CompressedImage
compressed_depth_image_transport compressedDepth 深度压缩
theora_image_transport theora theora_image_transport/Packet

可用 ros2 run image_transport list_transports 查看当前环境已声明/可加载的 transport。


5. 核心 API

5.1 两种使用风格

风格 A:自由函数(ROS 2 推荐)

1
2
3
auto pub = image_transport::create_publisher(node.get(), "camera/image");
auto sub = image_transport::create_subscription(
node.get(), "camera/image", callback, "raw");

风格 B:ImageTransport 类(ROS 1 兼容风格)

1
2
3
image_transport::ImageTransport it(node);
auto pub = it.advertise("camera/image", 10);
auto sub = it.subscribe("camera/image", 10, callback);

ImageTransport 内部仍调用 create_publisher / create_subscription

5.2 Publisher:多 transport 并行 advertise

1
2
3
4
5
6
7
8
9
10
11
Publisher::Publisher(...)
{
// 1. expand_topic_or_service_name 解析 remap
// 2. 读取 <topic>.enable_pub_plugins 参数(默认全部 declared transports)
// 3. 对每个 allowlist 中的 transport 创建 PublisherPlugin 并 advertise
for (const auto & transport_name : allowlist) {
auto pub = loader->createUniqueInstance(transport_name + "_pub");
pub->advertise(node, image_topic, custom_qos);
impl_->publishers_.push_back(std::move(pub));
}
}

Publisher 白名单参数:

  • 参数名:<base_topic 相对路径,/ 换 .>.enable_pub_plugins
  • 例:topic /camera/image → 参数 camera.image.enable_pub_plugins
  • 默认:所有已声明的 pub 插件(通常含 raw + compressed 等)

按需 publish:

1
2
3
4
5
for (const auto & pub : impl_->publishers_) {
if (pub->getNumSubscribers() > 0) {
pub->publish(message);
}
}

5.3 Subscriber:单 transport 加载

1
2
3
4
impl_->lookup_name_ = SubscriberPlugin::getLookupName(transport);
impl_->subscriber_ = loader->createSharedInstance(impl_->lookup_name_);
// ...
impl_->subscriber_->subscribe(node, base_topic, callback, custom_qos, options);

若用户误订阅 transport 专用话题(如 /camera/image/compressed),会 WARN 提示应订阅 base topic 并设置 image_transport 参数。

5.4 TransportHints:选择订阅 transport

1
2
3
4
5
6
TransportHints(const rclcpp::Node * node,
const std::string & default_transport = "raw",
const std::string & parameter_name = "image_transport")
{
node->get_parameter_or<std::string>(parameter_name, transport_, default_transport);
}

常用 launch 参数:

1
2
# 订阅 compressed 而非 raw
image_transport: compressed

命令行:_image_transport:=compressed


6. Camera 封装

6.1 CameraPublisher

组合 image_transport::Publisher + 原生 CameraInfo publisher:

1
2
impl_->image_pub_ = image_transport::create_publisher(node, image_topic, custom_qos);
impl_->info_pub_ = node->create_publisher<sensor_msgs::msg::CameraInfo>(info_topic, qos);

publish(image, info) 分别发布两路消息(不做时间戳强制同步,调用方应保证 stamp 一致)。

6.2 CameraSubscriber

message_filters::TimeSynchronizer 同步 image + camera_info:

1
2
3
4
5
impl_->image_sub_.subscribe(node, image_topic, transport, custom_qos);
impl_->info_sub_.subscribe(node, info_topic, custom_qos);
impl_->sync_.connectInput(impl_->image_sub_, impl_->info_sub_);
impl_->sync_.registerCallback(callback);
// 每 1s 检查 image/info 是否严重不同步,WARN
  • image 经 SubscriberFilter(支持 compressed 等 transport)
  • camera_info 直接 message_filters::Subscriber<CameraInfo>

7. 插件开发模板

7.1 SimplePublisherPlugin<M>

子类只需实现:

  1. getTransportName() — 返回 "compressed"
  2. publish(const Image&, const PublishFn&) — 编码后调用 publish_fn(transport_msg)

基类负责创建 rclcpp::Publisher<M> 并管理生命周期。

7.2 SimpleSubscriberPlugin<M>

子类只需实现:

  1. getTransportName()
  2. internalCallback(const M&, const Callback& user_cb) — 解码后调用 user_cb(image)

7.3 Raw 插件(最简单参考)

Publisher 直接透传,话题即 base topic:

1
2
3
4
5
6
7
8
void publish(const sensor_msgs::msg::Image & message, const PublishFn & publish_fn) const
{
publish_fn(message);
}
std::string getTopicToAdvertise(const std::string & base_topic) const
{
return base_topic;
}

Subscriber 直接 user_cb(message)


8. SubscriberFilter

Subscriber 包装为 message_filters::SimpleFilter<Image>,供 TimeSynchronizerChain 等使用:

1
2
3
sub_ = image_transport::create_subscription(
node, base_topic,
std::bind(&SubscriberFilter::cb, this, std::placeholders::_1), transport, custom_qos, options);

下游典型用法:RViz ImageTransportDisplayDepthCloudDisplay 多话题同步。


9. 工具程序

9.1 list_transports

遍历 pub/sub 插件,打印 transport 名、所属包、加载状态(SUCCESS / LIB_LOAD_FAILURE / CREATE_FAILURE)。

9.2 republish

传输格式转换 relay 节点:

1
2
3
4
5
# 从 compressed 订阅,以所有可用 transport 重新发布
ros2 run image_transport republish compressed in:=/camera/image out:=/relay/image

# 指定输出 transport
ros2 run image_transport republish compressed in:=/in out:=/out compressed

10. 依赖关系

1
2
3
4
5
image_transport
├── rclcpp # Node、Publisher、Subscription、参数
├── sensor_msgs # Image、CameraInfo
├── pluginlib # 插件加载(Publisher 链接 PRIVATE)
└── message_filters # CameraSubscriber / SubscriberFilter 同步

11. 测试

CMake 启用 6 个 gtest(ROS 2 已迁移):

测试 覆盖点
test_camera_common getCameraInfoTopic
test_publisher 多插件 advertise、参数白名单
test_subscriber transport 加载、错误 topic WARN
test_message_passing pub/sub 端到端消息传递
test_remapping topic remap
test_single_subscriber_publisher SingleSubscriberPublisher

12. ROS 2 迁移遗留

功能 状态
latch 参数 未实现(TODO ros2#464,参数被忽略)
SubscriberStatusCallback / connect_cb 未实现(CameraPublisher/ImageTransport 注释 TODO)
tracked_object 生命周期绑定 忽略(void) tracked_object
.h 遗留头 仍存在,新代码用 .hpp
list_transports 提示 仍写 catkin_make(ROS 1 文案)

13. 典型数据流

场景:驱动 raw 发布 + RViz compressed 订阅

RVizSubscriber(compressed)compressed_pubraw_pubPublisher相机驱动RVizSubscriber(compressed)compressed_pubraw_pubPublisher相机驱动publish(Image)getNumSubscribers()>0 ? publish : skipgetNumSubscribers()>0 ? encode+publish : skipCompressedImage on /camera/image/compresseddecode → Image callback

Publisher 同时 advertise /camera/image/camera/image/compressed;只有 RViz 订阅 compressed 时,驱动侧才执行 JPEG 编码。


14. 与 image_common 栈关系

CameraPublisher相机驱动\ncamera_info_managerimage_transportcompressed_image_transport\n(插件)image_pipeline\n(rectify/debayer)rviz_default_plugins
  • 上游:相机驱动应使用 CameraPublisher + camera_info_manager
  • 下游image_pipelinerviz2 通过 image_transport 订阅
  • 平行camera_info_manager 管理标定;image_transport 管理图像传输格式

15. 设计特点与局限

特点 说明
插件化扩展 新 transport 只需独立包 + XML 注册
Publisher 多播、Subscriber 单选 发布端兼容所有订阅偏好,订阅端只选一种
按需编码 无订阅者不压缩,节省 CPU
话题 remap 显式处理 expand_topic_or_service_name 保证 compressed 子话题正确 remap
Camera 同步诊断 定时 WARN image/info 不同步
局限 说明
camera_info 不支持压缩 transport 仅 image 走插件
Publisher 默认加载全部 transport 插件多时有构造开销(可通过 enable_pub_plugins 限制)
全局 static loader 测试/多进程场景需注意
Subscriber 五参 subscribeImpl 部分旧插件未覆写带 SubscriptionOptions 版本会 ERROR 回退

16. 推荐阅读顺序

  1. image_transport.hpp — 公共 API 全貌
  2. publisher.cpp + subscriber.cpp — 插件加载与 publish 逻辑
  3. raw_publisher.hpp / raw_subscriber.hpp — 最小插件示例
  4. simple_*_plugin.hpp — 编写 compressed 等插件的模板
  5. camera_subscriber.cpp — message_filters 同步模式
  6. republish.cpp — 理解 transport 转换
  7. 外部 compressed_image_transport — 真实压缩插件实现

17. 小结

image_transport 是 ROS 图像通信的 传输抽象框架:Publisher 通过 pluginlib 同时 advertise 多种 transport 子话题,Subscriber 按参数选择一种并解码为 sensor_msgs/Image;内置 raw 插件,压缩/视频类 transport 由独立包扩展。CameraPublisher/Subscriber 在其上封装 image + camera_info 双话题约定,是相机驱动、image_pipeline 和 RViz 的共同基础设施。

如需,我可以把本文写入 ros2doc/ros-perception/image_common/image_transport源码详细分析.md,或继续分析 compressed_image_transport 的 JPEG 编解码实现

文章互动

阅读 --

留言

0 条留言

正在加载留言…