polled_camera 源码详细分析

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 遗留包

状态
构建系统 catkinbuildtool_depend: catkin
C++ API roscppros::NodeHandleboost::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相机驱动 DriverCallbackimage_transportServiceClient\nrequest_imageCameraSubscriber\n订阅 response 话题PublicationServerGetPolledImage 服务硬件触发 / 采集CameraPublisher\nlatched
对比 streaming 驱动 polled 驱动
触发 定时/连续 publish 客户端 service 请求才拍
话题 固定 namespace 持续发布 response_namespace 动态创建
带宽 持续占用 按需,适合低频/同步采集
典型场景 USB webcam 工业相机、PR2 Prosilica

PR2 URDF 中仍有遗留引用:/prosilica/request_image(见 pr2_desc.urdfpollServiceName)。


4. 通信协议(mainpage.dox)

  1. 驱动 advertise 服务:<camera>/request_image
  2. 客户端调用 service,在 request 中指定 response_namespace
  3. 驱动捕获图像,在 response 中返回 stamp
  4. 驱动将 ImageCameraInfo latched 发布到:
    • <response_namespace>/image_raw
    • <response_namespace>/camera_info(由 image_transport 约定推导)
  5. 客户端订阅上述话题,用 rsp.stampImage.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_transportSubscriberStatusCallback(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
# 调用 /my_camera/request_image,响应发布到 /my_output/image_raw

注意: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)
{
// 按 req.timeout / roi / binning 配置硬件
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
// 1. 先订阅
image_transport::CameraSubscriber sub =
it.subscribeCamera("output_ns/image_raw", 1, imageCallback);

// 2. 再请求拍图
GetPolledImage srv;
srv.request.response_namespace = "output_ns";
if (client.call(srv) && srv.response.success) {
// 用 srv.response.stamp 匹配 callback 中的 frame
}

推荐顺序:先 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. 推荐阅读顺序

  1. srv/GetPolledImage.srv — 请求/响应字段语义
  2. mainpage.dox — 端到端协议
  3. publication_server.cpprequestCallback — 核心状态机
  4. publication_server.h 的 DriverCallback 文档 — 驱动实现契约
  5. poller.cpp — 最小客户端调用
  6. 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

文章互动

阅读 --

留言

0 条留言

正在加载留言…