SLAM 算法技能
同步定位与建图(Simultaneous Localization and Mapping)
何时使用
当需要以下帮助时使用此技能:
- 实现激光雷达 SLAM(GMapping、Cartographer)
- 实现视觉 SLAM(ORB-SLAM3、RTAB-Map)
- 配置 SLAM 实时运行
- 优化 SLAM 定位精度
- 融合多传感器 SLAM
快速参考
SLAM 类型对比
| 类型 |
传感器 |
优点 |
缺点 |
适用场景 |
| GMapping |
激光雷达 |
计算量小 |
精度一般 |
室内、小场景 |
| Cartographer |
激光雷达 |
高精度、实时 |
资源消耗大 |
室内+室外 |
| RTAB-Map |
深度相机 |
闭环检测强 |
内存消耗大 |
大场景 |
| ORB-SLAM3 |
单目/立体 |
多地图 |
需要纹理 |
室内有纹理 |
| LIO-SAM |
LiDAR+IMU |
高精度 |
需 IMU |
室外复杂地形 |
LaserSLAM 实现(GMapping)
节点结构
// laser_slam_node.cpp
#include <rclcpp/rclcpp.hpp>
#include <sensor_msgs/msg/laser_scan.hpp>
#include <nav_msgs/msg/odometry.hpp>
#include <tf2_ros/transform_broadcaster.h>
class LaserSlamNode : public rclcpp::Node
{
public:
LaserSlamNode() : Node("laser_slam")
{
// ── 激光雷达订阅 ───────────────────────
scan_sub_ = this->create_subscription<sensor_msgs::msg::LaserScan>(
"/scan", 10,
[this](const sensor_msgs::msg::LaserScan::SharedPtr msg) {
this->laser_callback(msg);
}
);
// ── 里程计订阅 ───────────────────────
odom_sub_ = this->create_subscription<nav_msgs::msg::Odometry>(
"/odom", 10,
[this](const nav_msgs::msg::Odometry::SharedPtr msg) {
this->odom_callback(msg);
}
);
// ── TF 广播 ─────────────────────────
tf_broadcaster_ = std::make_unique<tf2_ros::TransformBroadcaster>(*this);
// ── SLAM 初始化 ──────────────────────
slam_ = std::make_unique<GridSlam>(GridSlam::Params{
.particle_count = 30,
.map_resolution = 0.05, // 5cm 栅格
.range_max = 30.0
});
RCLCPP_INFO(this->get_logger(), "LaserSLAM started");
}
private:
void laser_callback(const sensor_msgs::msg::LaserScan::SharedPtr scan)
{
// 1. 里程计积分 → 获取机器人位姿
auto pose = odom_queue_.back();
// 2. 激光_scan → 栅格地图更新
slam_->process_scan(scan, pose);
// 3. 发布地图
publish_map();
// 4. 发布 TF(odom → map)
publish_tf(pose);
}
void odom_callback(const nav_msgs::msg::Odometry::SharedPtr odom)
{
odom_queue_.push_back(odom->pose.pose);
if (odom_queue_.size() > 10) odom_queue_.pop_front();
}
// GridSlam 核心算法
std::unique_ptr<GridSlam> slam_;
std::deque<geometry_msgs::msg::Pose> odom_queue_;
rclcpp::Subscription<sensor_msgs::msg::LaserScan>::SharedPtr scan_sub_;
rclcpp::Subscription<nav_msgs::msg::Odometry>::SharedPtr odom_sub_;
std::unique_ptr<tf2_ros::TransformBroadcaster> tf_broadcaster_;
};
Launch 文件
# laser_slam.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package="laser_slam",
executable="laser_slam_node",
name="laser_slam",
parameters=[{
"particle_count": 30,
"map_resolution": 0.05,
"range_max": 30.0,
"publish_map": True,
"publish_tf": True,
}],
remappings=[
("/scan", "/front_laser/scan"),
("/odom", "/robot_odom"),
],
),
])
Cartographer 配置
.lua 配置文件
-- cartographer.lua
INCLUDE "map_builder.lua"
INCLUDE "trajectory_builder.lua"
MAP_BUILDER = {
map_builder_pub_sub_state = true,
}
TRAJECTORY_BUILDER = {
trajectory_builder_2d = {
scan_matcher_config = {
occupied_space_weight = 20.0,
resolution = 0.05,
},
},
}
ROS2 集成 Launch
# cartographer.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package="cartographer_ros",
executable="cartographer_node",
name="cartographer",
parameters=[{"configuration_directory": "/path/to/config"},
{"configuration_basename": "cartographer.lua"}],
remappings=[
("/scan", "/front_laser/scan"),
],
),
Node(
package="cartographer_ros",
executable="cartographer_occupancy_grid_node",
name="occupancy_grid_node",
),
])
VisualSLAM(RTAB-Map)
// visual_slam_node.cpp — RTAB-Map 视觉 SLAM
#include <rtabmap_ros/rtabmap.h>
class VisualSlamNode : public rclcpp::Node
{
public:
VisualSlamNode() : Node("visual_slam")
{
// 深度相机话题
rtabmap_.init(this, "visual_slam");
// 订阅深度相机 + RGB
depth_sub_ = this->create_subscription<sensor_msgs::msg::Image>(
"/depth/image_rect_raw", 1,
[this](const sensor_msgs::msg::Image::SharedPtr img) {
rtabmap_.processDepth(img);
}
);
rgb_sub_ = this->create_subscription<sensor_msgs::msg::Image>(
"/rgb/image_rect_color", 1,
[this](const sensor_msgs::msg::Image::SharedPtr img) {
rtabmap_.processRGBD(img);
}
);
}
};
SLAM 质量自检
□ 激光雷达数据格式正确(/scan)
□ TF 树正确(map → odom → base_link)
□ 里程计话题已连接(/odom)
□ 地图分辨率合理(室内: 0.02-0.05m,室外: 0.05-0.1m)
□ 实时性满足(SLAM 处理 < 100ms/帧)
□ 地图更新频率合理(5-10Hz)
□ 定位精度 < 0.1m
□ 闭环检测工作正常(如使用 RTAB-Map)
□ 栅格地图覆盖率 > 80%
常见问题
| 问题 |
原因 |
解决 |
| 地图漂移 |
里程计累计误差 |
添加 IMU 融合 / 闭环检测 |
| 定位丢失 |
激光雷达数据质量差 |
清洁传感器 / 调整参数 |
| 实时性差 |
计算量过大 |
减少粒子数 / 降低分辨率 |
| 地图空洞 |
传感器视角不足 |
增加激光雷达数量 |
| 闭环失败 |
场景重复性高 |
使用视觉 SLAM 辅助闭环 |
性能指标
| 指标 |
目标 |
测量方法 |
| 处理延迟 |
< 50ms/帧 |
记录回调耗时 |
| 定位精度 |
< 0.1m |
与已知地图对比 |
| 地图分辨率 |
0.02-0.1m |
检查栅格大小 |
| TF 延迟 |
< 10ms |
ros2 topic delay /tf |
| 内存占用 |
< 2GB |
top / htop |