ROS 导航——用Gmapping构建一张地图

人盡茶涼 提交于 2020-02-03 21:14:31

Gmapping功能包介绍

参见wiki官网 Gmapping功能包.其中包括了Gmapping功能包订阅发布的话题,参数等。

使用Gmapping功能包的一些准备

根据官网介绍,Gmapping订阅了两个话题:/tf/scan

/tf

tf指的是坐标变换,就是将机器人各组件的相对位置关系表示出来,对于Gmapping而言,它需要三个tf:雷达机器人基坐标里程计

雷达和机器人基座标

对于雷达机器人基座标,我们可以有两种发布的方法。
第一种:通过urdf建立机器人模型,然后写一个launch文件将模型文件载入并发布机器人的状态信息给tf。urdf是一种机器人建模的一种方法,具体参见urdf wiki官网
launch文件示例如下:

<?xml version="1.0"?>
<launch>
    <!--param name="/use_sim_time" value="true"/ -->
    <arg name="model" />
    <!-- 加载机器人模型参数 -->
    <param name="robot_description" command="$(find xacro)/xacro --inorder $(find robot_description)/src/urdf/robot_model.xacro" />

    <!-- 设置GUI参数,显示关节控制插件 -->
    <param name="use_gui" value="false"/>

    <!-- 运行joint_state_publisher节点,发布机器人的关节状态  -->
    <node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher" />
    
    <!-- 运行robot_state_publisher节点,发布tf  -->
    <node name="robot_state_publisher" pkg="robot_state_publisher" type="state_publisher" />

    <!-- 运行rviz可视化界面 -->
    <node name="rviz" pkg="rviz" type="rviz"  />
</launch>

第二种:调用tf的API,编写代码发布tf。具体参见 tf API wiki官网

里程计

里程计发布的是base_link → odom坐标变换,其中base_link是上面所说的机器人基坐标,odom是世界坐标系中固定的一个坐标。里程计反映的是机器人行驶的轨迹,这个轨迹是由机器人的速度与时间计算出来的,所以里程计的发布实际上是不断发布机器人的速度信息时间信息
时间信息可以由ROS帮我们计算,而速度信息需要结合机器人的实际情况,通常由电机编码器将速度反馈给电机驱动器的控制板,然后由控制板发布给ROS。
以Arduino Uno作为驱动板为例,发布里程计的示例代码如下:

/* 
 * rosserial Planar Odometry Example
 */

#include <ros.h>
#include <ros/time.h>
#include <tf/tf.h>
#include <tf/transform_broadcaster.h>

ros::NodeHandle  nh;

geometry_msgs::TransformStamped t;
tf::TransformBroadcaster broadcaster;

double x = 1.0;
double y = 0.0;
double theta = 1.57;

char base_link[] = "/base_link";
char odom[] = "/odom";

void setup()
{
  nh.initNode();
  broadcaster.init(nh);
}

void loop()
{  
  // drive in a circle
  double dx = 0.2;
  double dtheta = 0.18;
  x += cos(theta)*dx*0.1;
  y += sin(theta)*dx*0.1;
  theta += dtheta*0.1;
  if(theta > 3.14)
    theta=-3.14;
    
  // tf odom->base_link
  t.header.frame_id = odom;
  t.child_frame_id = base_link;
  
  t.transform.translation.x = x;
  t.transform.translation.y = y;
  
  t.transform.rotation = tf::createQuaternionFromYaw(theta);
  t.header.stamp = nh.now();
  
  broadcaster.sendTransform(t);
  nh.spinOnce();
  
  delay(10);
}

/scan

/scan中发布的是激光雷达传感器的信息,这些信息用于描述周围障碍物。
该话题的发布需要结合激光雷达本身的数据来发布,消息格式参见wiki官网
发布话题的代码示例如下

#include <ros/ros.h>
#include <sensor_msgs/LaserScan.h>
 
int main(int argc, char** argv){
  ros::init(argc, argv, "laser_scan_publisher");
 
  ros::NodeHandle n;
  ros::Publisher scan_pub = n.advertise<sensor_msgs::LaserScan>("scan", 50);
 
  //A real application would pull the following data from their laser driver
  unsigned int num_readings = 100;
  double laser_frequency = 40;
  double ranges[num_readings];
  double intensities[num_readings];
 
  int count = 0;
  ros::Rate r(1.0);
  while(n.ok()){
    //generate some fake data for our laser scan,
    //Populate the dummy laser data with values that increase by one every second.
    for(unsigned int i = 0; i < num_readings; ++i){
      ranges[i] = count;
      intensities[i] = 100 + count;
    }
    ros::Time scan_time = ros::Time::now();
 
    //populate the LaserScan message,
    //Create a scan_msgs::LaserScan message and fill it with the data
    //that we've generated in preparation to send it over the wire.
    sensor_msgs::LaserScan scan;
    scan.header.stamp = scan_time;
    scan.header.frame_id = "laser_frame";
    scan.angle_min = -1.57;
    scan.angle_max = 1.57;
    scan.angle_increment = 3.14 / num_readings;
    scan.time_increment = (1 / laser_frequency) / (num_readings);
    scan.range_min = 0.0;
    scan.range_max = 100.0;
 
    scan.ranges.resize(num_readings);
    scan.intensities.resize(num_readings);
    for(unsigned int i = 0; i < num_readings; ++i){
      scan.ranges[i] = ranges[i];
      scan.intensities[i] = intensities[i];
    }
 
    //Publish the message over ROS
    scan_pub.publish(scan);
    ++count;
    r.sleep();
  }
}

使用Gmmaping功能包

/tf/scan话题都准备好后,我们就可以用一个launch文件来使用Gmapping功能包了。
launch文件示例如下:

<?xml version="1.0"?>
<launch>
	<!--Run robot model -- >
	<include file="$(find robot_description)/launch/display.launch" />
	
	<!--Publish odom-->
	<node name="odom_pub" pkg="odom_pub"  type="odom_pub_node">
	
	<!--Run lidar-->
	<node name="lidar"   pkg="lidar"  type="lidar_node">
	
	<!--Run Gmapping-->
	<node name="slam_gmapping" pkg="gmapping" type="slam_gmapping" output="screen">
    	<remap from="scan" to="/scan"/>
	</node>
</launch>

得到的地图

运行Gmapping后,进入rviz可以看到见图的实时进程。等地图建好后,可以用map_server功能包来保存地图,使用map_server的命令如下:

rosrun map_server map_saver map:=/<Map Topic> -f PATH_TO_YOUR_FILE/mymap

保存完成后得到如下所示的一张栅格图
地图

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!