news 2026/3/11 2:49:47

ROS2核心概念之通信接口

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ROS2核心概念之通信接口

的概念在各个领域随处可见,无论是硬件结构还是软件开发,都有广泛的应用。

1.1.1 硬件接口

比如生活中最为常见的插头和插座,两者必须匹配才能使用,电脑和手机上的USB接口也是,什么Micro-USB、TypeC等等,都是关于接口的具体定义。

1.1.2 软件接口

软件开发中,接口的使用就更多了,比如我们在编写程序时,使用的函数和函数的输入输出也称之为接口,每一次调用函数的时候,就像是把主程序和调用函数通过这个接口连接到一起,系统才能正常工作。

更为形象的是图形化编程中使用的程序模块,每一个模块都有固定的结构和形状,只有两个模块相互匹配,才能在一起工作,这就很好的讲代码形象化了。

1.1.3 定义

所以什么是接口,它是一种相互关系,只有彼此匹配,才能建立连接。

回到ROS的通信系统,它的主要目的就是传输数据,那就得让大家高效的建立连接,并且准确包装和解析传输的数据内容,话题、服务等机制也就诞生了,他们传输的数据,都要符合通信接口的标准定义;

比如摄像头驱动发布的图像话题,由每个像素点的R、G、B三原色值组成;

控制机器人运动的速度指令,由线速度和角速度组成;

进行机器人配置的服务,有配置的参数和反馈的结果组成等等;

类似这些常用的定义,在ROS系统中都有提供,我们也可以自己开发。

这些接口看上去像是给我们加了一些约束,但却是ROS系统的精髓所在;

举个例子,我们使用相机驱动节点的时候,完全不用关注它是如何驱动相机的,只要一句话运行,我们就可以知道发布出来的图像数据是什么样的了,直接开始我们的应用开发;

类似的,键盘控制我们也可以安装一个ROS包,如何实现的呢?不用关心,反正它发布出来的肯定是线速度和角速度。

1.2 ROS通信接口

接口可以让程序之间的依赖降低,便于我们使用别人的代码,也方便别人使用我们的代码,这就是ROS的核心目标,减少重复造轮子。

ROS有三种常用的通信机制,分别是话题、服务、动作,通过每一种通信种定义的接口,各种节点才能有机的联系到一起。

1.2.1 语言无关

为了保证每一个节点可以使用不同语言编程,ROS将这些接口的设计做成了和语言无关的,比如这里看到的:

int32表示32位的整型数;

int64表示64位的整型数;

bool表示布尔值;

还可以定义数组、结构体,这些定义在编译过程中,会自动生成对应到C++、Python等语言里的数据结构。

其中:

话题通信接口的定义使用的是.msg文件,由于是单向传输,只需要描述传输的每一帧数据是什么就行,比如在这个定义里,会传输两个32位的整型数,x、y,我们可以用来传输二维坐标的数值;

服务通信接口的定义使用的是.srv文件,包含请求和应答两部分定义,通过中间的---区分,比如之前我们学习的加法求和功能,请求数据是两个64位整型数a和b,应答是求和的结果sum;

动作是另外一种通信机制,用来描述机器人的一个运动过程,使用.action文件定义,比如我们让小海龟转90度,一边转一边周期反馈当前的状态,此时接口的定义分成了三个部分,分别是:

动作的目标,比如是开始运动;

运动的结果,最终旋转的90度是否完成;

还有一个周期反馈,比如每隔1s反馈一下当前转到第10度、20度还是30度了,让我们知道运动的进度。

1.2.2 标准接口

那么ROS系统到底给我们定义了哪些接口呢?我们可以在ROS安装路径中的share文件夹中找到,涵盖众多标准定义,我们可以打开几个看看;

pi@NanoPC-T6:~$ ls /opt/ros/humble/share/geometry_msgs/msg/

Accel.idl PointStamped.idl PoseStamped.idl TwistStamped.idl

Accel.msg PointStamped.msg PoseStamped.msg TwistStamped.msg

AccelStamped.idl Polygon.idl PoseWithCovariance.idl TwistWithCovariance.idl

AccelStamped.msg PolygonInstance.idl PoseWithCovariance.msg TwistWithCovariance.msg

AccelWithCovariance.idl PolygonInstance.msg PoseWithCovarianceStamped.idl TwistWithCovarianceStamped.idl

AccelWithCovariance.msg PolygonInstanceStamped.idl PoseWithCovarianceStamped.msg TwistWithCovarianceStamped.msg

AccelWithCovarianceStamped.idl PolygonInstanceStamped.msg Quaternion.idl Vector3.idl

AccelWithCovarianceStamped.msg Polygon.msg Quaternion.msg Vector3.msg

Inertia.idl PolygonStamped.idl QuaternionStamped.idl Vector3Stamped.idl

Inertia.msg PolygonStamped.msg QuaternionStamped.msg Vector3Stamped.msg

InertiaStamped.idl Pose2D.idl Transform.idl VelocityStamped.idl

InertiaStamped.msg Pose2D.msg Transform.msg VelocityStamped.msg

Point32.idl PoseArray.idl TransformStamped.idl Wrench.idl

Point32.msg PoseArray.msg TransformStamped.msg Wrench.msg

Point.idl Pose.idl Twist.idl WrenchStamped.idl

Point.msg Pose.msg Twist.msg WrenchStamped.msg

pi@NanoPC-T6:~$ ls /opt/ros/humble/share/std_srvs/srv/

Empty.idl Empty_Response.msg SetBool.idl SetBool_Response.msg Trigger.idl Trigger_Response.msg

Empty_Request.msg Empty.srv SetBool_Request.msg SetBool.srv Trigger_Request.msg Trigger.srv

pi@NanoPC-T6:~$ ls /opt/ros/humble/share/std_msgs/msg/

Bool.idl Empty.idl Header.idl Int64.idl MultiArrayLayout.idl UInt32MultiArray.idl

Bool.msg Empty.msg Header.msg Int64.msg MultiArrayLayout.msg UInt32MultiArray.msg

Byte.idl Float32.idl Int16.idl Int64MultiArray.idl String.idl UInt64.idl

Byte.msg Float32.msg Int16.msg Int64MultiArray.msg String.msg UInt64.msg

ByteMultiArray.idl Float32MultiArray.idl Int16MultiArray.idl Int8.idl UInt16.idl UInt64MultiArray.idl

ByteMultiArray.msg Float32MultiArray.msg Int16MultiArray.msg Int8.msg UInt16.msg UInt64MultiArray.msg

Char.idl Float64.idl Int32.idl Int8MultiArray.idl UInt16MultiArray.idl UInt8.idl

Char.msg Float64.msg Int32.msg Int8MultiArray.msg UInt16MultiArray.msg UInt8.msg

ColorRGBA.idl Float64MultiArray.idl Int32MultiArray.idl MultiArrayDimension.idl UInt32.idl UInt8MultiArray.idl

ColorRGBA.msg Float64MultiArray.msg Int32MultiArray.msg MultiArrayDimension.msg UInt32.msg UInt8MultiArray.msg

回到顶部

二、接口案例

接下来,我们就来看看, 接口到底该如何实现。我们创建my_learning_interface的C++版本的功能包;

pi@NanoPC-T6:~/dev_ws$ cd src

pi@NanoPC-T6:~/dev_ws/src$ ros2 pkg create --build-type ament_cmake my_learning_interface

移除当前目录下的文件:src、include。

修改package.xml,添加接口依赖:

<buildtool_depend>ament_cmake</buildtool_depend>

<!-- 核心依赖 -->

<build_depend>rosidl_default_generators</build_depend>

<exec_depend>rosidl_default_runtime</exec_depend>

<member_of_group>rosidl_interface_packages</member_of_group>

2.1 服务接口

了解了通信接口的概念,接下来我们再从代码实现的角度,研究下如何定义以及使用一个接口。

在前面介绍服务的文章中,我们编写了这样一个例程,我们再来回顾下。

有三个节点:

第一个相机驱动节点,发布图像话题;

第二个是机器视觉识别节点,封装了一个服务的服务端对象,提供目标识别位置的查询服务;

第三个节点在需要目标位置的时候,就可以发送请求,收到位置进行使用了。

2.1.1 接口定义

在这个例程中,我们使用GetObjectPosition.srv定义了服务通信的接口。那这个接口是怎么定义的呢?

我们使用VS Code加载功能包my_learning_interface,在my_learning_interface文件夹下创建子文件夹srv,接着新建文件GetObjectPosition.srv;

# 请求部分

bool get # 获取目标位置的指令

---

# 响应部分

int32 x # 目标的X坐标

int32 y # 目标的Y坐标

定义中有两个部分,上边是获取目标位置的指令,get为true的话,就表示我们需要一次位置,服务端就会反馈这个x、y坐标了。

完成定义后,还需要在功能包的CMakeLists.txt中配置编译选项,让编译器在编译过程中,根据接口定义,自动生成不同语言的代码:

...

find_package(ament_cmake REQUIRED)

find_package(rosidl_default_generators REQUIRED)

# 添加要生成的消息/服务/动作

rosidl_generate_interfaces(${PROJECT_NAME}

"srv/GetObjectPosition.srv"

)

...

编译程序:

pi@NanoPC-T6:~/dev_ws$ colcon build --paths src/my_learning_interface

2.1.2 使用接口包

我们在《ROS2核心概念之服务》文章中创建了功能包my_learning_service,那在客户端和服务端代码中是如何使用接口的呢。

修改功能包my_learning_service依赖package.xml;

<depend>my_learning_interface</depend>

2.1.2.3 客户端

在my_learning_service目录下的service_adder_client.py代码中我们引入了GetObjectPosition接口;

......

from learning_interface.srv import GetObjectPosition # 自定义的服务接口

class objectClient(Node):

def __init__(self, name):

super().__init__(name) # ROS2节点父类初始化

self.client = self.create_client(GetObjectPosition, 'get_target_position')

while not self.client.wait_for_service(timeout_sec=1.0):

self.get_logger().info('service not available, waiting again...')

self.request = GetObjectPosition.Request()

......

2.1.2.4 服务端

同样在my_learning_service目录下的service_object_server.py代码中我们引入了GetObjectPosition接口;

......

from my_learning_interface.srv import GetObjectPosition # 自定义的服务接口

# 橘子的HSV颜色范围

lower_orange = np.array([10, 100, 100]) # 橙色的HSV阈值下限

upper_orange = np.array([25, 255, 255]) # 橙色的HSV阈值上限

class ImageSubscriber(Node):

def __init__(self, name):

super().__init__(name) # ROS2节点父类初始化

self.sub = self.create_subscription(

Image, 'image_raw', self.listener_callback, 10) # 创建订阅者对象(消息类型、话题名、订阅者回调函数、队列长度)

self.cv_bridge = CvBridge() # 创建一个图像转换对象,用于OpenCV图像与ROS的图像消息的互相转换

self.srv = self.create_service(GetObjectPosition, # 创建服务器对象(接口类型、服务名、服务器回调函数)

'get_target_position',

self.object_position_callback)

self.objectX = 0

self.objectY = 0

......

2.2 话题接口

话题通信接口的定义也是类似的,继续从之前的机器视觉案例中来衍生,我们想把服务换成话题,周期发布目标识别的位置,不管有没有人需要。

接着我们采用话题通信的机制对物体位置识别进行改造,此时会有三个节点出现:

相机驱动节点,将驱动相机并发布图像话题,此时的话题数据使用的是ROS中标准定义的Image图像消息;

视觉识别发布者节点,订阅图像数据,并运行视觉识别功能,识别目标的位置,这个位置我们希望封装成话题消息,发布出去,谁需要使用谁就来订阅;

目标位置订阅者节点,订阅目标的位置,打印到终端中。

2.2.1 接口定义

在这个例程中,我们使用ObjectPosition.msg定义了话题通信的接口。那这个接口是怎么定义的呢?

我们在my_learning_interface文件夹下创建子文件夹msg,接着新建文件ObjectPosition.msg;

int32 x # 表示目标的X坐标

int32 y # 表示目标的Y坐标

话题消息的内容是一个位置,我们使用x、y坐标值进行描述。

完成定义后,还需要在功能包的CMakeLists.txt中配置编译选项,让编译器在编译过程中,根据接口定义,自动生成不同语言的代码:

...

rosidl_generate_interfaces(${PROJECT_NAME}

"msg/ObjectPosition.msg"

)

...

编译程序:

pi@NanoPC-T6:~/dev_ws$ colcon build --paths src/my_learning_interface

2.2.2 视觉识别发布者节点

打开my_learning_topic功能包,在my_learning_topic文件夹下创建interface_object_pub.py;

"""

ROS2接口示例-发布目标位置

@author: zy

@since : 2025/12/12

"""

import rclpy # ROS2 Python接口库

from rclpy.node import Node # ROS2 节点类

from sensor_msgs.msg import Image # 图像消息类型

from cv_bridge import CvBridge # ROS与OpenCV图像转换类

import cv2 # Opencv图像处理库

import numpy as np # Python数值计算库

from my_learning_interface.msg import ObjectPosition # 自定义的目标位置消息

# 橘子的HSV颜色范围

lower_orange = np.array([10, 100, 100]) # 橙色的HSV阈值下限

upper_orange = np.array([25, 255, 255]) # 橙色的HSV阈值上限

"""

创建一个订阅者节点

"""

class ImageSubscriber(Node):

def __init__(self, name):

super().__init__(name) # ROS2节点父类初始化

self.sub = self.create_subscription(

Image, 'image_raw', self.listener_callback, 10) # 创建订阅者对象(消息类型、话题名、订阅者回调函数、队列长度)

self.pub = self.create_publisher(

ObjectPosition, "object_position", 10) # 创建发布者对象(消息类型、话题名、队列长度)

self.cv_bridge = CvBridge() # 创建一个图像转换对象,用于OpenCV图像与ROS的图像消息的互相转换

self.objectX = 0

self.objectY = 0

def object_detect(self, image):

hsv_img = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # 图像从BGR颜色模型转换为HSV模型

mask_orange = cv2.inRange(hsv_img, lower_orange, upper_orange) # 图像二值化

contours, hierarchy = cv2.findContours(

mask_orange , cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE) # 图像中轮廓检测

for cnt in contours: # 去除一些轮廓面积太小的噪声

if cnt.shape[0] < 150:

continue

(x, y, w, h) = cv2.boundingRect(cnt) # 得到橘子所在轮廓的左上角xy像素坐标及轮廓范围的宽和高

cv2.drawContours(image, [cnt], -1, (0, 255, 0), 2) # 将橘子的轮廓勾勒出来

cv2.circle(image, (int(x+w/2), int(y+h/2)), 5, # 将橘子的图像中心点画出来

(0, 255, 0), -1)

self.objectX = int(x+w/2)

self.objectY = int(y+h/2)

cv2.imshow("object", image) # 使用OpenCV显示处理后的图像效果

cv2.waitKey(50)

def listener_callback(self, data):

self.get_logger().info('Receiving video frame') # 输出日志信息,提示已进入回调函数

image = self.cv_bridge.imgmsg_to_cv2(data, 'bgr8') # 将ROS的图像消息转化成OpenCV图像

position = ObjectPosition()

self.object_detect(image) # 橘子检测

position.x, position.y = int(self.objectX), int(self.objectY)

self.pub.publish(position) # 发布目标位置

def main(args=None): # ROS2节点主入口main函数

rclpy.init(args=args) # ROS2 Python接口初始化

node = ImageSubscriber("topic_webcam_sub") # 创建ROS2节点对象并进行初始化

rclpy.spin(node) # 循环等待ROS2退出

node.destroy_node() # 销毁节点对象

rclpy.shutdown() # 关闭ROS2 Python接口

完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:

entry_points={

'console_scripts': [

'topic_helloworld_pub = my_learning_topic.topic_helloworld_pub:main',

'topic_helloworld_sub = my_learning_topic.topic_helloworld_sub:main',

'topic_webcam_pub = my_learning_topic.topic_webcam_pub:main',

'topic_webcam_sub = my_learning_topic.topic_webcam_sub:main',

'interface_object_pub = my_learning_topic.interface_object_pub:main'

],

},

2.2.3 目标位置订阅者节点

在my_learning_topic文件夹下创建interface_object_sub.py;

"""

ROS2接口示例-订阅目标位置

@author: zy

@since : 2025/12/12

"""

import rclpy # ROS2 Python接口库

from rclpy.node import Node # ROS2 节点类

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

from my_learning_interface.msg import ObjectPosition # 自定义的目标位置消息

"""

创建一个订阅者节点

"""

class SubscriberNode(Node):

def __init__(self, name):

super().__init__(name) # ROS2节点父类初始化

self.sub = self.create_subscription(

ObjectPosition, "/object_position", self.listener_callback, 10) # 创建订阅者对象(消息类型、话题名、订阅者回调函数、队列长度

def listener_callback(self, msg): # 创建回调函数,执行收到话题消息后对数据的处理

self.get_logger().info('Target Position: "(%d, %d)"' % (msg.x, msg.y))# 输出日志信息,提示订阅收到的话题消息

def main(args=None): # ROS2节点主入口main函数

rclpy.init(args=args) # ROS2 Python接口初始化

node = SubscriberNode("interface_position_sub") # 创建ROS2节点对象并进行初始化

rclpy.spin(node) # 循环等待ROS2退出

node.destroy_node() # 销毁节点对象

rclpy.shutdown() # 关闭ROS2 Python接口

完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:

entry_points={

'console_scripts': [

'topic_helloworld_pub = my_learning_topic.topic_helloworld_pub:main',

'topic_helloworld_sub = my_learning_topic.topic_helloworld_sub:main',

'topic_webcam_pub = my_learning_topic.topic_webcam_pub:main',

'topic_webcam_sub = my_learning_topic.topic_webcam_sub:main',

'interface_object_pub = my_learning_topic.interface_object_pub:main',

'interface_object_sub = my_learning_topic.interface_object_sub:main'

],

},

2.2.4 编译运行

编译程序:

pi@NanoPC-T6:~/dev_ws$ colcon build --paths src/my_learning_topic

启动第一个终端,第一个节点是相机驱动节点,发布图像数据;

pi@NanoPC-T6:~/dev_ws$ ros2 run usb_cam usb_cam_node_exe --ros-args -p video_device:="/dev/video21"

启动第二个终端,第二个是视觉识别节点,订阅图像数据,并运行视觉识别功能,识别目标的位置,并封装成话题(/object_position)消息发布出去;

pi@NanoPC-T6:~/dev_ws$ ros2 run my_learning_topic interface_object_pub

[INFO] [1765721881.329790004] [topic_webcam_sub]: Receiving video frame

[INFO] [1765721881.389374344] [topic_webcam_sub]: Receiving video frame

[INFO] [1765721881.447783021] [topic_webcam_sub]: Receiving video frame

[INFO] [1765721881.503004156] [topic_webcam_sub]: Receiving video frame

[INFO] [1765721881.558974970] [topic_webcam_sub]: Receiving video frame

[INFO] [1765721881.614349516] [topic_webcam_sub]: Receiving video frame

[INFO] [1765721881.672467705] [topic_webcam_sub]: Receiving video frame

启动第三个终端,第三个节点是目标位置订阅者节点,订阅目标的位置,打印到终端中;

pi@NanoPC-T6:~/dev_ws$ ros2 run my_learning_topic interface_object_sub

[INFO] [1765721902.910225255] [interface_position_sub]: Target Position: "(387, 293)"

[INFO] [1765721902.965309682] [interface_position_sub]: Target Position: "(387, 293)"

[INFO] [1765721903.025286418] [interface_position_sub]: Target Position: "(387, 293)"

[INFO] [1765721903.078277510] [interface_position_sub]: Target Position: "(387, 292)"

[INFO] [1765721903.134822213] [interface_position_sub]: Target Position: "(387, 293)"

2.3 接口命令行操作

查看系统接口列表:

pi@NanoPC-T6:~/dev_ws$ ros2 interface list

pi@NanoPC-T6:~/dev_ws$ ros2 interface list

Messages:

action_msgs/msg/GoalInfo

action_msgs/msg/GoalStatus

action_msgs/msg/GoalStatusArray

actionlib_msgs/msg/GoalID

actionlib_msgs/msg/GoalStatus

......

my_learning_interface/msg/ObjectPosition

......

Services:

action_msgs/srv/CancelGoal

composition_interfaces/srv/ListNodes

composition_interfaces/srv/LoadNode

composition_interfaces/srv/UnloadNode

......

my_learning_interface/srv/AddTwoInts

my_learning_interface/srv/GetObjectPosition

......

Actions:

action_tutorials_interfaces/action/Fibonacci

example_interfaces/action/Fibonacci

learning_interface/action/MoveCircle

tf2_msgs/action/LookupTransform

turtlesim/action/RotateAbsolute

查看某个接口的详细定义:

pi@NanoPC-T6:~/dev_ws$ ros2 interface show my_learning_interface/msg/ObjectPosition

int32 x # 表示目标的X坐标

int32 y # 表示目标的Y坐标

查看某个功能包中的接口定义:

pi@NanoPC-T6:~/dev_ws$ ros2 interface package my_learning_interface

my_learning_interface/srv/GetObjectPosition

my_learning_interface/msg/ObjectPosition

my_learning_interface/srv/AddTwoInts

通过rqt_graph可以将节点和话题以及他们之间的连接可视化;

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/10 11:21:59

AutoGPT如何处理模糊目标?自然语言理解边界探讨

AutoGPT如何处理模糊目标&#xff1f;自然语言理解边界探讨 在今天的工作场景中&#xff0c;我们越来越习惯对AI说“帮我写个报告”或“整理一下这个项目的学习资料”&#xff0c;而不是一条条地下达“搜索Python教程”“列出五家竞品公司”这样的具体指令。这种从精确命令到高…

作者头像 李华
网站建设 2026/3/10 11:21:48

清华镜像站推荐:Miniconda下载提速80%的秘密武器

清华镜像站推荐&#xff1a;Miniconda下载提速80%的秘密武器 在人工智能项目开发中&#xff0c;你是否经历过这样的场景&#xff1f;刚拿到一台新服务器&#xff0c;兴致勃勃地准备搭建深度学习环境&#xff0c;结果执行 conda install pytorch 后&#xff0c;进度条卡在“Sol…

作者头像 李华
网站建设 2026/3/10 11:21:26

从GitHub获取Qwen3-8B最新镜像并完成本地化部署

从GitHub获取Qwen3-8B最新镜像并完成本地化部署 在生成式AI迅速渗透各行各业的今天&#xff0c;越来越多开发者和企业开始尝试将大语言模型&#xff08;LLM&#xff09;落地到实际业务中。然而&#xff0c;高昂的API调用成本、数据隐私风险以及网络延迟等问题&#xff0c;让不少…

作者头像 李华
网站建设 2026/3/10 11:21:16

Ubuntu安装完成后配置PyTorch-GPU的完整流程

Ubuntu安装完成后配置PyTorch-GPU的完整流程 在深度学习项目启动的第一天&#xff0c;最让人沮丧的往往不是模型不收敛&#xff0c;而是——torch.cuda.is_available() 返回了 False。 明明装了NVIDIA显卡&#xff0c;也下了PyTorch&#xff0c;为什么就是用不上GPU&#xff1f…

作者头像 李华
网站建设 2026/3/10 11:21:06

购买GPU算力租用Qwen3-14B实例的性价比分析

Qwen3-14B GPU算力租用的性价比深度解析 在当前AI技术快速渗透企业服务的浪潮中&#xff0c;如何以合理的成本获得高质量的语言模型能力&#xff0c;成为许多中小企业和初创团队的核心关切。大模型虽强&#xff0c;但动辄上百GB显存、多卡并行的部署门槛&#xff0c;让不少团队…

作者头像 李华