Skip to content

第18章 智能轮椅仿真

18 第 18 章 课程实验二:智能轮椅仿真

本章以智能轮椅为载体,在 ROS2 + Gazebo 仿真环境中实践机器人导航全栈技术:SLAM 建图、自主定位、路径规划与运动控制。这是对第 9-11 章理论知识的综合应用。

18.1 项目概述

18.1.1 项目背景

智能轮椅是服务机器人领域的重要应用方向,面向老年人和行动不便群体提供自主导航、避障、语音交互等辅助功能。本实验通过仿真环境,让学生掌握从环境建模到自主导航的完整技术链。

18.1.2 学习目标

表 18-1 学习目标

目标 具体内容 涉及章节
URDF 建模 Gazebo 仿真环境搭建、轮椅 URDF 模型创建 第7章 仿真
SLAM 建图 SLAM Toolbox 建图流程与参数调优 第13章 SLAM
自主导航 Nav2 导航栈配置、多点巡航编程 第14章 导航
传感器仿真 激光雷达、IMU、里程计数据流 第9章 传感器

上表对学习目标中各方案的特性进行了横向对比,便于读者根据实际需求选择最合适的技术路线。

18.1.3 系统架构

SLAM Nav2 TF2 Toolbox Stack cmd vel scan odom 2D IMU 智能轮椅仿真系统架构 Gazebo 仿真器 RViz2 可视化 ROS2 Humble 轮椅 URDF 模型 图路 径传 仿

图 18-1 上图以框图形式描绘了系统架构的系统架构,清晰呈现了各模块之间的连接关系与信号流向。

18.2 环境搭建

18.2.1 ROS2 工作空间

# 创建工作空间
mkdir -p ~/wheelchair_ws/src
cd ~/wheelchair_ws/src

# 创建功能包
ros2 pkg create wheelchair_description --build-type ament_cmake
ros2 pkg create wheelchair_gazebo --build-type ament_cmake \
    --dependencies gazebo_ros_pkgs
ros2 pkg create wheelchair_navigation --build-type ament_cmake \
    --dependencies nav2_bringup slam_toolbox

18.2.2 URDF 轮椅模型

<?xml version="1.0"?>
<robot name="smart_wheelchair" xmlns:xacro="http://www.ros.org/wiki/xacro">

  <!-- 底盘 -->
  <link name="base_link">
    <visual>
      <geometry>
        <box size="0.6 0.5 0.3"/>
      </geometry>
      <material name="gray">
        <color rgba="0.5 0.5 0.5 1"/>
      </material>
    </visual>
    <collision>
      <geometry>
        <box size="0.6 0.5 0.3"/>
      </geometry>
    </collision>
    <inertial>
      <mass value="30.0"/>
      <inertia ixx="0.5" ixy="0" ixz="0" iyy="0.8" iyz="0" izz="0.6"/>
    </inertial>
  </link>

  <!-- 左驱动轮 -->
  <link name="left_wheel">
    <visual>
      <geometry>
        <cylinder radius="0.15" length="0.05"/>
      </geometry>
    </visual>
    <collision>
      <geometry>
        <cylinder radius="0.15" length="0.05"/>
      </geometry>
    </collision>
    <inertial>
      <mass value="2.0"/>
      <inertia ixx="0.01" ixy="0" ixz="0" iyy="0.01" iyz="0" izz="0.005"/>
    </inertial>
  </link>

  <joint name="left_wheel_joint" type="continuous">
    <parent link="base_link"/>
    <child link="left_wheel"/>
    <origin xyz="0 0.275 -0.05" rpy="-1.5708 0 0"/>
    <axis xyz="0 0 1"/>
  </joint>

  <!-- 激光雷达 -->
  <link name="lidar_link">
    <visual>
      <geometry>
        <cylinder radius="0.04" length="0.05"/>
      </geometry>
    </visual>
  </link>

  <joint name="lidar_joint" type="fixed">
    <parent link="base_link"/>
    <child link="lidar_link"/>
    <origin xyz="0.25 0 0.175"/>
  </joint>

  <!-- Gazebo 差速驱动插件 -->
  <gazebo>
    <plugin name="diff_drive" filename="libgazebo_ros_diff_drive.so">
      <ros>
        <namespace>/wheelchair</namespace>
      </ros>
      <left_joint>left_wheel_joint</left_joint>
      <right_joint>right_wheel_joint</right_joint>
      <wheel_separation>0.55</wheel_separation>
      <wheel_diameter>0.30</wheel_diameter>
      <max_wheel_torque>50</max_wheel_torque>
      <publish_odom>true</publish_odom>
      <publish_odom_tf>true</publish_odom_tf>
      <odometry_frame>odom</odometry_frame>
      <robot_base_frame>base_link</robot_base_frame>
    </plugin>
  </gazebo>

  <!-- Gazebo 激光雷达插件 -->
  <gazebo reference="lidar_link">
    <sensor type="ray" name="lidar">
      <pose>0 0 0 0 0 0</pose>
      <visualize>true</visualize>
      <update_rate>10</update_rate>
      <ray>
        <scan>
          <horizontal>
            <samples>360</samples>
            <resolution>1</resolution>
            <min_angle>-3.14159</min_angle>
            <max_angle>3.14159</max_angle>
          </horizontal>
        </scan>
        <range>
          <min>0.12</min>
          <max>10.0</max>
        </range>
      </ray>
      <plugin name="gazebo_ros_lidar" filename="libgazebo_ros_ray_sensor.so">
        <ros>
          <namespace>/wheelchair</namespace>
          <remapping>~/out:=scan</remapping>
        </ros>
        <output_type>sensor_msgs/LaserScan</output_type>
        <frame_name>lidar_link</frame_name>
      </plugin>
    </sensor>
  </gazebo>
</robot>

18.3 SLAM 建图实验

18.3.1 SLAM 建图流程

cmd vel .pgm + .yaml 键盘遥控 SLAM Toolbox 地图 姿

图 18-2 该框图展示了 SLAM 建图流程的核心结构,读者可以从中把握各功能单元的层次划分与协作方式。

18.3.2 操作命令

# 终端 1:启动 Gazebo 仿真

ros2 launch wheelchair_gazebo hospital_world.launch.py

# 终端 2:启动 SLAM Toolbox
ros2 launch wheelchair_navigation slam.launch.py

# 终端 3:键盘遥控建图
ros2 run teleop_twist_keyboard teleop_twist_keyboard \
    --ros-args --remap cmd_vel:=/wheelchair/cmd_vel

# 终端 4:保存地图
ros2 run nav2_map_server map_saver_cli -f ~/maps/hospital

18.3.3 SLAM 参数调优

表 18-2 SLAM 参数调优

参数 推荐值 说明
resolution 0.05 地图分辨率(m/pixel),越小越精细但内存越大
max_laser_range 8.0 超出此距离的激光点被丢弃
minimum_travel_distance 0.3 触发新匹配的最小位移(m)
minimum_travel_heading 0.3 触发新匹配的最小旋转(rad)
do_loop_closing true 启用回环检测以修正累积误差

上述参数配置是 SLAM 参数调优的典型推荐值,实际工程中可根据硬件条件和性能需求进行适当调整。

18.3.3b 参数配置文件

# slam_toolbox 参数
slam_toolbox:
  ros__parameters:
    solver_plugin: solver_plugins::CeresSolver
    ceres_linear_solver: SPARSE_NORMAL_CHOLESKY
    ceres_preconditioner: SCHUR_JACOBI

    # 关键调优参数
    resolution: 0.05              # 地图分辨率(米/像素)
    max_laser_range: 8.0          # 激光最大有效距离
    minimum_travel_distance: 0.3  # 最小移动距离触发扫描匹配
    minimum_travel_heading: 0.3   # 最小旋转角度触发匹配

    # 回环检测
    do_loop_closing: true
    loop_match_minimum_chain_size: 10
    loop_match_maximum_variance_coarse: 3.0

18.4 Nav2 导航实验

18.4.1 Nav2 导航栈架构

NavFn A* DWB DWA vel scan + odom Nav2 导航栈 BT Navigator Planner Controller Costmap AMCL 为+ 径→plan 度→cmd 局+ 层+

图 18-3 上图直观呈现了 Nav2 导航栈架构的组成要素与数据通路,有助于理解系统整体的工作机理。

18.4.2 导航参数配置

# Nav2 参数文件
bt_navigator:
  ros__parameters:
    global_frame: map
    robot_base_frame: base_link
    default_bt_xml_filename: "navigate_w_replanning_and_recovery.xml"

controller_server:
  ros__parameters:
    controller_frequency: 20.0
    FollowPath:
      plugin: "dwb_core::DWBLocalPlanner"
      max_vel_x: 0.5           # 轮椅最大前进速度
      min_vel_x: -0.2          # 允许小幅后退
      max_vel_theta: 0.8       # 最大旋转速度
      min_speed_xy: 0.0
      max_speed_xy: 0.5
      acc_lim_x: 1.0           # 加速度限制(舒适性)
      decel_lim_x: -1.5
      acc_lim_theta: 1.5

planner_server:
  ros__parameters:
    GridBased:
      plugin: "nav2_navfn_planner/NavfnPlanner"
      tolerance: 0.5
      use_astar: true

local_costmap:
  local_costmap:
    ros__parameters:
      update_frequency: 5.0
      publish_frequency: 2.0
      rolling_window: true
      width: 3
      height: 3
      resolution: 0.05
      inflation_radius: 0.55    # 轮椅半径 + 安全余量
      cost_scaling_factor: 3.0

global_costmap:
  global_costmap:
    ros__parameters:
      update_frequency: 1.0
      publish_frequency: 1.0
      resolution: 0.05
      inflation_radius: 0.55

表 18-3

参数类别 关键参数 轮椅推荐值 理由
速度限制 max_vel_x 0.5 m/s 室内安全速度上限
加速度 acc_lim_x 1.0 m/s² 乘客舒适性考虑
减速度 decel_lim_x -1.5 m/s² 紧急制动能力
旋转速度 max_vel_theta 0.8 rad/s 避免乘客眩晕
膨胀半径 inflation_radius 0.55 m 轮椅半径 0.38m + 安全余量
代价缩放 cost_scaling_factor 3.0 平衡安全与通过性

18.4.3 导航启动

# 启动导航栈
ros2 launch wheelchair_navigation navigation.launch.py \
    map:=$HOME/maps/hospital.yaml

# Python 脚本:自动巡航
#!/usr/bin/env python3
"""智能轮椅自动巡航脚本"""
from geometry_msgs.msg import PoseStamped
from nav2_simple_commander.robot_navigator import BasicNavigator
import rclpy

def main():
    rclpy.init()
    navigator = BasicNavigator()

    # 设置初始位姿
    initial_pose = PoseStamped()
    initial_pose.header.frame_id = 'map'
    initial_pose.pose.position.x = 0.0
    initial_pose.pose.position.y = 0.0
    initial_pose.pose.orientation.w = 1.0
    navigator.setInitialPose(initial_pose)
    navigator.waitUntilNav2Active()

    # 定义巡航点(医院场景)
    waypoints = [
        create_pose(5.0, 0.0, 0.0),    # 走廊末端
        create_pose(5.0, 3.0, 1.57),   # 病房门口
        create_pose(0.0, 3.0, 3.14),   # 护士站
        create_pose(0.0, 0.0, -1.57),  # 返回起点
    ]

    # 执行多点导航
    navigator.followWaypoints(waypoints)
    while not navigator.isTaskComplete():
        feedback = navigator.getFeedback()
        if feedback:
            print(f'到达第 {feedback.current_waypoint + 1}/{len(waypoints)} 个目标点')

    print('巡航完成!')
    navigator.lifecycleShutdown()

def create_pose(x, y, yaw):
    import math
    pose = PoseStamped()
    pose.header.frame_id = 'map'
    pose.pose.position.x = x
    pose.pose.position.y = y
    pose.pose.orientation.z = math.sin(yaw / 2)
    pose.pose.orientation.w = math.cos(yaw / 2)
    return pose

if __name__ == '__main__':
    main()

18.5 实验拓展:多楼层导航

18.5.1 多楼层导航流程

AMCL 当前楼层 电梯 目标楼层 姿

图 18-4 上图以框图形式描绘了多楼层导航流程的系统架构,清晰呈现了各模块之间的连接关系与信号流向。

18.5.2 多楼层地图管理

在实际医院场景中,轮椅需要在多个楼层之间导航。可通过地图切换机制实现:

class MultiFloorNavigator:
    def __init__(self, navigator):
        self.navigator = navigator
        self.maps = {
            1: 'hospital_floor1.yaml',
            2: 'hospital_floor2.yaml',
            3: 'hospital_floor3.yaml',
        }
        self.current_floor = 1

    def switch_floor(self, target_floor):
        """切换楼层地图"""
        if target_floor == self.current_floor:
            return

        # 导航到电梯口
        elevator_pose = self.get_elevator_pose(self.current_floor)
        self.navigator.goToPose(elevator_pose)
        self.navigator.waitUntilTaskComplete()

        # 切换地图
        self.navigator.changeMap(self.maps[target_floor])
        self.current_floor = target_floor

        # 设置电梯出口位姿
        exit_pose = self.get_elevator_exit_pose(target_floor)
        self.navigator.setInitialPose(exit_pose)

18.6 本章小结

表 18-4 本章通过智能轮椅仿真项目,实践了机器人导航的完整技术栈:

实验环节 对应章节 关键技术
URDF 建模 第9章 ROS 机器人描述、坐标系、TF
Gazebo 仿真 第7章 仿真 物理引擎、传感器仿真
SLAM 建图 第10章 SLAM SLAM Toolbox、激光匹配
Nav2 导航 第11章 导航 A*、DWA、代价地图、AMCL
多点巡航 第12章 多机器人 任务编排、状态管理

上表对本章小结中各方案的特性进行了横向对比,便于读者根据实际需求选择最合适的技术路线。

18.7 本章测验

Quiz results are saved to your browser's local storage and will persist between sessions.

#

1) 在 Gazebo 仿真中,URDF 模型中 <inertial> 标签的主要作用是?

#

2) SLAM Toolbox 中 minimum_travel_distance 参数的作用是?

#

3) Nav2 中 inflation_radius 参数设为 0.55m 的依据是?

#

4) 使用 BasicNavigator.followWaypoints() 进行多点导航时,机器人依次经过所有目标点的行为称为?

#

5) 在多楼层导航场景中,切换楼层时需要做哪两个关键操作?

Quiz Progress

0 / 0 questions answered (0%)

0 correct