一、绪论
添加激光雷达数据addScan()是Gmaping中一个比较重要的函数。 该函数包含三个主要函数:
- getOdomPose
- setPose
- processScan
我们首先简单介绍addScan()这个函数的流程,后面再对其包含的几个函数进行逐个讲解。
二、addScan
首先我们看看该函数:
bool SlamGMapping::addScan(const sensor_msgs::LaserScan& scan, GMapping::OrientedPoint& gmap_pose)
{
// 确保获取到上一次里程计的位姿、时间戳以及相同数量的激光扫描点;
if(!getOdomPose(gmap_pose, scan.header.stamp))
return false;
if(scan.ranges.size() != gsp_laser_beam_count_)
return false;
// GMapping wants an array of doubles...
double* ranges_double = new double[scan.ranges.size()];
// If the angle increment is negative, we have to invert the order of the readings.
// 判断是否将激光扫描逆向反转
if (do_reverse_range_)
{
ROS_DEBUG("Inverting scan");
int num_ranges = scan.ranges.size();
for(int i=0; i < num_ranges; i++)
{
// Must filter out short readings, because the mapper won't
// 必须过滤掉短的读数,因为映射器不会
if(scan.ranges[num_ranges - i - 1] < scan.range_min)
ranges_double[i] = (double)scan.range_max;
else
ranges_double[i] = (double)scan.ranges[num_ranges - i - 1];
}
} else
{
for(unsigned int i=0; i < scan.ranges.size(); i++)
{
// Must filter out short readings, because the mapper won't
if(scan.ranges[i] < scan.range_min)
ranges_double[i] = (double)scan.range_max;
else
ranges_double[i] = (double)scan.ranges[i];
}
}
//执行RangeReading(),读取点云大小、点云扫描值、gsp_laser_(RangeSensor()返回值)、单位为秒的时间戳到reading,并设置reading的位姿;
GMapping::RangeReading reading(scan.ranges.size(),
ranges_double,
gsp_laser_,
scan.header.stamp.toSec());
// ...but it deep copies them in RangeReading constructor, so we don't
// need to keep our array around.
delete[] ranges_double;
reading.setPose(gmap_pose);
/*
ROS_DEBUG("scanpose (%.3f): %.3f %.3f %.3f\n",
scan.header.stamp.toSec(),
gmap_pose.x,
gmap_pose.y,
gmap_pose.theta);
*/
ROS_DEBUG("processing scan");
return gsp_->processScan(reading);
}
输入: scan
返回: pose、processScan()函数返回值
函数作用: 获取gmap_pose和scan信息先处理scan信息,放到range_double里头,然后将gmap_pose和ranges_double一起放进reading里一起处理。
具体流程:
- 确保获取到上一次里程计的位姿、时间戳以及相同数量的激光扫描点;
- 判断是否需将激光扫描逆向反转;
- 执行
RangeReading(),读取点云大小、点云扫描值、gsp_laser_(RangeSensor()返回值)、单位为秒的时间戳到reading,并设置reading的位姿; - 执行
processScan(reading)。
三、 getOdomPose()
该函数在前面提到过,主要就是获取对应激光雷达时间帧的激光雷达数据,然后进行坐标变换到对应的坐标系,最后输出数据。
四、 GMapping::RangeReading
该类的作用就是读取激光雷达的数据。
GMapping::RangeReading reading(scan.ranges.size(),
ranges_double,
gsp_laser_,
scan.header.stamp.toSec());
这一句主要的操作就是执行RangeReading(),读取点云大小、点云扫描值、gsp_laser_(RangeSensor()返回值)、单位为秒的时间戳到reading,并设置reading的位姿;
之后我们详细了解一下这个类:
class RangeReading: public SensorReading, public std::vector<double>{
public:
RangeReading(const RangeSensor* rs, double time=0);
RangeReading(unsigned int n_beams, const double* d, const RangeSensor* rs, double time=0);
virtual ~RangeReading();
inline const OrientedPoint& getPose() const {return m_pose;}
inline void setPose(const OrientedPoint& pose) {m_pose=pose;}
unsigned int rawView(double* v, double density=0.) const;
std::vector<Point> cartesianForm(double maxRange=1e6) const;
unsigned int activeBeams(double density=0.) const;
protected:
OrientedPoint m_pose;
};
该类定义了两个构造函数,一个虚析构函数,一个获取位姿m_pose的函数getPose(),一个设置位姿的函数setPose(),rawView()暂时不了解它的作用,cartesianForm()这个函数将极坐标的数据转换到笛卡尔坐标系下,activeBeam()用于计算给定过滤参数下激活状态的扫描束。然后该函数的主要变量就是存储位姿的m_pose这里的位姿是传感器的位置信息。
RangeReading这个类它继承了两个类。其中std::vector是C++标准库中定义的一种线性表实现,在这里它就是用于存储距离数据的列表。
然后我们再详细的看一下每个函数的作用。
- 首先是两个构造函数,它们有两个相同的参数rs和time,分别描述了传感器对象和产生数据的时间,用于初始化父类SensorReading。第二个构造函数的参数相对多一点,通过该函数,我们可以指定一组扫描数据来构建RangeReading对象。
RangeReading::RangeReading(const RangeSensor* rs, double time):
SensorReading(rs,time){}
RangeReading::RangeReading(unsigned int n_beams, const double* d, const RangeSensor* rs, double time):
SensorReading(rs,time){
assert(n_beams==rs->beams().size());
resize(n_beams);
for (unsigned int i=0; i<size(); i++)
(*this)[i]=d[i];
}
- RangeReading有一个protected权限的成员变量m_pose,该变量记录了获取激光传感器扫描数据时的传感器的位置信息。对应的,还有一对public权限的get-set函数,用于获取和设定位置信息。
public:
inline const OrientedPoint& getPose() const {return m_pose;}
inline void setPose(const OrientedPoint& pose) {m_pose=pose;}
protected:
OrientedPoint m_pose;
- 我们可以通过函数rawView将扫描数据拷贝到一个数组中。它有两个参数,其中v是保存数据的数组。density是一个过滤参数,如果临近的几个扫描数据所探测的点距离小于该参数, 就赋予一个很大的值,此时的扫描束被称为是抑制的(suppressed)。那些非抑制的扫描束称为激活的(actived)。缺省的情况下,density为0.0,过滤参数不起作用。 相应的还有函数activeBeams,用于计算给定过滤参数下激活状态的扫描束。
unsigned int rawView(double* v, double density=0.) const;
unsigned int activeBeams(double density=0.) const;
- 一般情况下,激光扫描数据都是用极坐标的形式描述的,使用到圆点距离以及相对于极轴的偏转角来确定空间中的一个点。这种方式虽然能够很直观的表述激光传感器的扫描过程, 但是并不利于计算,很多时候还需要将这些数据转换到笛卡尔坐标系下。RangeReading也提供了这一坐标变换的接口,它有一个参数maxRange用于限定测量距离的最大值。
std::vector<Point> cartesianForm(double maxRange=1e6) const;
五、reading.setPose(gmap_pose);
使用对象reading调用RangeReading中的函数setPose()设置位姿。该位姿是里程计位姿,应该是先获取里程计位姿作为估计后面再优化。
reading.setPose(gmap_pose);
至此本章的内容就结束了,但是AddScan()这个函数还没结束,因为最重要processScan()函数还没解析。该函数主要就是对激光数据进行处理。就是因为重要,因此我们决定在新的一章来分析它。