| Note: This tutorial assumes that you have completed the previous tutorials: image_transport Beginner Tutorials, pluginlib. |
Writing a New Transport
Description: This tutorials covers how to write publisher and subscriber plugins for a new image transport option.Tutorial Level: INTERMEDIATE
NOTE: This tutorial is incomplete.
Contents
Overview
The real power of image_transport comes from its extensibility. Nodes using image_transport to publish and receive images can take advantage of new transports without so much as a recompile. In this tutorial we will see how to write our own transport for a very simple kind of "compression": subsampling on the publisher side, then supersampling back to the original dimensions on the client side.
There are four steps to writing a transport:
Define your transport-specific message type. For example, a streaming video transport would use some Packet type for internal communication.
Write your publisher plugin. This encodes a sensor_msgs/Image into one or more instances of the transport-specific message type and publishes them on the dedicated transport topic.
Write your subscriber plugin. This listens on the transport topic and decodes the transport-specific messages back into sensor_msgs/Image messages passed to the user callback.
Register your transport plugins so that image_transport can find them.
The above outline assumes that your transport uses a single ROS topic for all publisher-subscriber communication, which should normally be the case. This assumption allows us to use the C++ base classes SimplePublisherPlugin and SimpleSubscriberPlugin. These classes take care of all the scaffolding and bookkeeping so that we can simply get on with writing our encoding and decoding functions. If for some reason your transport requires a more exotic communication structure, it is possible (but much less pleasant!) to subclass the lower-level base classes PublisherPlugin and SubscriberPlugin instead.
Defining the Message Type
Create msg/ResizedImage.msg in package learning_image_transport with your favorite editor, and place the following inside it:
uint32 original_height uint32 original_width sensor_msgs/Image image
Our message is very simple: we store the subsampled image along with its original dimensions, so that we can later restore it (with some loss of information, of course).
Writing a Simple Publisher Plugin
The Code
Create include/learning_image_transport/resized_publisher.h and place the following inside it:
1 #include <image_transport/simple_publisher_plugin.h>
2 #include "learning_image_transport/ResizedImage.h"
3
4 class ResizedPublisher : public image_transport::SimplePublisherPlugin<learning_image_transport::ResizedImage>
5 {
6 public:
7 virtual std::string getTransportName() const
8 {
9 return "resized";
10 }
11
12 protected:
13 virtual void publish(const sensor_msgs::Image& message,
14 const PublishFn& publish_fn) const;
15 };
Next create src/resized_publisher.cpp and place the following inside it:
1 #include "learning_image_transport/resized_publisher.h"
2 #include <opencv/cvwimage.h>
3 #include <opencv_latest/CvBridge.h>
4
5 void ResizedPublisher::publish(const sensor_msgs::Image& message,
6 const PublishFn& publish_fn) const
7 {
8 sensor_msgs::CvBridge bridge;
9 if (!bridge.fromImage(message, "bgr8")) {
10 ROS_ERROR("Could not convert from '%s' to 'bgr8'.", message.encoding.c_str());
11 return;
12 }
13 const IplImage* cv_image = bridge.toIpl();
14
15 // Retrieve subsampling factor from the parameter server
16 double subsampling_factor;
17 std::string param_name;
18 if (!nh().searchParam("resized_image_transport_subsampling_factor", param_name) ||
19 !nh().getParam(param_name, subsampling_factor, true))
20 subsampling_factor = 2.0;
21
22 // Rescale image
23 int new_width = cv_image->width / subsampling_factor + 0.5;
24 int new_height = cv_image->height / subsampling_factor + 0.5;
25 cv::WImageBuffer3_b buffer(new_width, new_height);
26 cvResize(cv_image, buffer.Ipl());
27
28 // Set up ResizedImage and publish
29 learning_image_transport::ResizedImage resized_image;
30 resized_image.original_height = cv_image->height;
31 resized_image.original_width = cv_image->width;
32 if (sensor_msgs::CvBridge::fromIpltoRosImage(buffer.Ipl(), resized_image.image, "bgr8"))
33 publish_fn(resized_image);
34 }
The Code Explained
Starting with resized_publisher.h:
We include the headers for our base class and transport-specific message type.
4 class ResizedPublisher : public image_transport::SimplePublisherPlugin<learning_image_transport::ResizedImage>
We declare our publisher plugin class, inheriting from SimplePublisherPlugin. SimplePublisherPlugin is templated on the transport-specific message type.
SimplePublisherPlugin requires us to implement only two functions, and one of them is dead simple! getTransportName() returns the name of the transport; we have chosen "resized".
The other required function is an internal (protected) version of publish(). This is where we encode the sensor_msgs/Image argument into a ResizedImage and publish it. But what is that PublishFn argument? This type is defined by SimplePublisherPlugin. PublishFn is a function object that takes our transport-specific message type as an argument and publishes it using some ROS primitive.
Why this indirection? It turns out that in ROS there are two distinct ways to publish messages to a subscriber. The normal way is to broadcast a message to all subscribers; the other way, seldom used, is to publish a message to a single subscriber within a subscriber status callback. SimplePublisherPlugin performs some tricky plumbing to use our implementation of publish() in both cases.
For the details of publish(), let's turn to resized_publisher.cpp:
Writing a Simple Subscriber Plugin
The Code
Create include/learning_image_transport/resized_subscriber.h and place the following inside it:
Next create src/resized_subscriber.cpp and place the following inside it:
The Code Explained
Registering the Transport Plugins
We have written our transport plugins, but some steps remain to register them with ROS so that image_transport can find them. For details on these steps, see pluginlib; below we will note only features of special relevance to image_transport.
Adding to the Plugin List
Create src/manifest.cpp and place the following inside it:
1 #include <pluginlib/class_list_macros.h>
2 #include "learning_image_transport/resized_publisher.h"
3 #include "learning_image_transport/resized_subscriber.h"
4
5 PLUGINLIB_REGISTER_CLASS(resized_pub, ResizedPublisher, image_transport::PublisherPlugin)
6
7 PLUGINLIB_REGISTER_CLASS(resized_sub, ResizedSubscriber, image_transport::SubscriberPlugin)
We register our plugin classes as resized_pub and resized_sub. These names follow an important convention; image_transport assumes that for a transport named <foo>, the publisher and subscriber plugins have respective lookup names <foo>_pub and <foo>_sub.
We register our plugins as instances of the low-level base classes PublisherPlugin and SubscriberPlugin. The "simple" base classes we used in our code subclass PublisherPlugin and SubscriberPlugin to provide a more convenient high-level interface.
Writing the Plugin Description File
Now create resized_plugins.xml in the learning_image_transport/ directory and place the following inside it:
<library path="lib/libresized_image_transport">
<class name="resized_pub" type="ResizedPublisher" base_class_type="image_transport::PublisherPlugin">
<description>
This plugin publishes a decimated version of the image.
</description>
</class>
<class name="resized_sub" type="ResizedSubscriber" base_class_type="image_transport::SubscriberPlugin">
<description>
This plugin rescales a decimated image to its original size.
</description>
</class>
</library>This file helps ROS to automatically discover, load and reason about our plugins.
Exporting the Plugins
Finally, we must point to our plugin description file within the package Manifest. Insert the following lines in your manifest.xml:
Building Your Transport
In your CMakeLists.txt, uncomment rosbuild_genmsg() (to generate our ResizedImage message) and build our plugins as a library:
#uncomment if you have defined messages rosbuild_genmsg() # SNIP rosbuild_add_library(resized_image_transport src/resized_publisher.cpp src/resized_subscriber.cpp src/manifest.cpp)






