ROS:两个节点同时具有发布和订阅图像信息的功能

本文介绍了一个使用ROS实现的图像处理通信实例,包括两个节点:image_node_a和image_node_b。image_node_a负责发布图像,而image_node_b订阅这些图像并对它们进行处理,然后再发布出来。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近来公司实习,开始接触ROS(机器人操作系统),以下为工作笔记,如有不足请各位大牛们指出奋斗

作用描述:

(1)建立两个节点image_node_a 和  image_node_b;

(2)节点image_node_a在一个topic——node_a下发布图像message;

(3)节点image_node_b订阅topic——node_a;

(4)然后节点image_node_b又把收到的信息转换为opencv格式的图像进行处理;

(5)接着节点image_node_b把处理后的信息又在topic——node_b下发布出去;

(6)最后,node_a订阅topic——node_b。

一、节点node_a

在工作空间~/catkin_ws/src下新建一个文件夹——image_node_a,该文件夹下包含一个src的文件夹、一个CMakeLists.txt、一个package.xml,具体内容如下所示:

1  src/image_node_a.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& tem_msg)
{
  try
  {
    cv::imshow("node_a listener from node_b", cv_bridge::toCvShare(tem_msg, "mono8")->image);
    cv::waitKey(30);
  }
  catch (cv_bridge::Exception& e)
  {
    ROS_ERROR("Could not convert from '%s' to 'mono8'.", tem_msg->encoding.c_str());
  }
}

int main(int argc, char** argv)
{
  ros::init(argc, argv, "image_node_a");
  ros::NodeHandle nh;
  image_transport::ImageTransport it(nh);
  image_transport::Publisher pub = it.advertise("node_a", 1);
  image_transport::Subscriber sub = it.subscribe("node_b",1,imageCallback);

  //cv::Mat image = cv::imread(argv[1], CV_LOAD_IMAGE_COLOR);
  cv::Mat image = cv::imread("/home/ding/lena.jpeg", CV_LOAD_IMAGE_COLOR);
  cv::waitKey(30);
  sensor_msgs::ImagePtr msg = cv_bridge::CvImage(std_msgs::Header(), "bgr8", image).toImageMsg();

  ros::Rate loop_rate(5);
  while (nh.ok()) {
    pub.publish(msg);
    ros::spinOnce();
    loop_rate.sleep();
  }
}

2  CMakeLists.txt

cmake_minimum_required(VERSION 2.8.3)
project(image_node_a)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
if(WIN32 AND NOT CYGWIN)
    set(HOME $ENV{PROFILE})
else()
    set(HOME $ENV{HOME})
endif()

find_package(catkin REQUIRED COMPONENTS
 roscpp
 rospy
 std_msgs

 sensor_msgs
 cv_bridge
 image_transport
)

find_package(OpenCV REQUIRED)

catkin_package(CATKIN_DEPENDS
roscpp
std_msgs

sensor_msgs
)

include_directories(
include
${OpenCV_INCLUDE_DIRS}
${catkin_INCLUDE_DIRS}
)


### find files

file(GLOB_RECURSE HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h*)
file(GLOB_RECURSE SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c*)

add_executable(image_node_a
${HEADER_FILES}
${SOURCE_FILES}
)

target_link_libraries(image_node_a
${catkin_LIBRARIES}
${OpenCV_LIBRARIES}

)




3  package.xml

<?xml version="1.0"?>
<package>
  <name>image_node_a</name>
  <version>0.0.0</version>
  <description>The image_node_a package</description>
  <maintainer email="abc@ding.com">abc</maintainer>
  <license>TODO</license>

   <build_depend>message_generation</build_depend> 
   <run_depend>message_runtime</run_depend>


   <build_depend>opencv2</build_depend>
   <run_depend>opencv2</run_depend>


  <buildtool_depend>catkin</buildtool_depend>
   
  <build_depend>roscpp</build_depend>
  <build_depend>sensor_msgs</build_depend>
  <build_depend>std_msgs</build_depend>
  <build_depend>cv_bridge</build_depend>
  <build_depend>image_transport</build_depend>
 
  <run_depend>roscpp</run_depend>
  <run_depend>sensor_msgs</run_depend>
  <run_depend>std_msgs</run_depend>
  <run_depend>cv_bridge</run_depend>
  <run_depend>image_transport</run_depend>

  <export>
  </export>
</package>

二、节点node_b

在工作空间~/catkin_ws/src下新建一个文件夹——image_node_b,该文件夹下包含一个src的文件夹、一个CMakeLists.txt、一个package.xml,具体内容如下所示:

1  image_node_b.cpp

    #include "ros/ros.h"  
    #include "image_transport/image_transport.h"  
    #include "cv_bridge/cv_bridge.h"  
    #include "sensor_msgs/image_encodings.h"  
    #include <opencv2/imgproc/imgproc.hpp>  
    #include <opencv2/highgui/highgui.hpp>  
    #include <iostream>  
      
    namespace enc = sensor_msgs::image_encodings;  
      
    /*准备再次发布的图像显示到本窗口*/  
    //static const char OUT_WINDOW[] = "Image Out";  
    /*读取订阅的图像并显示到本窗口*/  
    //static std::string IN_WINDOW = "Image In";  
      
    class ImageConvertor  
    {  
        ros::NodeHandle nh_;  
        image_transport::ImageTransport it_;  
        image_transport::Subscriber image_sub_;  
        image_transport::Publisher image_pub_;  
          
        public:  
        ImageConvertor():it_(nh_){  
            /*发布主题out*/  
            image_pub_ = it_.advertise("node_b", 1); 
                    /*订阅主题camera/image*/  
            image_sub_ = it_.subscribe("node_a", 1, &ImageConvertor::ImageCb, this);  
            //cv::namedWindow(OUT_WINDOW, CV_WINDOW_AUTOSIZE);  
            //cv::namedWindow(IN_WINDOW, CV_WINDOW_AUTOSIZE);  
        }  
      
        ~ImageConvertor()  
        {  
            //cv::destroyWindow(IN_WINDOW);  
            //cv::destroyWindow(OUT_WINDOW);  
        }  
      
        void ImageCb(const sensor_msgs::ImageConstPtr& msg)  
        {  
            cv_bridge::CvImagePtr cv_ptr;  
      
            try  
            {  
                 /*转化成CVImage*/  
                         cv_ptr = cv_bridge::toCvCopy(msg, enc::BGR8);  
                         cv::imshow("node_b listener from node_a",cv_ptr->image);
                         cv::waitKey(30);
            }  
      
            catch (cv_bridge::Exception& e)  
            {  
                ROS_ERROR("cv_bridge exception is %s", e.what());  
                return;  
            }  
      
            if(cv_ptr->image.rows > 60 && cv_ptr->image.cols > 60)  
              cv::circle(cv_ptr->image, cv::Point(50,50), 10, CV_RGB(255,0,0));  
      
            //cv::imshow(IN_WINDOW, cv_ptr->image);  
            cv::Mat img_out;  
      
                    /*convert RGB to GRAY*/  
                    cv::cvtColor(cv_ptr->image, img_out, CV_RGB2GRAY);  
            //cv::imshow(OUT_WINDOW, img_out);  
            cv::imshow("node_b talker to node_c",img_out);
            cv::waitKey(3);  
                    /*转化成ROS图像msg发布到主题out*/  
            image_pub_.publish(cv_ptr->toImageMsg());  
        }  
      
      
    };  
      
      
    int main(int argc, char** argv)  
    {  
        ros::init(argc, argv, "image_node_b");  
        ImageConvertor ic;  
        ros::spin();  
          
        return 0;  
    }


2  CMakeLists.txt

cmake_minimum_required(VERSION 2.8.3)
project(image_node_b)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
if(WIN32 AND NOT CYGWIN)
    set(HOME $ENV{PROFILE})
else()
    set(HOME $ENV{HOME})
endif()

find_package(catkin REQUIRED COMPONENTS
 roscpp
 rospy
 std_msgs

 sensor_msgs
 cv_bridge
 image_transport
)

find_package(OpenCV REQUIRED)

catkin_package(CATKIN_DEPENDS
roscpp
std_msgs
)

include_directories(
include
${OpenCV_INCLUDE_DIRS}
${catkin_INCLUDE_DIRS}
)


### find files

file(GLOB_RECURSE HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h*)
file(GLOB_RECURSE SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c*)

add_executable(image_node_b
${HEADER_FILES}
${SOURCE_FILES}
)

target_link_libraries(image_node_b
${catkin_LIBRARIES}
${OpenCV_LIBRARIES}

)




3  package.xml

<?xml version="1.0"?>
<package>
  <name>image_node_b</name>
  <version>0.0.0</version>
  <description>The image_node_b package</description>
  <maintainer email="abc@ding.com">abc</maintainer>
  <license>TODO</license>

   <build_depend>message_generation</build_depend> 
   <run_depend>message_runtime</run_depend>


   <build_depend>opencv2</build_depend>
   <run_depend>opencv2</run_depend>


  <buildtool_depend>catkin</buildtool_depend>
   
  <build_depend>roscpp</build_depend>
  <build_depend>sensor_msgs</build_depend>
  <build_depend>std_msgs</build_depend>
  <build_depend>cv_bridge</build_depend>
  <build_depend>image_transport</build_depend>
 
  <run_depend>roscpp</run_depend>
  <run_depend>sensor_msgs</run_depend>
  <run_depend>std_msgs</run_depend>
  <run_depend>cv_bridge</run_depend>
  <run_depend>image_transport</run_depend>

  <export>
  </export>
</package>

三、编译节点

分别按照如下步骤在终端上运行:

export ROS_PACKAGE_PATH=~/catkin_ws/src:$ROS_PACKAGE_PATH 

roscd image_node_a

cd ../..

catkin_make

四、运行节点


1   打开一个新的终端


roscore


2 再打开一个新的终端


cd ~/catkin_ws
source ./devel/setup.bash
rosrun image_node_a  image_node_a

3  再打开一个新的终端


cd ~/catkin_ws
source ./devel/setup.bash
rosrun image_node_b  image_node_b

到此终于大功告成了, 你可以看到如下的结果:




参考文献:


1 http://blog.youkuaiyun.com/github_30605157/article/details/50990493

2  http://blog.youkuaiyun.com/x_r_su/article/details/52704193

3 http://wiki.ros.org/image_transport/Tutorials

4 http://wiki.ros.org/ROS/Tutorials/WritingPublisherSubscriber%28c%2B%2B%29



<think>好的,用户现在问的是ROS话题的发布订阅节点是单向还是双向通信。之前他们问过ROSROS2的区别,所以我需要根据之前的回答来调整现在的回应,特别是因为ROSROS2在通信机制上有很大不同。 首先,我得确认用户指的是ROS1还是ROS2,但问题里没有明确说明。可能用户对ROS的基础概念不太清楚,所以需要分别解释两种情况,或者明确说明两者的差异。不过通常,当提到ROS话题时,默认可能是ROS1,但根据之前的讨论,用户可能对ROS2也感兴趣,所以应该涵盖两者。 接下来,回忆ROS1的通信机制。ROS1的话题是基于发布-订阅模型的,这是单向的。发布者发送消息,订阅者接收,没有直接的反馈机制。服务(Service)才是双向的,请求-响应模式。所以需要明确区分话题服务的不同。 对于ROS2,虽然底层使用了DDS,但话题的机制基本上还是单向的,不过因为DDS支持更多的QoS策略,可能在可靠性实时性上有增强,但本质上发布订阅还是单向的。不过ROS2的参数服务动作库可能更复杂,但用户的问题集中在话题,所以不需要涉及那些。 用户可能混淆了单向双向的概念,或者想了解是否在某些情况下话题可以实现双向。比如,如果有两个话题,一个发布,一个订阅,那么两个节点之间可以互相发布订阅,形成双向通信,但这其实是两个独立的单向通道,而不是同一个话题的双向。 另外,用户可能是在实际应用中遇到了需要双向通信的情况,所以想知道是否可以通过话题实现,还是需要使用服务。这时候需要建议他们根据需求选择合适的通信方式:单向用话题,双向用服务或动作。 需要确保回答准确,同时避免过于技术化,用简单明了的例子说明。例如,在ROS1中,话题是单向的,就像电台广播,听众不能直接回复电台。而服务像打电话,有问有答。 同时,可能需要提到ROS2是否有什么不同,但根据现有的知识,ROS2的话题机制在方向性上没有改变,仍然是单向,但增强了可靠性灵活性。因此,在回答中需要区分ROS1ROS2的情况,但指出在基本机制上话题都是单向的。 最后,检查是否有其他可能的误解,比如动作(Action)虽然基于话题服务,但提供了更复杂的交互,但这可能超出用户当前的问题范围,可以简要提及以供扩展。</think>在ROS(包括ROS 1ROS 2)中,**话题(Topic)的发布-订阅模型本质上是单向通信**。这是其核心设计特点,具体分析如下: --- ### **一、单向通信机制** #### 1. **数据流向** - **发布者(Publisher)** → 单向推送 → **订阅者(Subscriber)** - 类比:类似电视台(发布者)广播节目,观众(订阅者)只能接收信号,无法直接反向发送数据 #### 2. **典型应用场景** - 传感器数据流(如激光雷达点云、摄像头图像) - 状态监控信息(如机器人关节角度实时显示) - 控制指令下发(如导航目标点发送) #### 3. **技术实现** **ROS 1示例**: ```python # 发布节点(无返回通道) pub = rospy.Publisher('lidar_data', LaserScan, queue_size=10) # 订阅节点(仅接收) rospy.Subscriber('lidar_data', LaserScan, callback_function) ``` **ROS 2示例**(DDS加持但方向不变): ```cpp // 发布者 auto publisher = node->create_publisher<sensor_msgs::msg::LaserScan>("lidar_data", 10); // 订阅者 auto subscription = node->create_subscription<sensor_msgs::msg::LaserScan>( "lidar_data", 10, callback_function); ``` --- ### **二、与双向通信的对比** | 特性 | 话题(Topic) | 服务(Service)/动作(Action) | |---------------------|----------------------------|-------------------------------| | **通信方向** | 单向广播 | 双向交互(请求-响应) | | **数据时效性** | 持续流式数据 | 按需触发 | | **典型延迟** | 毫秒级 | 更高(需等待响应) | | **ROS 2 QoS支持** | 可配置可靠性/实时性 | 仅Basic Reliability | --- ### **三、常见误解澄清** #### 场景:两个节点互相发布/订阅 - **现象**:节点A订阅`topic_X`,同时发布`topic_Y`;节点B反之 - **本质**:这是**两个独立单向通道**,不是真正意义上的双向通信 - **风险**:可能引发循环依赖(如控制指令震荡) #### 正确双向方案选择: 1. **服务(Service)**:适合短时请求-响应(如查询传感器状态) ```bash ros2 service call /get_battery_status std_srvs/srv/Trigger ``` 2. **动作(Action)**:适合长时任务(如导航到目标点+进度反馈) ```python # 客户端发送目标,服务端反馈执行状态 action_client.send_goal(pose_target, feedback_callback=...) ``` --- ### **四、ROS 2的增强特性** 虽然话题保持单向性,但通过DDS的**QoS策略**可实现: 1. **可靠传输**:确保数据必达(如`RELIABLE`模式) 2. **截止时间监控**:设置`DEADLINE`检测通信延迟 3. **存活检测**:通过`LIVELINESS`策略感知节点状态 --- **总结**: ROS话题是专为流式数据设计的**单向通信**机制,若需要双向交互应选择服务或动作。这种设计使系统解耦更彻底——例如自动驾驶中,摄像头节点只管发布图像,而无需关心哪些模块在使用这些数据。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值