前言
“如果说我看得比别人更远些,那是因为我站在巨人的肩膀上。”——牛顿
本次ros2学习完全参考小鱼的教程,小鱼的教程中对ros2每个知识点的介绍已经非常详细。因此这份学习笔记是基于巨人的肩膀,不会面面俱到,不会再对ros2的知识点进行赘述,而主要注重学习过程中遇到的问题、报错的解决,以及一些拓展知识点的介绍。
一、学习环境与小鱼教程地址
本次ros2学习基于ubuntu20.04 foxy版本,当然,知识点具有互通性,要想将环境转移至ubuntu22.04 humble版本也非常简单,知识点基本一致。下面给出小鱼的两份教程:
基于ubuntu20.04 foxy版本:动手学ROS2(foxy)
基于ubuntu22.04 humble版本:动手学ROS2(humble)
二、ROS2安装时遇到的问题
ros2安装参考教程:动手学ROS2(安装教程)
当然完全可以用小鱼推出的一键安装,但是如果您选择的是手动安装,那么有可能跟我一样碰到以下问题:
1. 添加源对应密钥时软件包安装失败的问题
在根据教程添加源对应密钥时可能出现软件包安装失败的问题。即在执行以下语句安装curl软件包时:
sudo apt install curl gnupg2 -y
出现如下报错:
E:无法下载
http://security.ubuntu.com/ubuntu/pool/main/c/curl/curl_7.68.0-1ubuntu2.22_amd64.deb 404 Not Found [IP: 101.6.15.130 80]
E: 有几个软件包无法下载,要不运行 apt-get update 或者加上 --fix-missing 的选项再试试?
解决方法如下:
1)首先设置编码UTF-8,在终端输入locale,若已经支持UTF-8则无需设置。
2)然后添加源:
首先通过检查此命令的输出,确保已启用Ubuntu Universe存储库:
apt-cache policy | grep universe
若无正常输出,则执行以下两句语句:
sudo apt install software-properties-common
sudo add-apt-repository universe
若有正常输出,继续执行以下命令:
sudo apt update && sudo apt install curl gnupg2 lsb-release
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg
而在执行第二句语句时有可能会出现以下报错:
curl: (7) Failed to connect to raw.githubusercontent.com port 443: 拒绝连接
那么再执行一下curl安装语句保证curl软件包已被安装:
sudo apt install curl
然后重新添加源:
echo "deb [arch=$(dpkg --print-architecture)] https://repo.huaweicloud.com/ros2/ubuntu/ $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null
接下来再回到添加源对应的密钥,执行以下两句就不会报错了:
sudo apt install curl gnupg2 -y
这句执行后软件包将被正常安装。
curl -s https://gitee.com/ohhuo/rosdistro/raw/master/ros.asc | sudo apt-key add -
这句执行后正常会输出“OK”。
3)接下来更新:
sudo apt update
4)执行安装语句。
更新后就可以安装ros2了。Ubuntu20.04只能安装foxy版本,注意不要跟着foxy教程装humble(对应Ubuntu 22.04)。执行以下语句进行安装:
sudo apt install ros-foxy-desktop
后面跟着小鱼教程走即可。
2. 配置环境变量时因ros1和ros2冲突引起的报错
在配置环境变量时,会在终端执行:
source /opt/ros/foxy/setup.bash
如果之前已经安装过ros1,则ros1和ros2的环境变量会冲突,出现如下报错:
ROS_DISTRO was set to 'noetic' before. Please make sure that the environment does not mix paths from different distributions.
那么就在主文件夹目录键入ctrl+h,打开~/.bashrc文件,将ros1环境变量注释即可:
#source /opt/ros/noetic/setup.bash
注释后重新配置ros2环境变量。
我在安装时其他步骤应该没有什么问题。
二、ROS2 tf树安装问题
根据教程启动电脑相机并开rqt启动节点时,如果image_view下面复选框没有出现 /image 选项,且rqt终端出现以下报错:
PluginManager._load_plugin() could not load plugin "rqt_tf_tree/RosTfTree": plugin not available
则说明ros2中未安装tf树的库,执行以下命令安装即可解决以上问题:
sudo apt install ros-foxy-rqt ros-foxy-rqt-tf-tree
安装后重启rqt,启动相机即可看见图像。
顺便一提,如果你用的是华为电脑,电脑的相机在下面如图所示的这里昂:
三、字符串输出问题
ros2输出字符串数据时,需要将字符串先用c_str()转化为带有c风格的字符串,否则直接输出会乱码。示例如下(输出示例用的是小鱼的例子):
RCLCPP_INFO(this->get_logger(), "朕已阅:“%s”,打赏李四:%d 元的稿费",msg->data.c_str(),money.data);
这样订阅消息后即可正常输出订阅到的字符串内容。
四、查看特定文件夹下目录结构时的问题
要查看特定文件夹下属目录结构,首先用 cd 文件夹名 进入待查看文件夹,然后执行tree命令即可查看目录结构。
若输入tree后显示以下报错:
Command 'tree' not found, but can be installed with:
sudo snap install tree # version 1.8.0+pkg-3fd6, or
sudo apt install tree # version 1.8.0-1
See 'snap info tree' for additional versions.
说明tree功能命令未经过安装,则根据报错提示,执行以下命令进行安装:
sudo snap install tree
或 sudo apt install tree
安装后再次执行tree,即可成功查看目录结构。
五、CMakeLists.txt中功能包调用顺序问题
在CMakeLists.txt中添加消息文件的声明和依赖时要注意添加的先后顺序。若编译时出现如下报错:
CMake Error at /opt/ros/foxy/share/rosidl_cmake/cmake/rosidl_generate_interfaces.cmake:64 (message):
rosidl_generate_interfaces() must be called before ament_package()
Call Stack (most recent call first):
CMakeLists.txt:39 (rosidl_generate_interfaces)
这是CMakeLists.txt 文件中调用 rosidl_generate_interfaces() 的顺序问题。在ROS2的CMake构建系统中,rosidl_generate_interfaces() 这个函数(声明msg文件所属的工程名字、文件位置以及依赖DEPENDENCIES)必须在 ament_package()(确保 ROS 2 包的构建和安装过程正确无误地配置,使得包能够被 ROS 2 环境正确地找到和使用)调用之前被执行。这是因为 rosidl_generate_interfaces() 需要在包被最终构建前,生成所有必需的接口文件(如消息和服务)。正确的调用顺序如下:
find_package(sensor_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Novel.msg"
DEPENDENCIES sensor_msgs
)
ament_package()
六、服务通信中编译srv文件无法获取 .hpp 文件路径的问题
在头文件中引用服务消息srv文件时,若编译出现以下报错:
fatal error: install/village_interfaces/srv/sell_novel.hpp: 没有那个文件或目录 33 | #include "install/village_interfaces/srv/sell_novel.hpp"
compilation terminated.
一般是CMakeLists.txt以及package.xml配置不到位。注意必须进行以下配置(下面以village_interfaces服务通信包为例):
1. 在节点功能包的CMakeLists.txt中添加寻找消息服务包的依赖:
find_package(village_interfaces REQUIRED)
2. 在节点功能包的CMakeLists.txt中为待编译节点添加消息服务包依赖:
ament_target_dependencies(wang2_node
rclcpp
village_interfaces
)
3. 在节点功能包的package.xml文件中添加消息服务包的依赖:
<depend>village_interfaces</depend>
4. 在消息服务包的CMakeLists.txt中添加对sensor_msgs的声明和所需消息或服务文件的路径:
#添加对sensor_msgs的声明
find_package(sensor_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)
#添加消息文件和依赖
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Novel.msg"
"srv/BorrowMoney.srv"
"srv/SellNovel.srv"
DEPENDENCIES sensor_msgs
)
5. 在消息服务包的package.xml文件中添加以下依赖:
<depend>sensor_msgs</depend>
<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
七、查看录包文件注意事项
录包结束后若想对所录包进行相关操作,注意要进入包文件夹,在包文件夹目录下进行操作。下列为录包前后操作集合:
录制一个指定话题:
ros2 bag record /topic-name
录制多个指定话题:
ros2 bag record /topic-name1 /topic-name2
录制所有话题:
ros2 bag record -a
自定义输出文件的名字:
ros2 bag record -o file-name topic-name
查看录制出话题的信息:
ros2 bag info 包文件名
播放话题数据:
ros2 bag play xxx.db3
用topic指令查看数据:
ros2 topic echo /topic-name
(用topic命令查看录包数据时注意要同时执行上条命令播放话题数据)
倍速播放-r(十倍速):
ros2 bag play rosbag2_2021_10_03-15_31_41_0.db3 -r 10
循环播放-l:
ros2 bag play rosbag2_2021_10_03-15_31_41_0.db3 -l
播放单个话题:
ros2 bag play rosbag2_2021_10_03-15_31_41_0.db3 --topics /topic-name
八、运用transforms3d库实现多种姿态之间的坐标转换
下面对transforms3d库实现多姿态坐标转换的常用库函数进行集合,方便以后调用:
在运用python编写坐标转换时,首先一定要引入头文件:
import transforms3d as tfs
1. 四元数与旋转矩阵互转
# 四元数转旋转矩阵
tfs.quaternions.quat2mat([1,0,0,0])
# 旋转矩阵转四元数
tfs.quaternions.mat2quat(np.asarray([[1., 0., 0.],[0., 1., 0.],[0., 0., 1.]]))
2. 四元数与轴角互转
# 四元数转旋轴角
tfs.quaternions.quat2axangle([1,0,0,0])
# 轴角转四元数
tfs.quaternions.axangle2quat([1,0,0],0.5)
3. 欧拉角与四元数互转
# 固定轴欧拉角转四元数
tfs.euler.euler2quat(0,0,0,"sxyz")
# 四元数转固定轴欧拉角
tfs.euler.quat2euler([1,0,0,0],"sxyz")
4. 欧拉角与旋转矩阵
# 固定轴欧拉角转旋转矩阵
tfs.euler.euler2mat(0,0,0,"sxyz")
# 旋转矩阵转固定轴欧拉角
tfs.euler.mat2euler(np.asarray([[1., 0., 0.],[0., 1., 0.],[0., 0., 1.]]),"sxyz")
5. 欧拉角与轴角互转
# 固定轴欧拉角轴角
tfs.euler.euler2axangle(0,0,0,"sxyz")
# 轴角转固定轴欧拉角
tfs.euler.axangle2euler([1,0,0],0.5,"sxyz")
6. 轴角与旋转矩阵
# 轴角转旋转矩阵
tfs.axangles.axangle2mat([1,0,0],0.5)
# 旋转矩阵转轴角
tfs.axangles.mat2axangle(np.asarray([[1., 0., 0.],[0., 1., 0.],[0., 0., 1.]]))
7. 有关齐次坐标变换
1)齐次矩阵的合成
a)旋转矩阵+平移变量
首先需要定义旋转矩阵和平移向量:
R = np.asarray([[1., 0., 0.],[0., 1., 0.],[0., 0., 1.]])
T = np.asarray([1,0,1])
接下来有两种方法合成齐次变换矩阵。
一是使用numpy方法,通过水平与竖直拼接合成齐次变换矩阵:
temp = np.hstack((R,T.reshape(3,1)))
np.vstack((temp,[0,0,0,1]))
二是直接使用transforms3d库中的函数合成齐次变换矩阵:
tfs.affines.compose(T,R,[1,1,1])
b)四元数+平移变量
该合成方法的基本思路是先将四元数转换成旋转矩阵,再用前面a)中的方法合成齐次变换矩阵。因此该方法其实就是多了一个四元数转旋转矩阵的过程。该过程如下:
R = tfs.quaternions.quat2mat([1,0,0,0])
tfs.affines.compose(T,R,[1,1,1])
2)齐次矩阵的分解
齐次矩阵的分解是指已有齐次矩阵,将其分解为姿态和平移两部分。
分解方式可以有两种:
a)分解为固定轴欧拉角和平移向量
tfs.euler.mat2euler(T[0:3,0:3]),T[:3,3:4]
b)分解为四元数和平移向量
tfs.quaternions.mat2quat(T[0:3,0:3]),T[:3,3:4]
3)齐次矩阵的乘法
已知B到C的齐次变换矩阵T_BC、C到P的齐次变换矩阵T_CP,则通过矩阵乘法即可得到B到P的齐次变换矩阵:
T_BC = tfs.affines.compose([0,0,3],tfs.euler.euler2mat(math.pi,0,0),[1,1,1])
T_CP = tfs.affines.compose([2,1,2],np.identity(3),[1,1,1])
T_BP = np.dot(T_BC,T_CP)
4)齐次矩阵求逆
矩阵逆的求解用一条函数就能解决:
T_EC = np.linalg.inv(T_CE)
当然,目前的代码基本是用C++构建的,在C++中涉及到坐标转换时,一般用Eigen库实现。基于Eigen库的坐标转换库-TransForm3d开源地址:
transforms3d_cpp: 基于Eigen实现的坐标转换库,在机器人坐标转换中常用的库。
九、实现fishbot仿真demo时的问题
1. 下载编译
基于Ubuntu20.04需下载ros2 foxy版本,在克隆fishbot时要注意选择foxy分支,阅读README执行下面代码进行克隆:
git clone https://github.com/fishros/fishbot.git -b foxy
2. rosdep初始化
编译前须执行:
rosdep install --from-paths src -y
该命令行的物理意义在于查找 src 目录下的所有ROS包,分析每个包所需的依赖关系,并自动安装这些依赖项,确保工作空间中的ROS包能够成功编译和运行。
首次运行这一命令可能出现如下报错:
ERROR: your rosdep installation has not been initialized yet. Please run:
sudo rosdep init
rosdep update
该报错说明rosdep未完成初始化,根据报错提示执行sudo rosdep init和rosdep update进行初始化即可。
执行sudo rosdep init时可能出现如下报错:
ERROR: cannot download default sources list from:
https://raw.githubusercontent.com/ros/rosdistro/master/rosdep/sources.list.d/20-default.list
Website may be down.
报错提示初始化时无法访问指定网站,大概率是防火墙的问题(挂梯子再执行也没用)。我们可以直接绕开防火墙的问题,执行下面的安装命令,安装rosdepc:
sudo pip install rosdepc
rosdepc是替换rosdep的一款软件,其功能与rosdep是一模一样的,只不过为了解决墙的问题,把软件中的地址换成了gitee的国内地址,用法和安装方法与rosdep是一样的。其实跳过上面配置20-default.list的步骤,直接执行rosdepc安装也可以。
更新为国内源rosdepc后,再进行初始化和更新就可以成功了(无论前面执行过什么、有没有成功,一定要再初始化、更新一遍,否则rosdepc不生效),命令行要替换成(将前面的rosdep替换成rosdepc):
sudo rosdepc init
rosdepc update
注意下面所有涉及到rosdep的命令全部都要替换成rosdepc!!
完成后再执行:rosdepc install --from-paths src -y
就能将该ros包中所有依赖项安装好了,下面直接编译即可。
3. lua安装
编译时若出现以下报错:
CMake Error at cmake/modules/FindLuaGoogle.cmake:217 (MESSAGE):
Did not find Lua >= 5.2.
Call Stack (most recent call first):
CMakeLists.txt:34 (find_package)
说明未安装Lua,且要求Lua版本至少需要5.2版本。则执行以下语句安装Lua或更新版本:
sudo apt update
sudo apt install lua5.2 # 安装 Lua 5.2/Lua 5.3或者更高版本
安装后检查版本以确保安装成功:
lua -v
4. 安装Cartographer库
如果发现大量报错是出现在cartographer_core功能包里的,大概率还没安装Cartographer库,按照教程装即可:
sudo apt install ros-foxy-cartographer
sudo apt install ros-foxy-cartographer-ros
5. 安装Ceres库或调整适配版本
如果发现大量报错是出现在Ceres头文件里的(/usr/local/include/ceres/jet.h),则有两种可能的情况,一是ceres库未安装,二是当前功能包C++版本不适配,导致头文件不被正确识别。
如果是ceres库未安装,则根据以下步骤进行安装:
1)执行以下命令,安装依赖项
sudo apt-get install liblapack-dev libsuitesparse-dev libcxsparse3 libgflags-dev libgoogle-glog-dev libgtest-dev
2)下载源码
git clone https://github.com/ceres-solver/ceres-solver
3)进入ceres-solver文件夹,建立build文件并进入build文件、编译、最后安装
cd ceres-solver
mkdir build
cd build
cmake ..
make -j4
sudo make install
如果是当前功能包的C++版本不适配,假设该库仅由C++14或更高的版本支持,则需要在当前功能包的CMakeLists.txt中添加C++版本设置:
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
6. 安装test_msgs、behaviortree-cpp及其他类似的库和包
以test_msgs为例,若出现如下报错:
CMake Error at CMakeLists.txt:15 (find_package):
By not providing "Findtest_msgs.cmake" in CMAKE_MODULE_PATH this project has asked CMake to find a package configuration file provided by "test_msgs", but CMake did not find one.
Could not find a package configuration file provided by "test_msgs" withany of the following names:
test_msgsConfig.cmake
test_msgs-config.cmake
Add the installation prefix of "test_msgs" to CMAKE_PREFIX_PATH or set "test_msgs_DIR" to a directory containing one of the above files. If "test_msgs" provides a separate development package or SDK, be sure it has been installed.
则说明未安装test_msgs,按照以下步骤进行安装:
sudo apt update
sudo apt install ros-foxy-test-msgs
与此类似,只要报错提示一个库或包未安装,则一般都有两种安装方式,一是直接命令行安装,二是从github上下载源码编译安装。搜索一下就能知道用什么命令安装了。
十、手动杀死gazebo进程
如果启动launch文件时发现只有rviz启动了,gazebo无法正常开启,且出现以下报错:
[Err] [Master.cc:96] EXCEPTION: Unable to start server[bind: Address already in use]. There is probably another Gazebo process running.
[gazebo-1]
[gazebo-1] [Err] [Master.cc:96] EXCEPTION: Unable to start server[bind: Address already in use]. There is probably another Gazebo process running.
[gazebo-1]
[spawnentity.py-2] [INFO] [1727860193.444523147] [spawnentity]: Spawn Entity started
[spawnentity.py-2] [INFO] [1727860193.444801157] [spawnentity]: Loading entity XML from file /home/zhenghao/ros2demo/install/fishbotdescription/share/fishbotdescription/urdf/fishbotgazebo.urdf
[spawnentity.py-2] [INFO] [1727860193.446728252] [spawnentity]: Waiting for service /spawnentity, timeout = 30
[spawnentity.py-2] [INFO] [1727860193.446926100] [spawnentity]: Waiting for service /spawnentity
[ERROR] [gazebo-1]: process has died [pid 21047, exit code 255, cmd 'gazebo --verbose -s libgazeborosfactory.so /home/zhenghao/ros2demo/install/fishbotdescription/share/fishbot_description/world/fish.world'].
[gazebo-1]
该报错说明Gazebo服务器无法启动,因为存在另一个进程正在使用同一个端口。则可以通过以下命令终止其他端口:
首先用以下命令检查是否有正在运行的Gazebo实例:
ps aux | grep gazebo
一旦确定了运行中的Gazebo进程,就可以用kill命令结束它们:
kill -9 21047
结束所有进程后用gazebo命令重启即可。
十一、SLAM算法之扩展卡尔曼滤波与粒子滤波
教程中将SLAM算法分成了两类:
a)基于滤波,如扩展卡尔曼滤波、粒子滤波,ROS中的gmapping、hector_slam算法都是基于滤波实现的。
b)基于图优化,即先通过传感器进行构图,然后对图进行优化。
Cartographer就是基于图优化实现的。图优化相对于滤波,不用实时的进行计算,效率更高,消耗的资源更少,在实际场景中使用的更多。
教程对基于图优化的Cartographer进行了重点讲述,但是对基于滤波的算法未细讲,下面对扩展卡尔曼滤波与粒子滤波进行学习:
1)扩展卡尔曼滤波
无论是卡尔曼滤波还是扩展卡尔曼,其目的都是对系统的下一步走向作出有根据的预测,可以在任何含有不确定信息的动态系统中运用。在连续变化的系统中运用KF是非常理想的,内存小且速度快,这也就说明了该算法在计算实时连续变化的机器人位置以及预测下一点位置中的可应用性。在功能上,卡尔曼滤波和扩展卡尔曼滤波是相同的,接下来说明两者的不同。卡尔曼滤波假设每个变量都服从高斯分布,都有一个均值表示随机分布的中心(最可能的状态)以及方差(表示不确定性),通过考虑外部控制量建立控制矩阵对估计进行修正,再加上外部干扰,更新不确定性和最优估计,建立预测矩阵表示下一时刻的状态,再不断递归进行迭代。卡尔曼滤波和扩展卡尔曼的基本滤波过程都是这样的,区别就在于卡尔曼滤波必须应用于符合高斯分布的线性系统中,而扩展卡尔曼滤波解决了非线性问题,比普通卡尔曼多了把预测和测量部分线性化的过程,其核心思想是围绕滤波值将非线性函数展开成泰勒级数并略去二阶及以上项,得到一个近似的线性化模型,然后应用卡尔曼滤波完成状态估计。
2)粒子滤波
粒子滤波的核心思想是使用一组具有相应权值的随机样本(粒子)来表示状态的后验分布。该方法的基本思路是选取一个重要性概率密度并从中进行随机抽样,得到一些带有相应权值的随机样本后,在状态观测的基础上调节权值的大小。和粒子的位置,再使用这些样本来逼近状态后验分布,最后将这组样本的加权求和作为状态的估计值。这种采样后验证分布再调整加权的过程类似支持向量机的形式。粒子滤波不受系统模型的线性和高斯假设约束,采用样本形式而不是函数形式对状态概率密度进行描述,使其不需要对状态变量的概率分布进行过多的约束,因而在非线性非高斯动态系统中广泛应用。但是由于整个过程类似对采样点的遍历,因此效率较低,计算量大,对算力的要求高,有待优化。