话题
想象我们已经有两个节点,NodeA在机载相机上,负责采集图像,它需要将图像数据发送给负责处理图像的NodeB。
ROS2里,采用话题通信
- 发布、订阅模型 :一个节点发布数据,就是发布者,另一个节点接收数据,就是订阅者。二者之间传输的数据的模式也需是确定的
- 订阅者或发布者可不唯一
- 异步通信
- .msg文件定义通信的消息结构
创建发布者节点
我们可先创建一个新的python功能包,就叫它learning_topic。命令和结构上一节讲过。
然后在learning_topic/learining_topic目录里新建topic\_helloworld\_pub.py
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
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列表里添加
'topic_helloworld_pub = learning_topic.topic_helloworld_pub:main',
'topic_helloworld_sub = learning_topic.topic_helloworld_sub:main',话题通信,编译后再运行:
ros2 run learning_topic topic_helloworld_pub
ros2 run learning_topic topic_helloworld_sub以上是节点的基本用法
官方提供了各种现成的话题和节点,比如USB相机驱动:
sudo apt install ros-humble-usb-cam安装完后运行:
ros2 run usb_cam usb_cam_node_exe你可以自己写一个简单的订阅者节点,仅仅展示相机捕获的画面:
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()然后新建一个终端运行这个节点:
ros2 run learning_topic topic_webcam_sub此处能运行成功是因为image_raw 确实是usb_cam包的默认图像发布话题名称 我们也可以这样查:
ros2 topic list为了确保opencv窗口正常刷新,我们可以使用cv.startWindowThread(),因为cv.waitKey是阻塞模式的,而cv.startWindowThread() + cv.waitKey(1)是非阻塞模式的。
下面是完整的订阅者代码;
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()