Skip to content

话题

想象我们已经有两个节点,NodeA在机载相机上,负责采集图像,它需要将图像数据发送给负责处理图像的NodeB。

ROS2里,采用话题通信

  • 发布、订阅模型 :一个节点发布数据,就是发布者,另一个节点接收数据,就是订阅者。二者之间传输的数据的模式也需是确定的
  • 订阅者或发布者可不唯一
  • 异步通信
  • .msg文件定义通信的消息结构

创建发布者节点

我们可先创建一个新的python功能包,就叫它learning_topic。命令和结构上一节讲过。

然后在learning_topic/learining_topic目录里新建topic\_helloworld\_pub.py

python

import rclpy

from rclpy.node import Node

from std_msgs.msg import String		# 导入字符串消息类型



class PublisherNode(Node):

    def __init__(self,name):

        super().__init__(name)

	    self.pub = self.create_publisher(String,"chatter",10)

	    self.timer = self.create_timer(0.5,self.timer_callback)



    def timer_callback(self):

        msg = String()

    	msg.data = "Hello World"

    	self.pub.publish(msg)

    	self.get_logger().info("Publishing:"%s"" % msg.data)
	



def main(args=None):

    rclpy.init(args=args)

    node = PublisherNode("topic_helloworld_pub")

    rclpy.spin(node)

    node.destroy_node()

    rclpy.shutdown()

create_publisher:创建发布者对象的函数。 三个参数分别表示:消息类型,话题名,队列长度

create_timer: 创建定时器的函数。两个参数分别为:单位为秒的周期,定时器执行的回调函数。每隔一个周期就会执行一次回调函数

spin: 有了它就不用写while循环,自动循环等待ROS2退出。它会不断查询消息队列,一旦有话题数据出现就会自动跳转回调函数进行处理

创建订阅者节点

新建topic_helloworld_sub.py

python

import rclpy

from rclpy.node import Node

from std_msgs.msg import String



class SubscriberNode(Node):

    def __init__(self,name):

	    super().__init__(name)

	    self.sub = self.create_subscription(String,"chatter",self.listener_callback,10)

	

def listener_callback(self,msg):
    self.get_logger().info("I heard:"%s" " % msg.data)


def main(args=None):
    rclpy.init(args=args)
    node = SubscriberNode("topic_helloworld_sub")
    rclpy.spin(node)
    node.destory_node()
    rclpy.shutdown()

由于话题的名称是唯一的(此处为chatter),所以只要保证传参时没写错话题名称,订阅者就能自动获取发布者发来的msg

create_subscription: 创建订阅者对象,四个参数分别为: 消息类型,话题名,订阅者的回调函数(每次接收到发布者的消息就会调用一次),队列长度。

运行话题

先别忘了在setup.py的console_scripts列表里添加

python
    'topic_helloworld_pub  = learning_topic.topic_helloworld_pub:main',
    'topic_helloworld_sub  = learning_topic.topic_helloworld_sub:main',

话题通信,编译后再运行:

bash
ros2 run learning_topic topic_helloworld_pub
ros2 run learning_topic topic_helloworld_sub

以上是节点的基本用法

官方提供了各种现成的话题和节点,比如USB相机驱动:

bash
sudo apt install ros-humble-usb-cam

安装完后运行:

bash
ros2 run usb_cam usb_cam_node_exe

你可以自己写一个简单的订阅者节点,仅仅展示相机捕获的画面:

python
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image   # 图像消息类型
from cv_bridge import CvBridge  # ROS与OpenCV图像类型转换类
import cv2 as cv
import numpy as np

class ImageSubscriber(Node):
    def __init__(self,name):
        super().__init__(name)
        self.sub = self.create_subscription(Image,'image_raw',self.listener_callback,10)
        self.cv_bridge = CvBridge()

    def show_cam(self,image):
        cv.imshow("cam",image)
        cv.waitKey(10)

    def listener_callback(self,data):
        self.get_logger().info('Receiving cam frame')
        image = self.cv_bridge.imgmsg_to_cv2(data,'bgr8')   # 将ROS图像格式转化为opencv格式
        self.show_cam(image)

def main(args=None):
    rclpy.init(args=args)
    node = ImageSubscriber("topic_webcam_sub")
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

然后新建一个终端运行这个节点:

bash
ros2 run learning_topic topic_webcam_sub

此处能运行成功是因为image_raw 确实是usb_cam包的默认图像发布话题名称 我们也可以这样查:

bash
ros2 topic list

为了确保opencv窗口正常刷新,我们可以使用cv.startWindowThread(),因为cv.waitKey是阻塞模式的,而cv.startWindowThread() + cv.waitKey(1)是非阻塞模式的。

下面是完整的订阅者代码;

python
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
import cv2 as cv

class ImageSubscriber(Node):
    def __init__(self, name):
        super().__init__(name)
        self.sub = self.create_subscription(Image, 'image_raw', self.listener_callback, 10)
        self.cv_bridge = CvBridge()
        cv.startWindowThread()  # 确保 OpenCV 窗口正常刷新

    def show_cam(self, image):
        cv.imshow("cam", image)
        cv.waitKey(10)

    def listener_callback(self, data):
        self.get_logger().info('Receiving cam frame')
        image = self.cv_bridge.imgmsg_to_cv2(data, 'bgr8')
        self.show_cam(image)

def main(args=None):
    rclpy.init(args=args)
    node = ImageSubscriber("topic_webcam_sub")
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()