gmapping代码走读

Gmapping 代码走读
主要流程

  1. 1.1、laserCallback()

功能描述

  • 接受到激光雷达数据的回调函数 在这里面调用addScan()函数
  • 如果addScan()函数调用成功,也就是说激光数据被成功的插入到地图中后,
  • 如果到了地图更新的时间,则对地图进行更新,通过调用updateMap()函数来进行相应的操作。
  • laserCallback() ->addScan() ->gmapping::processScan() ->updateMap()

####1.2、addScan()
功能描述

  • 加入一个激光雷达的数据 主要函数 这里面会调用processScan()函数
  • 这个函数被laserCallback()函数调用
    addScan流程图
    ####1.3、updateMap()
    功能描述
    更新地图:这里的更新地图,只是为了可视化。因为真正的地图都存储在粒子里面。
    这里会拿一个权值最大的粒子的地图发布出来.
    得到权值最大的粒子,然后遍历这个粒子的整个轨迹,根据轨迹上记录的信息来进行建图
    然后把得到的地图发布出去
    这个函数被laserCallback()调用,每次addScan()成功了,就会调用这个函数来生成地图,并发布出去
2.1 processScan()

功能描述
 在scanmatcherprocessor里面也有一个这样的函数 但是那个函数是没有使用的
@desc 处理一帧激光数据 这里是gmapping算法的主要函数。
在这个函数里面调用其他的函数,包括里程计预测、激光测量更新、粒子采样等等步骤。
主要步骤:
利用运动模型更新里程计分布
利用最近的一次观测来提高proposal分布。(scan-match)
利用proposal分布+激光雷达数据来确定各个粒子的权重
对粒子进行重采样
这里写图片描述
####2.2 drawFromMotion()
功能描述
里程计运动模型。把加入了噪声的值,加入到粒子估计的最优位置上,得到新的位置(根据运动模型推算出来的位置)。
####2.3 scanMatch()
功能描述
为每一个粒子都计算扫描匹配。扫描匹配即为在里程计的基础上,通过优化求得位姿
这个函数是最终被使用的函数,这个函数不但对每个位姿进行scan-match来优化,再优化之后,还会计算每个粒子的权重
这里的权重用似然表示。
主要步骤:
1、对每个粒子迭代计算最有位姿,返回粒子得分。采用optimize()计算
2、如果矫正成功则更新位姿
  如果匹配不上,则采用历程数据。使用里程计数据无需进行更新,因为再扫描匹配前已经更新完成。
3、重新计算粒子的权重,相当于粒子滤波器中的观测步骤,计算p(z|x,m)),粒子的权重由粒子的似然来表示。采用
likelihoodAndScore()计算
4、计算出来最优的位姿之后,进行地图的扩充。采用
computeActiveArea
计算

#####2.3.1 optimize()
功能描述
根据地图、激光数据、位姿迭代求解一个最优的新的位姿出来
主要步骤
1、计算当前位置的得分,调用score函数
2、do-while循环
2.1 如果这一次(currentScore)算出来比上一次(bestScore)差,则有可能是走太多了,要减少搜索步长 这个策略跟LM有点像
2.2 把8个方向都搜索一次 得到这8个方向里面最好的一个位姿和对应的得分
2.3 返回最优位置和得分
#####2.3.2score()
功能描述
根据地图、机器人位置、激光雷达数据,计算出一个得分:原理为likelihood_field_range_finder_model
主要步骤

inline double ScanMatcher::score(const ScanMatcherMap& map, const OrientedPoint& p, const double* readings) const
{
    double s=0;
    const double * angle=m_laserAngles+m_initialBeamsSkip;
    OrientedPoint lp=p;
    /*
    把激光雷达的坐标转换到世界坐标系
    先旋转到机器人坐标系,然后再转换到世界坐标系
    p表示base_link在map中的坐标
    m_laserPose 表示base_laser在base_link坐标系中的坐标
    */
    lp.x+=cos(p.theta)*m_laserPose.x-sin(p.theta)*m_laserPose.y;
    lp.y+=sin(p.theta)*m_laserPose.x+cos(p.theta)*m_laserPose.y;
    lp.theta+=m_laserPose.theta;

    /*
     * map.getDelta表示地图分辨率 m_freeCellRatio = sqrt(2)
     * 意思是如果激光hit了某个点 那么沿着激光方向的freeDelta距离的地方要是空闲才可以
    */
    unsigned int skip=0;
    double freeDelta=map.getDelta()*m_freeCellRatio;


    //枚举所有的激光束
    for (const double* r=readings+m_initialBeamsSkip; r<readings+m_laserBeams; r++, angle++)
    {
        skip++;
        skip=skip>m_likelihoodSkip?0:skip;
        if (skip||*r>m_usableRange||*r==0.0) continue;

        /*被激光雷达击中的点 在地图坐标系中的坐标*/
        Point phit=lp;
        phit.x+=*r*cos(lp.theta+*angle);
        phit.y+=*r*sin(lp.theta+*angle);
        IntPoint iphit=map.world2map(phit);

        /*该束激光的最后一个空闲坐标,即紧贴hitCell的freeCell 原理为:假设phit是被激光击中的点,这样的话沿着激光方向的前面一个点必定的空闲的*/
        Point pfree=lp;
        //理论上来说 这个应该是一个bug。修改成下面的之后,改善不大。
        //		pfree.x+=(*r-map.getDelta()*freeDelta)*cos(lp.theta+*angle);
        //		pfree.y+=(*r-map.getDelta()*freeDelta)*sin(lp.theta+*angle);
        pfree.x+=(*r - freeDelta)*cos(lp.theta+*angle);
        pfree.y+=(*r - freeDelta)*sin(lp.theta+*angle);

        /*phit 和 freeCell的距离*/
        pfree=pfree-phit;
        IntPoint ipfree=map.world2map(pfree);

        /*在kernelSize大小的窗口中搜索出最优最可能被这个激光束击中的点 这个kernelSize在大小为1*/
        bool found=false;
        Point bestMu(0.,0.);
        for (int xx=-m_kernelSize; xx<=m_kernelSize; xx++)
            for (int yy=-m_kernelSize; yy<=m_kernelSize; yy++)
            {
                /*根据已开始的关系 搜索phit的时候,也计算出来pfree*/
                IntPoint pr=iphit+IntPoint(xx,yy);
                IntPoint pf=pr+ipfree;

                /*得到各自对应的Cell*/
                const PointAccumulator& cell=map.cell(pr);
                const PointAccumulator& fcell=map.cell(pf);
                /*
            (double)cell返回的是该cell被占用的概率
            这束激光要合法必须要满足cell是被占用的,而fcell是空闲的
            原理为:假设phit是被激光击中的点,这样的话沿着激光方向的前面一个点必定的空闲的
            */
                if (((double)cell )> m_fullnessThreshold && ((double)fcell )<m_fullnessThreshold)
                {
                    /*计算出被击中的点与对应的cell的currentScore距离*/
                    Point mu=phit-cell.mean();
                    if (!found)
                    {
                        bestMu=mu;
                        found=true;
                    }
                    else
                    {
                        bestMu=(mu*mu)<(bestMu*bestMu)?mu:bestMu;
                    }
                }
            }
        /*socre的计算公式exp(-d^2 / sigma)) 这里的sigma表示方差 不是标准差*/
        if (found)
        {
            double tmp_score = exp(-1.0/m_gaussianSigma*bestMu*bestMu);
            s += tmp_score;
            //只在周围的9个栅格里面寻找,因此平方的意义不大。
            //s += tmp_score*tmp_score;
        }
    }
        return s;
}

#####2.3.3 likelihoodAndScore()
功能描述
根据地图、机器人位置、激光雷达数据,同时计算出一个得分和似然:原理为likelihood_field_range_finder_model
计算出来的似然即为粒子的权重,这个函数被scanmatcher.cpp里面的optimize()调用
主要步骤

#####2.3.4 computeActiveArea()
功能描述
计算有效区域,通过激光雷达的数据计算出来哪个地图栅格应该要被更新了。(这里只是计算出来栅格的位置,然后插入地图中,并不对数据进行更新)
这里计算的有效区域的坐标都是patch坐标,不是cell坐标
这个函数在正常的进行SLAM算法的过程中,使用了m_generateMap = false。这个时候不会为了空闲区域分配内存。当要生成可视化的地图的时候,m_generateMap = true。这个时候就会为空闲区域分配内存
主要步骤

####2.4 updateTreeWeights()
功能描述
更新权重,调用*normalize()*把粒子的权重归一化
主要功能为归一化粒子的权重,同时计算出neff

主要步骤

####2.5 resample()
功能描述
粒子滤波器重采样。
主要步骤      
1.需要重采样,则所有保留下来的粒子的轨迹都加上一个新的节点,然后进行地图更新。
2.不需要冲采样,则所有的粒子的轨迹都加上一个新的节点,然后进行地图的更新
在重采样完毕之后,会调用registerScan函数来更新地图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值