04-ros1-occupancy_grid_map

ROS中的栅格地图

image-20241120095547817

地图数据格式

image-20241120095633692

ROS中默认的栅格为 0.05m

image-20241120095754332

栅格地图转换为一个 二维数组 —–> 一维数组

image-20241120095904292

这种数组 + 行列数消息便是 ros中 map_servernav_msgs

image-20241120100018122

  • header
    • seq
    • stamp
    • frame_id
    • image-20241120100220086
  • info 栅格地图信息
    • load_time
    • resolution
    • width
    • height
    • image-20241120100355398
  • []data 一维数组
    • [0,0] 位置是 栅格地图 左下角 的位置
    • image-20241120100100823

发布地图消息包

栅格地图样式

image-20241120100617867

C++实现

image-20241120100703645

map_pub_node.cpp

  • 创建软件包
catkin_create_pkg map_pkg rospy roscpp nav_msgs
  • map_pub_node.cpp
#include <ros/ros.h>
#include <nav_msgs/OccupancyGrid.h>

int main(int argc, char *argv[])
{
    ros::init(argc,argv,"map_pub_node");
    ros::NodeHandle n;
    ros::Publisher pub = n.advertise<nav_msgs::OccupancyGrid>("/map",10);

    ros::Rate r(1);

    while (ros::ok())
    {
        nav_msgs::OccupancyGrid msg;
        msg.header.frame_id = "map";
        // 设置时间
        msg.header.stamp = ros::Time::now();
        
        // 设置起始点
        msg.info.origin.position.x = 0,
        msg.info.origin.position.y = 0,
        // 设置地图尺寸
        msg.info.resolution = 1.0;
        msg.info.width = 4;
        msg.info.height = 2;

        msg.data.resize(4*2);
        msg.data[0]= 100;
        msg.data[1]= 100;
        msg.data[2]= 0;
        msg.data[3]= -1;
        pub.publish(msg);
        ROS_INFO("RUNNING!");
        r.sleep();
    }   
    return 0;
}

CmakeList.txt

末尾添加上

add_executable(map_pub_node src/map_pub_node.cpp)
add_dependencies(map_pub_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(map_pub_node
  ${catkin_LIBRARIES}
)

运行效果

image-20241120102929694

Python实现

map_pub_node.py

#!/usr/bin/env python3
# coding = utf-8
import rospy
from nav_msgs.msg import OccupancyGrid

def main():
    rospy.init_node("map_pub_node")
    pub = rospy.Publisher("/map",OccupancyGrid,queue_size=10)
    rate = rospy.Rate(1) # 1hz
    while not rospy.is_shutdown():
        msg = OccupancyGrid()

        msg.header.frame_id = "map"
        msg.header.stamp = rospy.Time.now()
        # 地图描述信息
        msg.info.origin.position.x = 0
        msg.info.origin.position.y = 0
        msg.info.resolution = 1.0 # 地图分辨率
        msg.info.width = 4
        msg.info.height = 2
        # 地图数据的赋值
        msg.data =[0]*4*2
        msg.data[0] = 100
        msg.data[1] = 100
        msg.data[2] = 0
        msg.data[3] = -1

        pub.publish(msg)
        rospy.loginfo("running")
        rate.sleep()


if __name__ == "__main__":
    main()

image-20241120104830962

SLAM

SLAM —-> Simultaneous Localization And Mapping

同时定位与地图创建

Localization && Mapping

Hector_Mapping 年轻人的第一次SLAM建图

image-20241120112818245

SLAM节点,我们直接复用别人的 SLAM ,等我们学会了再去研究这个节点写吧!!!复用!

下载 与 启动仿真环境

Hector_mapping 下载

sudo apt install ros-noetic-hector-mapping
roslaunch wpr_simulation wpb_stage_slam.launch

启动slam建图程序

rosrun hector_mapping hector_mapping

启动 rviz 程序

rviz

启动控制程序

rosrun rqt_robot_steering rqt_robot_steering 

使用控制程序来控制其运动

image-20241120115636642

通过launch文件来启动Hector_Mapping建图

创建 slam_pkg

catkin_create_pkg slam_pkg roscpp rospy std_msgs

将这些指令全部写入到一个launch文件中

image-20241120145927894

hector.launch

<launch>
    <include file="$(find wpr_simulation)/launch/wpb_stage_slam.launch" />

    <node pkg="hector_mapping" type="hector_mapping" name="hector_mapping" />

    <node pkg="rviz" type="rviz" name="rviz" />

    <node pkg="rqt_robot_steering" type="rqt_robot_steering" name="rqt_robot_steering" />
</launch>
  • "$(find wpr_simulation)"

image-20241120150149884

编译

编译的目的是为了让这个 slam_launch 文件进入 ros工作环境

保存 rviz 配置

在rviz下,选择 save config as

image-20241120153539827

存储到

~/catkin_ws/src/slam_pkg/rviz/

rviz 加载配置信息启动

rosrun rviz rviz -d /home/ysc/catkin_ws/src/slam_pkg/rviz/slam.rviz 

修改 launch信息

hector.launch

<launch>
    <!-- 载入机器人 和 SLAM 的仿真场景  -->
    <!-- 实体机器人就启动实体的启动程序 -->
    <include file="$(find wpr_simulation)/launch/wpb_stage_slam.launch" />

    <!-- Hector SLAM -->
    <node pkg="hector_mapping" type="hector_mapping" name="hector_mapping" />

    <!-- Rviz 显示 -->
    <node pkg="rviz" type="rviz" name="rviz" args="-d $(find slam_pkg)/rviz/slam.rviz" />

    <!-- 运动控制 -->
    <node pkg="rqt_robot_steering" type="rqt_robot_steering" name="rqt_robot_steering" />
</launch>

Hector_Mapping 参数

我们修改 这几个参数

image-20241120155055171

修改 hector.launch

<launch>
    <!-- 载入机器人 和 SLAM 的仿真场景  -->
    <!-- 实体机器人就启动实体的启动程序 -->
    <include file="$(find wpr_simulation)/launch/wpb_stage_slam.launch" />

    <!-- Hector SLAM -->
    <node pkg="hector_mapping" type="hector_mapping" name="hector_mapping" output="screen">
        <param name="map_update_distance_thresh" value="0.1" />
        <param name="map_update_angle_thresh" value="0.1" />
        <param name="map_pub_period" value="0.1" />
    </node>

    <!-- Rviz 显示 -->
    <node pkg="rviz" type="rviz" name="rviz" args="-d $(find slam_pkg)/rviz/slam.rviz" />

    <!-- 运动控制 -->
    <node pkg="rqt_robot_steering" type="rqt_robot_steering" name="rqt_robot_steering" />
</launch>

怎么看出差距

比较一下呗

image-20241120155535233

创建一个 companies.launch

<launch>

  <!-- 第一个 Hector_Mapping 建图节点 -->
  <group ns="slam_1">
    <node pkg="hector_mapping" type="hector_mapping" name="hector_mapping_1">

      <param name="map_update_distance_thresh" value="0.5"/>
      <param name="map_update_angle_thresh" value="0.5" />
      <param name="map_pub_period" value="0.2" />
      
      <param name="map_frame" value="slam_1/map" />
      <param name="base_frame" value="slam_1/base_footprint" />
      <param name="odom_frame" value="slam_1/odom" />
    </node>
  </group>

  <!-- 第二个 Hector_Mapping 建图节点 -->
  <group ns="slam_2">
    <node pkg="hector_mapping" type="hector_mapping" name="hector_mapping_2">

      <param name="map_update_distance_thresh" value="0.1"/>
      <param name="map_update_angle_thresh" value="0.1" />
      <param name="map_pub_period" value="0.2" />

      <param name="map_frame" value="slam_2/map" />
      <param name="base_frame" value="slam_2/base_footprint" />
      <param name="odom_frame" value="slam_2/odom" />
    </node>
  </group>

  <!-- **************************** 分割线 **************************** -->

  <!-- 载入 SLAM 的仿真场景 -->
  <include file="$(find gazebo_ros)/launch/empty_world.launch">
  <arg name="world_name" value="$(find wpr_simulation)/worlds/slam_simple.world"/>
  <arg name="paused" value="false"/>
  <arg name="use_sim_time" value="true"/>
  <arg name="gui" value="true"/>
  <arg name="recording" value="false"/>
  <arg name="debug" value="false"/>
  </include>

  <!-- 载入 1号机器人 -->
  <include file="$(find wpr_simulation)/launch/wpb_slam_template.launch">
      <arg name="robot_namespace" value="slam_1" /> 
      <arg name="local_x" value="0" /> 
      <arg name="local_y" value="-0.3" /> 
      <arg name="local_yaw" value="0" /> 
  </include>

  <!-- 载入 2号机器人 -->
  <include file="$(find wpr_simulation)/launch/wpb_slam_template.launch">
      <arg name="robot_namespace" value="slam_2" /> 
      <arg name="local_x" value="0" /> 
      <arg name="local_y" value="0.3" /> 
      <arg name="local_yaw" value="0" /> 
  </include>

  <!-- 运动控制 -->
  <node pkg="rqt_robot_steering" type="rqt_robot_steering" name="rqt_robot_steering"/>

  <!-- 速度话题分流 -->
  <node pkg = "topic_tools" type = "relay" name = "relay_1" args="/cmd_vel /slam_1/cmd_vel" />
  <node pkg = "topic_tools" type = "relay" name = "relay_2" args="/cmd_vel /slam_2/cmd_vel" />

</launch>

定位

机器人地盘的节点

  • `base_footprint·
    • 约定俗成的 节点,发布机器人的地盘信息

image-20241120161241192

父子坐标系

  • 坐标系起始点为 机器人 初始位置

image-20241120161303512

位置描述方式

  • 距离
  • 角度

TF [TransForm]

Transform 坐标系变换

image-20241120161506664

TF数据格式

image-20241120162218868

image-20241120162430018

  • Header

    • seq
    • stamp
    • frame_id 父坐标名称
  • child_frame_id 子坐标系名称

  • Transform

    • translation

      表示 子坐标系 相对于 父坐标系 的坐标距离偏差量

      • x
      • y
      • z
    • rotation 四元数据格式 —->

      表示 子坐标系 相对于 父坐标系 的角度偏差值

      • x
      • y
      • z
      • w

查看 /tf 话题

rostopic echo /tf

查看各个tf的父子关系

tf tree

rosrun rqt_tf_tree rqt_tf_tree

image-20241120163336002

里程计 odom

一种算法

减少单一SLAM 误差问题

image-20241120163501854

开启一个仿真程序

roslaunch wpr_simulation wpb_corridor_hector.launch

这个建图会出现 机器人还在走,但是 rviz 图不更新了

image-20241120163917156

激光雷达每一帧都完全相同,无法估计自己的位移

无法找到自己的区别

Gmapping 算法

  • 使用 odom 【里程计】
  • 误差使用 odom 来修正

启动仿真环境

roslaunch wpr_simulation wpb_corridor_gmapping.launch

里程计 只是一套软件

理论值和实际值有区别!!!

轮子会打滑!!

image-20241120164457967

  • 障碍物点云配准算法

修正

image-20241120164540696

image-20241120165359156

Hector_mapping 算法

  • 不考虑里程计的数据
  • odom是假里程计
  • 为了符合rviz,勉为其难得加了个odom得 tf 节点

image-20241120165621139

image-20241120165448187

odom 在 Hector_mapping 算法下会不断往后增长

为了抵消 scanmatcher_frame 的坐标

只考虑了 RViz中显示的情况

image-20241120170347823

如何使用Gampping进行SLAM建图

需求

image-20241120171222611

  • 雷达坐标系 —> base_link
  • base_link —> odom
  • /scan (话题)

启动仿真环境

roslaunch wpr_simulation wpb_stage_robocup.launch

这样的场景

image-20241120171601455

查看依赖

终端输入

  • 查看消息话题
rostopic list
  • 查看tf 树
rosrun rqt_tf_tree rqt_tf_tree

image-20241120171741636

都符合需求

启动Gampping节点

rosrun gmapping slam_gmapping

启动 rviz

运行键盘控制机器人节点

rosrun wpr_simulation keyboard_vel_ctrl

编写launch文件一键启动Gampping建图

创建文件 slam_pkg

catkin_create_pkg slam_pkg roscpp rospy std_msgs

参数设置

image-20241121095633074

image-20241121100256025

栅格 默认 0.05m

  • lskip 跳线

每隔多少个采纳一条射线—-> 降低算力消耗

image-20241121095944822

  • throttle_scans = 1
  • 选择第几帧处理,剩下全部抛弃,转几圈处理一次

gampping.launch 内容

<launch>
    <!-- 载入机器人 和 SLAM 的仿真场景  -->
    <!-- 实体机器人就启动实体的启动程序 -->
    <include file="$(find wpr_simulation)/launch/wpb_stage_robocup.launch" />

    <!-- Gamppping SLAM -->
    <node pkg="gmapping" type="slam_gmapping" name="slam_gmapping" />

    <!-- 运动控制 -->
    <node pkg="wpr_simulation" type="keyboard_vel_ctrl" name="keyboard_vel_ctrl" launch-prefix="gnome-terminal -e">
        <param name="maxUrange" value="3.0"/>
        <param name="map_update_interval" value="0.5"/>
        <param name="linearUpdate" value="0.1"/>
    </node>

    <!-- Rviz 显示 -->
    <node pkg="rviz" type="rviz" name="rviz" args="-d $(find slam_pkg)/rviz/slam.rviz" />
</launch>

如何保存建立好的地图

map_server

将节点保存到地图里

rosrun map_server map_server -f 【文件名称】

地图文件就长这样

image-20241121102534183

  • map.pgm
  • map.yaml
    • 解释文件
    • image-20241121102704773
    • origin 相对于 map初始位置的 偏移
      • [-100.000000,-100.000000,0.000000] x,y,偏移角
    • image-20241121102910061

加载地图

在终端上运行,map.yaml 文件里

rosrun map_server map_server map.yaml

image-20241121103321951

直接载入成功

github