1.C++的编译
在C++中,编译过程会生成多种形式的文件:预处理文件(.i/.ii)(展开宏和头文件)---> 汇编文件(.s)(转为平台相关的汇编代码)---> 目标文件(.o/.obj)(二进制机器码,未链接)---> 静态库(.a/.lib)(目标文件集合,直接嵌入程序)---> 动态库(.so/.dll/.dylib)(运行时加载的共享代码)以及可执行文件(如ELF、PE、Mach-O格式)。此外,Clang还可生成LLVM中间代码(.bc)用于跨平台优化。整个过程通过预处理器、编译器、汇编器和链接器协作完成,不同平台(Linux/Windows/macOS)的扩展名和工具略有差异,但核心流程一致。
2.hostname_ip
在 Ubuntu(以及其他类 Unix 系统)里,确实有一个本地文件用来手动设置 Hostname 和 IP 的映射,可以绕过 DNS 直接访问网站。/etc/hosts 是一个本地 DNS 表,用来手动指定 "主机名 <-> IP地址" 的映射关系。查找ip
当你在浏览器或代码里访问一个 hostname 时:
-
系统会先查
/etc/hosts看有没有对应记录。 -
如果找到了,就直接用这个 IP 地址去连接。
-
如果没有,再去找 DNS 服务器解析。
3.vscode使用技巧
0.安装、卸载
1.vscode怎么使代码块自动修正整齐,格式化
快捷键:Ctrl+Shift+I,可以自动把需要空格的地方和缩进的地方给你加上,使得你的代码看起来更整洁。(很好用!!)
2.vscode使用ctrl跳转到某函数定义说明之后,跳回原来的位置
快捷键:Ctrl+Alt+-
3.vscode展开/折叠所有子区域
快捷键:先Ctrl+K 后Ctrl+J 可以展开所有子区域;
先Ctrl+K 后Ctrl+0(零)可以折叠所有子区域。
4.vscode找不到自定义头文件
`参考这个 头文件路径添加,在文件里添加"${workspaceFolder}/include/**";
注意上面这一步在c_cpp_propertiest.json操作的作用是:告诉编译器头文件的位置,用于代码补全,控制 VS Code 智能提示、跳转、红线消除。
如果你的目录格局是下面这样的,还需要做其他配置,在tasks.json文件(这里才是告诉编译器如何编译操作的)加入一些编译的参数,如下下所示:
project
--include
swap.h
--src
swap.cpp
main.cpp
"tasks": [
{
"type": "cppbuild",
"label": "C/C++: g++ 生成活动文件",
"command": "/usr/bin/g++",
"args": [
"-fdiagnostics-color=always",
// 要编译的源文件
"-g",
"${file}",
"src/swap.cpp",
// 告诉编译器头文件的位置
"-I",
"include",
// 告诉编译器要生成的可执行文件的位置
"-o",
"${fileDirname}/${fileBasenameNoExtension}"
],
"options": {
"cwd": "${fileDirname}"
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "调试器生成的任务。"
},
5.报错关于CMake的)(这个不影响的话就别理了)
当提示:“[cmakefileapi-driver] 此版本的 CMake 不支持 "toolchains" 对象类型。编译器路径将通过读取 CMakeCache.txt 来确定。 ” (还可以解决找不到ros/ros.h的问题,很抽象)
说明cmake版本低于 3.21(toolchains 类型是从 3.21 开始支持的);
此时在CMakeLists.txt文件中添加:
set(CMAKE_C_COMPILER /usr/bin/gcc)
set(CMAKE_CXX_COMPILER /usr/bin/g++)
4. C++中的标识符命名最佳实践
0. 用于变量的蛇形命名法和用于函数的驼峰命名法
int my_variable_name; // 蛇形命名法
int myVariableName() // 驼峰命名法
1. 变量的标识符
C++中的一个约定是变量名应该以小写字母开头。如果变量名是一个单词,则整个内容应该用小写字母书写。
int value; // 推荐
int Value; // 不推荐
一个好的经验法则是使标识符的长度与它的使用范围成比例。具有平凡用途的标识符可以具有短名称(例如,i)。使用更广泛的标识符(例如,从程序中的许多不同位置调用的函数)应该具有更长、更具描述性的名称(例如,不要使用open,而是尝试使用openFileOnDisk)。
int open() //不推荐
int openFileOnDisk() //推荐
2. 用户定义的类型
以大写字母开头的标识符名称通常用于用户定义的类型(例如结构、类和枚举)
3. 变量的声明
对于变量声明,使用注释来描述变量将用于什么,或者解释任何其他不明显的东西。例如,假设我们已经声明了一个名为numberOfChars的变量,用于存储一段文本中的字符数。文本“Hello World!”有10个、11个或12个字符吗?这取决于我们是包括空格还是标点符号。与其命名变量numberOfCharsIncludingWhitespaceAndPunctions(相当长),不如在声明行上或上面放置一个适当的注释,以帮助用户理解。
// 统计文本中的字符串长度,包含空格和标点
int numberOfChars;
注:
最佳实践
在旧的程序中更改代码时,使用该程序的约定(即使它们不符合现代最佳实践)。编写新程序时,请使用现代最佳实践。
5. 代码中函数的理解
函数通常应执行一个(且只能执行一个)任务。
当函数变得太长、太复杂或难以理解时,可将其划分为多个子函数。称为重构。
6. C++中的显式命名空间限定符
「::」 符号是一个称为范围解析运算符。:: 符号左侧表示命名空间, :: 符号右侧则是在命名空间中的符号。如果 :: 符号左侧没有提供标识符,则认为是全局命名空间。因此,使用std::cout时,是在说“使用命名空间std中的cout”。这是使用cout的最安全的方法,因为引用的cout(std名称空间中)没有歧义。
避免在程序顶部或头文件中使用using指令(例如 “using namespace std;”)。它们违反了最初添加名称空间的目的。(以这种方式使用using指令时,定义的任何标识符都可能与std命名空间中的同名标识符冲突。更糟的是,也许标识符名称现在不会冲突,但可能会与未来的语言修订中添加到std命名空间的新标识符冲突。这就是将标准库中的所有标识符移动到std命名空间的好处!)
7.命令行指令"|"(pipe管道)的常见组合
管道的作用是将前一个命令(| 左边的命令)的标准输出(Standard Output),作为后一个命令(| 右边的命令)的标准输入(Standard Input)。
1. 过滤和搜索(Filtering & Searching)
这是管道最核心的用途之一:从大量文本中筛选出你关心的内容。
-
grep(全局搜索正则表达式并打印)-
ps aux | grep nginx-
ps aux:列出所有正在运行的进程(输出很多行)。 -
grep nginx:从进程列表中只筛选出包含“nginx”的行。 -
作用:快速检查某个进程(如Nginx)是否在运行。
-
-
cat /var/log/syslog | grep -i error-
cat:读取系统日志文件。 -
grep -i error:不区分大小写地筛选出包含“error”的行。 -
作用:在日志文件中快速查找错误信息。
-
-
-
head/tail(查看开头/结尾)-
dmesg | tail -20-
dmesg:显示内核环形缓冲区消息(启动信息等,通常很长)。 -
tail -20:只显示最后20行。 -
作用:查看最近的内核消息。
-
-
history | grep docker | head -5-
history:显示你执行过的所有命令历史。 -
grep docker:筛选出历史上所有包含“docker”的命令。 -
head -5:只显示最前面的5条结果。 -
作用:快速找到最近用过的大概5条docker命令。
-
-
2. 排序、去重和统计(Sorting, Deduplication & Statistics)
对数据进行整理和分析。
-
sort(排序)和uniq(去重)-
cat access.log | cut -d' ' -f1 | sort | uniq -c | sort -nr-
这是一个非常经典的分析Web服务器访问日志的流水线,用于找出访问最频繁的IP地址:
-
cat access.log:读取访问日志。 -
cut -d' ' -f1:用空格分割每一行,只取第一列(即IP地址)。 -
sort:对IP地址进行排序(uniq要求输入必须是排序后的)。 -
uniq -c:统计每个唯一的IP地址出现的次数。 -
sort -nr:按统计次数进行数字逆序排序(次数最多的排在最前面)。
-
-
-
wc(统计)-
ls /usr/bin | wc -l-
ls /usr/bin:列出/usr/bin目录下的所有文件(每行一个)。 -
wc -l:统计行数。 -
作用:快速计算一个目录下有多少个文件。
-
-
3. 实时监控(Real-time Monitoring)
管道可以让静态命令变成动态监控工具。
-
tail -f(实时追踪文件末尾新增的内容)-
tail -f /var/log/apache2/access.log | grep 404-
tail -f:实时输出日志文件新增的内容。 -
grep 404:只显示包含HTTP状态码“404”(未找到)的请求。 -
作用:实时监控网站是否有人访问了不存在的页面。
-
-
4. 分页浏览(Paging)
防止内容过多一屏显示不下。
-
less/more(分页器)-
ps aux | less-
ps aux:输出所有进程信息。 -
less:将结果用分页器打开,你可以用上下键、PageUp/PageDown键浏览,按q退出。 -
作用:优雅地浏览长输出,而不会被刷屏。
-
-
5. 转换和编辑(Transformation & Editing)
-
awk/sed(强大的文本处理工具)-
ls -lh | awk '{print $5, $9}'-
ls -lh:以人类可读的格式列出文件(包括大小、文件名等)。 -
awk '{print $5, $9}':只打印第5列(文件大小)和第9列(文件名)。 -
作用:自定义
ls命令的输出格式。
-
-
总结表
| 管道组合示例 | 作用描述 | ||
|---|---|---|---|
| `command | grep pattern` | 过滤,只显示包含特定模式的行 | |
| `command | tail -n` | 只显示结果的最后n行 | |
| `command | head -n` | 只显示结果的最前n行 | |
| `command | sort` | 对结果进行排序 | |
| `command | uniq -c` | 统计重复行的次数(需先sort) | |
| `command | wc -l` | 统计结果的行数 | |
| `command | less` | 将结果用分页器打开,便于浏览 | |
| `command | awk '{...}'` | 使用awk重新格式化输出 | |
| `command1 | command2 | command3` | 多重管道,将处理结果一步步传递下去 |
核心思想: 每个命令只做一件小事,但通过管道 | 将它们像乐高积木一样组合起来,就能完成非常复杂的任务。你练习得越多,就越能体会到这种“组合拳”的强大威力。
8.shell 的理解
当你在终端输入命令的时候,shell是需要在$PATH列出的目录列表中查找对应的命令执行文件然后解释给内核去执行(解释:~/.bashrc 是用户级脚本,用于定制Shell环境(包括修改 PATH),而 PATH 是一个具体的环境变量,定义了Shell查找命令的目录列表)。举个🌰:这就是为什么当下载了ROS之后每次使用需要在终端把ros的资源source一下或者添加到~/.bashrc里面,要不然你在终端输入ros的指令之后,shell在查找命令的目录列表的时候是定位不到对应的可执行程序的。
0.环境变量$PATH的理解
-
定义:
PATH是一个环境变量,其值是一个用冒号:分隔的目录路径列表。 -
作用:当你输入一个命令(如
ls,python,code)时,Shell会按照PATH中列出的目录顺序,依次在这些目录中查找该命令的可执行文件。如果找到就执行,如果找不到就会报错command not found.
1.~/.bashrc中的export和source的区别
export用于设置/导出一个环境变量,如export PATH="/opt/ros/humble/bin:$PATH"(将ROS2的可执行文件目录添加到PATH里面),用途:设置简单的、独立的环境变量;
source用于加载并执行一个脚本文件,将其中的命令(通常包含多个export)应用到当前Shell环境中,如 source /opt/ros/humble/setup.bash这行命令会执行 /opt/ros/humble/setup.bash 这个脚本文件里的所有命令(如上面的export PATH="/opt/ros/humble/bin:$PATH"也在里边),用途:加载一个复杂的配置脚本,该脚本内部包含了大量的 export 命令、函数定义、别名设置等。手动把这些内容全部复制到 ~/.bashrc 里会非常混乱,用 source 则干净利落。
9.ros2工作空间下同级目录的理解
"工作空间"是ROS中用于开发ROS 2的位置,是一个具有严格子目录排布的目录文件,colcon 是 ROS 2 的构建工具,它继承并改进了 ROS 1 的 catkin_make/catkin build。它的设计理念是“不污染源代码目录”,并将构建结果集中输出到指定的目录中。默认情况下,这些输出目录就创建在你的工作空间根目录下。
0.build/
这是 CMake / colcon 执行编译过程的地方,包含了所有在编译过程中生成的临时文件和中间文件。每一个功能包在这都会有一个对应的同名子文件夹,里面包含C++源代码编译过程中生成的二进制、链接库等中间文件和python工具的临时构建文件。用处: 通常不需要手动操作或查看这个目录,除非你在进行深度调试,需要查看 CMake 或编译器的详细输出日志。当你再次运行 colcon build 时,colcon 会利用这里的文件来判断哪些部分需要重新编译(增量编译)。
1.install/
这是 colcon build 命令的最终产出目录,也是 ROS 2 运行时所真正使用的目录,当你使用 ros2 run 或 ros2 launch 时,ROS 2 会从这个目录里查找并执行对应的节点和启动文件(借助上面shell的说明去理解,这就是为什么在使用你工作空间中的任何功能之前,必须先执行 source install/local_setup.bash。这个脚本会将 install/ 目录下的所有包添加到你的 ROS_PACKAGE_PATH 等环境变量中,使得 ROS 2 能够发现它们)。
内容:包含所有已编译的可执行文件、Python 模块、头文件、启动文件、消息/服务定义文件等,并且按照标准的 FHS(文件系统层次结构标准)组织。
install/your_package_name/lib/: 存放包的可执行文件(Python 脚本和 C++ 节点);
install/your_package_name/share/: 存放包的共享资源,如接口定义(msg/, srv/)、启动文件(launch/)、配置文件(config/)等;
install/your_package_name/include/: 存放 C++ 头文件;
install/setup.bash (或 .sh, .zsh): 最重要的脚本,用于“激活”或“注入”当前工作空间的环境。
分发与部署: 这个目录可以被直接打包并分发到其他机器上运行(前提是目标机器有兼容的系统库)
2.log/
colcon 在构建过程中会生成大量详细的日志信息,它们都被保存在这个目录里。
3.src/
所有的 ROS 2 包(package)的源代码都放在这里。每个包都是一个独立的文件夹。colcon build 命令会递归地查找 src/ 目录下的所有包并进行构建。colcon 严格遵守不修改源目录的原则,所以所有的构建产物(build/, install/)都放在与 src/ 同级的目录中。
4.src/package
功能包是ros2代码的组织单元,通过包,可以发布ROS 2工作并允许其他人轻松构建和使用它。功能包还整合了你这部分“工作”的实现需要哪些依赖项,以及程序的构建和编译规则。
10.github的镜像源
当终端使用git clone github出问题的话,可以git克隆下载GitHub项目镜像,在项目地址前加上https://gh-proxy.com/即可极速下载。
git clone https://gh-proxy.com/https://github.com/
11.launch包含launch
先上菜:
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription, GroupAction, ExecuteProcess, TimerAction, LogInfo
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node
from launch.substitutions import PathJoinSubstitution
from ament_index_python.packages import get_package_share_directory
import os
from launch.event_handlers import OnProcessStart
from launch.actions import RegisterEventHandler
from launch.event_handlers import OnProcessExit
from launch.events import Shutdown
from launch.event_handlers import OnShutdown
from launch.actions import EmitEvent
# ### 使用dlio
# ```bash
# # 启动雷达驱动
# ros2 launch livox_ros_driver2 msg_MID360_launch.py
# # 启动点云转化
# ros2 launch tron_navigation livox_to_pointcloud2_launch.py
# # 启动dlio
# ros2 launch direct_lidar_inertial_odometry dlio.launch.py rviz:=false
# # 启动点云转化
# ros2 launch pointcloud_to_laserscan pointcloud_to_laserscan_launch.py
# # 启动 map_odom.py 节点
# python3 tools/map_odom_brocaster.py
# # 启动websocket桥接
# ros2 launch cmd_bridge cmd_vel_bridge.launch.py
# # 启动rviz2
# ros2 launch tron_navigation rviz.launch.py # no include
# # 启动nav2
# ros2 launch tron_navigation tron_bringup.launch.py # no include
# ```
def generate_launch_description():
# ======== 1. 启动雷达驱动 ========
# livox_launch = IncludeLaunchDescription(
# PythonLaunchDescriptionSource(
# PathJoinSubstitution([
# get_package_share_directory('livox_ros_driver2'),
# 'launch',
# 'msg_MID360_launch.py'
# ])
# ),
# )
livox_launch = ExecuteProcess(
cmd=['ros2', 'launch', 'livox_ros_driver2', 'msg_MID360_launch.py'],
output='screen')
# ======== 2. 启动点云转换 ========
# livox_pointcloud2_launch = IncludeLaunchDescription(
# PythonLaunchDescriptionSource(
# PathJoinSubstitution([
# get_package_share_directory('tron_navigation'),
# 'launch',
# 'livox_to_pointcloud2_launch.py'
# ])
# ),
# )
livox_pointcloud2_launch = ExecuteProcess(
cmd=['ros2', 'launch', 'tron_navigation', 'livox_to_pointcloud2_launch.py'],
output='screen')
# ======== 3. 启动dlio ========
# dlio_launch = IncludeLaunchDescription(
# PythonLaunchDescriptionSource(
# PathJoinSubstitution([
# get_package_share_directory('direct_lidar_inertial_odometry'),
# 'launch',
# 'dlio.launch.py'
# ])
# ),
# launch_arguments={'rviz': 'false'}.items(),
# )
dlio_launch = ExecuteProcess(
cmd=['ros2', 'launch', 'direct_lidar_inertial_odometry', 'dlio.launch.py', 'rviz:=false'],
output='screen')
# ======== 4. 启动点云转激光 ========
# pcl_to_laserscan_launch = IncludeLaunchDescription(
# PythonLaunchDescriptionSource(
# PathJoinSubstitution([
# get_package_share_directory('pointcloud_to_laserscan'),
# 'launch',
# 'pointcloud_to_laserscan_launch.py'
# ])
# ),
# )
pcl_to_laserscan_launch = ExecuteProcess(
cmd=['ros2', 'launch', 'pointcloud_to_laserscan', 'pointcloud_to_laserscan_launch.py'],
output='screen')
# ======== 5. 启动 map_odom.py 节点 ========
map_odom_process = ExecuteProcess(
cmd=['python3', 'tools/map_odom_brocaster.py'],
output='screen'
)
# ======== 6. 启动 websocket 桥接 ========
# websocket_bridge = IncludeLaunchDescription(
# PythonLaunchDescriptionSource(
# PathJoinSubstitution([
# get_package_share_directory('cmd_bridge'),
# 'launch',
# 'cmd_vel_bridge.launch.py'
# ])
# ),
# )
websocket_bridge = ExecuteProcess(
cmd=['ros2', 'launch', 'cmd_bridge', 'cmd_vel_bridge.launch.py'],
output='screen')
'''
# # ======== 4. 启动 RViz ========
# rviz_launch = IncludeLaunchDescription(
# PythonLaunchDescriptionSource(
# PathJoinSubstitution([
# get_package_share_directory('tron_navigation'),
# 'launch',
# 'rviz.launch.py'
# ])
# ),
# )
# ======== 5. 启动 Nav2 ========
# nav2_launch = IncludeLaunchDescription(
# PythonLaunchDescriptionSource(
# PathJoinSubstitution([
# get_package_share_directory('tron_navigation'),
# 'launch',
# 'tron_bringup.launch.py'
# ])
# ),
# )
# ======== 延时与启动顺序控制(可选)========
# 先启动雷达 → 定位 → 转换 → rviz → nav2
'''
even_handler = [
LogInfo(msg="Starting LiDAR..."),
livox_launch,
RegisterEventHandler(
OnProcessStart(
target_action=livox_launch,
on_start=[
LogInfo(msg="Starting livox_pointcloud2_launch..."),
livox_pointcloud2_launch
]
)
),
RegisterEventHandler(
OnProcessStart(
target_action=livox_pointcloud2_launch,
on_start=[
LogInfo(msg="Starting dlio_launch..."),
dlio_launch
]
)
),
RegisterEventHandler(
OnProcessStart(
target_action=dlio_launch,
on_start=[
LogInfo(msg="Starting pcl_to_laserscan_launch..."),
pcl_to_laserscan_launch
]
)
),
RegisterEventHandler(
OnProcessStart(
target_action=pcl_to_laserscan_launch,
on_start=[
LogInfo(msg="Starting map_odom_process..."),
map_odom_process
]
)
),
RegisterEventHandler(
OnProcessStart(
target_action=map_odom_process,
on_start=[
LogInfo(msg="Starting websocket_bridge..."),
websocket_bridge
]
)
),
RegisterEventHandler(
OnProcessExit(
target_action=livox_launch,
on_exit=[
LogInfo(msg="LiDAR process has exited."),
EmitEvent(event=Shutdown(reason="LiDAR process exited") )
]
)
),
RegisterEventHandler(
OnProcessExit(
target_action=livox_pointcloud2_launch,
on_exit=[
LogInfo(msg="livox_pointcloud2 process has exited."),
EmitEvent(event=Shutdown(reason="livox_pointcloud2 process exited") )
]
)
),
RegisterEventHandler(
OnProcessExit(
target_action=dlio_launch,
on_exit=[
LogInfo(msg="dlio_launch process has exited."),
EmitEvent(event=Shutdown(reason="dlio_launch process exited") )
]
)
),
RegisterEventHandler(
OnProcessExit(
target_action=pcl_to_laserscan_launch,
on_exit=[
LogInfo(msg="pcl_to_laserscan_launch process has exited."),
EmitEvent(event=Shutdown(reason="pcl_to_laserscan_launch process exited") )
]
)
),
RegisterEventHandler(
OnShutdown(
on_shutdown=[
LogInfo(msg="Launch was asked to shut down, cleaning up...")
]
)
)
]
return LaunchDescription(even_handler)
有两个版本,第一个版本是用launchIncludedescription来包含launch文件,但是EventHandler不能处理launch文件,只能采用第二个版本Executeprocess把launch文件当成一个进程来用才可以,不过这样不利于Launch文件内部的管理。
原因:
事件处理器(如OnProcessStart、OnProcessExit)需要明确的目标动作来绑定。
-
ExecuteProcess: 创建一个明确的进程动作,launch系统可以直接跟踪和管理这个进程 -
IncludeLaunchDescription: 引入另一个launch文件,该文件可能包含多个动作和节点,launch系统将其视为一个"黑盒"。
IncludeLaunchDescription启动的进程被封装在子launch上下文中,父launch系统无法直接访问这些进程的详细信息。
12. URDF文件理解
0. visual和collision的mesh文件差异
<visual>常用.dae文件,纹理材质视觉效果更好;
<collision>常用.stl文件,属于简化的几何模型,更便于计算;
13. 里程计的理解
里程计并不是移动底盘的物理组件,里程计是一个计算值/估计值;
🔧 物理组件(硬件):
-
轮式编码器:测量车轮转动角度的传感器
-
IMU:测量加速度和角速度的芯片
-
GPS:接收卫星信号的模块
-
摄像头:视觉传感器
🧮 计算值(软件/算法):
-
里程计:基于传感器数据计算得出的位姿估计
-
"我从起点移动了多远?"
-
"我转了多少角度?"
-
"我现在的速度和角速度是多少?"
-
物理世界
↓
[硬件传感器] ←── 这些是物理组件
↓ 产生原始数据
轮编码器脉冲 | IMU读数 | 摄像头图像
↓
[算法/节点] ←── 这些计算里程计
↓ 计算得出里程计值
/wheel_odom | /imu_odom | /visual_odom
↓
[robot_localization] ←── 融合算法
↓ 输出最佳估计(提供了一个单一、可靠、权威的里程计源,所有其他节点(如move_base、rviz2等)都应该使用这个融合后的结果)
/odometry/filtered 和 odom→base_link TF
通常的机器人设置至少包括轮编码器和惯性测量单元 (IMU) 作为其里程计传感器来源。当``robot_localization``接收到多个传感器提供的里程计信息时,它可以通过使用状态估计节点来融合传感器提供的里程计信息。这些节点使用扩展卡尔曼滤波器(ekf_node)或无迹卡尔曼滤波器(ukf_node)来实现融合。如果在配置中启用了``robot_localization``软件包,它将通过``odometry/filtered``和``accel/filtered``主题发布融合的传感器数据。此外,它还可以在``/tf``主题上发布``odom`` => ``base_link``的变换。
14. dlio/fastlio2与slam与nav2
问题:dlio和fastlio2的作用是什么,我看这两个也都能实现定位和建图,他们和slam的关系是什么,然后我看在完整的项目里面,一般是先启动dlio或者fastlio2之后再启动navigation的相关包,这之间的联系又是什么,详细解释一下。
回答:
好——我把核心概念、二者在系统里的职责、它们跟“SLAM”的关系、以及为什么常常是先启动 DLIO / FAST-LIO2(LIO) 然后再启动 navigation(导航)包,做一个系统而详细的说明,并给出实际启动/集成建议(包含常见 topic / TF / 服务 / 顺序)。我尽量贴合你在 Tron1 轮足移动机器人上的实际场景(传感器:LiDAR、IMU、可能还有里程计/相机/轮速),直接可用。
先给结论性一句话:
DLIO / FASTLIO2 都是「LiDAR-Inertial Odometry(LIO)」模块 —— 实时把传感器数据融合成连续的位姿(里程计)并输出局部地图;而 SLAM 是一个更广义的流程/系统,除了里程计外还包含回环检测、位姿图优化、全局地图输出与长期一致性。导航(Nav)使用 LIO/SLAM 的输出(位姿和地图)来做定位、代价地图、规划与控制。下面分要点展开。
1) DLIO / FAST-LIO2 到底做什么?(职责、输出)
两者本质上都是 LiDAR + IMU → 实时里程计 / 局部地图 的系统,但实现细节、设计目标略有不同:
主要目标
在实时条件下把 LiDAR 点云和 IMU(以及有时轮速/里程计)融合,估计机器人在连续时间上的位姿(6DoF),并对 LiDAR 的运动畸变做校正(deskew)。
输出稳定的 odometry(里程计)、发布 TF(通常
odom->base_link)和局部点云/局部子图(供后端或可视化),并可能提供保存地图的接口。具体输出(典型)
一个
nav_msgs/Odometry或自定义 odom topic(频率高、实时)。
tf:发布base_link相对于odom(或直接odom->base_link)的变换(连续更新)。局部地图或关键帧点云(
sensor_msgs/PointCloud2、.pcd保存服务等)。有的实现带有轻量回环检测或重定位,但核心还是“里程计 + deskew + 局部配准”。
DLIO vs FASTLIO2 区别(简述)
DLIO(Direct LIO):论文/实现强调「连续时间轨迹建模」与“轻量级、直接的点云-IMU融合”,精于运动畸变校正,设计追求计算效率。
FAST-LIO2:以高效率著称的 LIO 实现(C++ 优化),实战中广泛用于高性能实时场景,通常包含良好的数据结构和速度优化。
实际上功能重叠很大:两者都能做定位和局部地图构建,但在算法细节、鲁棒性与参数敏感性上有差异(不同场景表现不同)。
nav_msgs/Odometry
header:
frame_id: "map"
pose:
pose:
position: {x: 1.23, y: 2.34, z: 0.45}
orientation: {x: 0.0, y: 0.0, z: 0.707, w: 0.707}
twist:
twist:
linear: {x: 0.1, y: 0.0, z: 0.0}
angular: {x: 0.0, y: 0.0, z: 0.02}
2) SLAM(同时定位与建图)与 LIO 的关系
SLAM(概念):定位(Localization)+ 建图(Mapping)。完整的 SLAM 系统通常包含:
里程计模块(odometry / frontend):实时估计连续运动(LIO 就在这里)。
回环检测(loop-closure):检测回到过去位置以约束误差。
后端优化(pose-graph / PGO / 全局优化):把回环、里程计约束组合,做全局一致性优化。
地图管理 / 保存 / 查询(map server):保存成全局地图,供重定位或导航使用。
因此:
LIO = SLAM 的前端(或“实时里程计”)。它负责把传感器转换成连续位姿与局部地图。
完整 SLAM = LIO 前端 + 回环检测 + 全局优化 + map server 等。很多工程里把 LIO 作为里程计前端,再在上面叠加 PGO(pose graph optimization)、HBA、回环检测等以得到全局一致地图。
后果:如果只用 LIO(没有回环/优化),导航可以运行但长期会有漂移(尤其是大范围)——这就是为什么很多项目里会把 LIO(或 DLIO/FASTLIO2)和 PGO/HBA/localizer 组合起来,得到既实时又一致的地图。
3) 为什么“先启动 LIO(DLIO/FASTLIO2)再启动 navigation”?两者如何衔接
导航栈(无论 ROS1 的
move_base还是 ROS2 的nav2)依赖于几类实时数据:
实时位姿(机器人在世界里的当前估计),通常通过
tf或odomtopic 提供(odom或tf)。环境信息 / 障碍物:通常来自激光(
/scan)或点云、深度相机的传感器数据(用来构建 costmap / obstacle layer)。地图(静态或全局):如果使用基于地图的导航(规划全局路径),需要一个 map_server 或 localization node(例如 AMCL / LiDAR localization)提供
map->odom的 TF 或局部化结果。因此启动顺序与原因:
传感器驱动先起(LiDAR、IMU、相机)——保证数据流入。
启动 LIO(DLIO/FASTLIO2):它去订阅点云与 IMU,发布
odom、tf(odom->base_link)、局部点云或障碍信息。为什么先?因为 navigation 的 costmap、localization、planner 都需要持续、低延迟的位姿与障碍物输入。没有 LIO,导航没有参考坐标和局部地图,不能安全规划。启动 map server / SLAM 后端(可选):如果使用全局地图或需要长期一致性,启动回环检测 / PGO / 地图保存模块,生成
map(静态地图)并发布map->odom或提供服务。启动 localization(可选):例如 AMCL(基于激光)或 LiDAR 重定位模块,把机器人定位到
map上,发布map->odomTF。启动 navigation(Nav2):它使用
tf树(map->odom->base_link),costmap 会订阅激光/点云以感知道路障,planner 基于map或local_costmap做路径规划并发布cmd_vel。关键点:navigation 依赖 LIO 提供稳定的里程计(odom)与障碍感知流;如果你先启动 navigation 而 LIO 后起,导航会因为短时间没有位姿/地图而报错或进入错误状态。
4) TF / Frame(坐标系)关系:理解这一点很重要
标准坐标链通常是(ROS 推荐):
map→odom→base_link→sensor frames含义:
base_link:机器人机体坐标。
odom:短期、连续、无漂移补偿的里程计坐标系(LIO 输出往往是odom->base_link)。当里程计漂移时,odom会累积误差。
map:全局一致的地图坐标系(SLAM 后端/重定位维护),用于长期导航和规划。谁发布哪个 TF?
LIO(DLIO/FASTLIO2):通常发布
odom->base_link(实时),并发布odom下的 tf 与/odomtopic。SLAM 后端 / relocalizer / map_server:负责
map->odom(当发现回环或做重定位时,会发布或调整 map->odom,以把 odom 校正到全局 map)。navigation(Nav2)使用
map->odom+odom->base_link得到map->base_link,进而规划和控制。如果只有 LIO 而没有 map->odom(即没有全局 SLAM/重定位),导航仍可在局部运行(基于 odom),但全局规划/重复运行会因为漂移出错。
5) 实战集成要点(你在 Tron1 上要特别注意的点)
下面给出一个清单,帮助你把 LIO(DLIO/FASTLIO2)和 navigation 整合好:
A. 保证时间同步 & 外参准确
LiDAR 与 IMU 必须时间同步(hardware sync 或 software approximate)。
LiDAR ↔ IMU 的外参(位移 + 旋转)要标定准确,误差会快速影响里程计。
B. 话题 / 数据接口(典型 names,可据你实现调整)
LiDAR 点云:
/points_raw或/velodyne_points(sensor_msgs/PointCloud2)IMU:
/imu/data(sensor_msgs/Imu)里程计:
/odom(nav_msgs/Odometry)(由 LIO 发布)TF:
odom -> base_link(LIO) 和map -> odom(SLAM/PGO/Localizer)地图:
/map(如果使用 map_server)cmd: navigation 发布
/cmd_vel给底层控制器C. 启动顺序示例(基于 ROS2 / Nav2 的推荐)
sensor drivers(LiDAR driver、IMU driver、wheel odom)
LIO 节点(DLIO / FASTLIO2) → 发布
/tf(odom->base_link)、/odom、局部点云(可选)PGO / 回环检测 / 地图优化 后端 → 生成全局
map并发布map->odom(或提供服务)map_server(如果使用静态 map) + localization(AMCL 或 LiDAR localizer)
nav2 lifecycle bringup(planner, controller, bt_navigator 等)→ 启动 costmap(订阅 pointcloud/scan)
application:send goals、monitor paths、调参
D. 如何把 LIO 的局部地图用到导航
costmap 的 obstacle layer 可以直接订阅 LIO 输出的点云或激光 topic(例如
/points_local-> 屏蔽动态点 -> 转入 costmap)。global planner 通常使用
map(静态或 SLAM 输出)来做长路径规划。若没有 map,可做纯基于odom的局部避障导航(适用于短任务)。E. 漂移与回环修正
若环境会大量重复、或任务长期航行,必须加回环检测 + PGO(或周期性地把 LIO 输出的轨迹做全局优化),并把
map->odom发布给 navigation。常见做法:LIO 做前端,PGO(GTSAM 等)做后端。后端一旦优化出新的全局位姿,就发布
map->odom修正 odom,从而让 nav 使用修正后一致的坐标。F. 多传感器融合建议
在轮足机器人上,融合轮速里程计(wheel odom)可以提高平面运动精度(尤其在特征稀少的场景)。考虑用 EKF / robot_localization 把 LIO 与 wheel odom、IMU、视觉里程计融合(取决你需要的精度与鲁棒性)。但注意时间同步与坐标变换正确配置。
6) 常见问题与排查(实操技巧)
导航突然漂移/规划失败:检查
map->odom是否存在/合理;若没有,导航可能在使用一个漂移的 odom。costmap 空或障碍识别不对:检查 LIO 是否发布点云到正确 topic,并且 costmap 配置的 topic 名和 frame(通常
base_link)一致。TF 问题:用
tf2_echo/ros2 run tf2_tools view_frames(或rosrun tf tf_monitor)检查map->odom、odom->base_link。CPU 占用高 / 频率低:LIO 节点实时性差会导致 navigation 卡顿,降低点云分辨率或采样率;或把重计算(PGO/HBA)放成离线/低频任务。
机器人定位在地图外:检查 localization(AMCL)是否使用正确的 initial pose,或调用 relocalize 服务(如果有)。
7) 一个具体的、简明的整合示例(ROS2 / Nav2 风格 — 供参考)
(假设你有:LiDAR driver →
/points_raw,IMU →/imu/data,正在用 FASTLIO2 作为 odom)启动步骤(示例):
启动 LiDAR 与 IMU 驱动。
启动 FASTLIO2(参数指定
/points_raw、/imu/data),它开始发布/odom与tf(odom->base_link)。启动 PGO / 回环后端(可选)来维护
map与map->odom。启动
map_server(如果你已经有 map)或让 PGO 输出并加载 map。启动 localization 节点(如果使用),它订阅
/scan或/points,并发布map->odom。启动 nav2(lifecycle bringup):planner、controller、bt-nav、costmap(costmap 的 sensor topics 指向 LIO 的局部点云或 LiDAR 原始话题)。
下发导航目标(
nav2_simple_commander或 RViz 2 发送 goal)。
8) 给你 Tron1 的具体建议(实务清单)
先在地面做低速采集,验证 LIO 在你的地形(地面反射、轮足扰动)下能稳定输出路径。
做 LiDAR-IMU 外参与时间同步标定(严格去做),IMU 噪声模型要在 cfg 中设好。
地面点滤波:地面分割(移除大量地面点)通常能提升点云配准的稳定性。
把 wheel odom 作为额外观测(若可靠),用 robot_localization 或 EKF 融合以减少漂移。
如果使用 ROS2:确认 DLIO/FASTLIO2 的 ROS1/ROS2 版本兼容性;若需要,优先用已经移植的 ROS2 分支或考虑做少量适配代码。
测试导航堆栈:从局部(小范围)到大范围逐步放开,观察回环时 map->odom 的修正效果。
1265

被折叠的 条评论
为什么被折叠?



