网站建设遵循的原则,网页制作模板蛋糕,钻磊云主机,制作游戏的app的概念在各个领域随处可见#xff0c;无论是硬件结构还是软件开发#xff0c;都有广泛的应用。 1.1.1 硬件接口
比如生活中最为常见的插头和插座#xff0c;两者必须匹配才能使用#xff0c;电脑和手机上的USB接口也是#xff0c;什么Micro-USB、TypeC等等#xff0c;都…的概念在各个领域随处可见无论是硬件结构还是软件开发都有广泛的应用。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文件夹中找到涵盖众多标准定义我们可以打开几个看看piNanoPC-T6:~$ ls /opt/ros/humble/share/geometry_msgs/msg/Accel.idl PointStamped.idl PoseStamped.idl TwistStamped.idlAccel.msg PointStamped.msg PoseStamped.msg TwistStamped.msgAccelStamped.idl Polygon.idl PoseWithCovariance.idl TwistWithCovariance.idlAccelStamped.msg PolygonInstance.idl PoseWithCovariance.msg TwistWithCovariance.msgAccelWithCovariance.idl PolygonInstance.msg PoseWithCovarianceStamped.idl TwistWithCovarianceStamped.idlAccelWithCovariance.msg PolygonInstanceStamped.idl PoseWithCovarianceStamped.msg TwistWithCovarianceStamped.msgAccelWithCovarianceStamped.idl PolygonInstanceStamped.msg Quaternion.idl Vector3.idlAccelWithCovarianceStamped.msg Polygon.msg Quaternion.msg Vector3.msgInertia.idl PolygonStamped.idl QuaternionStamped.idl Vector3Stamped.idlInertia.msg PolygonStamped.msg QuaternionStamped.msg Vector3Stamped.msgInertiaStamped.idl Pose2D.idl Transform.idl VelocityStamped.idlInertiaStamped.msg Pose2D.msg Transform.msg VelocityStamped.msgPoint32.idl PoseArray.idl TransformStamped.idl Wrench.idlPoint32.msg PoseArray.msg TransformStamped.msg Wrench.msgPoint.idl Pose.idl Twist.idl WrenchStamped.idlPoint.msg Pose.msg Twist.msg WrenchStamped.msgpiNanoPC-T6:~$ ls /opt/ros/humble/share/std_srvs/srv/Empty.idl Empty_Response.msg SetBool.idl SetBool_Response.msg Trigger.idl Trigger_Response.msgEmpty_Request.msg Empty.srv SetBool_Request.msg SetBool.srv Trigger_Request.msg Trigger.srvpiNanoPC-T6:~$ ls /opt/ros/humble/share/std_msgs/msg/Bool.idl Empty.idl Header.idl Int64.idl MultiArrayLayout.idl UInt32MultiArray.idlBool.msg Empty.msg Header.msg Int64.msg MultiArrayLayout.msg UInt32MultiArray.msgByte.idl Float32.idl Int16.idl Int64MultiArray.idl String.idl UInt64.idlByte.msg Float32.msg Int16.msg Int64MultiArray.msg String.msg UInt64.msgByteMultiArray.idl Float32MultiArray.idl Int16MultiArray.idl Int8.idl UInt16.idl UInt64MultiArray.idlByteMultiArray.msg Float32MultiArray.msg Int16MultiArray.msg Int8.msg UInt16.msg UInt64MultiArray.msgChar.idl Float64.idl Int32.idl Int8MultiArray.idl UInt16MultiArray.idl UInt8.idlChar.msg Float64.msg Int32.msg Int8MultiArray.msg UInt16MultiArray.msg UInt8.msgColorRGBA.idl Float64MultiArray.idl Int32MultiArray.idl MultiArrayDimension.idl UInt32.idl UInt8MultiArray.idlColorRGBA.msg Float64MultiArray.msg Int32MultiArray.msg MultiArrayDimension.msg UInt32.msg UInt8MultiArray.msg回到顶部二、接口案例接下来我们就来看看 接口到底该如何实现。我们创建my_learning_interface的C版本的功能包piNanoPC-T6:~/dev_ws$ cd srcpiNanoPC-T6:~/dev_ws/src$ ros2 pkg create --build-type ament_cmake my_learning_interface移除当前目录下的文件src、include。修改package.xml添加接口依赖buildtool_dependament_cmake/buildtool_depend!-- 核心依赖 --build_dependrosidl_default_generators/build_dependexec_dependrosidl_default_runtime/exec_dependmember_of_grouprosidl_interface_packages/member_of_group2.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)...编译程序piNanoPC-T6:~/dev_ws$ colcon build --paths src/my_learning_interface2.1.2 使用接口包我们在《ROS2核心概念之服务》文章中创建了功能包my_learning_service那在客户端和服务端代码中是如何使用接口的呢。修改功能包my_learning_service依赖package.xmldependmy_learning_interface/depend2.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_sec1.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 0self.objectY 0......2.2 话题接口话题通信接口的定义也是类似的继续从之前的机器视觉案例中来衍生我们想把服务换成话题周期发布目标识别的位置不管有没有人需要。接着我们采用话题通信的机制对物体位置识别进行改造此时会有三个节点出现相机驱动节点将驱动相机并发布图像话题此时的话题数据使用的是ROS中标准定义的Image图像消息视觉识别发布者节点订阅图像数据并运行视觉识别功能识别目标的位置这个位置我们希望封装成话题消息发布出去谁需要使用谁就来订阅目标位置订阅者节点订阅目标的位置打印到终端中。2.2.1 接口定义在这个例程中我们使用ObjectPosition.msg定义了话题通信的接口。那这个接口是怎么定义的呢我们在my_learning_interface文件夹下创建子文件夹msg接着新建文件ObjectPosition.msgint32 x # 表示目标的X坐标int32 y # 表示目标的Y坐标话题消息的内容是一个位置我们使用x、y坐标值进行描述。完成定义后还需要在功能包的CMakeLists.txt中配置编译选项让编译器在编译过程中根据接口定义自动生成不同语言的代码...rosidl_generate_interfaces(${PROJECT_NAME}msg/ObjectPosition.msg)...编译程序piNanoPC-T6:~/dev_ws$ colcon build --paths src/my_learning_interface2.2.2 视觉识别发布者节点打开my_learning_topic功能包在my_learning_topic文件夹下创建interface_object_pub.pyROS2接口示例-发布目标位置author: zysince : 2025/12/12import 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 0self.objectY 0def 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(xw/2), int(yh/2)), 5, # 将橘子的图像中心点画出来(0, 255, 0), -1)self.objectX int(xw/2)self.objectY int(yh/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(argsNone): # ROS2节点主入口main函数rclpy.init(argsargs) # 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.pyROS2接口示例-订阅目标位置author: zysince : 2025/12/12import 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(argsNone): # ROS2节点主入口main函数rclpy.init(argsargs) # 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 编译运行编译程序piNanoPC-T6:~/dev_ws$ colcon build --paths src/my_learning_topic启动第一个终端第一个节点是相机驱动节点发布图像数据piNanoPC-T6:~/dev_ws$ ros2 run usb_cam usb_cam_node_exe --ros-args -p video_device:/dev/video21启动第二个终端第二个是视觉识别节点订阅图像数据并运行视觉识别功能识别目标的位置并封装成话题/object_position消息发布出去piNanoPC-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启动第三个终端第三个节点是目标位置订阅者节点订阅目标的位置打印到终端中piNanoPC-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 接口命令行操作查看系统接口列表piNanoPC-T6:~/dev_ws$ ros2 interface listpiNanoPC-T6:~/dev_ws$ ros2 interface listMessages:action_msgs/msg/GoalInfoaction_msgs/msg/GoalStatusaction_msgs/msg/GoalStatusArrayactionlib_msgs/msg/GoalIDactionlib_msgs/msg/GoalStatus......my_learning_interface/msg/ObjectPosition......Services:action_msgs/srv/CancelGoalcomposition_interfaces/srv/ListNodescomposition_interfaces/srv/LoadNodecomposition_interfaces/srv/UnloadNode......my_learning_interface/srv/AddTwoIntsmy_learning_interface/srv/GetObjectPosition......Actions:action_tutorials_interfaces/action/Fibonacciexample_interfaces/action/Fibonaccilearning_interface/action/MoveCircletf2_msgs/action/LookupTransformturtlesim/action/RotateAbsolute查看某个接口的详细定义piNanoPC-T6:~/dev_ws$ ros2 interface show my_learning_interface/msg/ObjectPositionint32 x # 表示目标的X坐标int32 y # 表示目标的Y坐标查看某个功能包中的接口定义piNanoPC-T6:~/dev_ws$ ros2 interface package my_learning_interfacemy_learning_interface/srv/GetObjectPositionmy_learning_interface/msg/ObjectPositionmy_learning_interface/srv/AddTwoInts通过rqt_graph可以将节点和话题以及他们之间的连接可视化