最近工作涉及到自动驾驶的,需要学习ROS,学习中总结了一些知识点,分享给大家。
ROS基本介绍
机器人操作系统ROS,是一种分布式处理框架(又名Nodes),ROS常用C++和python编程语言开发;(这里项目开发采用C++ 11版本)。ROS的点对点设计以及服务和节点管理器等机制,可以分散由计算机视觉和语音识别等功能带来的实时计算压力,能够适应多机器人遇到的挑战。ROS免费并且开源。
ROS常用的概念
1、node: 节点. 节点就是一些直行运算任务的进程。节点之间是通过传送消息进行通讯的;ROS中,通常来讲我们写的c++程序主函数所在的程序称为一个节点;
2、message: 消息.机器人需要传感器,传感器采集到的信息,即这儿的message.(如:位置消息,温度、湿度等);消息以一种发布/订阅的方式传递;
3、topic: 话题.node交换Messages的命名总线异步通讯机制,传输消息;
4、package: 包 是组织ROS代码的最基本单位,每一个Package都可以包括库文件,可执行文件,脚本及其它的一些文件。
5、workSpace: 工作空间 用来存放很多不同package的。
6、roslaunch: 启动文件,其目的是一次性启动多个节点s。
7、Master: 节点管理器,ROS名称服务,帮助节点找到彼此。
8、publish : 发布器,把相关的信息发送到topic
9、subscribe: 订阅器,订阅相应的topic,接收话题的信息
ROS的结构是怎样的?
ROS分为两层,底层是操作系统层,上层则是广大用户编写提供的各种功能不同的软件包,比如定位导航,行动规划等等。
所以ROS实际上可以看成是一个中间层,提供和重新封装了底层硬件调用的API,这些重新封装的API称为客户端库,运用这些库可是实现硬件调用,以此实现各种不同的功能,如使用激光雷达扫描生成周围环境的2D地图……
ROS框架基于集中式拓扑图结构,它的进程(即节点,ROS以节点形式进行通信,以此实现功能)是分布式的,进程分布在各个功能不同的功能包里面。
ROS发布消息-- publish
1、流程
- 初始化 ROS 系统
- 在 ROS 网络内广播中,我们将要在 chatter 话题上,发布 std_msgs/String 类型的消息
- 以每秒 10 次的频率(可以设置修改)在 chatter 上发布消息
2、Ros发布信息 例子:
/** *本程序演示了通过ROS系统简单发送消息。 */ #include "ros/ros.h" //要使用ROS,得包含这个头文件 #include "std_msgs/String.h" //导入 String类型的头文件 #include <sstream> //c++自带的头文件 实现输入输出流等 int main(int argc, char **argv) { ros::init(argc, argv, "talker"); //ros初始化,talker就是node(节点)的名字 ros::NodeHandle n; //为这个进程的节点创建一个句柄。 ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000); //定义要publish(发布)信息的对象 ros::Rate loop_rate(10); //发布的信息的快慢 速度为10Hz int count = 0; while (ros::ok()) { std_msgs::String msg; std::stringstream ss; ss << "hello world " << count; msg.data = ss.str(); ROS_INFO("%s", msg.data.c_str()); //可以理解为ROS里的printf() chatter_pub.publish(msg); //用来发布信息(msg中的内容) ros::spinOnce(); //这个函数是用于接收器,是检测一次 loop_rate.sleep(); ++count; } return 0; }
3、程序重点降解:
ros::NodeHandle n;
- 第一个创建的 NodeHandle 会为节点进行初始化,最后一个销毁的 NodeHandle 则会释放该节点所占用的所有资源
ros:: Publisher chatter_pub = n.advertise < std_msgs::String>("chatter", 1000);
- n.advertise 通过NodeHandle的对象n告诉ROS系统我要创建一个可以发布信息的对象
- < std_msgs::String>告诉ROS我要发布的是标准信息中的String类型,chatter 通讯时的topic(主题)
- 1000这个数字的意思是要缓冲的信息数量
- advertise返回一个 ros::Publisher 对象,它有两个作用:
1) 它有一个 publish() 成员函数可以让你在topic上发布消息;
2) 如果消息类型不对,它会拒绝发布。
4、为什么需要缓冲呢?
这发布和接收之间并不是瞬间进行的,发布消息和接收到消息之间的时间差。缓冲区接收最新的信息放到信息序列的最后。即缓冲区的信息的数据结构是queue。第一条来的信息在序列满了的情况下会被第一个丢弃。
在 ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
1000这个数字的意思是要缓冲的信息数量
ROS订阅消息—subscribe
1、流程
- 初始化ROS系统
- 订阅 chatter 话题
- 进入自循环,等待消息的到达
- 当消息到达,调用 chatterCallback() 函数
Ros订阅信息 例子:
/** * 本程序演示了通过ROS系统简单接收消息。 */ #include "ros/ros.h" #include "std_msgs/String.h" void chatterCallback(const std_msgs::String::ConstPtr& msg) //是一个回调函数,当接收到 chatter 话题的时候就会被调用。{ ROS_INFO("I heard: [%s]", msg->data.c_str());//msg->data就是一个std::string类型的量 } int main(int argc, char **argv) { ros::init(argc, argv, "listener");//节点的名字换成了listener ros::NodeHandle n; ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback); //定义接收器,topic话题:chatter,第三个参数chatterCallback称为回调函数 ros::spin(); //会使程序在循环中,一直检测有没有接收到新的消息 return 0; }
3、注意点
- 如果ROS遇到了相同的节点名字那么他会停止掉旧节点的名字然后使用新节点的那个程序(这时候旧的节点如果 有ros::ok(),那么他会就变得不OK了 = = 。这是ros::ok()返回false的第三种情况)
- 注意node的名字得独一无二,但是topic的名字得和你想接收的信息的topic一样!
- chatterCallback称为回调函数,接收器每一次接收到消息,就会调用名字为它的函数;一般命名为...Callback这样一看就知道这是ROS使用得回调函数
- std_msgs::String对象msg,类包含数据成员data,调用方式为msg.data。如果类的指针叫msg,那么调用该成员的方式是msg->data
ROS 编译程序
1、流程
- 编辑CMakeLists文件(指定要编译的文件)
- 再使用catkin_make编译
2、编译例子:
来到编写好程序的包目录中
cd ~/catkin_ws/src/pub_sub_test/
编辑CMakeLists文件
gedit CMakeLists.txt
在后面添加如下内容:
add_executable(pub_string src/pub_string.cpp) target_link_libraries(pub_string ${catkin_LIBRARIES})
第一行表示我们要编译add_executable表示我们要添加一个可执行文件,
pub_string是这个可执行文件的名字
src/pub_string.cpp指定要编译的源文件的位置.
第二行target_link_libraries表示我们要将可执行文件链接到一个库,我们要使用ROS当然是要链接到ROS的库了,
pub_string指定要链接可执行文件的名字,后面是指定要链接的库的名字.
来到工作空间中,执行catkin_make命令
cd ~/catkin_ws/ catkin_make
执行ROS程序方法1:
方法1:
打开第一个terminal,执行roscore命令(打开rosmaster 服务器)
roscore
roscore是为了让各种节点之间能够沟通用的
打开第二个terminal,进入工作空间,执行source devel/setup.bash 命令,
cd ~/catkin_ws/ source devel/setup.bash
使用rosrun 命令运行程序(例如:执行pub_string.cpp):
rosrun pub_sub_test pub_string
第一个参数:程序pub_string.cpp所在的包(package)的名字
第二个参数:运行程序的名称
方法2: 使用roslaunch来运行程序
特点:
可以便捷开启多个节点,自动开启rosmaster服务
区别:
- 方法一在使用rosrun之前,我们一定得需要启动rosmaster,即开启一个窗口输入roscore,来开启rosmaster 服务器;
- 运行roslaunch文件后rosmaster会自动启动.当然你关闭了roslaucn之后rosmaster也会关闭
流程:
- 来到程序所在包的文件夹目录下,新建一个名字叫launch 的文件夹
- 在launch中建立的一个文件,名字可以随意,后缀必须是launch.
- 编译程序
- 编辑launch文件
- 通过roslaunch,执行节点程序
详细说明:
- 编辑launch文件
格式: <
launch> <node name="pub_string" pkg = "pub_sub_test" type = "pub_string" output = "screen"> </node> <node name="pub_int8" pkg = "pub_sub_test" type = "pub_int8" output = "screen"> </node> </launch>
roslaunch使用的是xml语言,launch文件的内容是跑一个node简单的形式
<launch>,<\launch>表示launch文件的开始和结束
<node ....>表示接下来输入node相关的内容,比如说首先输入的是node的名字,这个东西一般和type后面输入的内容一样,
type需要被赋值为节点对应的可执行文件的名字,
name则是节点的名字.具体区别是你在CMakeLists.txt文件里编译文件的命令
output = "screen" :设置通过print()输出的信息,打印到命令窗口中,默认时关闭的
通过roslaunch,执行节点程序
格式:
关键字:roslaunch
第一个参数: xx.launch 所在的包的名称
第二个参数: 要执行的.launch 的文件
ROS发布和接收图
编辑
rosbag
简介
- rosbag 指命令行中数据包相关命令;
- rosbag 主要用于记录、回放、分析 rostopic 中的数据。它可以将指定 rostopic 中的数据记录到 .bag 后缀的数据包中,便于对其中的数据进行离线分析和处理。
- 对于 subscribe 某个 topic 的节点来说,它无法区分这个 topic 中的数据到底是实时获取的数据还是从 rosbag 中回放的数据。这就有助于我们基于离线数据快速重现曾经的实际场景,进行可重复、低成本的分析和调试。
rosbag 的命令
编辑
rosbag录制:
录制所有发布出来的话题,此时默认将话题保存在一个以当时时间戳命名的文件夹中:
$ rosbag record –a
录制指定话题:
$ rosbag record /topic1 /topic12
rosbag回放:
基本功能:
$ rosbag play <your bagfile name>
等待一定时间之后发布bag文件中的内容
$ rosbag play <your bagfile name> -d <delay time>
按一定频率回放,-r选项用来设定消息发布速率,如下面命令则表示以3倍原始速率发布话题
$ rosbag play -r 3 <your bagfile name>
回放指定话题:
$ rosbag play <your bagfile name> --topics <topics>
rosbag检查和回放
- rosbag info指令可以显示数据包中的信息:
rosbag info filename.bag
这些信息包括 topic 的名称、类型和 message 数量。
2) 接下来回放数据包中的 topic。
首先在turtle_teleop_key 所在的终端窗口中按Ctrl+C退出该键盘控制节点。保留turtlesim节点继续运行。在终端中bag文件所在目录下运行以下命令:
rosbag play <bagfile>
就能够回放出 bag 中包含的 topic 内容了。
如果想改变消息的发布速率,可以用下面的命令
rosbag play -r 2 <bagfile>
这时的轨迹相当于以两倍的速度通过按键发布控制命令时产生的轨迹。 -r 后面的数字对应播放速率。
rosbag play -l <bagfile> # -l == --loop
rosbag play <bagfile> --topic /topic1
在上述播放命令执行期间,空格键可以暂停播放。
CMakeLists
简介:
ROS中创建软件包所依赖的文件为CMakeList.txt,catkin_make会根据你写的CMakeList.txt来配置编译软件包。
格式:
所需CMake版本 cmake_minimum_required(VERSION 2.8.3)
软件包名称 project()
查找构建此包所需的包 find_package()
消息 / 服务 / 动作生成器 add_message_files(),add_service_files(),add_action_files
消息 / 服务 / 动作生成 generate_messages()
指定包构建的消息导出 catkin_package()
要建立的库 / 可执行文件 add_library() / add_executable() / target_link_libraries())
例如:
编辑
其中:add_executable(read_param src/show_param.cpp)和target_link_libraries(read_param ${catkin_LIBRARIES}) 是新添加的,指定可执行文件
详细解释:
1.所需CMake版本
catkin_make的底层是使用cmake进行编译的,这里指定cmake的版本(最低版本)
2.软件包名称
project(package_name)
在使用catkin_create_pkg创建包时,后面跟的参数(包名)就是此处的package_name
在CMakeList.txt后面的部分可以使用 ${PROJECT_NAME} 来使用此参数
3. 查找此包创建时所需要的其它包(依赖包)
可以将所依赖的包写成下面的形式:
find_package(catkin REQUIRED)
find_package(roscpp REQUIRED)
find_package(rospy REQUIRED)
find_package(std_msgs REQUIRED)
其中catkin是创建每个包所必须的依赖项,创建包时所依赖的其它项又可以将其组成catkin的组件,所以上面可以总写为:
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
)
上面中的roscpp、rospy、std_msgs 是运行程序所需要的依赖包;
roscpp: 用C++ 语言进行ros开发要用到的包
rospy: 用python 语言进行ros开发要用到的包
std_msgs: 基本的数据类型int 、string、float、double等的依赖包
4.add_message_files()
像message service 和action的定义需要在catkin_package()之前
## Generate messages in the 'msg' folder
# add_message_files(
# FILES
# Message1.msg
# Message2.msg
# )
比如:
add_message_files(
FILES Num1.msg Num2.msg
)
5.catkin_package()
用来向编译系统指明catkin-specific的信息,格式如下:
catkin_package(
INCLUDE_DIRS include # 此项打开之后该软件包的include文件可以被其它包所引用
LIBRARIES ${PROJECT_NAME} #同理
CATKIN_DEPENDS roscpp nodelet
DEPENDS eigen opencv)
6.包含文件目录
include_directories(include ${catkin_INCLUDE_DIRS})
其中include是指包含本软件包下的头文件, ${catkin_INCLUDE_DIRS}是指ROS下其它包的头文件,include需要写在${catkin_INCLUDE_DIRS}前面。
7.生成可执行文件
#其中talker为将要生成的二进制文件,在ros所有的包中必须是独一无二不能重复的,src/talker.cpp为需要编译的源文件
add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
生成的可执行文件会存放在./devel/lib/*pack_name*/下:
问题与解决方案总结
缺失依赖库
编辑
解决思路:
1.在错误原因中,找到错误代码端,分析,确定缺失的包
2.在程序所在包下,添加依赖包
添加依赖包,详细说明:
思路:
- 一个是包目录下的的CMakeLists.txt,添加依赖包
- 然后在位于同一位置的package.xml中,添加添加依赖包
例如
- 打开的CMakeLists.txt,发现就在最前面几行,有下面的内容。
find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs )
括号中的内容正好一一对应我们创建包时添加的依赖项,在后面添加geometry_msgs,变成下面的样子,保存退出。
find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs geometry_msgs )
打开位于同一目录下的package.xml
<build_depend>roscpp</build_depend> <build_depend>rospy</build_depend> <build_depend>std_msgs</build_depend> <build_export_depend>roscpp</build_export_depend> <build_export_depend>rospy</build_export_depend> <build_export_depend>std_msgs</build_export_depend> <exec_depend>roscpp</exec_depend> <exec_depend>rospy</exec_depend> <exec_depend>std_msgs</exec_depend>
发现std_msgs, rospy, roscpp,每个出现了三次,所以只我们需要按照这个文档里相同的语法让geometry_msgs出现三次就行了。更改之后该文件同样位置变成下面的内容:
<build_depend>roscpp</build_depend> <build_depend>rospy</build_depend> <build_depend>std_msgs</build_depend> <build_depend>geometry_msgs</build_depend> <build_export_depend>roscpp</build_export_depend> <build_export_depend>rospy</build_export_depend> <build_export_depend>std_msgs</build_export_depend> <build_export_depend>geometry_msgs</build_export_depend> <exec_depend>roscpp</exec_depend> <exec_depend>rospy</exec_depend> <exec_depend>std_msgs</exec_depend> <exec_depend>geometry_msgs</exec_depend> <exec_depend>geometry_msgs</exec_depend>
保存退出。这时候再用catkin_make编译,就成功了。改变上面两个文档的内容就相当于我们在创建包时添加了依赖项geometry_msgs。
catkin_make编译错误
问题:
Could not find a package configuration file provided by
"gazebo_ros_control" with any of the following names:
gazebo_ros_controlConfig.cmake
gazebo_ros_control-config.cmake
分析
提示缺少“gazebo_ros_control”功能包,
解决方案:
sudo apt-get install ros-kinetic-gazebo-ros-control
问题:
Could not find a package configuration file provided by "move_base_msgs"
with any of the following names:
move_base_msgsConfig.cmake
move_base_msgs-config.cmake
分析
提示缺少“gazebo_ros_control”功能包
解决方案:
sudo apt-get install ros-kinetic-move-base-msgs
问题:
alsa/asoundlib.h: No such file or directory
分析
缺少一个库,libasound2-dev
解决方案:
sudo apt-get install libasound2-dev
希望对你有帮助。