ROS學習筆記------ROS深度解析----- day 4 2019/3/14 帥某(GMapping SLAM源碼閱讀)

GMapping SLAM源碼閱讀

博主地址:
http://www.cnblogs.com/yhlx125/p/5634128.html
博主GitHub:
https://github.com/yhexie

目前可以從很多地方得到RBPF的代碼,主要看的是Cyrill Stachniss的代碼,據此進行理解。

Author:Giorgio Grisetti; Cyrill Stachniss
http://openslam.org/
https://github.com/Allopart/rbpf-gmapping 和文獻[1]上結合的比較好,方法都可以找到對應的原理。
https://github.com/MRPT/mrpt MRPT中可以採用多種掃描匹配的方式,可以通過配置文件進行配置。

雙線程和程序的基本執行流程

GMapping採用GridSlamProcessorThread後臺線程進行數據的讀取和運算,在gsp_thread.h和相應的實現文件gsp_thread.cpp中可以看到初始化,參數配置,掃描數據讀取等方法。

最關鍵的流程是在後臺線程調用的方法void * GridSlamProcessorThread::fastslamthread(GridSlamProcessorThread* gpt)

而這個方法中最主要的處理掃描數據的過程在428行,bool processed=gpt->processScan(*rr);同時可以看到GMapping支持在線和離線兩種模式。

注意到struct GridSlamProcessorThread : public GridSlamProcessor ,這個後臺線程執行類GridSlamProcessorThread 繼承自GridSlamProcessor,所以執行的是GridSlamProcessor的processScan方法。

//後臺線程處理掃描數據的方法
bool GridSlamProcessor::processScan(const RangeReading & reading, int adaptParticles)
{
    /**retireve the position from the reading, and compute the odometry*/
    OrientedPoint relPose=reading.getPose();
    if (!m_count)
    {
        m_lastPartPose=m_odoPose=relPose;
    }
    
    //write the state of the reading and update all the particles using the motion model
    for (ParticleVector::iterator it=m_particles.begin(); it!=m_particles.end(); it++)
    {
        OrientedPoint& pose(it->pose);
        pose=m_motionModel.drawFromMotion(it->pose, relPose, m_odoPose);//運動模型,更新t時刻的粒子
    }

    // update the output file
    if (m_outputStream.is_open())
    {
        m_outputStream << setiosflags(ios::fixed) << setprecision(6);
        m_outputStream << "ODOM ";
        m_outputStream << setiosflags(ios::fixed) << setprecision(3) << m_odoPose.x << " " << m_odoPose.y << " ";
        m_outputStream << setiosflags(ios::fixed) << setprecision(6) << m_odoPose.theta << " ";
        m_outputStream << reading.getTime();
        m_outputStream << endl;
    }
    if (m_outputStream.is_open())
    {
        m_outputStream << setiosflags(ios::fixed) << setprecision(6);
        m_outputStream << "ODO_UPDATE "<< m_particles.size() << " ";
        for (ParticleVector::iterator it=m_particles.begin(); it!=m_particles.end(); it++)
        {
            OrientedPoint& pose(it->pose);
            m_outputStream << setiosflags(ios::fixed) << setprecision(3) << pose.x << " " << pose.y << " ";
            m_outputStream << setiosflags(ios::fixed) << setprecision(6) << pose.theta << " " << it-> weight << " ";
        }
        m_outputStream << reading.getTime();
        m_outputStream << endl;
    }
    
    //invoke the callback
    onOdometryUpdate();
    
    // accumulate the robot translation and rotation
    OrientedPoint move=relPose-m_odoPose;
    move.theta=atan2(sin(move.theta), cos(move.theta));
    m_linearDistance+=sqrt(move*move);
    m_angularDistance+=fabs(move.theta);
    
    // if the robot jumps throw a warning
    if (m_linearDistance>m_distanceThresholdCheck)
    {
        cerr << "***********************************************************************" << endl;
        cerr << "********** Error: m_distanceThresholdCheck overridden!!!! *************" << endl;
        cerr << "m_distanceThresholdCheck=" << m_distanceThresholdCheck << endl;
        cerr << "Old Odometry Pose= " << m_odoPose.x << " " << m_odoPose.y 
        << " " <<m_odoPose.theta << endl;
        cerr << "New Odometry Pose (reported from observation)= " << relPose.x << " " << relPose.y 
        << " " <<relPose.theta << endl;
        cerr << "***********************************************************************" << endl;
        cerr << "** The Odometry has a big jump here. This is probably a bug in the   **" << endl;
        cerr << "** odometry/laser input. We continue now, but the result is probably **" << endl;
        cerr << "** crap or can lead to a core dump since the map doesn't fit.... C&G **" << endl;
        cerr << "***********************************************************************" << endl;
    }
    
    m_odoPose=relPose;
    
    bool processed=false;

    // process a scan only if the robot has traveled a given distance
    if (! m_count || m_linearDistance>m_linearThresholdDistance || m_angularDistance>m_angularThresholdDistance)
    {     
        if (m_outputStream.is_open())
        {
            m_outputStream << setiosflags(ios::fixed) << setprecision(6);
            m_outputStream << "FRAME " <<  m_readingCount;
            m_outputStream << " " << m_linearDistance;
            m_outputStream << " " << m_angularDistance << endl;
        }
      
        if (m_infoStream)
            m_infoStream << "update frame " <<  m_readingCount << endl
                << "update ld=" << m_linearDistance << " ad=" << m_angularDistance << endl;
      
      
        cerr << "Laser Pose= " << reading.getPose().x << " " << reading.getPose().y 
        << " " << reading.getPose().theta << endl;
      
      
        //this is for converting the reading in a scan-matcher feedable form
        assert(reading.size()==m_beams);
        double * plainReading = new double[m_beams];
        for(unsigned int i=0; i<m_beams; i++)
        {
            plainReading[i]=reading[i];
        }
        m_infoStream << "m_count " << m_count << endl;
        if (m_count>0)
        {
            scanMatch(plainReading);//掃描匹配
            if (m_outputStream.is_open())
            {
                m_outputStream << "LASER_READING "<< reading.size() << " ";
                m_outputStream << setiosflags(ios::fixed) << setprecision(2);
                for (RangeReading::const_iterator b=reading.begin(); b!=reading.end(); b++)
                {
                    m_outputStream << *b << " ";
                }
                OrientedPoint p=reading.getPose();
                m_outputStream << setiosflags(ios::fixed) << setprecision(6);
                m_outputStream << p.x << " " << p.y << " " << p.theta << " " << reading.getTime()<< endl;
                m_outputStream << "SM_UPDATE "<< m_particles.size() << " ";
                for (ParticleVector::const_iterator it=m_particles.begin(); it!=m_particles.end(); it++)
                {
                    const OrientedPoint& pose=it->pose;
                    m_outputStream << setiosflags(ios::fixed) << setprecision(3) <<  pose.x << " " << pose.y << " ";
                    m_outputStream << setiosflags(ios::fixed) << setprecision(6) <<  pose.theta << " " << it-> weight << " ";
                }
                m_outputStream << endl;
            }
            onScanmatchUpdate();
            updateTreeWeights(false);//更新權重
                
            if (m_infoStream)
            {
                m_infoStream << "neff= " << m_neff  << endl;
            }
            if (m_outputStream.is_open())
            {
                m_outputStream << setiosflags(ios::fixed) << setprecision(6);
                m_outputStream << "NEFF " << m_neff << endl;
            }
            resample(plainReading, adaptParticles);//重採樣
        } 
        else 
        {
            m_infoStream << "Registering First Scan"<< endl;
            for (ParticleVector::iterator it=m_particles.begin(); it!=m_particles.end(); it++)
            {    
                m_matcher.invalidateActiveArea();
                m_matcher.computeActiveArea(it->map, it->pose, plainReading);//計算有效區域
                m_matcher.registerScan(it->map, it->pose, plainReading);
      
                // cyr: not needed anymore, particles refer to the root in the beginning!
                TNode* node=new    TNode(it->pose, 0., it->node,  0);
                node->reading=0;
                it->node=node;
      
            }
        }
        //cerr  << "Tree: normalizing, resetting and propagating weights at the end..." ;
        updateTreeWeights(false);//再次更新權重
        //cerr  << ".done!" <<endl;
      
        delete [] plainReading;
        m_lastPartPose=m_odoPose; //update the past pose for the next iteration
        m_linearDistance=0;
        m_angularDistance=0;
        m_count++;
        processed=true;
      
        //keep ready for the next step
        for (ParticleVector::iterator it=m_particles.begin(); it!=m_particles.end(); it++)
        {
            it->previousPose=it->pose;
        }
      
    }
    if (m_outputStream.is_open())
        m_outputStream << flush;
    m_readingCount++;
    return processed;
}

GridSlamProcessor::processScan

可以依次看到下面介紹的1-7部分的內容。

1.運動模型

t時刻粒子的位姿最初由運動模型進行更新。在初始值的基礎上增加高斯採樣的noisypoint,參考MotionModel::drawFromMotion()方法。原理參考文獻[1]5.4.1節,sample_motion_model_odometry算法。

p是粒子的t-1時刻的位姿,pnew是當前t時刻的里程計讀數,pold是t-1時刻的里程計讀數。
參考博客:
http://www.cnblogs.com/yhlx125/p/5677135.html

OrientedPoint MotionModel::drawFromMotion(const OrientedPoint& p, const OrientedPoint& pnew, const OrientedPoint& pold) const{
    double sxy=0.3*srr;
    OrientedPoint delta=absoluteDifference(pnew, pold);
    OrientedPoint noisypoint(delta);
    noisypoint.x+=sampleGaussian(srr*fabs(delta.x)+str*fabs(delta.theta)+sxy*fabs(delta.y));
    noisypoint.y+=sampleGaussian(srr*fabs(delta.y)+str*fabs(delta.theta)+sxy*fabs(delta.x));
    noisypoint.theta+=sampleGaussian(stt*fabs(delta.theta)+srt*sqrt(delta.x*delta.x+delta.y*delta.y));
    noisypoint.theta=fmod(noisypoint.theta, 2*M_PI);
    if (noisypoint.theta>M_PI)
        noisypoint.theta-=2*M_PI;
    return absoluteSum(p,noisypoint);
}

2.掃描匹配

掃描匹配獲取最優的採樣粒子。GMapping默認採用30個採樣粒子。

/**Just scan match every single particle.
If the scan matching fails, the particle gets a default likelihood.*/
inline void GridSlamProcessor::scanMatch(const double* plainReading){
  // sample a new pose from each scan in the reference
  double sumScore=0;
  for (ParticleVector::iterator it=m_particles.begin(); it!=m_particles.end(); it++){
    OrientedPoint corrected;
    double score, l, s;
    score=m_matcher.optimize(corrected, it->map, it->pose, plainReading);
    //    it->pose=corrected;
    if (score>m_minimumScore){
      it->pose=corrected;
    } else {
    if (m_infoStream){
      m_infoStream << "Scan Matching Failed, using odometry. Likelihood=" << l <<std::endl;
      m_infoStream << "lp:" << m_lastPartPose.x << " "  << m_lastPartPose.y << " "<< m_lastPartPose.theta <<std::endl;
      m_infoStream << "op:" << m_odoPose.x << " " << m_odoPose.y << " "<< m_odoPose.theta <<std::endl;
    }
    }

    m_matcher.likelihoodAndScore(s, l, it->map, it->pose, plainReading);
    sumScore+=score;
    it->weight+=l;
    it->weightSum+=l;

    //set up the selective copy of the active area
    //by detaching the areas that will be updated
    m_matcher.invalidateActiveArea();
    m_matcher.computeActiveArea(it->map, it->pose, plainReading);
  }
  if (m_infoStream)
    m_infoStream << "Average Scan Matching Score=" << sumScore/m_particles.size() << std::endl;    
}

GridSlamProcessor::scanMatch

注意ScanMatcher::score()函數的原理是likehood_field_range_finder_model方法,參考《概率機器人》手稿P143頁。

ScanMatcher::optimize()方法獲得了一個最優的粒子,基本流程是按照預先設定的步長不斷移動粒子的位置,根據提議分佈計算s,得到score最小的那個作爲粒子的新的位姿。

//此處的方法是likehood_field_range_finder_model方法,參考《概率機器人》手稿P143
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;
    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;
    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 (*r>m_usableRange) continue;
        if (skip) continue;
        Point phit=lp;
        phit.x+=*r*cos(lp.theta+*angle);
        phit.y+=*r*sin(lp.theta+*angle);
        IntPoint iphit=map.world2map(phit);
        Point pfree=lp;
        pfree.x+=(*r-map.getDelta()*freeDelta)*cos(lp.theta+*angle);
        pfree.y+=(*r-map.getDelta()*freeDelta)*sin(lp.theta+*angle);
         pfree=pfree-phit;
        IntPoint ipfree=map.world2map(pfree);
        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++){
            IntPoint pr=iphit+IntPoint(xx,yy);
            IntPoint pf=pr+ipfree;
            //AccessibilityState s=map.storage().cellState(pr);
            //if (s&Inside && s&Allocated){
                const PointAccumulator& cell=map.cell(pr);
                const PointAccumulator& fcell=map.cell(pf);
                if (((double)   )> m_fullnessThreshold && ((double)fcell )<m_fullnessThreshold){
                    Point mu=phit-cell.mean();
                    if (!found){
                        bestMu=mu;
                        found=true;
                    }else
                        bestMu=(mu*mu)<(bestMu*bestMu)?mu:bestMu;
                }
            //}
        }
        if (found)
            s+=exp(-1./m_gaussianSigma*bestMu*bestMu);//高斯提議分佈
    }
    return s;
}

ScanMatcher::likelihoodAndScore()和ScanMatcher::score()方法基本一致,但是是將新獲得的粒子位姿進行計算q,爲後續的權重計算做了準備。

ScanMatcher::optimize()方法——粒子的運動+score()中激光掃描觀測數據。

其他的掃描匹配方法也是可以使用的:比如ICP算法(rbpf-gmapping的使用的是ICP方法,先更新了旋轉角度,然後採用提議分佈再次優化粒子)、Cross Correlation、線特徵等等。

3.提議分佈 (Proposal distribution)

注意是混合了運動模型和觀測的提議分佈,將掃描觀測值整合到了提議分佈中[2](公式9)。因此均值和方差的計算與單純使用運動模型作爲提議分佈的有所區別。提議分佈的作用是獲得一個最優的粒子,同時用來計算權重,這個體現在ScanMatcher::likelihoodAndScore()和ScanMatcher::score()方法中,score方法中採用的是服從0均值的高斯分佈近似提議分佈(文獻[1] III.B節)。關於爲什麼採用混合的提議分佈,L(i)區域小的原理在文獻[1]中III.A節有具體介紹。

rbpf-gmapping的使用的是運動模型作爲提議分佈。
4.權重計算

在重採樣之前進行了一次權重計算updateTreeWeights(false);

重採樣之後又進行了一次。代碼在gridslamprocessor_tree.cpp文件中。

void  GridSlamProcessor::updateTreeWeights(bool weightsAlreadyNormalized)
{
  if (!weightsAlreadyNormalized)
  {
    normalize();
  }
  resetTree();
  propagateWeights();
}

5.重採樣

原理:粒子集對目標分佈的近似越差,則權重的方差越大。

因此用Neff作爲權重值離差的量度。

//判斷並進行重採樣
inline bool GridSlamProcessor::resample(const double* plainReading, int adaptSize, const RangeReading* )
{
    bool hasResampled = false;
    TNodeVector oldGeneration;
    for (unsigned int i=0; i<m_particles.size(); i++)
    {
        oldGeneration.push_back(m_particles[i].node);
    }
    if (m_neff<m_resampleThreshold*m_particles.size())
    {
        if (m_infoStream)
            m_infoStream  << "*************RESAMPLE***************" << std::endl;
    
        uniform_resampler<double, double> resampler;
        m_indexes=resampler.resampleIndexes(m_weights, adaptSize);//執行重採樣
    
        if (m_outputStream.is_open())
        {
            m_outputStream << "RESAMPLE "<< m_indexes.size() << " ";
            for (std::vector<unsigned int>::const_iterator it=m_indexes.begin(); it!=m_indexes.end(); it++)
            {
                m_outputStream << *it <<  " ";
            }
            m_outputStream << std::endl;
        } 
        onResampleUpdate();
        //BEGIN: BUILDING TREE
        ParticleVector temp;//重採樣的粒子集合臨時變量
        unsigned int j=0;
        std::vector<unsigned int> deletedParticles;          //this is for deleteing the particles which have been resampled away.
    
        //cerr << "Existing Nodes:" ;
        for (unsigned int i=0; i<m_indexes.size(); i++)
        {
            //cerr << " " << m_indexes[i];
            while(j<m_indexes[i])
            {
                deletedParticles.push_back(j);
                j++;
            }
            if (j==m_indexes[i])
            j++;
            Particle & p=m_particles[m_indexes[i]];
            TNode* node=0;
            TNode* oldNode=oldGeneration[m_indexes[i]];
            //cerr << i << "->" << m_indexes[i] << "B("<<oldNode->childs <<") ";
            node=new TNode(p.pose, 0, oldNode, 0);
            node->reading=0;
            //cerr << "A("<<node->parent->childs <<") " <<endl;
      
            temp.push_back(p);
            temp.back().node=node;
            temp.back().previousIndex=m_indexes[i];
        }
        while(j<m_indexes.size())
        {
            deletedParticles.push_back(j);
            j++;
        }
        //cerr << endl;
        std::cerr <<  "Deleting Nodes:";
        //刪除粒子
        for (unsigned int i=0; i<deletedParticles.size(); i++)
        {
            std::cerr <<" " << deletedParticles[i];
            delete m_particles[deletedParticles[i]].node;
            m_particles[deletedParticles[i]].node=0;
        }
        std::cerr  << " Done" <<std::endl;
    
        //END: BUILDING TREE
        std::cerr << "Deleting old particles..." ;
        m_particles.clear();
        std::cerr << "Done" << std::endl;
        std::cerr << "Copying Particles and  Registering  scans...";
        for (ParticleVector::iterator it=temp.begin(); it!=temp.end(); it++)
        {
            it->setWeight(0);
            m_matcher.invalidateActiveArea();
            m_matcher.registerScan(it->map, it->pose, plainReading);
            m_particles.push_back(*it);
        }
        std::cerr  << " Done" <<std::endl;
        hasResampled = true;
    }
    else 
    {
        int index=0;
        std::cerr << "Registering Scans:";
        TNodeVector::iterator node_it=oldGeneration.begin();
        for (ParticleVector::iterator it=m_particles.begin(); it!=m_particles.end(); it++)
        {
            //create a new node in the particle tree and add it to the old tree
            //BEGIN: BUILDING TREE  
            TNode* node=0;
            node=new TNode(it->pose, 0.0, *node_it, 0);
      
            node->reading=0;
            it->node=node;

            //END: BUILDING TREE
            m_matcher.invalidateActiveArea();
            m_matcher.registerScan(it->map, it->pose, plainReading);//註冊到全局地圖中
            it->previousIndex=index;
            index++;
            node_it++;   
        }
        std::cerr  << "Done" <<std::endl; 
    }
    //END: BUILDING TREE
    return hasResampled;
}

GridSlamProcessor::resample

6.佔用概率柵格地圖

此處的方法感覺有點奇怪,在resample方法中執行ScanMatcher::registerScan()方法,計算佔用概率柵格地圖。採樣兩種方式,採用信息熵的方式和文獻[1] 9.2節的計算方法不一樣。

double ScanMatcher::registerScan(ScanMatcherMap& map, const OrientedPoint& p, const double* readings)
{
    if (!m_activeAreaComputed)
        computeActiveArea(map, p, readings);
        
    //this operation replicates the cells that will be changed in the registration operation
    map.storage().allocActiveArea();
    
    OrientedPoint lp=p;
    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;//激光器中心點
    IntPoint p0=map.world2map(lp);//轉到地圖座標?
    
    
    const double * angle=m_laserAngles+m_initialBeamsSkip;
    double esum=0;
    for (const double* r=readings+m_initialBeamsSkip; r<readings+m_laserBeams; r++, angle++)//每一條掃描線
        if (m_generateMap)
        {
            double d=*r;
            if (d>m_laserMaxRange)
                continue;
            if (d>m_usableRange)
                d=m_usableRange;
            Point phit=lp+Point(d*cos(lp.theta+*angle),d*sin(lp.theta+*angle));//掃描到的點
            IntPoint p1=map.world2map(phit);//轉到地圖座標?
            IntPoint linePoints[20000] ;
            GridLineTraversalLine line;
            line.points=linePoints;
            GridLineTraversal::gridLine(p0, p1, &line);//計算掃描線佔據的柵格?
            for (int i=0; i<line.num_points-1; i++)
            {
                PointAccumulator& cell=map.cell(line.points[i]);
                double e=-cell.entropy();
                cell.update(false, Point(0,0));
                e+=cell.entropy();
                esum+=e;
            }
            if (d<m_usableRange)
            {
                double e=-map.cell(p1).entropy();
                map.cell(p1).update(true, phit);
                e+=map.cell(p1).entropy();
                esum+=e;
            }
        } 
        else
        {
            if (*r>m_laserMaxRange||*r>m_usableRange) continue;
            Point phit=lp;
            phit.x+=*r*cos(lp.theta+*angle);
            phit.y+=*r*sin(lp.theta+*angle);
            IntPoint p1=map.world2map(phit);
            assert(p1.x>=0 && p1.y>=0);
            map.cell(p1).update(true,phit);
        }
    //cout  << "informationGain=" << -esum << endl;
    return esum;
}

ScanMatcher::registerScan

rbpf-gmapping的使用的是文獻[1] 9.2節的計算方法,在Occupancy_Grid_Mapping.m文件中實現,同時調用Inverse_Range_Sensor_Model方法。

gridlineTraversal實現了beam轉成柵格的線。對每一束激光束,設start爲該激光束起點,end爲激光束端點(障礙物位置),使用Bresenham劃線算法確定激光束經過的格網。

連接:http://www.cnblogs.com/yhlx125/p/6794661.html
小豆包的學習之旅:佔用概率柵格地圖和cost-map

7.計算有效區域

第一次掃描,count==0時,如果激光觀測數據超出了範圍,更新柵格地圖的範圍。同時確定有效區域。

void ScanMatcher::computeActiveArea(ScanMatcherMap& map, const OrientedPoint& p, const double* readings){
    if (m_activeAreaComputed)
        return;
    OrientedPoint lp=p;
    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;
    IntPoint p0=map.world2map(lp);
    
    Point min(map.map2world(0,0));
    Point max(map.map2world(map.getMapSizeX()-1,map.getMapSizeY()-1));
           
    if (lp.x<min.x) min.x=lp.x;
    if (lp.y<min.y) min.y=lp.y;
    if (lp.x>max.x) max.x=lp.x;
    if (lp.y>max.y) max.y=lp.y;
    
    /*determine the size of the area*/
    const double * angle=m_laserAngles+m_initialBeamsSkip;
    for (const double* r=readings+m_initialBeamsSkip; r<readings+m_laserBeams; r++, angle++){
        if (*r>m_laserMaxRange) continue;
        double d=*r>m_usableRange?m_usableRange:*r;
        Point phit=lp;
        phit.x+=d*cos(lp.theta+*angle);
        phit.y+=d*sin(lp.theta+*angle);
        if (phit.x<min.x) min.x=phit.x;
        if (phit.y<min.y) min.y=phit.y;
        if (phit.x>max.x) max.x=phit.x;
        if (phit.y>max.y) max.y=phit.y;
    }
    //min=min-Point(map.getDelta(),map.getDelta());
    //max=max+Point(map.getDelta(),map.getDelta());
    
    if ( !map.isInside(min)    || !map.isInside(max)){
        Point lmin(map.map2world(0,0));
        Point lmax(map.map2world(map.getMapSizeX()-1,map.getMapSizeY()-1));
        //cerr << "CURRENT MAP " << lmin.x << " " << lmin.y << " " << lmax.x << " " << lmax.y << endl;
        //cerr << "BOUNDARY OVERRIDE " << min.x << " " << min.y << " " << max.x << " " << max.y << endl;
        min.x=( min.x >= lmin.x )? lmin.x: min.x-m_enlargeStep;
        max.x=( max.x <= lmax.x )? lmax.x: max.x+m_enlargeStep;
        min.y=( min.y >= lmin.y )? lmin.y: min.y-m_enlargeStep;
        max.y=( max.y <= lmax.y )? lmax.y: max.y+m_enlargeStep;
        map.resize(min.x, min.y, max.x, max.y);
        //cerr << "RESIZE " << min.x << " " << min.y << " " << max.x << " " << max.y << endl;
    }
    
    HierarchicalArray2D<PointAccumulator>::PointSet activeArea;
    /*allocate the active area*/
    angle=m_laserAngles+m_initialBeamsSkip;
    for (const double* r=readings+m_initialBeamsSkip; r<readings+m_laserBeams; r++, angle++)
        if (m_generateMap)
        {
            double d=*r;
            if (d>m_laserMaxRange)
                continue;
            if (d>m_usableRange)
                d=m_usableRange;
            Point phit=lp+Point(d*cos(lp.theta+*angle),d*sin(lp.theta+*angle));
            IntPoint p0=map.world2map(lp);
            IntPoint p1=map.world2map(phit);
            
            IntPoint linePoints[20000] ;
            GridLineTraversalLine line;
            line.points=linePoints;
            GridLineTraversal::gridLine(p0, p1, &line);
            for (int i=0; i<line.num_points-1; i++)
            {
                assert(map.isInside(linePoints[i]));
                activeArea.insert(map.storage().patchIndexes(linePoints[i]));
                assert(linePoints[i].x>=0 && linePoints[i].y>=0);
            }
            if (d<m_usableRange){
                IntPoint cp=map.storage().patchIndexes(p1);
                assert(cp.x>=0 && cp.y>=0);
                activeArea.insert(cp);
            }
        } 
        else 
        {
            if (*r>m_laserMaxRange||*r>m_usableRange) continue;
            Point phit=lp;
            phit.x+=*r*cos(lp.theta+*angle);
            phit.y+=*r*sin(lp.theta+*angle);
            IntPoint p1=map.world2map(phit);
            assert(p1.x>=0 && p1.y>=0);
            IntPoint cp=map.storage().patchIndexes(p1);
            assert(cp.x>=0 && cp.y>=0);
            activeArea.insert(cp);
        }
    
    //this allocates the unallocated cells in the active area of the map
    //cout << "activeArea::size() " << activeArea.size() << endl;
/*    
    cerr << "ActiveArea=";
    for (HierarchicalArray2D<PointAccumulator>::PointSet::const_iterator it=activeArea.begin(); it!= activeArea.end(); it++){
        cerr << "(" << it->x <<"," << it->y << ") ";
    }
    cerr << endl;
*/        
    map.storage().setActiveArea(activeArea, true);
    m_activeAreaComputed=true;
}

computeActiveArea

每次掃描匹配獲取t時刻的最優粒子後會計算有效區域。

重採樣之後,調用ScanMatcher::registerScan() 方法,也會重新計算有效區域。

參考文獻:

[1]Sebastian Thrun et al. “Probabilistic Robotics(手稿).”

[2]Grisetti, G. and C. Stachniss “Improved Techniques for Grid Mapping with Rao-Blackwellized Particle Filters.”

發佈了43 篇原創文章 · 獲贊 266 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章