initMapper()对地图进行初始化
函数作用
收到第一帧的激光数据并对该数据进行初始化。
bool
SlamGMapping::initMapper(const sensor_msgs::LaserScan& scan)
{
laser_frame_ = scan.header.frame_id;
// Get the laser's pose, relative to base.
tf::Stamped<tf::Pose> ident;
tf::Stamped<tf::Transform> laser_pose;
ident.setIdentity();
ident.frame_id_ = laser_frame_;
ident.stamp_ = scan.header.stamp;
try
{
tf_.transformPose(base_frame_, ident, laser_pose);
}
catch(tf::TransformException e)
{
ROS_WARN("Failed to compute laser pose, aborting initialization (%s)",
e.what());
return false;
}
// create a point 1m above the laser position and transform it into the laser-frame
tf::Vector3 v;
v.setValue(0, 0, 1 + laser_pose.getOrigin().z());
tf::Stamped<tf::Vector3> up(v, scan.header.stamp,base_frame_);
try
{
tf_.transformPoint(laser_frame_, up, up);
ROS_DEBUG("Z-Axis in sensor frame: %.3f", up.z());
}
catch(tf::TransformException& e)
{
ROS_WARN("Unable to determine orientation of laser: %s",
e.what());
return false;
}
// gmapping doesnt take roll or pitch into account. So check for correct sensor alignment.
if (fabs(fabs(up.z()) - 1) > 0.001)
{
ROS_WARN("Laser has to be mounted planar! Z-coordinate has to be 1 or -1, but gave: %.5f",
up.z());
return false;
}
gsp_laser_beam_count_ = scan.ranges.size();
double angle_center = (scan.angle_min + scan.angle_max)/2;
if (up.z() > 0)
{
do_reverse_range_ = scan.angle_min > scan.angle_max;
centered_laser_pose_ = tf::Stamped<tf::Pose>(tf::Transform(tf::createQuaternionFromRPY(0,0,angle_center),
tf::Vector3(0,0,0)), ros::Time::now(), laser_frame_);
ROS_INFO("Laser is mounted upwards.");
}
else
{
do_reverse_range_ = scan.angle_min < scan.angle_max;
centered_laser_pose_ = tf::Stamped<tf::Pose>(tf::Transform(tf::createQuaternionFromRPY(M_PI,0,-angle_center),
tf::Vector3(0,0,0)), ros::Time::now(), laser_frame_);
ROS_INFO("Laser is mounted upside down.");
}
// Compute the angles of the laser from -x to x, basically symmetric and in increasing order
laser_angles_.resize(scan.ranges.size());
// Make sure angles are started so that they are centered
double theta = - std::fabs(scan.angle_min - scan.angle_max)/2;
for(unsigned int i=0; i<scan.ranges.size(); ++i)
{
laser_angles_[i]=theta;
theta += std::fabs(scan.angle_increment);
}
ROS_DEBUG("Laser angles in laser-frame: min: %.3f max: %.3f inc: %.3f", scan.angle_min, scan.angle_max,
scan.angle_increment);
ROS_DEBUG("Laser angles in top-down centered laser-frame: min: %.3f max: %.3f inc: %.3f", laser_angles_.front(),
laser_angles_.back(), std::fabs(scan.angle_increment));
GMapping::OrientedPoint gmap_pose(0, 0, 0);
// setting maxRange and maxUrange here so we can set a reasonable default
ros::NodeHandle private_nh_("~");
if(!private_nh_.getParam("maxRange", maxRange_))
maxRange_ = scan.range_max - 0.01;
if(!private_nh_.getParam("maxUrange", maxUrange_))
maxUrange_ = maxRange_;
// The laser must be called "FLASER".
// We pass in the absolute value of the computed angle increment, on the
// assumption that GMapping requires a positive angle increment. If the
// actual increment is negative, we'll swap the order of ranges before
// feeding each scan to GMapping.
gsp_laser_ = new GMapping::RangeSensor("FLASER",
gsp_laser_beam_count_,
fabs(scan.angle_increment),
gmap_pose,
0.0,
maxRange_);
ROS_ASSERT(gsp_laser_);
GMapping::SensorMap smap;
smap.insert(make_pair(gsp_laser_->getName(), gsp_laser_));
gsp_->setSensorMap(smap);
gsp_odom_ = new GMapping::OdometrySensor(odom_frame_);
ROS_ASSERT(gsp_odom_);
/// @todo Expose setting an initial pose
GMapping::OrientedPoint initialPose;
if(!getOdomPose(initialPose, scan.header.stamp))
{
ROS_WARN("Unable to determine inital pose of laser! Starting point will be set to zero.");
initialPose = GMapping::OrientedPoint(0.0, 0.0, 0.0);
}
gsp_->setMatchingParameters(maxUrange_, maxRange_, sigma_,
kernelSize_, lstep_, astep_, iterations_,
lsigma_, ogain_, lskip_);
gsp_->setMotionModelParameters(srr_, srt_, str_, stt_);
gsp_->setUpdateDistances(linearUpdate_, angularUpdate_, resampleThreshold_);
gsp_->setUpdatePeriod(temporalUpdate_);
gsp_->setgenerateMap(false);
gsp_->GridSlamProcessor::init(particles_, xmin_, ymin_, xmax_, ymax_,
delta_, initialPose);
gsp_->setllsamplerange(llsamplerange_);
gsp_->setllsamplestep(llsamplestep_);
/// @todo Check these calls; in the gmapping gui, they use
/// llsamplestep and llsamplerange intead of lasamplestep and
/// lasamplerange. It was probably a typo, but who knows.
gsp_->setlasamplerange(lasamplerange_);
gsp_->setlasamplestep(lasamplestep_);
gsp_->setminimumScore(minimum_score_);
// Call the sampling function once to set the seed.
GMapping::sampleGaussian(1,seed_);
ROS_INFO("Initialization complete");
return true;
}
复制代码
函数框架
- 利用监听tf_ 获取激光相对于基座的位姿laser_pose
- 创建一个激光上方1m的点up(base_frame下 (0,0,lase_pose.z+1)),然后转化为相对于激光的坐标
- 如果up的z轴坐标和1有相差,则说明激光未安装水平。则初始化失败
- 获取激光中心位置 ,并将原始激光数据赋值给laser_angles (类型为vector);
- 初始化激光传感器模型对象RangeSensor ,里程计模型对象OdometrySensor
- 利用getOdomPose 设置初始激光位姿,设置失败则设为zero(0,0,0)
- 设置处理器的匹配参数,更新距离、更新频率、采样范围、采样步骤等参数
- 调用采样一次函数,设置随机种子
函数解析
-
首先我们看到有一个tf函数
tf_.transformPose(base_frame_, ident, laser_pose);
该函数的作用是将A坐标系中的数据转到B坐标系上。该函数的定义为:
void transformPose(const std::string &target_frame, const geometry_msgs::PoseStamped &stamped_in, geometry_msgs::PoseStamped &stamped_out) const
复制代码
target_frame就是你要把源pose转换成哪个frame上的pose。这里需要将laser_pose(雷达坐标系)转到base_frame_(基坐标系)上所以该函数的的traget_frame为base_frame_。
stamped_in就是源pose,而stamped_out就是目标数据了,也就是转换完成的数据。需要注意的是,从参数上来看,转换时是不需要指定源frame_id的,这是因为它已经包含在了stamped_in中,换句话说,就是这个函数一个隐含的使用条件是,stamped_in中必须指明源pose属于哪个frame。
- 该函数创建一个1m高的点在雷达位置的上方并且将它转换到激光雷达帧。
首先我们看一下tf::Stamped这个类,是一种包含了除了Transform的其他几种基本的数据结构的一种数据结构:
template <typename T> //模版结构可以是tf::Pose tf:Point 这些基本的结构
class Stamped : public T{
public:
ros::Time stamp_; //记录时间
std::string frame_id_; //ID
Stamped() :frame_id_ ("NO_ID_STAMPED_DEFAULT_CONSTRUCTION"){}; //Default constructor used only for preallocation
Stamped(const T& input, const ros::Time& timestamp, const std::string & frame_id);
void setData(const T& input);
};
复制代码
首先初始化这个点,然后将这个点转到laser_frame_(激光雷达坐标系)。
- gmapping没有横滚角(roll)和俯仰角(pitch)。所以需要检查传感器是否正常
- 检查传感器是否过高或过低
- 计算激光从-x到x的角度是否对称且顺序递增。
- 确保开始的角度是递增的
-
获取激光中心位置 ,并将原始激光数据赋值给laser_angles
-
初始化激光传感器对象RangeSensor和里程计传感器对象OdometrySensor
然后我们来到这一句:
gsp_laser_ = new GMapping::RangeSensor("FLASER",
gsp_laser_beam_count_,
fabs(scan.angle_increment),
gmap_pose,
0.0,
maxRange_);
复制代码
这个类定义了一个激光传感器对象(后面单独讲)。
gsp_odom_ = new GMapping::OdometrySensor(odom_frame_);
复制代码
然后又定义一个里程计的对象(后面单独讲)。
- 利用getOdomPose 设置初始激光位姿,设置失败则设为zero(0,0,0)
里面函数getOdomPose()获取里程计位姿。
- 设置处理器的匹配参数,更新距离、更新频率、采样范围、采样步骤等参数
使用 class GridSlamProcessor 类的对象gsp_进行处理。
- 调用采样一次函数,设置随机种子
填坑
在函数介绍中挖了许多坑,一些在函数中使用的类,使用的函数都没有详解介绍,本小结将一一填坑。
class RangeSensor
class RangeSensor: public Sensor{
friend class Configuration;
friend class CarmenConfiguration;
friend class CarmenWrapper;
public:
struct Beam{
OrientedPoint pose; //pose relative to the center of the sensor
double span; //spam=0 indicates a line-like beam
double maxRange; //maximum range of the sensor
double s,c; //sinus and cosinus of the beam (optimization);
};
RangeSensor(std::string name);
RangeSensor(std::string name, unsigned int beams, double res, const OrientedPoint& position=OrientedPoint(0,0,0), double span=0, double maxrange=89.0);
inline const std::vector<Beam>& beams() const {return m_beams;}
inline std::vector<Beam>& beams() {return m_beams;}
inline OrientedPoint getPose() const {return m_pose;}
void updateBeamsLookup();
bool newFormat;
protected:
OrientedPoint m_pose;
std::vector<Beam> m_beams;//放置激光的距离
};
复制代码
该类声明了一个激光传感器对象,该类继承了传感器类Sensor
该类里也没什么,主要就一个传感器名的变量。 该类的友元类有,Configuration
、CarmenConfiguration
、CarmenWrapper
。这里简单说明一下友元类。
C++中的friend关键字其实做这样的事情:在一个类中指明其他的类(或者)函数能够直接访问该类中的private和protected成员。
该类的public成员定义了一个Beam
结构体。同时还定义了位姿的变量m_pose和放置激光的距离m_beams。
class OdometrySensor
class OdometrySensor
中也没啥东西,主要就定义了一个m_ideal
它的作用暂时不太清除。
getOdomPose()获取里程计位姿
bool SlamGMapping::getOdomPose(GMapping::OrientedPoint& gmap_pose, const ros::Time& t)
{
// Get the pose of the centered laser at the right time
centered_laser_pose_.stamp_ = t;
// Get the laser's pose that is centered
tf::Stamped<tf::Transform> odom_pose;
try
{
tf_.transformPose(odom_frame_, centered_laser_pose_, odom_pose);
}
catch(tf::TransformException e)
{
ROS_WARN("Failed to compute odom pose, skipping scan (%s)", e.what());
return false;
}
double yaw = tf::getYaw(odom_pose.getRotation());
gmap_pose = GMapping::OrientedPoint(odom_pose.getOrigin().x(),
odom_pose.getOrigin().y(),
yaw);
return true;
}
复制代码
该函数输入时间戳,返回对应的里程计位姿。 注意: 将centered_laser_pose(激光雷达位姿)变换到odom_frame_坐标系上即当前里程计位姿。 下一章,我们详细看一下addScan()函数,这个函数也是gmapping中比较核心的部分。