ROS下Nodelet与Topic通信数据传输速度的比较 - maohaihua/ros_study GitHub Wiki

https://blog.csdn.net/weixin_41478936/article/details/80420527

在ROS当中,Node与Node之间通过Topic进行通讯是十分普遍的一种方式,但是由于Topic通信方式存在一定的延时和阻塞,虽然在数据量较小的情况下可以正常运行,但针对于数据量较大的Message便会存在较大的消息延迟现象。而Nodelet的出现在一定程度上解决了这个问题,它实现了在同一个进程中运行多个算法程序,从而达到零拷贝的数据传输。本文将针对于图像的传输在Topic通信与Nodelet方式之间进行传输速度的比较。

所有代码我已经放在github上,有需要的朋友可以查看:https://github.com/josephxue/transport_speed_test.git

比较方法:

本次传输的图像为高清摄像头设置的1080P图像,大小在1.7M~3.3M之间

1.新建imagePub.cpp图像发布节点与imageSub.cpp图像接收节点,两者通过camera/image话题进行通讯,并记录消息发布与接收的两个时间戳之差。

2.新建imageNodeletSub.cpp与imageNodeletPub定义两个继承于nodelet::Nodelet的子类,使用Nodelet的方式进行通讯,并记录消息发布与接收的两个时间戳之差。

图像处理与通讯:

在发布节点当中首先使用OpenCV读入图像文件,并通过cv_bridge::CvImage.toImageMsg()将其转换为ROS图像信息,之后将其发布到camera/image上,发布完成之后使用ROS_INFO输出时间戳。在订阅节点当中设置回调函数,在接收到图像消息后使用ROS_INFO输出时间戳。 过程介绍:

1.创建软件包transport_speed_test

首先进入你的工作空间,我的工作空间为catkin_ws,并切换的src文件夹,使用catkin_create_pkg创建软件包,并依赖于image_transport、cv_bridge以及nodelet

$ cd catkin_ws/ $ cd src/ $ catkin_create_pkg transport_speed_test image_transport cv_bridge nodelet

之后编译该功能包

$ cd .. $ catkin_make

编译完成之后更新环境变量

$ source devel/setup.sh

2.1编写使用Topic通信的发布者节点

新建并进入src文件夹新建名为imageSpeedPub.cpp的文件并打开

$ mkdir src $ cd src $ vi imageSpeedPub.cpp

将下列的代码复制进去

#include <ros/ros.h> #include <image_transport/image_transport.h> #include <opencv2/highgui/highgui.hpp> #include <cv_bridge/cv_bridge.h> #include

using namespace std;

int main(int argc, char** argv) { //节点初始化 ros::init(argc, argv, "image_publisher"); ros::NodeHandle nh; //表示当前读取图片位置 int filePos = 1; //定义发布话题与缓存区大小 image_transport::ImageTransport it(nh); image_transport::Publisher pub = it.advertise("camera/image", 1); //主循环频率为10Hz ros::Rate loop_rate(10); //主循环开始 while (nh.ok()) { //五张图片读取完毕后回到第一张图片 if(filePos == 6) { filePos = 1; } //生成读取图片路径 stringstream stream; stream << "/home/josephxue/catkin_ws/src/transport_speed_test/Pictures/" << filePos << ".png"; string fileName = stream.str(); //图片读入以及转换为ROS消息 cv::Mat image = cv::imread(fileName, CV_LOAD_IMAGE_COLOR); if(!image.data) { cout << "Picture loading failed !" << endl; return -1; } sensor_msgs::ImagePtr msg = cv_bridge::CvImage(std_msgs::Header(), "bgr8", image).toImageMsg(); //消息发布并输出时间戳 pub.publish(msg); ROS_INFO("The image has been published"); //休眠至0.1s后继续循环 filePos ++; loop_rate.sleep(); } }

2.2编写使用Topic通信的订阅者节点

进入src文件夹新建名为imageSpeedSub.cpp的文件并打开

$ cd src $ vi imageSpeedSub.cpp

将下面的代码复制进去

#include <ros/ros.h> #include <image_transport/image_transport.h> #include <opencv2/highgui/highgui.hpp> #include <cv_bridge/cv_bridge.h>

//回调函数 void imageCallback(const sensor_msgs::ImageConstPtr& msg) { //在窗口显示接收到的图片并输出时间戳 try { cv::imshow("view", cv_bridge::toCvShare(msg, "bgr8")->image); ROS_INFO("The image has been subscribed"); } catch (cv_bridge::Exception& e) { ROS_ERROR("Could not convert from '%s' to 'bgr8'.", msg->encoding.c_str()); } }

int main(int argc, char **argv) { //节点初始化 ros::init(argc, argv, "image_listener"); ros::NodeHandle nh; //定义订阅话题,缓存区大小以及回调函数 image_transport::ImageTransport it(nh); image_transport::Subscriber sub = it.subscribe("camera/image", 1, imageCallback); //新建窗口 cv::namedWindow("view"); cv::startWindowThread(); //进入死循环,接收到消息会自动执行回调函数 ros::spin(); //关闭窗口 cv::destroyWindow("view");

    return 0;

}

2.3配置CMakeList.txt和package.xml

在CMakeList.txt的相应位置添加下列语句

#这里视实际情况而定 set(OpenCV_DIR ~/opencv/build)

find_package(OpenCV)

include_directories(include ${OpenCV_INCLUDE_DIRS})

add_executable(imagePub src/imagePub.cpp) target_link_libraries(imagePub ${catkin_LIBRARIES}) #这里请链接你自己的OpenCV库路径 target_link_libraries(imagePub ${OpenCV_LIBS})

add_executable(imageSub src/imageSub.cpp) target_link_libraries(imageSub ${catkin_LIBRARIES}) #这里请链接你自己的OpenCV库路径 target_link_libraries(imageSub ${OpenCV_LIBS})

在package.xml中添加下列代码

<build_depend>opencv2</build_depend> <exec_depend>opencv2</exec_depend>

2.4编译并运行

切换到工作空间目录下进行编译

$ cd ~/catkin_ws $ catkin_make

之后打开多个终端,在各个终端中分别运行以下命令

$ roscore $ rosrun transport_speed_test imageSub $ rosrun transport_speed_test imagePub

然后程序便可以正常运行,通过视窗可以确认图片能够被接收

通过消息发布与接收的时间戳来看,8次传输平均每次时间在5.25ms左右

3.1编写Nodelet发布节点子类

进入src文件夹新建imageNodeletPublisher.cpp并打开

$ cd src $ vi imageNodeletPublisher.cpp

将以下代码拷贝进去

#include <ros/ros.h> #include <image_transport/image_transport.h> #include <opencv2/highgui/highgui.hpp> #include <cv_bridge/cv_bridge.h> #include <nodelet/nodelet.h> #include <pluginlib/class_list_macros.h> #include

using namespace std;

namespace transport_speed_test { class imageNodeletPublisher : public nodelet::Nodelet { public: imageNodeletPublisher() {} private: virtual void onInit() { //生成句柄 ros::NodeHandle& nh = getNodeHandle(); //表示图片读取位置 int filePos = 1; //定义发布话题以及缓存区大小 image_transport::ImageTransport it(nh); pub = it.advertise("camera/image", 1); //设置主循环频率为10Hz ros::Rate loop_rate(10); //进入主循环 while (nh.ok()) { //五张图片全部读取完毕后回到第一张图片 if(filePos == 6) { filePos = 1; } //生成图片路径 stringstream stream; stream << "/home/josephxue/catkin_ws/src/transport_speed_test/Pictures/" << filePos << ".png"; string fileName = stream.str(); //读取图片并转换为ROS消息 cv::Mat image = cv::imread(fileName, CV_LOAD_IMAGE_COLOR); if(!image.data) { cout << "Picture loading failed !" << endl; ros::shutdown(); } sensor_msgs::ImagePtr msg = cv_bridge::CvImage(std_msgs::Header(), "bgr8", image).toImageMsg(); //发布消息并输出时间戳 pub.publish(msg); ROS_INFO("The image has been published"); //休眠至0.1s后继续循环 ros::spinOnce(); filePos ++; loop_rate.sleep(); } } image_transport::Publisher pub; }; //输出为动态库 PLUGINLIB_EXPORT_CLASS(transport_speed_test::imageNodeletPublisher, nodelet::Nodelet) }

3.2编写Nodelet订阅节点子类

进入src文件夹新建imageNodeletSubscriber.cpp并打开

$ cd src $ vi imageNodeletSubscriber.cpp

将下面的代码复制进去

#include <ros/ros.h> #include <image_transport/image_transport.h> #include <opencv2/highgui/highgui.hpp> #include <cv_bridge/cv_bridge.h> #include <nodelet/nodelet.h> #include <pluginlib/class_list_macros.h>

namespace transport_speed_test { class imageNodeletSubscriber : public nodelet::Nodelet { public: imageNodeletSubscriber() {} ~imageNodeletSubscriber() { //析构函数中关闭窗口 cv::destroyWindow("view"); ros::shutdown(); } private: virtual void onInit() { //生成句柄 ros::NodeHandle& nh = getNodeHandle(); //定义订阅话题,缓存区大小以及回调函数 image_transport::ImageTransport it(nh); sub = it.subscribe("camera/image", 1, &imageNodeletSubscriber::imageCallback, this); //新建窗口 cv::namedWindow("view"); cv::startWindowThread(); } void imageCallback(const sensor_msgs::ImageConstPtr& msg) { //显示接收到的图片并输出时间戳 try { cv::imshow("view", cv_bridge::toCvShare(msg, "bgr8")->image); ROS_INFO("The image has been subscribed"); } catch (cv_bridge::Exception& e) { ROS_ERROR("Could not convert from '%s' to 'bgr8'.", msg->encoding.c_str()); } } image_transport::Subscriber sub; }; //输出为动态库 PLUGINLIB_EXPORT_CLASS(transport_speed_test::imageNodeletSubscriber, nodelet::Nodelet) }

3.3进行相关配置

在工程目录下新建imageNodeletTransport.xml

touch imageNodeletTransport.xml gedit imageNodeletTransport.xml

在其中复制下列代码,这个xml文件主要记录了动态库的相关信息

This is a nodelet class to publish the images. This is a nodelet class to subscribe and show the images.

之后进入package.xml文件,将下列代码添加进去

之后向CMakeLists.txt中添加如下代码

add_library(transport_speed_test src/imageNodeletSubscriber.cpp src/imageNodeletPublisher.cpp) target_link_libraries(transport_speed_test ${catkin_LIBRARIES})

3.4编译并运行

切换到工作空间目录下进行编译

$ cd ~/catkin_ws $ catkin_make

之后打开多个终端,在各个终端中分别运行以下命令

$ roscore $ rosrun nodelet nodelet manager __name:=nodelet_manager $ rosrun nodelet nodelet load transport_speed_test/imageNodeletSubscriber nodelet_manager _output:=screen $ rosrun nodelet nodelet load transport_speed_test/imageNodeletPublisher nodelet_manager _output:=screen

然后程序便可以正常运行,通过视窗可以确认图片能够被接收

通过消息发布与接收的时间戳来看,8次传输平均每次时间在2.875ms左右

4.结论

通过上面的比较发现,使用Nodelet这种方法进行通信在速度上确实有一定的优势,但是这个例子上表现不是特别明显。这可能是因为即便是图像传输,这里的模拟只是限于单节点订阅,在这种情况下使用Topic也可以取得较为理想的传输速度。但是在多节点订阅的情况下,Topic方式会进行多次的消息拷贝,这将会比内存共享的Nodelet方式产生较大的速度差异,这种情况还有待后续的实验。

⚠️ **GitHub.com Fallback** ⚠️