简介

        本文基于ROS2 jazzy 和 Gazebo harmonic创建了一个可移动的小车机器人仿真环境,利用激光雷达对周边环境进行扫描并建图,使用SLAM建图、导航。

        项目连接:

        https://github.com/HulinCal/ros2_autopilot_robot.git

        预览:

1 机器人模型描述文件

        文件位置:src/qxwy_description/urdf/qxwy/base.urdf.xacro

    <xacro:macro name="base_xacro" params="length radius">
        <link name="base_footprint"/>

        <link name="base_link">
            <visual>
                <origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0"/>
                <geometry>
                    <cylinder radius="${radius}" length="${length}"/>
                </geometry>
                <material name="white">
                    <color rgba="1.0 1.0 1.0 1.0"/>
                </material>
            </visual>

            <collision>
                <origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0"/>
                <geometry>
                    <cylinder radius="${radius}" length="${length}"/>
                </geometry>
                <material name="white">
                    <color rgba="1.0 1.0 1.0 1.0"/>
                </material>
            </collision>

            <xacro:cylinder_inertial mass="1.0" radius="${radius}" height="${length}">
                 <origin xyz="0 0 0" rpy="0 0 0"/>
            </xacro:cylinder_inertial>
        </link>
        
        <joint name="joint_name" type="fixed">
            <origin xyz="0.0 0.0 ${length/2 + 0.032 - 0.001}" rpy="0.0 0.0 0.0"/>
            <parent link="base_footprint"/>
            <child link="base_link"/>
        </joint>
    </xacro:macro>

  <xacro:macro name="base_xacro" params="length radius">:

        -把机器人本体定义为一个宏,方便后面引用,名称为base_xacro,两个输入参数:length和radius。在qxwy-vehicle‌/src/qxwy_description/urdf/qxwy/qxwy.urdf.xacro文件中引用base_xacro。

<link name="base_footprint"/>:

        - base_footprint 是一个 虚拟 link (无几何形状),作为机器人的地面接触点和运动学基准
        - 在 URDF 中, base_link 通过固定关节连接到 base_footprint ,Z轴偏移为 ${length/2 +         0.032 - 0.001} (即机器人底盘中心到地面的高度)
        - 在 Gazebo 仿真启动时,通过静态变换发布器设置 base_footprint 在 odom 坐标系中的初始位置为 (0, 0, 0) ,姿态为 (0, 0, 0)。(位置:qxwy-vehicle‌/src/qxwy_description/launch/gazebo_sim.launch.py)

<link name="base_link">:

        基座部件定义。

        外观:<visual>包含初始位置<origin>、形状<geometry>、材质<metarial>,材质只定义了颜色<color>;

        碰撞属性:<collition>,碰撞属性的内容与外观的一致;

        惯性属性:<inertial>,这个使用了宏定义,位置:qxwy-vehicle‌/src/qxwy_description/urdf/qxwy/common_inertial.xacro。

<joint name="joint_name" type="fixed">:

        基座base_xarco和虚拟关机base_footprint的关节,连接方式为固定(fixed),父为base_footprint,<origin>定义的是base_xacro相对于base_footprint的相对位置:base_xacro的x,y方向不变,升高大概半个轮子的高度(让轮子在地面)。

        机器人本体会在qxwy-vehicle‌/src/qxwy_description/urdf/qxwy/qxwy.urdf.xacro文件中引入。

 其他部件描述文件:

        万向轮:qxwy_description/urdf/qxwy/actuator/caster.urdf.xacro

        轮子:qxwy_description/urdf/qxwy/actuator/wheel.urdf.xacro

        摄像头:qxwy_description/urdf/qxwy/sensor/camera.urdf.xacro

        惯性测量单元:qxwy_description/urdf/qxwy/sensor/imu.urdf.xacro

        激光雷达:qxwy_description/urdf/qxwy/sensor/laser.urdf.xacro

        这些部件将会在qxwy-vehicle‌/src/qxwy_description/urdf/qxwy/qxwy.urdf.xacro文件中与机器人基座一同导入。

        模型在gazebo中显示如下:

        

2 使用gazebo的传感器

2.1 激光雷达

        在qxwy-vehicle‌/src/qxwy_description/urdf/qxwy/sensor/laser.urdf.xacro文件进行配置,内容如下:

        <gazebo reference="laser_link">
            <material>Gazebo/Black</material>
            <sensor name='gpu_lidar' type='gpu_lidar'>
                <always_on>true</always_on>
                <topic>scan</topic>
                <update_rate>20</update_rate>
                <visualize>true</visualize> 
                <lidar>
                    <scan>
                        <horizontal>
                            <samples>720</samples>
                            <resolution>1</resolution>
                            <min_angle>-3.14159</min_angle>
                            <max_angle>3.14159</max_angle>
                        </horizontal>

                    <vertical>
                        <samples>16</samples>
                        <resolution>1</resolution>
                        <min_angle>-0.261799</min_angle>
                        <max_angle>0.261799</max_angle>
                    </vertical>
                    </scan>

                    <range>
                        <min>0.12</min>
                        <max>8.0</max>
                        <resolution>0.01</resolution>
                    </range>

                    <noise>
                        <type>gaussian</type>
                        <mean>0.0</mean>
                        <stddev>0.01</stddev>
                    </noise>
                </lidar>     
            </sensor>
        </gazebo>

基础的关联和外观

        <gazebo reference="laser_link">
            <material>Gazebo/Black</material>

    ‌        reference="laser_link"‌:指定该传感器附着在名为 laser_link 的连杆上。传感器的坐标系原点将与该 Link 的原点重合。
    ‌        <material>Gazebo/Black</material>‌:设置该 Link 在 Gazebo 可视化界面中的材质颜色为黑色。这仅影响视觉显示,不影响传感器数据。

传感器核心属性
    <sensor name='gpu_lidar' type='gpu_lidar'>
        <always_on>true</always_on>
        <topic>scan</topic>
        <update_rate>20</update_rate>
        <visualize>true</visualize> 

    ‌        type='gpu_lidar'‌:关键标识。声明这是一个基于 GPU 加速的激光雷达。它比传统的 ray 类型更适合高密度、多线束的 3D 激光雷达仿真。
    ‌        <always_on>true</always_on>‌:即使没有 ROS/GZ 节点订阅数据,传感器也持续运行。设为 false 可在无人订阅时节省算力。
    ‌        <topic>scan</topic>‌:定义 Gazebo 内部发布数据的话题名称为 /scan。
    ‌        <update_rate>20</update_rate>‌:数据更新频率为 ‌20 Hz‌。
    ‌        <visualize>true</visualize>‌:在 Gazebo 图形界面中实时渲染激光点云或扫描线。调试完成后建议设为 false 以提升仿真帧率。

 激光扫描几何参数 (Lidar Scan)

        这是定义激光雷达“如何扫描”的核心部分,决定了它是单线还是多线,以及覆盖范围。
        <lidar>
            <scan>
                <horizontal>
                    <samples>720</samples>
                    <resolution>1</resolution>
                    <min_angle>-3.14159</min_angle>
                    <max_angle>3.14159</max_angle>
                </horizontal>
                <vertical>
                    <samples>16</samples>
                    <resolution>1</resolution>
                    <min_angle>-0.261799</min_angle>
                    <max_angle>0.261799</max_angle>
                </vertical>
            </scan>

    ‌水平方向 (Horizontal)‌:
        ‌samples=720‌:水平一圈采集 ‌720 个点‌。
        ‌min/max_angle‌:−π−π 到 +π+π(即 -180° 到 +180°),表示水平方向全覆盖(360°)。
        ‌角分辨率‌:360∘/720=0.5∘360∘/720=0.5∘。

    ‌垂直方向 (Vertical)‌:
        ‌samples=16‌:垂直方向有 ‌16 线‌(16 beams)。这表明它是一个 ‌16 线 3D 激光雷达‌(类似 Velodyne VLP-16)。
        ‌min/max_angle‌:−0.261799 rad≈−15∘−0.261799 rad≈−15∘, +0.261799 rad≈+15∘+0.261799 rad≈+15∘。垂直视场角总共约 30°。
        ‌垂直分辨率‌:30∘/16≈1.875∘30∘/16≈1.875∘。

测距范围与精度
            <range>
                <min>0.12</min>
                <max>8.0</max>
                <resolution>0.01</resolution>
            </range>

    ‌<min>0.12</min>‌:最小探测距离为 ‌0.12 米‌。小于此距离的物体可能无法被检测或数据无效。
    ‌<max>8.0</max>‌:最大探测距离为 ‌8.0 米‌。超过此距离返回无穷大或最大值。
    ‌<resolution>0.01</resolution>‌:测距分辨率为 ‌1 厘米‌。

噪声模型
            <noise>
                <type>gaussian</type>
                <mean>0.0</mean>
                <stddev>0.01</stddev>
            </noise>

    ‌type=gaussian‌:使用高斯分布模拟测量噪声。
    ‌stddev=0.01‌:标准差为 ‌0.01 米 (1 cm)‌。这意味着测量值会在真实距离附近波动,模拟真实传感器的误差。如果设为 0,则数据是完美的,不利于测试算法的鲁棒性。

        点击gazebo中右上角的三个点按钮,输入lidar搜索,在visualize lidar的窗口中按刷新按钮,就会显示激光雷达的图像:

2.2 图像传感器

        位置:qxwy-vehicle‌/src/qxwy_description/urdf/qxwy/sensor/camera.urdf.xacro

       <gazebo reference="camera_link">
            <sensor name="depth_camera_name" type="depth_camera">
                <always_on>true</always_on>
                <update_rate>10</update_rate>
                <topic>depth_camera</topic>
                <camera>
                    <horizontal_fov>1.05</horizontal_fov>
                    <image>
                    <width>256</width>
                    <height>256</height>
                    <format>R_FLOAT32</format>
                    </image>
                    <clip>
                    <near>0.1</near>
                    <far>10.0</far>
                    </clip>

                    <depth_camera>
                        <output>depth</output>
                    </depth_camera>
                </camera>
            </sensor>
        </gazebo>


传感器绑定与基础属性
<gazebo reference="camera_link">
    <sensor name="depth_camera_name" type="depth_camera">
        <always_on>true</always_on>
        <update_rate>10</update_rate>
        <topic>depth_camera</topic>

    ‌reference="camera_link"‌:指定该传感器附着在名为 camera_link 的连杆上。传感器的坐标系原点与该 Link 重合。
    ‌type="depth_camera"‌:声明传感器类型为‌深度相机‌。这种类型专门用于生成深度图像(Depth Image),每个像素值代表距离相机的深度(单位:米)。
    ‌<always_on>true</always_on>‌:即使没有节点订阅数据,传感器也保持运行。设为 false 可在无人使用时节省仿真资源。
    ‌<update_rate>10</update_rate>‌:数据更新频率为 ‌10 Hz‌(每秒10帧)。对于深度相机来说,这是一个较低的频率,有助于降低 CPU/GPU 负载。
    ‌<topic>depth_camera</topic>‌:定义 Gazebo 内部发布数据的话题名称为 /depth_camera。

相机光学与图像参数
        <camera>
            <horizontal_fov>1.05</horizontal_fov>
            <image>
                <width>256</width>
                <height>256</height>
                <format>R_FLOAT32</format>
            </image>

    ‌<horizontal_fov>1.05</horizontal_fov>‌:水平视场角(FOV)为 ‌1.05 弧度‌(约 ‌60 度‌)。这决定了相机能看到的水平宽度。
    ‌<width>256</width> & <height>256</height>‌:输出图像的分辨率为 ‌256x256‌ 像素。这是一个非常低的分辨率,通常用于快速原型验证或对计算资源极其敏感的场景。
    ‌<format>R_FLOAT32</format>‌:图像数据格式为 ‌32位浮点数‌。
        这是深度相机的标准格式,每个像素存储一个浮点数值,表示该点到相机的距离(单位:米)。
        如果是普通 RGB 相机,格式通常为 R8G8B8。

裁剪平面 (Clipping Planes)
            <clip>
                <near>0.1</near>
                <far>10.0</far>
            </clip>

    ‌<near>0.1</near>‌:近裁剪面为 ‌0.1 米‌。距离相机小于 10 厘米的物体不会被渲染,深度值可能无效。
    ‌<far>10.0</far>‌:远裁剪面为 ‌10.0 米‌。距离相机超过 10 米的物体不会被渲染,深度值将显示为最大值或无效。
        合理设置这两个值可以优化渲染性能,避免处理过近或过远的无用数据。

深度相机特定配置
            <depth_camera>
                <output>depth</output>
            </depth_camera>

    ‌<depth_camera>‌:这是深度相机特有的配置块。
    ‌<output>depth</output>‌:指定输出类型为‌纯深度图‌。
      有些深度相机传感器支持同时输出 RGB 和 Depth(即 RGB-D 相机),此时可能需要配置 <output>rgb_depth</output> 或使用 rgbd_camera 类型。

        在gazebo中查看图像传感器结果:

        点击右上角三个点的按钮,搜索image,选择相应话题即可:

2.3 与ROS2的桥接

        在ROS2 JAZZY中gazebo中的数据需要配合 ros_gz_bridge 使用才能将数据发布到 ROS 2 话题中。有两种方式实现桥接:

 a.在启动时指定转接话题及参数

        代码位置:qxwy-vehicle‌/src/qxwy_description/launch/gazebo_sim.launch.py

        注意:深度相机只能用ros_gz_image包

  camera_bridge = Node(   
        package='ros_gz_bridge',
        executable='parameter_bridge',
        arguments=[
            '/camera@sensor_msgs/msg/Image@gz.msgs.Image',
            '/camera/camera_info@sensor_msgs/msg/CameraInfo@gz.msgs.CameraInfo',
            '/clock@rosgraph_msgs/msg/Clock[gz.msgs.Clock',
            '/scan@sensor_msgs/msg/LaserScan@gz.msgs.LaserScan' # 激光雷达
        ],
        output='screen'
    )

    # 深度相机
    depth_camera_bridge  = Node(
        package='ros_gz_image',
        executable='image_bridge',
        arguments=['/depth_camera'],
        output='screen',
        parameters=[{"use_sim_time": True}], 
    )

  b.使用yaml指定转接内容

        这种方式比较容易理解和维护。

        3D激光雷达的yaml文件示例:

    bridges:
      - ros_topic_name: "/points"  # ROS端话题名
        gz_topic_name: "/scan"     # 对应上面配置的 <topic>scan</topic>
        ros_type_name: "sensor_msgs/msg/PointCloud2" 
        gz_type_name: "gz.msgs.PointCloudPacked"
        direction: GZ_TO_ROS

        深度相机专用配置yaml文件示例

        针对 Gazebo Harmonic 中的深度相机(depth_camera 或 `rgbd_camera),通常需要桥接‌深度图像‌和‌点云数据‌。

# bridge.yaml
bridges:
  # 1. 桥接深度图像 (Depth Image)
  - ros_topic_name: "/camera/depth_image"
    gz_topic_name: "/depth_camera"  # 注意:需与SDF/URDF中<topic>标签一致
    ros_type_name: "sensor_msgs/msg/Image"
    gz_type_name: "gz.msgs.Image"
    direction: GZ_TO_ROS

  # 2. 桥接点云数据 (Point Cloud)
  - ros_topic_name: "/camera/points"
    gz_topic_name: "/depth_camera/points" # Gazebo通常自动发布子话题 /points
    ros_type_name: "sensor_msgs/msg/PointCloud2"
    gz_type_name: "gz.msgs.PointCloudPacked"
    direction: GZ_TO_ROS
    
  # 3. (可选) 如果使用 RGBD相机,还需桥接彩色图像
  - ros_topic_name: "/camera/image_raw"
    gz_topic_name: "/depth_camera/image"
    ros_type_name: "sensor_msgs/msg/Image"
    gz_type_name: "gz.msgs.Image"
    direction: GZ_TO_ROS
    
  # 4. (可选) 桥接相机信息 (Camera Info),用于去畸变等
  - ros_topic_name: "/camera/camera_info"
    gz_topic_name: "/depth_camera/camera_info"
    ros_type_name: "sensor_msgs/msg/CameraInfo"
    gz_type_name: "gz.msgs.CameraInfo"
    direction: GZ_TO_ROS

        在 Python Launch 文件中添加桥接节点,使其随仿真一起启动,示例:

from launch import LaunchDescription
from launch_ros.actions import Node
import os

def generate_launch_description():
    # 假设 bridge.yaml 放在当前包的 config 目录下
    pkg_share = '/path/to/your/ros2_package' 
    bridge_config = os.path.join(pkg_share, 'config', 'bridge.yaml')

    bridge_node = Node(
        package='ros_gz_bridge',  #深度相机的需要换成ros_gz_image
        executable='parameter_bridge',#深度相机的需要换成image_bridge
        arguments=['--config-file', bridge_config],
        output='screen'
    )

    return LaunchDescription([
        bridge_node
    ])


3 两轮差速控制

3.1 Gazebo Sim与 ros2_control框架集成

        代码位置:qxwy-vehicle‌/src/qxwy_description/urdf/qxwy/qxwy.ros2_control.xacro

        ros2 jazzy版本的Gazebo控制器使用方法变化比较大。如果你使用的是旧版 Gazebo Classic (Gazebo 11),则需要使用 gazebo_ros2_control 插件和不同的库文件名

        <ros2_control name="QxwyGazeboSystem" type="system">
            <hardware>
            <!-- 新版 Gazebo 必须用这个插件! -->
            <plugin>gz_ros2_control/GazeboSimSystem</plugin>
            </hardware>

            <joint name="left_wheel_joint">
                <command_interface name="velocity">
                    <param name="min">-1</param>
                    <param name="max">1</param>
                </command_interface>

                <command_interface name="effort">
                    <param name="min">-0.1</param>
                    <param name="max">0.1</param>
                </command_interface>
                <state_interface name="effort" />
                <state_interface name="velocity" />
                <state_interface name="position" />
            </joint>

            <joint name="right_wheel_joint">
                <command_interface name="velocity">
                    <param name="min">-1</param>
                    <param name="max">1</param>
                </command_interface>

                <command_interface name="effort">
                    <param name="min">-0.1</param>
                    <param name="max">0.1</param>
                </command_interface>
                <state_interface name="effort" />
                <state_interface name="velocity" />
                <state_interface name="position" />
            </joint>
        </ros2_control>

        <gazebo>
            <plugin filename="/opt/ros/jazzy/lib/libgz_ros2_control-system.so" name="gz_ros2_control::GazeboSimROS2ControlPlugin">
                <robot_param_name>robot_description</robot_param_name>
                <robot_param_node>robot_state_publisher</robot_param_node>
                <parameters>$(find qxwy_description)/config/qxwy_ros2_controller.yaml</parameters>
                <ros>
                    <remapping>/qxwy_diff_drive_controller/cmd_vel:=cmd_vel</remapping>
                    <remapping>/qxwy_diff_drive_controller/odom:=odom</remapping>
                </ros>
            </plugin>
        </gazebo>

<ros2_control> 标签:硬件抽象层定义

        定义机器人的“虚拟硬件”接口,告诉 ros2_control 如何与仿真中的关节交互。       name="QxwyGazeboSystem" & type="system"‌:定义了一个名为 QxwyGazeboSystem的系统级硬件组件,这个名字可以自己取。type="system" 表示这是一个完整的硬件系统(包含多个关节和传感器),这是最常用的类型。

     <plugin>gz_ros2_control/GazeboSimSystem</plugin>‌:
        ‌关键点‌:指定使用 gz_ros2_control 提供的仿真硬件后端。该插件负责读取 Gazebo 中的关节状态(位置、速度、力矩),并将 ros2_control 发出的控制命令施加到 Gazebo 模型的关节上。

     关节接口定义 (left_wheel_joint & right_wheel_joint)‌:
        ‌Command Interfaces (命令接口)‌:控制器可以发送指令给这些接口。
            velocity:速度控制接口。限制范围 -1 到 1 (单位通常是 rad/s)。
            effort:力矩/电流控制接口。限制范围 -0.1 到 0.1 (单位通常是 N·m 或 A)。
            注意:同时暴露速度和力矩接口允许使用混合控制器或更高级的控制策略,但通常差速控制器主要使用 velocity。
        ‌State Interfaces (状态接口)‌:控制器可以从这些接口读取反馈数据。
            effort:当前关节受力/力矩。
            velocity:当前关节角速度。
            position:当前关节角度。
        作用:这些接口使得 PID 控制器或其他算法能够获取实时反馈并计算输出。

<gazebo> 标签:插件加载与配置

        这部分告诉 Gazebo 仿真器加载 gz_ros2_control 插件,并配置其与 ROS 2 的通信。如果是真实的场景,这部分设置方式将由硬件厂商提供。

    ‌<plugin filename="..." name="...">‌:
        filename: 指向动态库文件 libgz_ros2_control-system.so。路径 /opt/ros/jazzy/lib/... 表明你正在使用 ‌ROS 2 Jazzy‌ 版本(自己搜索下这个插件在什么地方,然后替换路径)。
        name: 插件类名 gz_ros2_control::GazeboSimROS2ControlPlugin。这是 Gazebo Sim 识别并加载该插件的关键标识。

        filename和name是固定的名称,不要更改。

    ‌<robot_param_name>robot_description</robot_param_name>‌:
        指定从哪个 ROS 参数中获取机器人的 URDF/Xacro描述。默认为 robot_description。

    ‌<robot_param_node>robot_state_publisher</robot_param_node>‌:
        指定哪个节点发布了上述参数。通常是 robot_state_publisher 节点。插件会从这个节点读取模型描述以初始化硬件接口。

    ‌<parameters>$(find qxwy_description)/config/qxwy_ros2_controller.yaml</parameters>‌:
        ‌核心配置‌:指向控制器管理器(Controller Manager)的配置文件。
        该 YAML 文件定义了具体加载哪些控制器(如 diff_drive_controller、joint_state_broadcaster)、它们的类型、更新频率以及参数(如轮距、半径等)。
        $(find ...) 是 ROS 的资源查找语法,确保路径正确。

    ‌<ros> 话题重映射 (Remapping)‌:
    <remapping>/qxwy_diff_drive_controller/cmd_vel:=cmd_vel</remapping>
    <remapping>/qxwy_diff_drive_controller/odom:=odom</remapping>

        ‌目的‌:简化话题名称,使其符合常规习惯。
        ‌默认行为‌:ros2_control 的控制器通常会发布/订阅带有控制器命名空间的话题,例如 /qxwy_diff_drive_controller/cmd_vel。
        ‌重映射后‌:
            外部节点只需向 /cmd_vel 发送速度指令,插件会自动将其转发给控制器。
            外部节点只需订阅 /odom 即可获取里程计数据,无需关心内部命名空间。
        优势:提高了代码的通用性,使得导航栈(Nav2)等标准模块可以直接使用标准话题名,无需修改配置。

3.2 启动控制器

        控制器需要启动,文件位置:

        qxwy-vehicle‌/src/qxwy_description/launch/gazebo_sim.launch.py

    # 加载 joint_state_broadcaster 控制器
    action_load_joint_state_controller = launch.actions.ExecuteProcess(
        cmd=['ros2', 'control', 'load_controller', 'qxwy_joint_state_broadcaster', '--set-state', 'active'],
        output='screen'
    )

    # 加载 diff_drive_controller 控制器
    action_load_diff_drive_controller = launch.actions.ExecuteProcess(
        cmd=['ros2', 'control', 'load_controller', 'qxwy_diff_drive_controller', '--set-state', 'active'],
        output='screen'
    )

4 SLAM建图

 4.1 slam_toolbox简介

         简介

                slam_toolbox 是 ROS 2 官方主推、工业级的2D 激光 SLAM 工具,替代了 ROS1 的 gmapping,深度集成 Navigation2,主打稳定、高效、可长期建图。

       核心功能:在线建图、离线定位、地图保存 / 加载、多会话建图、终身地图更新。

       特点:基于图优化(Pose-Graph),支持同步 / 异步模式;依赖外部里程计(轮速 + IMU 融合);用Ceres Solver做后端优化,精度高、内存可控。

        适用场景:室内仓库、写字楼、家庭等大规模 2D 环境,支持 24 小时连续建图  

        核心原理(图优化 SLAM)

                整体是前端扫描匹配 + 后端位姿图优化 + 闭环检测的经典三层架构。

        前端:Scan-to-Map 匹配

                输入:激光雷达(LaserScan)+ 里程计(Odometry/TF)ROS。

                流程:每帧激光数据与当前地图做ICP / 多分辨率匹配,得到当前帧的精确位姿;

                            把 “位姿 + 激光” 打包成PosedScan,加入位姿图作为节点(Vertex)ROS。

                作用:实时修正里程计漂移,输出连续轨迹与局部地

         后端:位姿图(Pose Graph)优化

                图结构:

                        节点:机器人关键帧位姿 (x,y,θ)。

                        边(约束):

                                里程计边:连续节点间的运动约束。

                                 激光匹配边:节点与地图的匹配约束。

                                闭环边:检测到回环时添加的强约束。

                优化器:用Ceres Solver做增量式稀疏位姿调整(SPA),最小化所有约束的残差,

                               得到全局一致的轨迹与地图。

                策略:局部高频优化(保证实时)+ 全局低频优化(保证精度)。

        闭环检测(Loop Closure)

                目的:解决长期漂移,让地图 “首尾闭合”ROS。

                方法:用FPFH 特征 + KD-Tree 检索 + RANSAC+ICP 验证,识别 revisited 区域;

                            成功则添加闭环约束边,触发全局优化。

        地图生成与管理

                栅格地图:用优化后的位姿把所有激光扫描融合成占据栅格地图(.pgm+.yaml)。

                终身映射:动态增删节点,支持地图更新、合并与热启动。

4.2 slam_toolbox的使用

        安装slam_toolbox:

sudo apt install ros-$ROS_DISTRO-slam-toolbox

        启动slam_toolbox:

ros2 launch slam_toolbox online_async_launch.py

        启动slam_toolbox后,slam_toolbox会发布/map话题。

        gazebo和rviz显示如下:

        启动键盘控制节点,控制小车运行:

   ros2 run teleop_twist_keyboard teleop_twist_keyboard --ros-args -p stamped:=true

        这个命令会让 teleop_twist_keyboard 直接发布 TwistStamped 消息(而不是常见的Twist消息),与控制器的期望类型匹配,从而实现对小车的控制。

4.3 存储已建好的图

        先安装navigation2:

sudo apt install ros-$ROS_DISTRO-nav2-map-server

        在qxwy_navigation2包中新建一个maps文件夹,在终端进入maps文件夹,运行以下命令:

ros2 run nav2_map_server map_saver_cli -f room

        运行成功后,将会在maps目录下生成room.pgm和room.yaml,把room改成自己的名字。

room.pgm是图片,不同值表示不同的环境状况,一般是三值图。room.yaml图片的描述性文件,例如图片的比例,原点等。

        

5 导航仿真

        先启动小车仿真,因为需要TF来确定小车位置:

ros2 launch qxwy_description gazebo_sim.launch.py

        然后启动导航:

ros2 launch qxwy_navigation2 navigation2.launch.py

    启动后将会看到带地图的rviz,因为要初始化导航位置,所以左边会有错误提示。你需要告诉机器人它在地图上的初始位置。有两种方式:

        第一 通过RViz设置(推荐):
           在RViz中找到工具栏上的 "2D Pose Estimate" 工具(绿色的定位图标),点击地图上机器人所在的位置(通常在房间的某个角落)。
        第二 通过命令行设置():

ros2 topic pub -1 /initialpose geometry_msgs/msg/PoseWithCovarianceStamped "{header: {stamp: {sec: 0, nanosec: 0}, frame_id: 'map'}, pose: {pose: {position: {x: 0.0, y: 0.0, z: 0.0}, orientation: {x: 0.0, y: 0.0, z: 0.0, w: 1.0}}, covariance: [0.25, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.06853892326654787]}"

        设置初始位置后的图片:

  

        其中的Navigation和Location的状态必须是active,否则就是启动失败。

        设置目标点,模拟导航:

        在RViz中找到工具栏上的 "Nav2 Goal" 工具,在地图上任意白色部分(已探索没有障碍物部分)点击,小车就会自动导航到目标点。

        

6 语音播放及多点自动巡航

      先启动小车仿真,因为需要TF来确定小车位置:

ros2 launch qxwy_description gazebo_sim.launch.py

        然后启动导航界面:

ros2 launch qxwy_navigation2 navigation2.launch.py

       最后运行:

 ros2 launch autopilot_robot autopilot.launch.py

   运行结果为,先会听到一段提示初始化的声音,然后进行多点循环导航。到了巡航点进行拍照及保持图片到本地。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐