polled_camera 源码详细分析 工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros-perception/image_common/polled_camera 版本:1.11.13 ,许可证 BSD 。
polled_camera 提供 按需拉取(polled)相机图像 的协议与服务定义:客户端通过 service 请求拍一张图,驱动端捕获后把 Image + CameraInfo 发布到指定 namespace 的 latched 话题,客户端再像普通相机流一样订阅。与连续发布的 streaming 驱动形成互补,适用于工业相机、PR2 Prosilica 等 触发式采集 场景。
1. 重要前提:仍为 ROS 1 遗留包
项
状态
构建系统
catkin (buildtool_depend: catkin)
C++ API
roscpp (ros::NodeHandle、boost::function)
ROS 2 端口
未完成 (同目录下 image_transport 等已是 ament)
在 Humble 中
源码随 image_common 保留,通常不会被 colcon 构建
package.xml 自述 API 仍在开发中、仅供内部使用 (for internal use as the API is still under development)。
2. 仓库结构 1 2 3 4 5 6 7 8 9 10 11 12 polled_camera/ ├── include/polled_camera/ │ └── publication_server.h # 服务端辅助类 + advertise() 工厂 ├── src/ │ ├── publication_server.cpp # 核心实现(~160 行) │ └── poller.cpp # 示例 CLI 客户端 ├── srv/ │ └── GetPolledImage.srv # 拉取图像服务定义 ├── CMakeLists.txt # catkin 构建 ├── package.xml ├── mainpage.dox # Doxygen 协议说明 └── CHANGELOG.rst
构建产物:
目标
类型
说明
libpolled_camera.so
库
PublicationServer
poller
可执行
按固定频率调用 service 的 demo
3. 在 image_common 栈中的位置 客户端 polled_camera 相机驱动 DriverCallback image_transport ServiceClient\nrequest_image CameraSubscriber\n订阅 response 话题 PublicationServer GetPolledImage 服务 硬件触发 / 采集 CameraPublisher\nlatched
对比
streaming 驱动
polled 驱动
触发
定时/连续 publish
客户端 service 请求才拍
话题
固定 namespace 持续发布
按 response_namespace 动态创建
带宽
持续占用
按需,适合低频/同步采集
典型场景
USB webcam
工业相机、PR2 Prosilica
PR2 URDF 中仍有遗留引用:/prosilica/request_image(见 pr2_desc.urdf 的 pollServiceName)。
4. 通信协议(mainpage.dox)
驱动 advertise 服务:<camera>/request_image
客户端调用 service,在 request 中指定 response_namespace
驱动捕获图像,在 response 中返回 stamp
驱动将 Image、CameraInfo latched 发布到:
<response_namespace>/image_raw
<response_namespace>/camera_info(由 image_transport 约定推导)
客户端订阅上述话题,用 rsp.stamp 与 Image.header.stamp 对齐
目前没有官方 Client 类 ,客户端需自行:ServiceClient + image_transport::CameraSubscriber。
5. 服务定义:GetPolledImage.srv Request:
字段
类型
含义
response_namespace
string
响应话题所在 namespace
timeout
duration
设备采集超时(0 = 无限制)
binning_x/y
uint32
像素合并(驱动可选支持)
roi
RegionOfInterest
感兴趣区域(驱动可选支持)
Response:
字段
类型
含义
success
bool
是否成功捕获
status_message
string
失败时的错误信息
stamp
time
捕获时间戳(与 Image header 一致)
1 2 3 4 5 6 7 8 9 string response_namespace duration timeout uint32 binning_x uint32 binning_y sensor_msgs/RegionOfInterest roi --- bool success string status_message time stamp
6. 核心类:PublicationServer 6.1 公共 API 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class PublicationServer { public: typedef boost::function<void (GetPolledImage::Request&, GetPolledImage::Response&, sensor_msgs::Image&, sensor_msgs::CameraInfo&)> DriverCallback; void shutdown(); std::string getService() const; operator void*() const; }; PublicationServer advertise(ros::NodeHandle& nh, const std::string& service, const PublicationServer::DriverCallback& cb, ...);
驱动侧只需实现 DriverCallback :填充 image/info,设置 rsp.success;失败时填 rsp.status_message。
6.2 内部结构 Impl 1 2 3 4 5 6 7 8 class PublicationServer::Impl { ros::ServiceServer srv_server_; DriverCallback driver_cb_; image_transport::ImageTransport it_; std::map<std::string, image_transport::CameraPublisher> client_map_; // ... };
成员
作用
srv_server_
ROS service 服务端
driver_cb_
驱动注册的采集回调
client_map_
按 response 话题缓存 CameraPublisher
it_
创建 latched CameraPublisher
6.3 服务回调流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 bool requestCallback(GetPolledImage::Request& req, GetPolledImage::Response& rsp) { std::string image_topic = req.response_namespace + "/image_raw"; image_transport::CameraPublisher& pub = client_map_[image_topic]; if (!pub) { pub = it_.advertiseCamera(image_topic, 1, ..., true /*latch*/); } req.binning_x = std::max(req.binning_x, (uint32_t)1); req.binning_y = std::max(req.binning_y, (uint32_t)1); sensor_msgs::Image image; sensor_msgs::CameraInfo info; driver_cb_(req, rsp, image, info); if (rsp.success) { assert(image.header.stamp == info.header.stamp); rsp.stamp = image.header.stamp; pub.publish(image, info); } return true; // ROS service 层始终 true,业务成败看 rsp.success }
要点:
懒创建 Publisher :每个不同的 response_namespace/image_raw 对应一个 CameraPublisher
Latch = true :新订阅者能立即收到最后一帧(适合 polled 单次响应)
binning 归一化 :0 自动改为 1,避免驱动端除零
时间戳约束 :成功时 image.header.stamp == info.header.stamp,并写回 rsp.stamp
6.4 订阅者断开自动清理 1 2 3 4 5 6 void disconnectCallback(const image_transport::SingleSubscriberPublisher& ssp) { if (ssp.getNumSubscribers() == 0) { client_map_.erase(ssp.getTopic()); } }
当某 response 话题订阅数降为 0 时,从 client_map_ 移除对应 CameraPublisher,释放 advertise 资源。该机制依赖 ROS 1 image_transport 的 SubscriberStatusCallback(ROS 2 版 image_transport 尚未实现此回调)。
6.5 生命周期保护 析构时若创建后 < 1ms 即销毁,会 WARN:
1 2 if (ros::WallTime::now().toSec() - constructed_ < 0.001) ROS_WARN("PublicationServer destroyed immediately after creation. Did you forget to store the handle?");
PublicationServer 必须 保存句柄 (类似 ros::Publisher 的 pimpl 引用计数模式)。
7. 示例客户端:poller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int main(int argc, char** argv) { ros::init(argc, argv, "poller", ros::init_options::AnonymousName); // Usage: poller <Hz> camera:=<namespace> output:=<namespace> double hz = boost::lexical_cast<double>(argv[1]); std::string service_name = nh.resolveName("camera") + "/request_image"; ros::ServiceClient client = nh.serviceClient<GetPolledImage>(service_name); req.response_namespace = nh.resolveName("output"); ros::Rate loop_rate(hz); while (nh.ok()) { if (client.call(req, rsp)) std::cout << "Timestamp: " << rsp.stamp << std::endl; else { ROS_ERROR("Service call failed"); client.waitForExistence(); } } }
用法示例(ROS 1):
1 2 rosrun polled_camera poller 1.0 camera:=/my_camera output:=/my_output
注意:poller 只打印 stamp ,并不订阅图像话题;完整客户端需额外 subscribeCamera("output/image_raw", ...)。
8. 典型集成示例 8.1 驱动端(PublicationServer) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void captureCallback (GetPolledImage::Request& req, GetPolledImage::Response& rsp, sensor_msgs::Image& image, sensor_msgs::CameraInfo& info) { if (!hardware.capture (image, info)) { rsp.success = false ; rsp.status_message = "capture timeout" ; return ; } rsp.success = true ; } int main (int argc, char ** argv) { ros::init (argc, argv, "my_polled_camera" ); ros::NodeHandle nh; polled_camera::PublicationServer server = polled_camera::advertise (nh, "request_image" , captureCallback); ros::spin (); }
8.2 客户端(service + subscribe) 1 2 3 4 5 6 7 8 9 10 image_transport::CameraSubscriber sub = it.subscribeCamera ("output_ns/image_raw" , 1 , imageCallback); GetPolledImage srv; srv.request.response_namespace = "output_ns" ; if (client.call (srv) && srv.response.success) { }
推荐顺序:先 subscribe 再 call ,避免 latched 消息在订阅建立前丢失(虽 latch 可缓解,但时序更清晰)。
9. 依赖关系 1 2 3 4 5 6 polled_camera (ROS 1 catkin) ├── roscpp ├── sensor_msgs # Image, CameraInfo, RegionOfInterest ├── std_msgs # 消息生成依赖 ├── message_generation / message_runtime └── image_transport # CameraPublisher + latch + disconnect 回调
与 camera_info_manager 无直接依赖 ;驱动可在 DriverCallback 内自行填充 CameraInfo。
10. 测试与文档
项
状态
单元测试
无
mainpage.dox
有完整协议与代码示例
CHANGELOG
末次 meaningful 更新约 2017(gcc6 修复等)
11. 设计特点与局限
特点
说明
按需采集
节省带宽与传感器寿命
动态 response namespace
同一驱动可服务多客户端命名空间
Latched 发布
适合单次/低频 polled 语义
懒 Publisher + 自动回收
无订阅时不占 advertise 资源
可扩展 request
timeout / ROI / binning 留给驱动解释
局限
说明
未移植 ROS 2
Humble 工作区中不可直接使用
API 未稳定
官方标注 internal / under development
无 Client 库
客户端需手写 service + subscribe
依赖 ROS 1 image_transport 特性
latch、SingleSubscriberPublisher disconnect 回调在 ROS 2 未完整对等
无并发/排队模型
service 同步阻塞,多客户端同时请求时驱动需自行串行化
response_namespace 无校验
任意字符串,需驱动/部署侧保证合法
12. 与 image_common 其他包对比
包
ROS 2
职责
image_transport
✅
图像传输插件框架
camera_info_manager
✅
标定 load/save
camera_calibration_parsers
✅
标定文件解析
polled_camera
❌
按需拉取图像协议(遗留)
现代 ROS 2 相机驱动多采用 连续 publish + trigger service(自定义) 或 action 替代此包;polled_camera 更多是历史兼容(PR2 工业相机生态)。
13. 推荐阅读顺序
srv/GetPolledImage.srv — 请求/响应字段语义
mainpage.dox — 端到端协议
publication_server.cpp 的 requestCallback — 核心状态机
publication_server.h 的 DriverCallback 文档 — 驱动实现契约
poller.cpp — 最小客户端调用
image_transport CameraPublisher — latched 发布与 camera_info 话题规则
14. 小结 polled_camera 是一个 体量很小但语义清晰 的 ROS 1 包:用 GetPolledImage service 触发采集,用 PublicationServer 管理 latched 的 image_transport::CameraPublisher,按 response_namespace 动态创建/销毁话题。它填补了 非连续流相机 的通信模式,但在 ROS 2 Humble 中 尚未端口 ,属于 image_common 中的 遗留源码 ;若要在 ROS 2 实现类似能力,需自行用 rclcpp::Service + image_transport::CameraPublisher(或 action)重写,并处理 ROS 2 下 latch/disconnect 回调缺失问题。
如需,我可以把本文写入 ros2doc/ros-perception/image_common/polled_camera源码详细分析.md,或给出一份 ROS 2 版 PublicationServer 移植 sketch 。
正在加载留言…