HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • 技术面试完全指南

    • 技术面试完全指南
    • 8年面试官告诉你:90%的简历在第一轮就被刷掉了
    • 刷了500道LeetCode,终于明白大厂算法面试到底考什么
    • 高频算法题精讲-双指针与滑动窗口
    • 03-高频算法题精讲-二分查找与排序
    • 04-高频算法题精讲-树与递归
    • 05-高频算法题精讲-图与拓扑排序
    • 06-高频算法题精讲-动态规划
    • Go面试必问:一道GMP问题,干掉90%的候选人
    • 08-数据库面试高频题
    • 09-分布式系统面试题
    • 10-Kubernetes与云原生面试题
    • 11-系统设计面试方法论
    • 前端面试高频题
    • AI 与机器学习面试题
    • 行为面试与软技能

第五章 SLAM与导航

5.1 SLAM原理

5.1.1 SLAM基本概念

SLAM(Simultaneous Localization and Mapping)即同时定位与建图,解决"在未知环境中,机器人如何通过传感器确定自身位置并构建环境地图"的问题。

SLAM框架:

传感器数据 → 前端(数据关联) → 后端(优化) → 地图
                    ↓              ↓
                位置估计 ←  回环检测

核心问题:

  1. 机器人不知道自己位置
  2. 地图未知
  3. 两者相互依赖

5.1.2 前端:特征提取与匹配

激光SLAM前端:

class LaserSLAMFrontend:
    def __init__(self):
        self.current_scan = None
        self.previous_scan = None

    def extract_features(self, scan):
        """提取角点特征"""
        features = []

        for i in range(1, len(scan) - 1):
            # 计算曲率
            diff = scan[i-1] + scan[i+1] - 2*scan[i]
            curvature = abs(diff)

            if curvature > threshold:
                features.append(i)

        return features

    def match_scans(self, scan1, scan2):
        """扫描匹配(ICP)"""
        # 转换为点云
        points1 = self.scan_to_points(scan1)
        points2 = self.scan_to_points(scan2)

        # ICP配准
        R, t = self.icp(points1, points2)

        return R, t

    def scan_to_points(self, scan):
        """扫描转点云"""
        angles = np.linspace(-np.pi, np.pi, len(scan))
        x = scan * np.cos(angles)
        y = scan * np.sin(angles)
        return np.column_stack([x, y])

视觉SLAM前端:

class VisualSLAMFrontend:
    def __init__(self):
        self.detector = cv2.ORB_create(1000)
        self.matcher = cv2.BFMatcher(cv2.NORM_HAMMING)

    def extract_features(self, image):
        """提取特征点"""
        kp, desc = self.detector.detectAndCompute(image, None)
        return kp, desc

    def match_frames(self, desc1, desc2):
        """帧间匹配"""
        matches = self.matcher.knnMatch(desc1, desc2, k=2)

        # Lowe's ratio test
        good_matches = []
        for m, n in matches:
            if m.distance < 0.75 * n.distance:
                good_matches.append(m)

        return good_matches

    def estimate_motion(self, kp1, kp2, matches, K):
        """估计相机运动"""
        # 提取匹配点
        pts1 = np.float32([kp1[m.queryIdx].pt for m in matches])
        pts2 = np.float32([kp2[m.trainIdx].pt for m in matches])

        # 本质矩阵
        E, mask = cv2.findEssentialMat(pts1, pts2, K)

        # 恢复位姿
        _, R, t, mask = cv2.recoverPose(E, pts1, pts2, K)

        return R, t

5.1.3 后端:图优化

位姿图:

import g2o

class PoseGraphOptimizer:
    def __init__(self):
        self.optimizer = g2o.SparseOptimizer()
        solver = g2o.BlockSolverSE2(g2o.LinearSolverCholmodSE2())
        solver = g2o.OptimizationAlgorithmLevenberg(solver)
        self.optimizer.set_algorithm(solver)

    def add_vertex(self, id, pose):
        """添加位姿节点"""
        v = g2o.VertexSE2()
        v.set_id(id)
        v.set_estimate(pose)

        if id == 0:
            v.set_fixed(True)  # 固定第一个节点

        self.optimizer.add_vertex(v)

    def add_edge(self, id1, id2, measurement, information):
        """添加约束边"""
        edge = g2o.EdgeSE2()
        edge.set_vertex(0, self.optimizer.vertex(id1))
        edge.set_vertex(1, self.optimizer.vertex(id2))
        edge.set_measurement(measurement)
        edge.set_information(information)

        self.optimizer.add_edge(edge)

    def optimize(self, iterations=10):
        """执行优化"""
        self.optimizer.initialize_optimization()
        self.optimizer.optimize(iterations)

    def get_pose(self, id):
        """获取优化后位姿"""
        return self.optimizer.vertex(id).estimate()

5.1.4 回环检测

class LoopClosureDetector:
    def __init__(self):
        self.keyframes = []
        self.descriptors = []

    def add_keyframe(self, image, pose):
        """添加关键帧"""
        # 提取BoW特征
        desc = self.extract_bow_features(image)

        self.keyframes.append({'image': image, 'pose': pose})
        self.descriptors.append(desc)

    def detect_loop(self, current_image, current_id):
        """检测回环"""
        current_desc = self.extract_bow_features(current_image)

        candidates = []

        # 搜索历史帧
        for i in range(max(0, current_id - 50)):  # 跳过最近的帧
            similarity = self.compute_similarity(current_desc, self.descriptors[i])

            if similarity > threshold:
                candidates.append(i)

        if candidates:
            # 验证几何一致性
            best_match = self.verify_geometry(current_image,
                                              [self.keyframes[i]['image'] for i in candidates])
            return best_match

        return None

5.2 GMapping

5.2.1 GMapping原理

GMapping基于Rao-Blackwellized粒子滤波,将SLAM问题分解为:

  1. 定位问题(粒子滤波)
  2. 建图问题(栅格地图)

核心思想:

每个粒子维护:
- 机器人轨迹
- 对应的地图

权重计算:
w = p(z|m,x) * p(x|u,x')

重采样:
根据权重选择优秀粒子

5.2.2 GMapping配置

# gmapping_params.yaml
map_update_interval: 5.0
maxUrange: 6.0          # 最大有效距离
maxRange: 8.0           # 最大探测距离
sigma: 0.05
kernelSize: 1
lstep: 0.05
astep: 0.05
iterations: 5
lsigma: 0.075
ogain: 3.0
lskip: 0                # 跳帧数

# 粒子数
particles: 30

# 分辨率
delta: 0.05             # 地图分辨率(m)

# 更新参数
linearUpdate: 0.5       # 线性移动阈值
angularUpdate: 0.3      # 角度旋转阈值

# 似然度参数
llsamplerange: 0.01
llsamplestep: 0.01
lasamplerange: 0.005
lasamplestep: 0.005

5.2.3 GMapping launch文件

<launch>
  <!-- GMapping节点 -->
  <node name="slam_gmapping" pkg="gmapping" type="slam_gmapping" output="screen">
    <!-- 坐标系 -->
    <param name="base_frame" value="base_link"/>
    <param name="odom_frame" value="odom"/>
    <param name="map_frame" value="map"/>

    <!-- 话题 -->
    <remap from="scan" to="scan"/>

    <!-- 加载参数 -->
    <rosparam file="$(find my_robot)/config/gmapping_params.yaml" command="load"/>
  </node>

  <!-- RVIZ可视化 -->
  <node name="rviz" pkg="rviz" type="rviz" args="-d $(find my_robot)/rviz/slam.rviz"/>
</launch>

5.2.4 地图保存与加载

# 保存地图
rosrun map_server map_saver -f my_map

# 生成文件:
# my_map.yaml  (元数据)
# my_map.pgm   (图像)

# 加载地图
rosrun map_server map_server my_map.yaml

地图元数据(my_map.yaml):

image: my_map.pgm
resolution: 0.050000
origin: [-10.0, -10.0, 0.0]
negate: 0
occupied_thresh: 0.65
free_thresh: 0.196

5.3 Cartographer

5.3.1 Cartographer原理

Cartographer使用子图(Submap)匹配和位姿图优化。

核心概念:

  1. 局部SLAM:扫描匹配 + 子图构建
  2. 全局SLAM:回环检测 + 位姿图优化
  3. 分支定界:快速扫描匹配

5.3.2 配置文件

cartographer.lua:

include "map_builder.lua"
include "trajectory_builder.lua"

options = {
  map_builder = MAP_BUILDER,
  trajectory_builder = TRAJECTORY_BUILDER,
  map_frame = "map",
  tracking_frame = "base_link",
  published_frame = "odom",
  odom_frame = "odom",
  provide_odom_frame = false,
  publish_frame_projected_to_2d = true,
  use_odometry = true,
  use_nav_sat = false,
  use_landmarks = false,
  num_laser_scans = 1,
  num_multi_echo_laser_scans = 0,
  num_subdivisions_per_laser_scan = 1,
  num_point_clouds = 0,
  lookup_transform_timeout_sec = 0.2,
  submap_publish_period_sec = 0.3,
  pose_publish_period_sec = 5e-3,
  trajectory_publish_period_sec = 30e-3,
  rangefinder_sampling_ratio = 1.,
  odometry_sampling_ratio = 1.,
  fixed_frame_pose_sampling_ratio = 1.,
  imu_sampling_ratio = 1.,
  landmarks_sampling_ratio = 1.,
}

TRAJECTORY_BUILDER_2D.use_imu_data = false
TRAJECTORY_BUILDER_2D.min_range = 0.1
TRAJECTORY_BUILDER_2D.max_range = 10.0
TRAJECTORY_BUILDER_2D.missing_data_ray_length = 5.0

MAP_BUILDER.use_trajectory_builder_2d = true
MAP_BUILDER.num_background_threads = 4

POSE_GRAPH.optimization_problem.huber_scale = 1e2
POSE_GRAPH.optimize_every_n_nodes = 90
POSE_GRAPH.constraint_builder.min_score = 0.65

return options

5.3.3 Launch文件

<launch>
  <node name="cartographer_node" pkg="cartographer_ros"
        type="cartographer_node" args="
            -configuration_directory $(find my_robot)/config
            -configuration_basename cartographer.lua"
        output="screen">
    <remap from="scan" to="scan"/>
  </node>

  <node name="cartographer_occupancy_grid_node" pkg="cartographer_ros"
        type="cartographer_occupancy_grid_node" />

  <node name="rviz" pkg="rviz" type="rviz"
        args="-d $(find my_robot)/rviz/cartographer.rviz" />
</launch>

5.4 路径规划

5.4.1 Costmap配置

global_costmap.yaml:

global_costmap:
  global_frame: map
  robot_base_frame: base_link
  update_frequency: 1.0
  publish_frequency: 1.0
  static_map: true
  transform_tolerance: 0.5

  # 障碍物层
  obstacle_layer:
    enabled: true
    observation_sources: scan
    scan:
      data_type: LaserScan
      topic: /scan
      marking: true
      clearing: true
      min_obstacle_height: 0.0
      max_obstacle_height: 2.0

  # 膨胀层
  inflation_layer:
    enabled: true
    inflation_radius: 0.5
    cost_scaling_factor: 5.0

local_costmap.yaml:

local_costmap:
  global_frame: odom
  robot_base_frame: base_link
  update_frequency: 5.0
  publish_frequency: 5.0
  static_map: false
  rolling_window: true
  width: 6.0
  height: 6.0
  resolution: 0.05
  transform_tolerance: 0.5

  plugins:
    - {name: obstacle_layer, type: "costmap_2d::ObstacleLayer"}
    - {name: inflation_layer, type: "costmap_2d::InflationLayer"}

5.4.2 全局规划器

Dijkstra/A*配置(base_global_planner):

base_global_planner: "global_planner/GlobalPlanner"

GlobalPlanner:
  allow_unknown: true
  default_tolerance: 0.0
  visualize_potential: false
  use_dijkstra: true        # true=Dijkstra, false=A*
  use_quadratic: true
  use_grid_path: false
  old_navfn_behavior: false
  lethal_cost: 253
  neutral_cost: 50
  cost_factor: 3.0
  publish_potential: true
  orientation_mode: 0
  orientation_window_size: 1

5.4.3 局部规划器(DWA)

dwa_local_planner.yaml:

DWALocalPlanner:
  # 机器人配置
  max_vel_x: 0.5
  min_vel_x: 0.0
  max_vel_y: 0.0
  min_vel_y: 0.0

  max_vel_trans: 0.5
  min_vel_trans: 0.1
  trans_stopped_vel: 0.1

  max_vel_theta: 1.0
  min_vel_theta: 0.4
  theta_stopped_vel: 0.4

  # 加速度限制
  acc_lim_x: 2.5
  acc_lim_y: 0.0
  acc_lim_theta: 3.2

  # 轨迹参数
  sim_time: 1.5
  sim_granularity: 0.025
  vx_samples: 20
  vy_samples: 0
  vtheta_samples: 40

  # 评分参数
  path_distance_bias: 32.0
  goal_distance_bias: 24.0
  occdist_scale: 0.01
  forward_point_distance: 0.325
  stop_time_buffer: 0.2
  scaling_speed: 0.25
  max_scaling_factor: 0.2

  # 振荡预防
  oscillation_reset_dist: 0.05
  oscillation_reset_angle: 0.2

5.4.4 TEB局部规划器

teb_local_planner.yaml:

TebLocalPlannerROS:
  # 轨迹配置
  teb_autosize: True
  dt_ref: 0.3
  dt_hysteresis: 0.1
  max_samples: 500
  global_plan_overwrite_orientation: True
  allow_init_with_backwards_motion: False
  max_global_plan_lookahead_dist: 3.0
  global_plan_viapoint_sep: -1
  global_plan_prune_distance: 1
  exact_arc_length: False
  feasibility_check_no_poses: 5
  publish_feedback: False

  # 机器人配置
  max_vel_x: 0.5
  max_vel_x_backwards: 0.2
  max_vel_y: 0.0
  max_vel_theta: 0.5
  acc_lim_x: 0.5
  acc_lim_theta: 0.5
  min_turning_radius: 0.0
  footprint_model:
    type: "circular"
    radius: 0.2

  # 目标容忍度
  xy_goal_tolerance: 0.2
  yaw_goal_tolerance: 0.1
  free_goal_vel: False

  # 障碍物
  min_obstacle_dist: 0.25
  inflation_dist: 0.5
  include_costmap_obstacles: True
  costmap_obstacles_behind_robot_dist: 1.5
  obstacle_poses_affected: 15

  dynamic_obstacle_inflation_dist: 0.6
  include_dynamic_obstacles: True

  # 优化
  no_inner_iterations: 5
  no_outer_iterations: 4
  optimization_activate: True
  optimization_verbose: False
  penalty_epsilon: 0.1
  obstacle_cost_exponent: 4
  weight_max_vel_x: 2
  weight_max_vel_theta: 1
  weight_acc_lim_x: 1
  weight_acc_lim_theta: 1
  weight_kinematics_nh: 1000
  weight_kinematics_forward_drive: 1
  weight_kinematics_turning_radius: 1
  weight_optimaltime: 1
  weight_shortest_path: 0
  weight_obstacle: 100
  weight_inflation: 0.2
  weight_dynamic_obstacle: 10
  weight_dynamic_obstacle_inflation: 0.2
  weight_viapoint: 1
  weight_adapt_factor: 2

5.5 自主导航

5.5.1 move_base配置

move_base.launch:

<launch>
  <!-- move_base节点 -->
  <node pkg="move_base" type="move_base" respawn="false" name="move_base" output="screen">
    <!-- 参数文件 -->
    <rosparam file="$(find my_robot)/config/costmap_common_params.yaml" command="load" ns="global_costmap" />
    <rosparam file="$(find my_robot)/config/costmap_common_params.yaml" command="load" ns="local_costmap" />
    <rosparam file="$(find my_robot)/config/global_costmap_params.yaml" command="load" />
    <rosparam file="$(find my_robot)/config/local_costmap_params.yaml" command="load" />
    <rosparam file="$(find my_robot)/config/base_local_planner_params.yaml" command="load" />

    <!-- 全局规划器 -->
    <param name="base_global_planner" value="global_planner/GlobalPlanner"/>

    <!-- 局部规划器 -->
    <param name="base_local_planner" value="dwa_local_planner/DWAPlannerROS"/>

    <!-- 话题重映射 -->
    <remap from="cmd_vel" to="cmd_vel"/>
    <remap from="odom" to="odom"/>
    <remap from="scan" to="scan"/>
  </node>
</launch>

5.5.2 AMCL定位

amcl.launch:

<launch>
  <!-- 地图服务器 -->
  <node name="map_server" pkg="map_server" type="map_server" args="$(find my_robot)/maps/my_map.yaml"/>

  <!-- AMCL节点 -->
  <node pkg="amcl" type="amcl" name="amcl" output="screen">
    <!-- 坐标系 -->
    <param name="odom_frame_id" value="odom"/>
    <param name="odom_model_type" value="diff"/>
    <param name="base_frame_id" value="base_link"/>
    <param name="global_frame_id" value="map"/>

    <!-- 粒子滤波器 -->
    <param name="min_particles" value="500"/>
    <param name="max_particles" value="5000"/>
    <param name="kld_err" value="0.05"/>
    <param name="kld_z" value="0.99"/>

    <!-- 更新阈值 -->
    <param name="update_min_d" value="0.2"/>
    <param name="update_min_a" value="0.5"/>

    <!-- 重采样 -->
    <param name="resample_interval" value="1"/>

    <!-- 激光模型 -->
    <param name="laser_model_type" value="likelihood_field"/>
    <param name="laser_likelihood_max_dist" value="2.0"/>
    <param name="laser_max_range" value="10.0"/>
    <param name="laser_min_range" value="0.1"/>
    <param name="laser_max_beams" value="180"/>

    <!-- 运动模型 -->
    <param name="odom_alpha1" value="0.2"/>
    <param name="odom_alpha2" value="0.2"/>
    <param name="odom_alpha3" value="0.2"/>
    <param name="odom_alpha4" value="0.2"/>

    <!-- 初始位姿 -->
    <param name="initial_pose_x" value="0.0"/>
    <param name="initial_pose_y" value="0.0"/>
    <param name="initial_pose_a" value="0.0"/>
  </node>
</launch>

5.5.3 导航Python接口

#!/usr/bin/env python
import rospy
import actionlib
from move_base_msgs.msg import MoveBaseAction, MoveBaseGoal
from geometry_msgs.msg import PoseStamped

class NavigationController:
    def __init__(self):
        rospy.init_node('navigation_controller')

        # Action客户端
        self.client = actionlib.SimpleActionClient('move_base', MoveBaseAction)
        self.client.wait_for_server()

    def goto(self, x, y, theta):
        """导航到目标点"""
        goal = MoveBaseGoal()
        goal.target_pose.header.frame_id = "map"
        goal.target_pose.header.stamp = rospy.Time.now()

        goal.target_pose.pose.position.x = x
        goal.target_pose.pose.position.y = y
        goal.target_pose.pose.orientation.z = np.sin(theta / 2)
        goal.target_pose.pose.orientation.w = np.cos(theta / 2)

        rospy.loginfo(f"导航到: ({x}, {y}, {theta})")
        self.client.send_goal(goal)

        # 等待结果
        wait = self.client.wait_for_result()

        if not wait:
            rospy.logerr("Action服务器不可用")
            return False
        else:
            state = self.client.get_state()
            if state == actionlib.GoalStatus.SUCCEEDED:
                rospy.loginfo("导航成功!")
                return True
            else:
                rospy.logwarn("导航失败")
                return False

    def cancel(self):
        """取消导航"""
        self.client.cancel_goal()

    def patrol(self, waypoints):
        """巡逻"""
        for i, (x, y, theta) in enumerate(waypoints):
            rospy.loginfo(f"前往航点 {i+1}/{len(waypoints)}")
            success = self.goto(x, y, theta)

            if not success:
                rospy.logwarn(f"航点{i+1}导航失败")
                break

            rospy.sleep(2)  # 停留2秒

# 使用示例
if __name__ == '__main__':
    nav = NavigationController()

    # 单点导航
    nav.goto(x=2.0, y=3.0, theta=0.0)

    # 巡逻
    waypoints = [
        (1.0, 1.0, 0.0),
        (2.0, 1.0, 1.57),
        (2.0, 2.0, 3.14),
        (1.0, 2.0, -1.57)
    ]
    nav.patrol(waypoints)

5.6 高频面试题

问题1: SLAM中的前端和后端分别做什么?

答案:

前端(Frontend):

  • 功能: 数据关联和初步位姿估计
  • 任务:
    1. 特征提取(激光角点/视觉特征点)
    2. 数据匹配(帧间匹配/扫描匹配)
    3. 运动估计(里程计)
    4. 局部地图构建

后端(Backend):

  • 功能: 全局优化和一致性维护
  • 任务:
    1. 位姿图优化(Graph Optimization)
    2. 回环检测(Loop Closure Detection)
    3. 全局地图优化
    4. 误差分配

对比:

前端: 快速、局部、增量式
后端: 慢速、全局、批处理

前端输出 → 后端输入
初步位姿 → 精确位姿

问题2: GMapping和Cartographer有什么区别?

答案:

特性GMappingCartographer
算法粒子滤波子图匹配+图优化
地图类型2D栅格2D/3D
计算量中高
精度中高
大场景一般好
回环检测弱强
传感器激光+里程计多传感器融合
实时性好一般

选择建议:

  • 小场景、实时性要求高: GMapping
  • 大场景、精度要求高: Cartographer
  • 3D SLAM: Cartographer

问题3: 什么是Costmap?如何配置障碍物膨胀?

答案:

Costmap定义: 代价地图,用数值表示每个栅格的通行代价:

0: 完全自由
1-252: 部分障碍(膨胀区)
253: 内切障碍
254: 致命障碍
255: 未知

膨胀配置:

inflation_layer:
  enabled: true
  inflation_radius: 0.5      # 膨胀半径
  cost_scaling_factor: 10.0  # 代价衰减系数

代价计算:
cost = 253 * exp(-k * distance)
其中 k = cost_scaling_factor

多层Costmap:

最终代价 = max(静态层, 障碍层, 膨胀层)

1. 静态层: 来自地图
2. 障碍层: 传感器实时检测
3. 膨胀层: 安全距离膨胀

参数调优:

  • inflation_radius大: 安全但路径长
  • cost_scaling_factor大: 代价衰减快,敢于靠近障碍物

问题4: 导航中的恢复行为(Recovery Behavior)是什么?

答案:

当机器人被困时,move_base会执行恢复行为:

默认恢复序列:

1. 清除costmap
2. 原地旋转
3. 再次清除costmap
4. 再次原地旋转
5. 放弃

配置:

recovery_behavior_enabled: true

recovery_behaviors:
  - name: 'conservative_reset'
    type: 'clear_costmap_recovery/ClearCostmapRecovery'
  - name: 'rotate_recovery'
    type: 'rotate_recovery/RotateRecovery'
  - name: 'aggressive_reset'
    type: 'clear_costmap_recovery/ClearCostmapRecovery'

# 参数
conservative_reset:
  reset_distance: 3.0

rotate_recovery:
  sim_granularity: 0.017
  frequency: 20.0

自定义恢复:

class CustomRecovery:
    def execute(self):
        # 1. 后退
        self.move_backward(0.5)  # 后退0.5m

        # 2. 旋转
        self.rotate(np.pi)  # 转180度

        # 3. 重新规划
        self.replan()

问题5: 如何提高导航的成功率?

答案:

1. 硬件优化

- 提高传感器质量(激光雷达精度)
- 增加传感器冗余(前后激光)
- 改善底盘响应性(PID调参)

2. 地图质量

- 建图时速度慢、匀速
- 多次建图取最优
- 手动编辑地图(去除噪点)

3. Costmap调优

# 膨胀半径适中
inflation_radius: 0.3  # 机器人半径+安全余量

# 障碍物检测
obstacle_range: 2.5    # 检测范围
raytrace_range: 3.0    # 清除范围

# 更新频率
update_frequency: 5.0  # 提高响应

4. 规划器参数

# DWA
path_distance_bias: 32.0    # 增加路径跟踪权重
goal_distance_bias: 24.0    # 目标吸引力
occdist_scale: 0.01         # 障碍物代价

# TEB
min_obstacle_dist: 0.25     # 最小障碍物距离
weight_obstacle: 50         # 障碍物权重

5. 容忍度设置

# 目标容忍度
xy_goal_tolerance: 0.1      # 位置容忍
yaw_goal_tolerance: 0.1     # 角度容忍

# 路径容忍度
controller_patience: 5.0    # 控制器超时
planner_patience: 5.0       # 规划器超时

6. 动态调整

def adaptive_navigation(distance_to_goal):
    if distance_to_goal > 5.0:
        # 远距离: 快速
        set_max_vel(0.8)
        set_tolerance(0.2)
    else:
        # 近距离: 精确
        set_max_vel(0.3)
        set_tolerance(0.05)

7. 多传感器融合

- 激光雷达: 远距离障碍
- 深度相机: 低矮障碍
- 超声波: 盲区补充
- IMU: 姿态补偿

8. 异常处理

class RobustNavigation:
    def navigate_with_retry(self, goal, max_retries=3):
        for attempt in range(max_retries):
            result = self.goto(goal)

            if result == SUCCESS:
                return True
            elif result == BLOCKED:
                # 被困,换路径
                self.find_alternative_path()
            elif result == TIMEOUT:
                # 超时,降低速度
                self.reduce_speed()

        return False  # 失败