全国大学生智能汽车竞赛--讯飞组解读(二)

2025.10.05

ROS(Robot Operating System,机器人操作系统)是一个面向机器人开发的开源软件框架与工具集。它为机器人系统的构建、运行和协作提供了标准化的基础环境。本篇文章将从ROS的机制原理入手,告诉你ROS能做什么。

推荐课程

ROS入门视频,个人认为是b站上最详细的讲解视频。从原理到实操,只需较低的学习成本即可快速入门。强烈建议使用虚拟机跟着课程敲一遍代码,只需一遍就可以自己编写程序让小车动起来。

安装ROS

使用鱼香ROS提供的一键安装命令,具体的使用请自行搜索

wget http://fishros.com/install -O fishros && . fishros

ROS架构

WorkSpace --- 自定义的工作空间

    |--- build:编译空间,用于存放CMake和catkin的缓存信息、配置信息和其他中间文件。

    |--- devel:开发空间,用于存放编译后生成的目标文件,包括头文件、动态&静态链接库、可执行文件等。

    |--- src: 源码

        |-- package:功能包(ROS基本单元)包含多个节点、库与配置文件,包名所有字母小写,只能由字母、数字与下划线组成

            |-- CMakeLists.txt 配置编译规则,比如源文件、依赖项、目标文件

            |-- package.xml 包信息

            |-- scripts 存储python文件

            |-- src 存储C++源文件

            |-- include 头文件

            |-- msg 消息通信格式文件

            |-- srv 服务通信格式文件

            |-- action 动作格式文件

            |-- launch 可一次性运行多个节点 

            |-- config 配置信息

        |-- CMakeLists.txt: 编译的基本配置

工作空间

ROS工作空间是最外层文件夹,每个工作空间都是独立的存在。一个小车上可以有不同的工作空间,比如不同队伍使用同一台小车就可以各自维护自己的工作空间。在官方源码中,工作空间名字为ucar_ws

如何创建一个ROS工作空间:

mkdir -p my_ws/src
cd my_ws
catkin_make

catkin_make命令执行之后,会在工作空间目录下产生builddevel文件夹,这两个文件夹中存放的缓存和编译后的目标文件都是依赖于src中文件产生的。任何时候,你都可以彻底删除builddevel文件夹,并通过catkin_make重新生成。建议在存储代码或者使用git推送时将build和devel删除,只维护src,这样可以大大减小仓库大小。

功能包

功能包-package是一个或者若干个功能的集合。一个功能包中可以包含若干个节点。在目录上看,功能包是一个个文件夹,位于src目录下。在官方源码中,ucar_controller就是一个功能包,关于底盘的一切都由它管理。你可以创建自己的功能包,并在其中添加节点来实现功能。

功能包的创建

  1. 进入src目录

    cd ~/catkin_ws/src
    
  2. 创建功能包,这里功能包名字为benz,后面的std_msgs rospy roscpp是所需的依赖。

    catkin_create_pkg benz_pkg std_msgs rospy roscpp
    

    std_msgs包含了各种基本的消息类型,在后续使用节点订阅和接收话题时会使用;rospy和roscpp是关于python和c++的接口,加入这两个依赖你才可以用python或者c++来开发。

节点

节点是ROS中的可执行程序,通俗来讲每一个节点实现一个功能。当然你也可以将多个功能写到一个节点中,但是这样会增加代码维护的难度。

ROS的节点可以用C++或者Python编写,各有各的好处,C++处理更快,但是每次修改都需要编译来重新生成可执行文件;Python修改后直接生效,因为其本质是脚本。在处理速度无明显差异的情况下推荐使用Python。

创建一个节点

  1. 在功能包下创建一个scripts目录,专门用于存放python脚本;而功能包中的src存放cpp文件。在scripts中创建一个新python文件benz.py。

  2. 在文件开头标明解释器,重要!

    #!/usr/bin/env python3
    import rospy	# 使用python开发需要导入rospy
    if __name__ == "__main__":
        rospy.init_node("benz")	# 初始化一个ros节点
    

    这样就创建了一个python的ROS节点,只是初始化而已,并没有功能。

  3. 如果创建的是python节点,那么需要对该文件赋予权限。

    cd ~/catkin_ws/src/benz_pkg/scripts
    

    在终端中打开scripts后,使用如下命令赋予可执行权限

    chmod +x benz.py
    

    然后执行

    ls
    

    你可以看到benz.py变成绿色,说明这个节点可以执行了。

  4. 启动节点,在ucar_ws工作空间的目录下执行。

    rosrun benz_pkg benz.py
    

话题通信

ROS有三种通信机制,分别是话题通信、服务通信和动作通信。其中话题通信时最常见的,其他不多做介绍。

话题通信原理

采用发布/订阅的模型,来实现节点之间的消息传递。允许一个或多个节点发布消息,同时一个或多个节点订阅这些消息。

发布/订阅模型概述

  • 发布者(Publisher): 发布者节点负责将消息发送到某个话题上。发布者并不关心谁订阅了它发布的消息,也不需要知道有多少订阅者。
  • 订阅者(Subscriber): 订阅者节点从某个话题上接收消息。它只关心从该话题接收到的消息,不需要知道谁在发布消息。

ROS 系统内部负责管理话题的注册、发布者和订阅者的匹配,并在它们之间传递消息。

话题的基本概念

  • 话题名称: 每个话题有唯一的名称,节点通过话题名称进行消息传递。比如雷达话题是/scan,摄像头图像话题是/img等
  • 消息类型: 每个话题都必须定义其传递的消息类型。消息类型可以是 ROS 提供的标准消息(如 std_msgs/String),也可以是自定义消息。
  • 队列大小: 订阅者和发布者都可以定义队列大小,用于控制消息缓存。如果队列满了,新的消息会替换掉旧的消息。

关于话题的应用,比如视觉识别中,我们可以创建一个节点专门打开摄像头获取图像,然后将每一帧图像发布到话题/img;另外一个节点订阅该话题就可以获取到每一帧图像,然后对图像进行处理。这样做的好处是,分解耦合,只需要一个节点来打开摄像头,其他很多节点都可以订阅,用来执行不同的功能。

话题通信实操

创建发布者

#!/usr/bin/env python3
import rospy
from std_msgs.msg import String
if __name__ == "__main__":
    rospy.init_node("benz")
    # 创建一个话题的发布者,话题名称为talk,类型为字符串
    pub = rospy.Publisher("talk",String,queue_size=1000)
    # 创建发送的文字
    msg = String()
    msg.data = "hello word"
    # 设置发送频率
    rate = rospy.Rate(10)
    # 在循环中发布,只要ros节点没有关闭
    while not rospy.is_shutdown():
        pub.publish(msg)
        rate.sleep()

创建订阅者

#!/usr/bin/env python3
import rospy
from std_msgs.msg import String

# 每收到一次消息就会进入一次回调函数
def call_back(msg):
    rospy.loginfo(msg.data)
    
if __name__ == "__main__":
    rospy.init_node("sub_python")
    # 创建订阅者,订阅talk话题,类型为String,并且指定回调函数
    sub = rospy.Subscriber("talk",String,call_back)
    # 这一句保证节点持续运行,直到ctrl+c关闭
    rospy.spin()

launch文件

在功能包的launch目录下,你会看到有launch文件。顾名思义这是一个可以同时启动多个节点的文件。启动单个节点可以使用rosrun命令,但是如果你想启动多个节点,一个一个启动就很麻烦。所以你可以在一个launch文件中包含进多个节点,一键启动。

以下是launch文件的一般格式及其常用标签:

<launch>
    <!-- arg参数,可在launch中任意位置通过$(arg hello)调用其值 -->
    <arg name="hello" default = "0.55" />
    <param name="param" value="$(arg xxx)" />
    
    <!-- 定义参数服务器中的参数 -->
    <param name="param_name" value="param_value" />
    
    <!-- 启动ROS节点 -->
    <node pkg="package_name" type="executable_name" name="node_name" output="screen" >
        <!-- 向节点传递参数,会自动添加命名空间前缀,相当于私有参数 -->
        <param name="param_name" value="param_value" />
        
        <!-- 重映射话题 -->
        <remap from="/original_topic" to="/new_topic" />
        
        <!-- 环境变量 -->
        <env name="ENV_VAR" value="env_value" />
        
        <!-- node内传递参数文件(私有) -->
        <rosparam file="$(find hello_vscode)/config/config_file.yaml" command="load"/>
    </node>
    
    <!-- 包含其他launch文件 -->
    <include file="$(find hello_vscode)/launch/other_launch_file.launch" />
</launch>
	<!-- node外传递参数文件(公有) -->
	<rosparam file="$(find hello_vscode)/config/config_file.yaml" command="load"/>

常用标签说明

  1. <launch>: 每个launch文件的根标签。

  2. <node>: 启动节点。常用属性包括:

    • pkg: 节点所在的ROS包名。

    • name: 节点的名称。

    • type: 可执行文件的名称。

      add_executable(${PROJECT_NAME}_node src/hello_vscode.cpp)
      

      hello_vscode.cpp==生成的可执行文件的名称为==hello_vscode_node

    • output: 输出的目标,通常为screenlogscreen表示将输出打印到终端。

    • respawn:如果节点崩溃或意外退出,是否自动重启节点。

  3. <param>: 定义ROS参数,可以在参数服务器name(参数名)和value(参数值)。

  4. <remap>: 重新映射话题名称,from表示原话题,to表示重映射后的话题。

  5. <env>: 设置环境变量,name为环境变量名称,value为环境变量值。

  6. <rosparam>: 加载YAML格式的参数文件,file指向参数文件的路径,command常用为load,用于加载文件;dump用于导出文件。

  7. <include>: 包含其他launch文件,通常用于将多个launch文件组合。

常用命令

  1. rosnode list:列出当前运行的所有节点。

    rosnode info <node_name>:查看某个节点的详细信息,包括发布和订阅的主题、服务等。

    rosnode ping <node_name>:测试与某个节点的连接性。

    rosnode kill <node_name>:关闭指定的节点。

    rosnode machine <machine_name>:查看某台机器上运行的节点。

    rostopic list:列出当前所有的 ROS 话题。

    rostopic echo <topic_name>:打印发布到该话题的数据。

    rostopic pub <topic_name> <msg_type> <msg_data>:发布消息到指定话题(手动发布)。

    rostopic hz <topic_name>:检查话题的发布频率。

    rostopic info <topic_name>:显示某个话题的详细信息(包括发布者和订阅者)。

    rosservice list:列出当前可用的服务。

    rosservice call <service_name> <args>:调用一个服务并传递参数。

    rosservice info <service_name>:查看某个服务的详细信息(包括请求和响应消息类型)。

    rosservice type <service_name>:查看服务的类型。

    rosservice find <service_type>:查找使用某种类型的服务。

    rosmsg list:列出所有可用的 ROS 消息类型。

    rosmsg show <msg_type>:显示某个消息类型的结构定义。

    rosmsg package <package_name>:列出某个包中定义的所有消息类型。

    rossrv list:列出所有可用的 ROS 服务类型。

    rossrv show <srv_type>:显示某个服务类型的结构定义。

    rossrv package <package_name>:列出某个包中定义的所有服务类型。

    rosparam list:列出所有当前存在的参数。

    rosparam get <param_name>:获取某个参数的值。

    rosparam set <param_name> <value>:设置某个参数的值。

    rosparam delete <param_name>:删除某个参数。

    rosparam load <file>:从 YAML 文件中加载参数到参数服务器。

    rosparam dump <file>:将参数服务器上的参数导出到文件。

  2. 设置环境变量

    在工作空间下输入命令:

    nano ~/.bashrc
    

    在末尾增加

    source /home/prac_ws/devel/setup.bash
    

    此后在该工作空间下打开终端会自动刷新环境变量。

  3. ==功能相同但传入参数不同时,可以传不同的参启动该节点多次,但是要使用不同的name,可以提高代码复用性。==

  4. 没有传参时,argc = 1argv[0] 始终是可执行文件的名称。

  5. 参数实时调节器:

    rosrun rqt_reconfigure rqt_reconfigure
    

以上就是一些关于ROS的预备知识,更多详细内容请自行查阅或者借助AI工具。千里之行,始于足下。只有强大的基础才能支持你走的更远。


更多关于全国大学生智能汽车的分享,请访问个人主页。 期待你的评论与交流!