无人驾驶学习笔记 – A-LOAM 算法代码解析总结

目录

1、概述

2、scanRegistration.cpp

2.1、代码注释

2.1.1、主函数

2.1.2、removeClosedPointCloud(雷达周边过近点移除)

2.1.3 laserCloudHandler激光处理回调函数

2.2、总结概括

3、LaserOdometry.cpp

3.1、代码注释

3.2、总结概括

4、laserMapping.cpp

4.1、代码注释

4.2、总结概括

5、lidarFactor.cpp

6、参考连接

PS: 在看了loam的论文和代码以后,紧接着看了A-LOAM,核心算法没有太大变换,应用了ceres 替代了手推高斯推导,LOAM代码中大量的基于欧拉角转来转去的推导实在容易劝退众生。A-LOAM相对整体结构也更清楚,避免遗忘,将代码的学习总结于此。

该总结包括代码和文字总共有6万字左右,建议结合目录阅读,首先要熟悉loam Aloam 论文中核心的方法,然后可以先阅读每一部分的总结概括,有了认知以后再去每个节点的详细注释中学习

1、概述

A-LOAM 的运行节点关系图如下

无人驾驶学习笔记 - A-LOAM 算法代码解析总结

关于 A-LOAM 算法原理可以参考: 无人驾驶学习笔记 – LOAM 算法论文核心关键点总结_ppipp1109的博客-CSDN博客

本文总结主要从扫描匹配、低精度里程计、高精度建图三部分对应的函数讲解

2、scanRegistration.cpp

scanRegistration.cpp用来读取激光雷达数据并且对激光雷达数据进行整理,去除无效点云以及激光雷达周围的点云,并且计算每一个点的曲率,按照提前设定好的曲率范围来分离出不同类型的点。

2.1、代码注释

2.1.1、主函数

int main(int argc, char **argv)
{
    ros::init(argc, argv, "scanRegistration");
    ros::NodeHandle nh;
    // 从配置文件中获取多少线的激光雷达
    nh.param<int>("scan_line", N_SCANS, 16);
    // &#x6700;&#x5C0F;&#x6709;&#x6548;&#x8DDD;&#x79BB;
    nh.param<double>("minimum_range", MINIMUM_RANGE, 0.1);

    printf("scan line number %d \n", N_SCANS);
    // &#x53EA;&#x6709;&#x7EBF;&#x675F;&#x662F;16 32 64&#x7684;&#x624D;&#x53EF;&#x4EE5;&#x7EE7;&#x7EED;
    if(N_SCANS != 16 && N_SCANS != 32 && N_SCANS != 64)
    {
        printf("only support velodyne with 16, 32 or 64 scan line!");
        return 0;
    }

    ros::Subscriber subLaserCloud = nh.subscribe<sensor_msgs::pointcloud2>("/velodyne_points", 100, laserCloudHandler);

    pubLaserCloud = nh.advertise<sensor_msgs::pointcloud2>("/velodyne_cloud_2", 100);

    pubCornerPointsSharp = nh.advertise<sensor_msgs::pointcloud2>("/laser_cloud_sharp", 100);

    pubCornerPointsLessSharp = nh.advertise<sensor_msgs::pointcloud2>("/laser_cloud_less_sharp", 100);

    pubSurfPointsFlat = nh.advertise<sensor_msgs::pointcloud2>("/laser_cloud_flat", 100);

    pubSurfPointsLessFlat = nh.advertise<sensor_msgs::pointcloud2>("/laser_cloud_less_flat", 100);

    pubRemovePoints = nh.advertise<sensor_msgs::pointcloud2>("/laser_remove_points", 100);

    if(PUB_EACH_LINE)
    {
        for(int i = 0; i < N_SCANS; i++)
        {
            ros::Publisher tmp = nh.advertise<sensor_msgs::pointcloud2>("/laser_scanid_" + std::to_string(i), 100);
            pubEachScan.push_back(tmp);
        }
    }
    ros::spin();

    return 0;
}</sensor_msgs::pointcloud2></sensor_msgs::pointcloud2></sensor_msgs::pointcloud2></sensor_msgs::pointcloud2></sensor_msgs::pointcloud2></sensor_msgs::pointcloud2></sensor_msgs::pointcloud2></sensor_msgs::pointcloud2></double></int>

2.1.2、removeClosedPointCloud(雷达周边过近点移除)

void removeClosedPointCloud(const pcl::PointCloud<pointt> &cloud_in,
                              pcl::PointCloud<pointt> &cloud_out, float thres)
{
    // &#x5047;&#x5982;&#x8F93;&#x5165;&#x8F93;&#x51FA;&#x70B9;&#x4E91;&#x4E0D;&#x4F7F;&#x7528;&#x540C;&#x4E00;&#x4E2A;&#x53D8;&#x91CF;&#xFF0C;&#x5219;&#x9700;&#x8981;&#x5C06;&#x8F93;&#x51FA;&#x70B9;&#x4E91;&#x7684;&#x65F6;&#x95F4;&#x6233;&#x548C;&#x5BB9;&#x5668;&#x5927;&#x5C0F;&#x4E0E;&#x8F93;&#x5165;&#x70B9;&#x4E91;&#x540C;&#x6B65;
    if (&cloud_in != &cloud_out)
    {
        cloud_out.header = cloud_in.header;
        cloud_out.points.resize(cloud_in.points.size());
    }

    size_t j = 0;
    // &#x628A;&#x70B9;&#x4E91;&#x8DDD;&#x79BB;&#x5C0F;&#x4E8E;&#x7ED9;&#x5B9A;&#x9608;&#x503C;&#x7684;&#x53BB;&#x9664;&#x6389;,&#x4E5F;&#x5C31;&#x662F;&#x8DDD;&#x79BB;&#x96F7;&#x8FBE;&#x8FC7;&#x8FD1;&#x7684;&#x70B9;&#x53BB;&#x9664;
    for (size_t i = 0; i < cloud_in.points.size(); ++i)
    {
        if (cloud_in.points[i].x * cloud_in.points[i].x + cloud_in.points[i].y * cloud_in.points[i].y + cloud_in.points[i].z * cloud_in.points[i].z < thres * thres)
            continue;
        cloud_out.points[j] = cloud_in.points[i];
        j++;
    }
    if (j != cloud_in.points.size())
    {
        cloud_out.points.resize(j);
    }

    // &#x8FD9;&#x91CC;&#x662F;&#x5BF9;&#x6BCF;&#x6761;&#x626B;&#x63CF;&#x7EBF;&#x4E0A;&#x7684;&#x70B9;&#x4E91;&#x8FDB;&#x884C;&#x76F4;&#x901A;&#x6EE4;&#x6CE2;&#xFF0C;&#x56E0;&#x6B64;&#x8BBE;&#x7F6E;&#x70B9;&#x4E91;&#x7684;&#x9AD8;&#x5EA6;&#x4E3A;1&#xFF0C;&#x5BBD;&#x5EA6;&#x4E3A;&#x6570;&#x91CF;&#xFF0C;&#x7A20;&#x5BC6;&#x70B9;&#x4E91;
    cloud_out.height = 1;
    cloud_out.width = static_cast<uint32_t>(j);
    cloud_out.is_dense = true;
}</uint32_t></pointt></pointt>

2.1.3 laserCloudHandler激光处理回调函数

预处理

void laserCloudHandler(const sensor_msgs::PointCloud2ConstPtr &laserCloudMsg)
{
    // &#x5982;&#x679C;&#x7CFB;&#x7EDF;&#x6CA1;&#x6709;&#x521D;&#x59CB;&#x5316;&#x7684;&#x8BDD;&#xFF0C;&#x5C31;&#x7B49;&#x51E0;&#x5E27;
    if (!systemInited)
    {
        systemInitCount++;
        if (systemInitCount >= systemDelay)
        {
            systemInited = true;
        }
        else
            return;
    }

    //&#x4F5C;&#x8005;&#x81EA;&#x5DF1;&#x8BBE;&#x8BA1;&#x7684;&#x8BA1;&#x65F6;&#x7C7B;&#xFF0C;&#x4EE5;&#x6784;&#x9020;&#x51FD;&#x6570;&#x4E3A;&#x8D77;&#x59CB;&#x65F6;&#x95F4;&#xFF0C;&#x4EE5;toc()&#x51FD;&#x6570;&#x4E3A;&#x7EC8;&#x6B62;&#x65F6;&#x95F4;&#xFF0C;&#x5E76;&#x8FD4;&#x56DE;&#x65F6;&#x95F4;&#x95F4;&#x9694;(ms)
    TicToc t_whole;
    TicToc t_prepare;

    //&#x6BCF;&#x6761;&#x626B;&#x63CF;&#x7EBF;&#x4E0A;&#x7684;&#x53EF;&#x4EE5;&#x8BA1;&#x7B97;&#x66F2;&#x7387;&#x7684;&#x70B9;&#x4E91;&#x70B9;&#x7684;&#x8D77;&#x59CB;&#x7D22;&#x5F15;&#x548C;&#x7ED3;&#x675F;&#x7D22;&#x5F15;
    //&#x5206;&#x522B;&#x7528;scanStartInd&#x6570;&#x7EC4;&#x548C;scanEndInd&#x6570;&#x7EC4;&#x8BB0;&#x5F55;
    std::vector<int> scanStartInd(N_SCANS, 0);
    std::vector<int> scanEndInd(N_SCANS, 0);

    pcl::PointCloud<pcl::pointxyz> laserCloudIn;
    // &#x628A;&#x70B9;&#x4E91;&#x4ECE;ros&#x683C;&#x5F0F;&#x8F6C;&#x5230;pcl&#x7684;&#x683C;&#x5F0F;
    pcl::fromROSMsg(*laserCloudMsg, laserCloudIn);
    std::vector<int> indices;
    // &#x53BB;&#x9664;&#x6389;&#x70B9;&#x4E91;&#x4E2D;&#x7684;nan&#x70B9;
    pcl::removeNaNFromPointCloud(laserCloudIn, laserCloudIn, indices);
    // &#x53BB;&#x9664;&#x8DDD;&#x79BB;&#x5C0F;&#x4E8E;&#x9608;&#x503C;&#x7684;&#x70B9;
    removeClosedPointCloud(laserCloudIn, laserCloudIn, MINIMUM_RANGE);</int></pcl::pointxyz></int></int>

计算点云角度范围,目的是求解点所在的扫描线,并通过论文中方法,对齐时间戳,根据时间占比求出所有点对应的角度范围。其中根据扫描范围在不同象限做了不同处理,考虑了扫描范围在二三象限时候,对角度进行加2pi将其约束到 -pi/2 – 3/2 * pi范围内。类似地如果扫描范围在一四象限,需要将结束点角度也减2pi。详细解释可参考下文的3.2.2节

A_LOAM源码解析1——scanRegistration – 知乎

    // &#x8BA1;&#x7B97;&#x8D77;&#x59CB;&#x70B9;&#x548C;&#x7ED3;&#x675F;&#x70B9;&#x7684;&#x89D2;&#x5EA6;&#xFF0C;&#x7531;&#x4E8E;&#x6FC0;&#x5149;&#x96F7;&#x8FBE;&#x662F;&#x987A;&#x65F6;&#x9488;&#x65CB;&#x8F6C;&#xFF0C;&#x8FD9;&#x91CC;&#x53D6;&#x53CD;&#x5C31;&#x76F8;&#x5F53;&#x4E8E;&#x8F6C;&#x6210;&#x4E86;&#x9006;&#x65F6;&#x9488;
    int cloudSize = laserCloudIn.points.size();
    float startOri = -atan2(laserCloudIn.points[0].y, laserCloudIn.points[0].x);
    // atan2&#x8303;&#x56F4;&#x662F;[-Pi,PI]&#xFF0C;&#x8FD9;&#x91CC;&#x52A0;&#x4E0A;2PI&#x662F;&#x4E3A;&#x4E86;&#x4FDD;&#x8BC1;&#x8D77;&#x59CB;&#x5230;&#x7ED3;&#x675F;&#x76F8;&#x5DEE;2PI&#x7B26;&#x5408;&#x5B9E;&#x9645;
    float endOri = -atan2(laserCloudIn.points[cloudSize - 1].y,
                          laserCloudIn.points[cloudSize - 1].x) +
                   2 * M_PI;

    // &#x901A;&#x8FC7;&#x4E0A;&#x9762;&#x7684;&#x516C;&#x5F0F;&#xFF0C;endOri - startOri&#x7406;&#x5E94;&#x5728;pi~3pi&#x4E4B;&#x95F4;
    // &#x6B63;&#x5E38;&#x60C5;&#x51B5;&#x4E0B;&#x5728;&#x8FD9;&#x4E2A;&#x8303;&#x56F4;&#x5185;&#xFF1A;pi < endOri - startOri < 3*pi&#xFF0C;&#x5F02;&#x5E38;&#x5219;&#x4FEE;&#x6B63;
    // &#x5982;&#x679C;&#x51FA;&#x73B0;&#x4E86;&#x5F02;&#x5E38;&#xFF0C;&#x4E0D;&#x8BA4;&#x4E3A;&#x662F;&#x626B;&#x63CF;&#x70B9;&#x672C;&#x8EAB;&#x7684;&#x95EE;&#x9898;&#xFF0C;&#x800C;&#x662F;&#x516C;&#x5F0F;&#x5148;&#x9A8C;&#x5730;+2pi&#x51FA;&#x4E86;&#x95EE;&#x9898;

    // &#x603B;&#x6709;&#x4E00;&#x4E9B;&#x4F8B;&#x5916;&#xFF0C;&#x6BD4;&#x5982;&#x8FD9;&#x91CC;&#x5927;&#x4E8E;3PI&#xFF0C;&#x548C;&#x5C0F;&#x4E8E;PI&#xFF0C;&#x5C31;&#x9700;&#x8981;&#x505A;&#x4E00;&#x4E9B;&#x8C03;&#x6574;&#x5230;&#x5408;&#x7406;&#x8303;&#x56F4;
    //&#x626B;&#x63CF;&#x573A;&#x5728;&#x53F3;&#x534A;&#x90E8;&#x5206;&#xFF08;&#x4E00;&#x3001;&#x56DB;&#x8C61;&#x9650;&#xFF09;&#x7684;&#x60C5;&#x51B5;
    if (endOri - startOri > 3 * M_PI)
    {
        endOri -= 2 * M_PI;
    }
    //&#x626B;&#x63CF;&#x573A;&#x5728;&#x5DE6;&#x534A;&#x90E8;&#x5206;&#xFF08;&#x4E8C;&#x3001;&#x4E09;&#x8C61;&#x9650;&#xFF09;&#x7684;&#x60C5;&#x51B5;
    else if (endOri - startOri < M_PI)
    {
        endOri += 2 * M_PI;
    }
    //printf("end Ori %f\n", endOri);

对于每个点云点计算对应的扫描线,主要掌握其计算方法,实际现在的3d激光雷达驱动中都已经集成了每个点云点的线号、时间戳和角度等信息,不需要再单独计算。


    bool halfPassed = false;
    int count = cloudSize;
    PointType point;
    std::vector<pcl::pointcloud<pointtype>> laserCloudScans(N_SCANS);
    // &#x904D;&#x5386;&#x6BCF;&#x4E00;&#x4E2A;&#x70B9;
    for (int i = 0; i < cloudSize; i++)
    {
        point.x = laserCloudIn.points[i].x;
        point.y = laserCloudIn.points[i].y;
        point.z = laserCloudIn.points[i].z;
        // &#x8BA1;&#x7B97;&#x4ED6;&#x7684;&#x4FEF;&#x4EF0;&#x89D2;
        // &#x8BA1;&#x7B97;&#x5782;&#x76F4;&#x89C6;&#x573A;&#x89D2;&#xFF0C;&#x51B3;&#x5B9A;&#x8FD9;&#x4E2A;&#x6FC0;&#x5149;&#x70B9;&#x6240;&#x5728;&#x7684;scanID
        // &#x901A;&#x8FC7;&#x8BA1;&#x7B97;&#x5782;&#x76F4;&#x89C6;&#x573A;&#x89D2;&#x786E;&#x5B9A;&#x6FC0;&#x5149;&#x70B9;&#x5728;&#x54EA;&#x4E2A;&#x626B;&#x63CF;&#x7EBF;&#x4E0A;&#xFF08;N_SCANS&#x7EBF;&#x6FC0;&#x5149;&#x96F7;&#x8FBE;&#xFF09;
        float angle = atan(point.z / sqrt(point.x * point.x + point.y * point.y)) * 180 / M_PI;
        int scanID = 0;
        // &#x8BA1;&#x7B97;&#x662F;&#x7B2C;&#x51E0;&#x6839;scan
        if (N_SCANS == 16)
        {
            // +-15&#xB0;&#x7684;&#x5782;&#x76F4;&#x89C6;&#x573A;&#xFF0C;&#x5782;&#x76F4;&#x89D2;&#x5EA6;&#x5206;&#x8FA8;&#x7387;2&#xB0;&#xFF0C;-15&#xB0;&#x65F6;&#x7684;scanID = 0
            /*
             * &#x5782;&#x76F4;&#x89C6;&#x573A;&#x89D2;&#xFF0C;&#x53EF;&#x4EE5;&#x7B97;&#x4F5C;&#x6BCF;&#x4E2A;&#x70B9;&#x7684;
             * &#x5982;&#x679C;&#x662F;16&#x7EBF;&#x6FC0;&#x5149;&#x96F7;&#x8FBE;&#xFF0C;&#x7ED3;&#x7B97;&#x51FA;&#x7684;angle&#x5E94;&#x8BE5;&#x5728;-15~15&#x4E4B;&#x95F4;
             */
            scanID = int((angle + 15) / 2 + 0.5);
            // scanID(0~15), &#x8FD9;&#x4E9B;&#x662F;&#x65E0;&#x6548;&#x7684;&#x70B9;&#xFF0C;cloudSize--
            if (scanID > (N_SCANS - 1) || scanID < 0)
            {
                count--;
                continue;
            }
        }
        else if (N_SCANS == 32)
        {
            scanID = int((angle + 92.0/3.0) * 3.0 / 4.0);
            if (scanID > (N_SCANS - 1) || scanID < 0)
            {
                count--;
                continue;
            }
        }
        else if (N_SCANS == 64)
        {
            if (angle >= -8.83)
                scanID = int((2 - angle) * 3.0 + 0.5);
            else
                scanID = N_SCANS / 2 + int((-8.83 - angle) * 2.0 + 0.5);

            // use [0 50]  > 50 remove outlies
            if (angle > 2 || angle < -24.33 || scanID > 50 || scanID < 0)
            {
                count--;
                continue;
            }
        }
        else
        {
            printf("wrong scan number\n");
            ROS_BREAK();
        }
        //printf("angle %f scanID %d \n", angle, scanID);
        // &#x8BA1;&#x7B97;&#x6C34;&#x5E73;&#x89D2;
        float ori = -atan2(point.y, point.x);

        // &#x6839;&#x636E;&#x626B;&#x63CF;&#x7EBF;&#x662F;&#x5426;&#x65CB;&#x8F6C;&#x8FC7;&#x534A;&#x9009;&#x62E9;&#x4E0E;&#x8D77;&#x59CB;&#x4F4D;&#x7F6E;&#x8FD8;&#x662F;&#x7EC8;&#x6B62;&#x4F4D;&#x7F6E;&#x8FDB;&#x884C;&#x5DEE;&#x503C;&#x8BA1;&#x7B97;&#xFF0C;&#x4ECE;&#x800C;&#x8FDB;&#x884C;&#x8865;&#x507F;
        /*
         * &#x5982;&#x679C;&#x6B64;&#x65F6;&#x626B;&#x63CF;&#x6CA1;&#x6709;&#x8FC7;&#x534A;&#xFF0C;&#x5219;halfPassed&#x4E3A;false
         */
        if (!halfPassed)
        {
            // &#x786E;&#x4FDD;-PI / 2 < ori - startOri < 3 / 2 * PI
            //&#x5982;&#x679C;ori-startOri&#x5C0F;&#x4E8E;-0.5pi&#x6216;&#x5927;&#x4E8E;1.5pi&#xFF0C;&#x5219;&#x8C03;&#x6574;ori&#x7684;&#x89D2;&#x5EA6;
            if (ori < startOri - M_PI / 2)
            {
                // &#x5728;-pi&#x4E0E;pi&#x7684;&#x65AD;&#x70B9;&#x5904;&#x5BB9;&#x6613;&#x51FA;&#x8FD9;&#x79CD;&#x95EE;&#x9898;
                ori += 2 * M_PI;
            }
            else if (ori > startOri + M_PI * 3 / 2)
            {
                ori -= 2 * M_PI;
            }
            // &#x5982;&#x679C;&#x8D85;&#x8FC7;180&#x5EA6;&#xFF0C;&#x5C31;&#x8BF4;&#x660E;&#x8FC7;&#x4E86;&#x4E00;&#x534A;&#x4E86;
            if (ori - startOri > M_PI)
            {
                halfPassed = true;
            }
        }
        else
        {
            // &#x786E;&#x4FDD;-PI * 3 / 2 < ori - endOri < PI / 2
            ori += 2 * M_PI;    // &#x5148;&#x8865;&#x507F;2PI
            if (ori < endOri - M_PI * 3 / 2)
            {
                ori += 2 * M_PI;
            }
            else if (ori > endOri + M_PI / 2)
            {
                ori -= 2 * M_PI;
            }
        }
        // &#x89D2;&#x5EA6;&#x7684;&#x8BA1;&#x7B97;&#x662F;&#x4E3A;&#x4E86;&#x8BA1;&#x7B97;&#x76F8;&#x5BF9;&#x7684;&#x8D77;&#x59CB;&#x65F6;&#x523B;&#x7684;&#x65F6;&#x95F4;
        /*
         * relTime &#x662F;&#x4E00;&#x4E2A;0~1&#x4E4B;&#x95F4;&#x7684;&#x5C0F;&#x6570;&#xFF0C;&#x4EE3;&#x8868;&#x5360;&#x7528;&#x626B;&#x63CF;&#x65F6;&#x95F4;&#x7684;&#x6BD4;&#x4F8B;&#xFF0C;&#x4E58;&#x4EE5;&#x626B;&#x63CF;&#x65F6;&#x95F4;&#x5F97;&#x5230;&#x771F;&#x5B9E;&#x626B;&#x63CF;&#x65F6;&#x523B;&#xFF0C;
         * scanPeriod&#x626B;&#x63CF;&#x65F6;&#x95F4;&#x9ED8;&#x8BA4;&#x4E3A;0.1s 100hz
         */
        float relTime = (ori - startOri) / (endOri - startOri);
        // &#x6574;&#x6570;&#x90E8;&#x5206;&#x662F;scan&#x7684;&#x7D22;&#x5F15;&#xFF0C;&#x5C0F;&#x6570;&#x90E8;&#x5206;&#x662F;&#x76F8;&#x5BF9;&#x8D77;&#x59CB;&#x65F6;&#x523B;&#x7684;&#x65F6;&#x95F4;
        point.intensity = scanID + scanPeriod * relTime;
        // &#x6839;&#x636E;scan&#x7684;idx&#x9001;&#x5165;&#x5404;&#x81EA;&#x6570;&#x7EC4;
        laserCloudScans[scanID].push_back(point);
    }
    // cloudSize&#x662F;&#x6709;&#x6548;&#x7684;&#x70B9;&#x4E91;&#x7684;&#x6570;&#x76EE;
    cloudSize = count;
    printf("points size %d \n", cloudSize);
</pcl::pointcloud<pointtype>

接下来就是对雷达数据的特征提取,和论文中一致,分别提取角点和面点。主要思想是利用点云点的曲率来提取特征点。曲率是指一定范围内拟合出的圆的半径的倒数所以曲率大的为角点,曲率小的为面点。

代码中的方法是,同一条扫描线上取目标点左右则各5个点,求均值。并与之作差。

无人驾驶学习笔记 - A-LOAM 算法代码解析总结
  // &#x5C06;&#x6BCF;&#x6761;&#x626B;&#x63CF;&#x7EBF;&#x4E0A;&#x7684;&#x70B9;&#x8F93;&#x5165;&#x5230;laserCloud&#x6307;&#x5411;&#x7684;&#x70B9;&#x4E91;&#xFF0C;
    // &#x5E76;&#x8BB0;&#x5F55;&#x597D;&#x6BCF;&#x6761;&#x7EBF;&#x4E0A;&#x53EF;&#x4EE5;&#x8BA1;&#x7B97;&#x66F2;&#x7387;&#x7684;&#x521D;&#x59CB;&#x70B9;&#x548C;&#x7ED3;&#x675F;&#x70B9;&#x7684;&#x7D22;&#x5F15;&#xFF08;&#x5728;laserCloud&#x4E2D;&#x7684;&#x7D22;&#x5F15;&#xFF09;
    pcl::PointCloud<pointtype>::Ptr laserCloud(new pcl::PointCloud<pointtype>());
    // &#x5168;&#x90E8;&#x96C6;&#x5408;&#x5230;&#x4E00;&#x4E2A;&#x70B9;&#x4E91;&#x91CC;&#x9762;&#x53BB;&#xFF0C;&#x4F46;&#x662F;&#x4F7F;&#x7528;&#x4E24;&#x4E2A;&#x6570;&#x7EC4;&#x6807;&#x8BB0;&#x8D77;&#x59CB;&#x548C;&#x7ED3;&#x675F;&#xFF0C;&#x8FD9;&#x91CC;&#x5206;&#x522B;+5&#x548C;-6&#x662F;&#x4E3A;&#x4E86;&#x8BA1;&#x7B97;&#x66F2;&#x7387;&#x65B9;&#x4FBF;
    for (int i = 0; i < N_SCANS; i++)
    {
        // &#x524D;5&#x4E2A;&#x70B9;&#x548C;&#x540E;5&#x4E2A;&#x70B9;&#x90FD;&#x65E0;&#x6CD5;&#x8BA1;&#x7B97;&#x66F2;&#x7387;&#xFF0C;&#x56E0;&#x4E3A;&#x4ED6;&#x4EEC;&#x4E0D;&#x6EE1;&#x8DB3;&#x5DE6;&#x53F3;&#x4E24;&#x4FA7;&#x5404;&#x6709;5&#x4E2A;&#x70B9;
        scanStartInd[i] = laserCloud->size() + 5;
        *laserCloud += laserCloudScans[i];
        scanEndInd[i] = laserCloud->size() - 6;
    }

    // &#x5C06;&#x4E00;&#x5E27;&#x65E0;&#x5E8F;&#x70B9;&#x4E91;&#x8F6C;&#x6362;&#x6210;&#x6709;&#x5E8F;&#x70B9;&#x4E91;&#x6D88;&#x8017;&#x7684;&#x65F6;&#x95F4;
    printf("prepare time %f \n", t_prepare.toc());
    // &#x8BA1;&#x7B97;&#x6BCF;&#x4E00;&#x4E2A;&#x70B9;&#x7684;&#x66F2;&#x7387;&#xFF0C;&#x8FD9;&#x91CC;&#x7684;laserCloud&#x662F;&#x6709;&#x5E8F;&#x7684;&#x70B9;&#x4E91;&#xFF0C;&#x6545;&#x53EF;&#x4EE5;&#x76F4;&#x63A5;&#x8FD9;&#x6837;&#x8BA1;&#x7B97;&#xFF08;&#x8BBA;&#x6587;&#x4E2D;&#x8BF4;&#x5BF9;&#x6BCF;&#x6761;&#x7EBF;&#x626B;scan&#x8BA1;&#x7B97;&#x66F2;&#x7387;&#xFF09;
    // &#x4F46;&#x662F;&#x5728;   &#x6BCF;&#x6761;scan&#x7684;&#x4EA4;&#x754C;&#x5904;   &#x8BA1;&#x7B97;&#x5F97;&#x5230;&#x7684;&#x66F2;&#x7387;&#x662F;&#x4E0D;&#x51C6;&#x786E;&#x7684;&#xFF0C;&#x8FD9;&#x53EF;&#x901A;&#x8FC7;scanStartInd[i]&#x3001;scanEndInd[i]&#x6765;&#x9009;&#x53D6;
    /*
     * &#x8868;&#x9762;&#x4E0A;&#x9664;&#x4E86;&#x524D;&#x540E;&#x4E94;&#x4E2A;&#x70B9;&#x90FD;&#x5E94;&#x8BE5;&#x6709;&#x66F2;&#x7387;&#xFF0C;&#x4F46;&#x662F;&#x7531;&#x4E8E;&#x4E34;&#x8FD1;&#x70B9;&#x90FD;&#x5728;&#x626B;&#x63CF;&#x4E0A;&#x9009;&#x53D6;&#xFF0C;&#x5B9E;&#x9645;&#x4E0A;&#x6BCF;&#x6761;&#x626B;&#x63CF;&#x7EBF;&#x4E0A;&#x7684;&#x524D;&#x540E;&#x4E94;&#x4E2A;&#x70B9;&#x4E5F;&#x4E0D;&#x592A;&#x51C6;&#x786E;&#xFF0C;
     * &#x5E94;&#x8BE5;&#x7531;scanStartInd[i]&#x3001;scanEndInd[i]&#x6765;&#x786E;&#x5B9A;&#x8303;&#x56F4;
     */
    for (int i = 5; i < cloudSize - 5; i++)
    {
        float diffX = laserCloud->points[i - 5].x + laserCloud->points[i - 4].x + laserCloud->points[i - 3].x + laserCloud->points[i - 2].x + laserCloud->points[i - 1].x - 10 * laserCloud->points[i].x + laserCloud->points[i + 1].x + laserCloud->points[i + 2].x + laserCloud->points[i + 3].x + laserCloud->points[i + 4].x + laserCloud->points[i + 5].x;
        float diffY = laserCloud->points[i - 5].y + laserCloud->points[i - 4].y + laserCloud->points[i - 3].y + laserCloud->points[i - 2].y + laserCloud->points[i - 1].y - 10 * laserCloud->points[i].y + laserCloud->points[i + 1].y + laserCloud->points[i + 2].y + laserCloud->points[i + 3].y + laserCloud->points[i + 4].y + laserCloud->points[i + 5].y;
        float diffZ = laserCloud->points[i - 5].z + laserCloud->points[i - 4].z + laserCloud->points[i - 3].z + laserCloud->points[i - 2].z + laserCloud->points[i - 1].z - 10 * laserCloud->points[i].z + laserCloud->points[i + 1].z + laserCloud->points[i + 2].z + laserCloud->points[i + 3].z + laserCloud->points[i + 4].z + laserCloud->points[i + 5].z;
        // &#x5B58;&#x50A8;&#x66F2;&#x7387;&#xFF0C;&#x7D22;&#x5F15;
        // &#x5BF9;&#x5E94;&#x8BBA;&#x6587;&#x4E2D;&#x7684;&#x516C;&#x5F0F;&#xFF08;1&#xFF09;&#xFF0C;&#x4F46;&#x662F;&#x6CA1;&#x6709;&#x8FDB;&#x884C;&#x9664;&#x6CD5;
        cloudCurvature[i] = diffX * diffX + diffY * diffY + diffZ * diffZ;
        /*
         * cloudSortInd[i] = i&#x76F8;&#x5F53;&#x4E8E;&#x6240;&#x6709;&#x70B9;&#x7684;&#x521D;&#x59CB;&#x81EA;&#x7136;&#x5E8F;&#x5217;&#xFF0C;&#x4ECE;&#x6B64;&#x4EE5;&#x540E;&#xFF0C;&#x6BCF;&#x4E2A;&#x70B9;&#x5C31;&#x6709;&#x4E86;&#x5B83;&#x81EA;&#x5DF1;&#x7684;&#x5E8F;&#x53F7;&#xFF08;&#x7D22;&#x5F15;&#xFF09;
         * &#x5BF9;&#x4E8E;&#x6BCF;&#x4E2A;&#x70B9;&#xFF0C;&#x6211;&#x4EEC;&#x5DF2;&#x7ECF;&#x9009;&#x62E9;&#x4E86;&#x5B83;&#x9644;&#x8FD1;&#x7684;&#x7279;&#x5F81;&#x70B9;&#x6570;&#x91CF;&#x521D;&#x59CB;&#x5316;&#x4E3A;0&#xFF0C;
         * &#x6BCF;&#x4E2A;&#x70B9;&#x7684;&#x70B9;&#x7C7B;&#x578B;&#x521D;&#x59CB;&#x8BBE;&#x7F6E;&#x4E3A;0&#xFF08;&#x6B21;&#x6781;&#x5C0F;&#x5E73;&#x9762;&#x70B9;&#xFF09;
         */
        cloudSortInd[i] = i;
        cloudNeighborPicked[i] = 0;
        cloudLabel[i] = 0;
    }</pointtype></pointtype>

提取特征需要注意几条原则:

1、为了提高效率,论文中对每条扫描线分成了4个扇形,而代码中实际是分成了6份,在每份中寻找曲率最大的20个点最为极大角点,对提取数据做了约束,最大点不大于2个,极小面点不大于4个,剩下的编辑未次极小面点;

2、为防止特征点过于集中,每提取一个特征点,就对该点和它附近的点的标志位设置未”已选中”,在循环提取时会跳过已提取的特征点,对于次极小面点采取下采样方式避免特征点扎堆。

    pcl::PointCloud<pointtype> cornerPointsSharp;       // &#x6781;&#x5927;&#x8FB9;&#x7EBF;&#x70B9;
    pcl::PointCloud<pointtype> cornerPointsLessSharp;   // &#x6B21;&#x6781;&#x5927;&#x8FB9;&#x7EBF;&#x70B9;
    pcl::PointCloud<pointtype> surfPointsFlat;          // &#x6781;&#x5C0F;&#x5E73;&#x9762;&#x70B9;
    pcl::PointCloud<pointtype> surfPointsLessFlat;      // &#x6B21;&#x6781;&#x5C0F;&#x5E73;&#x9762;&#x70B9;&#xFF08;&#x7ECF;&#x8FC7;&#x964D;&#x91C7;&#x6837;&#xFF09;

    // &#x5BF9;&#x6BCF;&#x6761;&#x7EBF;&#x626B;scan&#x8FDB;&#x884C;&#x64CD;&#x4F5C;&#xFF08;&#x66F2;&#x7387;&#x6392;&#x5E8F;&#xFF0C;&#x9009;&#x53D6;&#x5BF9;&#x5E94;&#x7279;&#x5F81;&#x70B9;&#xFF09;
    float t_q_sort = 0; // &#x7528;&#x6765;&#x8BB0;&#x5F55;&#x6392;&#x5E8F;&#x82B1;&#x8D39;&#x7684;&#x603B;&#x65F6;&#x95F4;
    // &#x904D;&#x5386;&#x6BCF;&#x4E2A;scan
    for (int i = 0; i < N_SCANS; i++)
    {
        /// &#x5982;&#x679C;&#x6700;&#x540E;&#x4E00;&#x4E2A;&#x53EF;&#x7B97;&#x66F2;&#x7387;&#x7684;&#x70B9;&#x4E0E;&#x7B2C;&#x4E00;&#x4E2A;&#x7684;&#x5DEE;&#x5C0F;&#x4E8E;6&#xFF0C;&#x8BF4;&#x660E;&#x65E0;&#x6CD5;&#x5206;&#x6210;6&#x4E2A;&#x6247;&#x533A;&#xFF0C;&#x8DF3;&#x8FC7;
        if( scanEndInd[i] - scanStartInd[i] < 6)
            continue;
        // &#x7528;&#x6765;&#x5B58;&#x50A8;&#x4E0D;&#x592A;&#x5E73;&#x6574;&#x7684;&#x70B9;
        pcl::PointCloud<pointtype>::Ptr surfPointsLessFlatScan(new pcl::PointCloud<pointtype>);
        // &#x5C06;&#x6BCF;&#x4E2A;scan&#x7B49;&#x5206;&#x6210;6&#x7B49;&#x5206;
        for (int j = 0; j < 6; j++)
        {
            // &#x6BCF;&#x4E2A;&#x7B49;&#x5206;&#x7684;&#x8D77;&#x59CB;&#x548C;&#x7ED3;&#x675F;&#x70B9;
            int sp = scanStartInd[i] + (scanEndInd[i] - scanStartInd[i]) * j / 6;
            int ep = scanStartInd[i] + (scanEndInd[i] - scanStartInd[i]) * (j + 1) / 6 - 1;

            TicToc t_tmp;
            // &#x5BF9;&#x70B9;&#x4E91;&#x6309;&#x7167;&#x66F2;&#x7387;&#x8FDB;&#x884C;&#x6392;&#x5E8F;&#xFF0C;&#x5C0F;&#x7684;&#x5728;&#x524D;&#xFF0C;&#x5927;&#x7684;&#x5728;&#x540E;
            std::sort (cloudSortInd + sp, cloudSortInd + ep + 1, comp);
            // t_q_sort&#x7D2F;&#x8BA1;&#x6BCF;&#x4E2A;&#x6247;&#x533A;&#x66F2;&#x7387;&#x6392;&#x5E8F;&#x65F6;&#x95F4;&#x603B;&#x548C;
            t_q_sort += t_tmp.toc();

            // &#x9009;&#x53D6;&#x6781;&#x5927;&#x8FB9;&#x7EBF;&#x70B9;&#xFF08;2&#x4E2A;&#xFF09;&#x548C;&#x6B21;&#x6781;&#x5927;&#x8FB9;&#x7EBF;&#x70B9;&#xFF08;20&#x4E2A;&#xFF09;
            int largestPickedNum = 0;
            // &#x4ECE;&#x6700;&#x5927;&#x66F2;&#x7387;&#x5F80;&#x6700;&#x5C0F;&#x66F2;&#x7387;&#x904D;&#x5386;&#xFF0C;&#x5BFB;&#x627E;&#x8FB9;&#x7EBF;&#x70B9;&#xFF0C;&#x5E76;&#x8981;&#x6C42;&#x5927;&#x4E8E;0.1
            for (int k = ep; k >= sp; k--)
            {
                // &#x6392;&#x5E8F;&#x540E;&#x987A;&#x5E8F;&#x5C31;&#x4E71;&#x4E86;&#xFF0C;&#x8FD9;&#x4E2A;&#x65F6;&#x5019;&#x7D22;&#x5F15;&#x7684;&#x4F5C;&#x7528;&#x5C31;&#x4F53;&#x73B0;&#x51FA;&#x6765;&#x4E86;
                 // &#x53D6;&#x51FA;&#x70B9;&#x7684;&#x7D22;&#x5F15;
                int ind = cloudSortInd[k];

                // &#x770B;&#x770B;&#x8FD9;&#x4E2A;&#x70B9;&#x662F;&#x5426;&#x662F;&#x6709;&#x6548;&#x70B9;&#xFF0C;&#x540C;&#x65F6;&#x66F2;&#x7387;&#x662F;&#x5426;&#x5927;&#x4E8E;&#x9608;&#x503C;
                // &#x6CA1;&#x88AB;&#x9009;&#x8FC7; && &#x66F2;&#x7387; > 0.1
                if (cloudNeighborPicked[ind] == 0 &&
                    cloudCurvature[ind] > 0.1)
                {

                    largestPickedNum++;
                    // &#x6BCF;&#x6BB5;&#x9009;2&#x4E2A;&#x66F2;&#x7387;&#x5927;&#x7684;&#x70B9;
                    if (largestPickedNum <= 2) { label为2是曲率大的标记 cloudlabel[ind]="2;" cornerpointssharp存放大曲率的点 既放入极大边线点容器,也放入次极大边线点容器 cornerpointssharp.push_back(lasercloud->points[ind]);
                        cornerPointsLessSharp.push_back(laserCloud->points[ind]);
                    }
                    // &#x4EE5;&#x53CA;20&#x4E2A;&#x66F2;&#x7387;&#x7A0D;&#x5FAE;&#x5927;&#x4E00;&#x4E9B;&#x7684;&#x70B9;
                    else if (largestPickedNum <= 20) { label置1表示曲率稍微大 超过2个选择点以后,设置为次极大边线点,仅放入次极大边线点容器 cloudlabel[ind]="1;" cornerpointslesssharp.push_back(lasercloud->points[ind]);
                    }
                    // &#x8D85;&#x8FC7;20&#x4E2A;&#x5C31;&#x7B97;&#x4E86;
                    else
                    {
                        break;
                    }
                    // &#x8FD9;&#x4E2A;&#x70B9;&#x88AB;&#x9009;&#x4E2D;&#x540E; pick&#x6807;&#x5FD7;&#x4F4D;&#x7F6E;1
                    cloudNeighborPicked[ind] = 1;
                    // &#x4E3A;&#x4E86;&#x4FDD;&#x8BC1;&#x7279;&#x5F81;&#x70B9;&#x4E0D;&#x8FC7;&#x5EA6;&#x96C6;&#x4E2D;&#xFF0C;&#x5C06;&#x9009;&#x4E2D;&#x7684;&#x70B9;&#x5468;&#x56F4;5&#x4E2A;&#x70B9;&#x90FD;&#x7F6E;1,&#x907F;&#x514D;&#x540E;&#x7EED;&#x4F1A;&#x9009;&#x5230;
                    // ID&#x4E3A;ind&#x7684;&#x7279;&#x5F81;&#x70B9;&#x7684;&#x76F8;&#x90BB;scan&#x70B9;&#x8DDD;&#x79BB;&#x7684;&#x5E73;&#x65B9; <= 0.05的点标记为选择过,避免特征点密集分布 右侧 for (int l="1;" <="5;" l++) { 查看相邻点距离是否差异过大,如果差异过大说明点云在此不连续,是特征边缘,就会是新的特征,因此就不置位了 float diffx="laserCloud-">points[ind + l].x - laserCloud->points[ind + l - 1].x;
                        float diffY = laserCloud->points[ind + l].y - laserCloud->points[ind + l - 1].y;
                        float diffZ = laserCloud->points[ind + l].z - laserCloud->points[ind + l - 1].z;
                        if (diffX * diffX + diffY * diffY + diffZ * diffZ > 0.05)
                        {
                            break;
                        }

                        cloudNeighborPicked[ind + l] = 1;
                    }
                    //&#x5DE6;&#x4FA7; &#x540C;&#x7406;
                    for (int l = -1; l >= -5; l--)
                    {
                        float diffX = laserCloud->points[ind + l].x - laserCloud->points[ind + l + 1].x;
                        float diffY = laserCloud->points[ind + l].y - laserCloud->points[ind + l + 1].y;
                        float diffZ = laserCloud->points[ind + l].z - laserCloud->points[ind + l + 1].z;
                        if (diffX * diffX + diffY * diffY + diffZ * diffZ > 0.05)
                        {
                            break;
                        }

                        cloudNeighborPicked[ind + l] = 1;
                    }
                }
            }
            // &#x4E0B;&#x9762;&#x5F00;&#x59CB;&#x6311;&#x9009;&#x9762;&#x70B9;&#xFF08;4&#x4E2A;&#xFF09;
            int smallestPickedNum = 0;
            for (int k = sp; k <= 0 ep; k++) { int ind="cloudSortInd[k];" 确保这个点没有被pick且曲率小于阈值 if (cloudneighborpicked[ind]="=" && cloudcurvature[ind] < 0.1) -1认为是平坦的点 cloudlabel[ind]="-1;" surfpointsflat.push_back(lasercloud->points[ind]);

                    smallestPickedNum++;
                    // &#x8FD9;&#x91CC;&#x4E0D;&#x533A;&#x5206;&#x5E73;&#x5766;&#x548C;&#x6BD4;&#x8F83;&#x5E73;&#x5766;&#xFF0C;&#x56E0;&#x4E3A;&#x5269;&#x4E0B;&#x7684;&#x70B9;label&#x9ED8;&#x8BA4;&#x662F;0,&#x5C31;&#x662F;&#x6BD4;&#x8F83;&#x5E73;&#x5766;
                    if (smallestPickedNum >= 4)
                    {
                        break;
                    }
                    // &#x540C;&#x6837;&#x8DDD;&#x79BB; < 0.05&#x7684;&#x70B9;&#x5168;&#x8BBE;&#x4E3A;&#x5DF2;&#x7ECF;&#x9009;&#x62E9;&#x8FC7;
                    cloudNeighborPicked[ind] = 1;
                    for (int l = 1; l <= 5; l++) { float diffx="laserCloud-">points[ind + l].x - laserCloud->points[ind + l - 1].x;
                        float diffY = laserCloud->points[ind + l].y - laserCloud->points[ind + l - 1].y;
                        float diffZ = laserCloud->points[ind + l].z - laserCloud->points[ind + l - 1].z;
                        if (diffX * diffX + diffY * diffY + diffZ * diffZ > 0.05)
                        {
                            break;
                        }

                        cloudNeighborPicked[ind + l] = 1;
                    }
                    for (int l = -1; l >= -5; l--)
                    {
                        float diffX = laserCloud->points[ind + l].x - laserCloud->points[ind + l + 1].x;
                        float diffY = laserCloud->points[ind + l].y - laserCloud->points[ind + l + 1].y;
                        float diffZ = laserCloud->points[ind + l].z - laserCloud->points[ind + l + 1].z;
                        if (diffX * diffX + diffY * diffY + diffZ * diffZ > 0.05)
                        {
                            break;
                        }

                        cloudNeighborPicked[ind + l] = 1;
                    }
                }
            }

            // &#x9009;&#x53D6;&#x6B21;&#x6781;&#x5C0F;&#x5E73;&#x9762;&#x70B9;&#xFF0C;&#x9664;&#x4E86;&#x6781;&#x5927;&#x5E73;&#x9762;&#x70B9;&#x3001;&#x6B21;&#x6781;&#x5927;&#x5E73;&#x9762;&#x70B9;&#xFF0C;&#x5269;&#x4E0B;&#x7684;&#x90FD;&#x662F;&#x6B21;&#x6781;&#x5C0F;&#x5E73;&#x9762;&#x70B9;
            for (int k = sp; k <= ep; k++) { 这里可以看到,剩下来的点都是一般平坦,这个也符合实际 if (cloudlabel[k] <="0)" surfpointslessflatscan->push_back(laserCloud->points[k]);
                }
            }
        }

        // &#x5BF9;&#x6BCF;&#x4E00;&#x6761;scan&#x7EBF;&#x4E0A;&#x7684;&#x6B21;&#x6781;&#x5C0F;&#x5E73;&#x9762;&#x70B9;&#x8FDB;&#x884C;&#x4E00;&#x6B21;&#x964D;&#x91C7;&#x6837;
        pcl::PointCloud<pointtype> surfPointsLessFlatScanDS;
        pcl::VoxelGrid<pointtype> downSizeFilter;
        // &#x4E00;&#x822C;&#x5E73;&#x5766;&#x7684;&#x70B9;&#x6BD4;&#x8F83;&#x591A;&#xFF0C;&#x6240;&#x4EE5;&#x8FD9;&#x91CC;&#x505A;&#x4E00;&#x4E2A;&#x4F53;&#x7D20;&#x6EE4;&#x6CE2;
        downSizeFilter.setInputCloud(surfPointsLessFlatScan);
        downSizeFilter.setLeafSize(0.2, 0.2, 0.2);
        downSizeFilter.filter(surfPointsLessFlatScanDS);

        surfPointsLessFlat += surfPointsLessFlatScanDS;
    }
    printf("sort q time %f \n", t_q_sort);
    printf("seperate points time %f \n", t_pts.toc());
</pointtype></pointtype></=></=></=></=></=></=></pointtype></pointtype></pointtype></pointtype></pointtype></pointtype>

接下来就是对当前点云、四种特征点云进行 打包发布,不再赘述,目的是给laserodometry 通信,将预处理的数据传递给里程计节点。

2.2、总结概括

该部分代码主要工作就是处理无效点云,计算点云线束和对齐时间戳,计算曲率、并提取特征点。

总体处理流程,下面博主流程图总结的很好,不再单独整理了,借鉴于此

SLAM前端入门框架-A_LOAM源码解析 – 知乎

无人驾驶学习笔记 - A-LOAM 算法代码解析总结

上述流程图应该已经很清楚的表明这个scanRegistration文件的执行流程。可能会存在几个关键问题,在下面具体展开一下。
关于步骤2和步骤3中去除无效点云和自身点云应该是很正常的流程
步骤4计算这一帧起始角度和终止角度是为了构建一个”整体”,下面就可以通过这个点和初始角度的偏差来计算,这个点到底在这一帧内部进行了多久的运动,并且将这个时间存储起来,从而进行雷达去畸变。(具体什么是雷达去畸变可以参考LOAM中的论文)
步骤5,计算出每一个点对应的线束,并且重新组织,是为了下面计算曲率使用。
计算方式是通过同一条线上的角度相同。利用atan(z/(sqr(xx+yx))来计算。(具体的线束分布需要按照不同的激光雷达给出的说明手册)
步骤6,利用论文中给出的公式来计算(只能说是大体上是),代码中做了一些改变。
步骤7,人为给定一个阈值(0.1),将每一条扫描线分为六段,目的是不能让特征过于集中,每一段计算曲率大于阈值的点(少量 2个),大于阈值的点(多量 20个),小于阈值的点(4个),其他所有没有被选中的点。在这个过程中,如果一个个点被选中了,就立刻标记周围五个距离小于0.05的点,目的也是防止特征过于集中。

3、LaserOdometry.cpp

laserOdometry.cpp主要是通过读取scanRegistration.cpp中的信息来计算帧与帧之间的变化,最终得到里程计坐标系下的激光雷达的位姿。也就是前端激光里程计和位姿粗估计,帧间匹配应用scan-scan 思想

3.1、代码注释

主函数 定义订阅点云数据的话题与发布里程计的角点面点,以及里程计等

int main(int argc, char **argv)
{
    ros::init(argc, argv, "laserOdometry");
    ros::NodeHandle nh;

    nh.param<int>("mapping_skip_frame", skipFrameNum, 2);

    printf("Mapping %d Hz \n", 10 / skipFrameNum);
    // &#x8BA2;&#x9605;&#x63D0;&#x53D6;&#x51FA;&#x6765;&#x7684;&#x70B9;&#x4E91;
    // &#x6781;&#x5927;&#x8FB9;&#x7EBF;&#x70B9;  &#x6B21;&#x6781;&#x5927;&#x8FB9;&#x7EBF;&#x70B9;   &#x6781;&#x5C0F;&#x5E73;&#x9762;&#x70B9;  &#x6B21;&#x6781;&#x5C0F;&#x5E73;&#x9762;&#x70B9;
    ros::Subscriber subCornerPointsSharp = nh.subscribe<sensor_msgs::pointcloud2>("/laser_cloud_sharp", 100, laserCloudSharpHandler);

    ros::Subscriber subCornerPointsLessSharp = nh.subscribe<sensor_msgs::pointcloud2>("/laser_cloud_less_sharp", 100, laserCloudLessSharpHandler);

    ros::Subscriber subSurfPointsFlat = nh.subscribe<sensor_msgs::pointcloud2>("/laser_cloud_flat", 100, laserCloudFlatHandler);

    ros::Subscriber subSurfPointsLessFlat = nh.subscribe<sensor_msgs::pointcloud2>("/laser_cloud_less_flat", 100, laserCloudLessFlatHandler);

    //&#x5168;&#x90E8;&#x70B9;&#x70B9;&#x4E91;
    ros::Subscriber subLaserCloudFullRes = nh.subscribe<sensor_msgs::pointcloud2>("/velodyne_cloud_2", 100, laserCloudFullResHandler);

    // &#x53D1;&#x5E03;&#x4E0A;&#x4E00;&#x5E27;&#x7684;&#x8FB9;&#x7EBF;&#x70B9;
    ros::Publisher pubLaserCloudCornerLast = nh.advertise<sensor_msgs::pointcloud2>("/laser_cloud_corner_last", 100);

    // &#x53D1;&#x5E03;&#x4E0A;&#x4E00;&#x5E27;&#x7684;&#x5E73;&#x9762;&#x70B9;
    ros::Publisher pubLaserCloudSurfLast = nh.advertise<sensor_msgs::pointcloud2>("/laser_cloud_surf_last", 100);

    // &#x53D1;&#x5E03;&#x5168;&#x90E8;&#x6709;&#x5E8F;&#x70B9;&#x4E91;&#xFF0C;&#x5C31;&#x662F;&#x4ECE;scanRegistration&#x8BA2;&#x9605;&#x6765;&#x7684;&#x70B9;&#x4E91;&#xFF0C;&#x672A;&#x7ECF;&#x5176;&#x4ED6;&#x5904;&#x7406;
    ros::Publisher pubLaserCloudFullRes = nh.advertise<sensor_msgs::pointcloud2>("/velodyne_cloud_3", 100);

    // &#x53D1;&#x5E03;&#x5E27;&#x95F4;&#x7684;&#x4F4D;&#x59FF;&#x53D8;&#x6362;
    ros::Publisher pubLaserOdometry = nh.advertise<nav_msgs::odometry>("/laser_odom_to_init", 100);

    // &#x53D1;&#x5E03;&#x5E27;&#x95F4;&#x7684;&#x5E73;&#x79FB;&#x8FD0;&#x52A8;
    ros::Publisher pubLaserPath = nh.advertise<nav_msgs::path>("/laser_odom_path", 100);

    nav_msgs::Path laserPath;

    int frameCount = 0;
    ros::Rate rate(100);

    while (ros::ok())
    {
        ros::spinOnce();
        //........

        rate.sleep();
    }
    return 0;
}</nav_msgs::path></nav_msgs::odometry></sensor_msgs::pointcloud2></sensor_msgs::pointcloud2></sensor_msgs::pointcloud2></sensor_msgs::pointcloud2></sensor_msgs::pointcloud2></sensor_msgs::pointcloud2></sensor_msgs::pointcloud2></sensor_msgs::pointcloud2></int>

主函数中的主循环未10HZ的高频周期,主要是帧间匹配。

预处理 取出需要的点云数据转化为pcl格式

   while (ros::ok())
    {
        ros::spinOnce();    // &#x89E6;&#x53D1;&#x4E00;&#x6B21;&#x56DE;&#x8C03;&#xFF0C;&#x53C2;&#x8003;https://www.cnblogs.com/liu-fa/p/5925381.html

        // &#x9996;&#x5148;&#x786E;&#x4FDD;&#x8BA2;&#x9605;&#x7684;&#x4E94;&#x4E2A;&#x6D88;&#x606F;&#x90FD;&#x6709;&#xFF0C;&#x6709;&#x4E00;&#x4E2A;&#x961F;&#x5217;&#x4E3A;&#x7A7A;&#x90FD;&#x4E0D;&#x884C;
        if (!cornerSharpBuf.empty() && !cornerLessSharpBuf.empty() &&
            !surfFlatBuf.empty() && !surfLessFlatBuf.empty() &&
            !fullPointsBuf.empty())
        {
            // &#x5206;&#x522B;&#x6C42;&#x51FA;&#x961F;&#x5217;&#x7B2C;&#x4E00;&#x4E2A;&#x65F6;&#x95F4;
            timeCornerPointsSharp = cornerSharpBuf.front()->header.stamp.toSec();
            timeCornerPointsLessSharp = cornerLessSharpBuf.front()->header.stamp.toSec();
            timeSurfPointsFlat = surfFlatBuf.front()->header.stamp.toSec();
            timeSurfPointsLessFlat = surfLessFlatBuf.front()->header.stamp.toSec();
            timeLaserCloudFullRes = fullPointsBuf.front()->header.stamp.toSec();
            // &#x56E0;&#x4E3A;&#x540C;&#x4E00;&#x5E27;&#x7684;&#x65F6;&#x95F4;&#x6233;&#x90FD;&#x662F;&#x76F8;&#x540C;&#x7684;&#xFF0C;&#x56E0;&#x6B64;&#x8FD9;&#x91CC;&#x6BD4;&#x8F83;&#x662F;&#x5426;&#x662F;&#x540C;&#x4E00;&#x5E27;
            //&#x6BD4;&#x8F83;&#x65F6;&#x95F4;&#x662F;&#x5426;&#x540C;&#x6B65;
            if (timeCornerPointsSharp != timeLaserCloudFullRes ||
                timeCornerPointsLessSharp != timeLaserCloudFullRes ||
                timeSurfPointsFlat != timeLaserCloudFullRes ||
                timeSurfPointsLessFlat != timeLaserCloudFullRes)
            {
                printf("unsync messeage!");
                ROS_BREAK();
            }
            // &#x5206;&#x522B;&#x5C06;&#x4E94;&#x4E2A;&#x70B9;&#x4E91;&#x6D88;&#x606F;&#x53D6;&#x51FA;&#x6765;&#xFF0C;&#x540C;&#x65F6;&#x8F6C;&#x6210;pcl&#x7684;&#x70B9;&#x4E91;&#x683C;&#x5F0F;
            mBuf.lock();
            cornerPointsSharp->clear();
            pcl::fromROSMsg(*cornerSharpBuf.front(), *cornerPointsSharp);
            cornerSharpBuf.pop();

            cornerPointsLessSharp->clear();
            pcl::fromROSMsg(*cornerLessSharpBuf.front(), *cornerPointsLessSharp);
            cornerLessSharpBuf.pop();

            surfPointsFlat->clear();
            pcl::fromROSMsg(*surfFlatBuf.front(), *surfPointsFlat);
            surfFlatBuf.pop();

            surfPointsLessFlat->clear();
            pcl::fromROSMsg(*surfLessFlatBuf.front(), *surfPointsLessFlat);
            surfLessFlatBuf.pop();

            laserCloudFullRes->clear();
            pcl::fromROSMsg(*fullPointsBuf.front(), *laserCloudFullRes);
            fullPointsBuf.pop();
            mBuf.unlock();

            TicToc t_whole;
            // initializing
            // &#x4E00;&#x4E2A;&#x4EC0;&#x4E48;&#x4E5F;&#x4E0D;&#x5E72;&#x7684;&#x521D;&#x59CB;&#x5316;
            if (!systemInited)
            {
                // &#x4E3B;&#x8981;&#x7528;&#x6765;&#x8DF3;&#x8FC7;&#x7B2C;&#x4E00;&#x5E27;&#x6570;&#x636E;
                // &#x4EC5;&#x4EC5;&#x5C06;cornerPointsLessSharp&#x4FDD;&#x5B58;&#x81F3;laserCloudCornerLast
                // &#x5C06;surfPointsLessFlat&#x4FDD;&#x5B58;&#x81F3;laserCloudSurfLast&#xFF0C;&#x4EE5;&#x53CA;&#x66F4;&#x65B0;&#x5BF9;&#x5E94;&#x7684;kdtree
                systemInited = true;
                std::cout << "Initialization finished \n";
            }
            else
            {
                // &#x6781;&#x5927;&#x8FB9;&#x7EBF;&#x70B9;&#x7684;&#x6570;&#x91CF;
                int cornerPointsSharpNum = cornerPointsSharp->points.size();
                // &#x6781;&#x5C0F;&#x5E73;&#x9762;&#x70B9;&#x7684;&#x6570;&#x91CF;
                int surfPointsFlatNum = surfPointsFlat->points.size();

                TicToc t_opt;
                // &#x70B9;&#x5230;&#x7EBF;&#x4EE5;&#x53CA;&#x70B9;&#x5230;&#x9762;&#x7684;&#x975E;&#x7EBF;&#x6027;&#x4F18;&#x5316;&#xFF0C;&#x8FED;&#x4EE3;2&#x6B21;&#xFF08;&#x9009;&#x62E9;&#x5F53;&#x524D;&#x4F18;&#x5316;&#x7684;&#x4F4D;&#x59FF;&#x7684;&#x7279;&#x5F81;&#x70B9;&#x5339;&#x914D;&#xFF0C;&#x5E76;&#x4F18;&#x5316;&#x4F4D;&#x59FF;&#xFF08;4&#x6B21;&#x8FED;&#x4EE3;&#xFF09;&#xFF0C;
                //&#x7136;&#x540E;&#x91CD;&#x65B0;&#x9009;&#x62E9;&#x7279;&#x5F81;&#x70B9;&#x5339;&#x914D;&#x5E76;&#x4F18;&#x5316;&#xFF09;
                for (size_t opti_counter = 0; opti_counter < 2; ++opti_counter)
                {
                    corner_correspondence = 0;
                    plane_correspondence = 0;

                    //ceres::LossFunction *loss_function = NULL;
                    // &#x4F7F;&#x7528;Huber&#x6838;&#x51FD;&#x6570;&#x6765;&#x51CF;&#x5C11;&#x5916;&#x70B9;&#x7684;&#x5F71;&#x54CD;&#xFF0C;Algorithm 1: Lidar Odometry&#x4E2D;&#x6709;
                    // &#x5B9A;&#x4E49;&#x4E00;&#x4E0B;ceres&#x7684;&#x6838;&#x51FD;&#x6570;
                    ceres::LossFunction *loss_function = new ceres::HuberLoss(0.1);
                    // &#x7531;&#x4E8E;&#x65CB;&#x8F6C;&#x4E0D;&#x6EE1;&#x8DB3;&#x4E00;&#x822C;&#x610F;&#x4E49;&#x7684;&#x52A0;&#x6CD5;&#xFF0C;&#x56E0;&#x6B64;&#x8FD9;&#x91CC;&#x4F7F;&#x7528;ceres&#x81EA;&#x5E26;&#x7684;local param
                    ceres::LocalParameterization *q_parameterization =
                        new ceres::EigenQuaternionParameterization();
                    ceres::Problem::Options problem_options;

                    ceres::Problem problem(problem_options);
                    // &#x5F85;&#x4F18;&#x5316;&#x7684;&#x53D8;&#x91CF;&#x662F;&#x5E27;&#x95F4;&#x4F4D;&#x59FF;&#xFF0C;&#x5E73;&#x79FB;&#x548C;&#x65CB;&#x8F6C;&#xFF0C;&#x8FD9;&#x91CC;&#x65CB;&#x8F6C;&#x4F7F;&#x7528;&#x56DB;&#x5143;&#x6570;&#x6765;&#x8868;&#x793A;
                    problem.AddParameterBlock(para_q, 4, q_parameterization);
                    problem.AddParameterBlock(para_t, 3);

                    pcl::PointXYZI pointSel;
                    std::vector<int> pointSearchInd;
                    std::vector<float> pointSearchSqDis;

                    TicToc t_data;</float></int>

分别通过两次迭代计算点线和面线之间的关系,并创建KD-tree。其中点线基于最近邻原理建立corner特征点(边线点)之间的关联,每一个极大边线点去上一帧的次极大边线点中找匹配;采用边线点匹配方法:假如在第k+1帧中发现了边线点i,通过KD-tree查询在第k帧中的最近邻点j,查询j的附近扫描线上的最近邻点l,j与l相连形成一条直线l-j,让点i与这条直线的距离最短。

构建一个非线性优化问题:以点i与直线lj的距离为代价函数,以位姿变换T(四元数表示旋转+位移t)为优化变量。下面是优化的程序。

                    // find correspondence for corner features
                    // &#x5BFB;&#x627E;&#x89D2;&#x70B9;&#x7684;&#x7EA6;&#x675F;
                    // &#x57FA;&#x4E8E;&#x6700;&#x8FD1;&#x90BB;&#x539F;&#x7406;&#x5EFA;&#x7ACB;corner&#x7279;&#x5F81;&#x70B9;&#x4E4B;&#x95F4;&#x5173;&#x8054;&#xFF0C;&#x6BCF;&#x4E00;&#x4E2A;&#x6781;&#x5927;&#x8FB9;&#x7EBF;&#x70B9;&#x53BB;&#x4E0A;&#x4E00;&#x5E27;&#x7684;&#x6B21;&#x6781;&#x5927;&#x8FB9;&#x7EBF;&#x70B9;&#x96C6;&#x4E2D;&#x627E;&#x5339;&#x914D;
                    for (int i = 0; i < cornerPointsSharpNum; ++i)
                    {
                        // &#x8FD0;&#x52A8;&#x8865;&#x507F;
                        // &#x8FD9;&#x4E2A;&#x51FD;&#x6570;&#x7C7B;&#x4F3C;&#x8BBA;&#x6587;&#x4E2D;&#x516C;&#x5F0F;&#xFF08;5&#xFF09;&#x7684;&#x529F;&#x80FD;
                        // &#x5C06;&#x5F53;&#x524D;&#x5E27;&#x7684;&#x6781;&#x5927;&#x8FB9;&#x7EBF;&#x70B9;&#xFF08;&#x8BB0;&#x4E3A;&#x70B9;O_cur&#xFF09;&#xFF0C;&#x53D8;&#x6362;&#x5230;&#x4E0A;&#x4E00;&#x5E27;Lidar&#x5750;&#x6807;&#x7CFB;&#xFF08;&#x8BB0;&#x4E3A;&#x70B9;O&#xFF09;&#xFF0C;&#x4EE5;&#x5229;&#x4E8E;&#x5BFB;&#x627E;&#x6781;&#x5927;&#x8FB9;&#x7EBF;&#x70B9;&#x7684;correspondence
                        TransformToStart(&(cornerPointsSharp->points[i]), &pointSel);
                        // &#x5728;&#x4E0A;&#x4E00;&#x5E27;&#x6240;&#x6709;&#x89D2;&#x70B9;&#x6784;&#x6210;&#x7684;kdtree&#x4E2D;&#x5BFB;&#x627E;&#x8DDD;&#x79BB;&#x5F53;&#x524D;&#x5E27;&#x6700;&#x8FD1;&#x7684;&#x4E00;&#x4E2A;&#x70B9;
                        kdtreeCornerLast->nearestKSearch(pointSel, 1, pointSearchInd, pointSearchSqDis);

                        int closestPointInd = -1, minPointInd2 = -1;
                        // &#x53EA;&#x6709;&#x5C0F;&#x4E8E;&#x7ED9;&#x5B9A;&#x95E8;&#x9650;&#x624D;&#x8BA4;&#x4E3A;&#x662F;&#x6709;&#x6548;&#x7EA6;&#x675F;
                        if (pointSearchSqDis[0] < DISTANCE_SQ_THRESHOLD)
                        {
                            closestPointInd = pointSearchInd[0];    // &#x5BF9;&#x5E94;&#x7684;&#x6700;&#x8FD1;&#x8DDD;&#x79BB;&#x7684;&#x7D22;&#x5F15;&#x53D6;&#x51FA;&#x6765;
                            // &#x627E;&#x5230;&#x5176;&#x6240;&#x5728;&#x7EBF;&#x675F;id&#xFF0C;&#x7EBF;&#x675F;&#x4FE1;&#x606F;&#x85CF;&#x5728;intensity&#x7684;&#x6574;&#x6570;&#x90E8;&#x5206;
                            int closestPointScanID = int(laserCloudCornerLast->points[closestPointInd].intensity);

                            double minPointSqDis2 = DISTANCE_SQ_THRESHOLD;
                            // search in the direction of increasing scan line
                            // &#x5BFB;&#x627E;&#x89D2;&#x70B9;&#xFF0C;&#x5728;&#x521A;&#x521A;&#x89D2;&#x70B9;id&#x4E0A;&#x4E0B;&#x5206;&#x522B;&#x7EE7;&#x7EED;&#x5BFB;&#x627E;&#xFF0C;&#x76EE;&#x7684;&#x662F;&#x627E;&#x5230;&#x6700;&#x8FD1;&#x7684;&#x89D2;&#x70B9;&#xFF0C;&#x7531;&#x4E8E;&#x5176;&#x6309;&#x7167;&#x7EBF;&#x675F;&#x8FDB;&#x884C;&#x6392;&#x5E8F;&#xFF0C;&#x6240;&#x4EE5;&#x5C31;&#x662F;&#x5411;&#x4E0A;&#x627E;
                            for (int j = closestPointInd + 1; j < (int)laserCloudCornerLast->points.size(); ++j)
                            {
                                // if in the same scan line, continue
                                // &#x4E0D;&#x627E;&#x540C;&#x4E00;&#x6839;&#x7EBF;&#x675F;&#x7684;
                                if (int(laserCloudCornerLast->points[j].intensity) <= closestpointscanid) continue; if not in nearby scans, end the loop 要求找到的线束距离当前线束不能太远 (int(lasercloudcornerlast->points[j].intensity) > (closestPointScanID + NEARBY_SCAN))
                                    break;
                                // &#x8BA1;&#x7B97;&#x548C;&#x5F53;&#x524D;&#x627E;&#x5230;&#x7684;&#x89D2;&#x70B9;&#x4E4B;&#x95F4;&#x7684;&#x8DDD;&#x79BB;
                                double pointSqDis = (laserCloudCornerLast->points[j].x - pointSel.x) *
                                                        (laserCloudCornerLast->points[j].x - pointSel.x) +
                                                    (laserCloudCornerLast->points[j].y - pointSel.y) *
                                                        (laserCloudCornerLast->points[j].y - pointSel.y) +
                                                    (laserCloudCornerLast->points[j].z - pointSel.z) *
                                                        (laserCloudCornerLast->points[j].z - pointSel.z);
                                // &#x5BFB;&#x627E;&#x8DDD;&#x79BB;&#x6700;&#x5C0F;&#x7684;&#x89D2;&#x70B9;&#x53CA;&#x5176;&#x7D22;&#x5F15;
                                if (pointSqDis < minPointSqDis2)
                                {
                                    // find nearer point
                                    // &#x8BB0;&#x5F55;&#x5176;&#x7D22;&#x5F15;
                                    minPointSqDis2 = pointSqDis;
                                    minPointInd2 = j;
                                }
                            }

                            // search in the direction of decreasing scan line
                            // &#x540C;&#x6837;&#x53E6;&#x4E00;&#x4E2A;&#x65B9;&#x5411;&#x5BFB;&#x627E;&#x5BF9;&#x5E94;&#x89D2;&#x70B9;
                            for (int j = closestPointInd - 1; j >= 0; --j)
                            {
                                // if in the same scan line, continue
                                if (int(laserCloudCornerLast->points[j].intensity) >= closestPointScanID)
                                    continue;

                                // if not in nearby scans, end the loop
                                if (int(laserCloudCornerLast->points[j].intensity) < (closestPointScanID - NEARBY_SCAN))
                                    break;

                                double pointSqDis = (laserCloudCornerLast->points[j].x - pointSel.x) *
                                                        (laserCloudCornerLast->points[j].x - pointSel.x) +
                                                    (laserCloudCornerLast->points[j].y - pointSel.y) *
                                                        (laserCloudCornerLast->points[j].y - pointSel.y) +
                                                    (laserCloudCornerLast->points[j].z - pointSel.z) *
                                                        (laserCloudCornerLast->points[j].z - pointSel.z);

                                if (pointSqDis < minPointSqDis2)
                                {
                                    // find nearer point
                                    minPointSqDis2 = pointSqDis;
                                    minPointInd2 = j;
                                }
                            }
                        }
                        // &#x5982;&#x679C;&#x8FD9;&#x4E2A;&#x89D2;&#x70B9;&#x662F;&#x6709;&#x6548;&#x7684;&#x89D2;&#x70B9;
                        // &#x5373;&#x7279;&#x5F81;&#x70B9;i&#x7684;&#x4E24;&#x4E2A;&#x6700;&#x8FD1;&#x90BB;&#x70B9;j&#x548C;m&#x90FD;&#x6709;&#x6548;&#xFF0C;&#x6784;&#x5EFA;&#x975E;&#x7EBF;&#x6027;&#x4F18;&#x5316;&#x95EE;&#x9898;
                        if (minPointInd2 >= 0) // both closestPointInd and minPointInd2 is valid
                        {
                            // &#x53D6;&#x51FA;&#x5F53;&#x524D;&#x70B9;&#x548C;&#x4E0A;&#x4E00;&#x5E27;&#x7684;&#x4E24;&#x4E2A;&#x89D2;&#x70B9;
                            Eigen::Vector3d curr_point(cornerPointsSharp->points[i].x,
                                                       cornerPointsSharp->points[i].y,
                                                       cornerPointsSharp->points[i].z);
                            Eigen::Vector3d last_point_a(laserCloudCornerLast->points[closestPointInd].x,
                                                         laserCloudCornerLast->points[closestPointInd].y,
                                                         laserCloudCornerLast->points[closestPointInd].z);
                            Eigen::Vector3d last_point_b(laserCloudCornerLast->points[minPointInd2].x,
                                                         laserCloudCornerLast->points[minPointInd2].y,
                                                         laserCloudCornerLast->points[minPointInd2].z);

                            double s;
                            if (DISTORTION)
                                s = (cornerPointsSharp->points[i].intensity - int(cornerPointsSharp->points[i].intensity)) / SCAN_PERIOD;
                            else
                                s = 1.0;
                            ceres::CostFunction *cost_function = LidarEdgeFactor::Create(curr_point, last_point_a, last_point_b, s);
                            problem.AddResidualBlock(cost_function, loss_function, para_q, para_t);
                            corner_correspondence++;
                        }
                    }
</=>

下面采用平面点匹配方法:假如在第k+1帧中发现了平面点i,通过KD-tree查询在第k帧(上一帧)中的最近邻点j,查询j的附近扫描线上的最近邻点l和同一条扫描线的最近邻点m,这三点确定一个平面,让点i与这个平面的距离最短;

构建一个非线性优化问题:以点i与平面lmj的距离为代价函数,以位姿变换T(四元数表示旋转+t)为优化变量。

                   // find correspondence for plane features
                    // &#x4E0E;&#x4E0A;&#x8FF0;&#x7C7B;&#x4F3C;&#xFF0C;&#x5BFB;&#x627E;&#x5E73;&#x9762;&#x70B9;&#x7684;&#x6700;&#x8FD1;&#x90BB;&#x70B9;j,l,m
                    for (int i = 0; i < surfPointsFlatNum; ++i)
                    {
                        // &#x8FD9;&#x4E2A;&#x51FD;&#x6570;&#x7C7B;&#x4F3C;&#x8BBA;&#x6587;&#x4E2D;&#x516C;&#x5F0F;&#xFF08;5&#xFF09;&#x7684;&#x529F;&#x80FD;
                        // &#x5C06;&#x5F53;&#x524D;&#x5E27;&#x7684;&#x6781;&#x5927;&#x8FB9;&#x7EBF;&#x70B9;&#xFF08;&#x8BB0;&#x4E3A;&#x70B9;O_cur&#xFF09;&#xFF0C;&#x53D8;&#x6362;&#x5230;&#x4E0A;&#x4E00;&#x5E27;Lidar&#x5750;&#x6807;&#x7CFB;&#xFF08;&#x8BB0;&#x4E3A;&#x70B9;O&#xFF09;&#xFF0C;&#x4EE5;&#x5229;&#x4E8E;&#x5BFB;&#x627E;&#x6781;&#x5927;&#x8FB9;&#x7EBF;&#x70B9;&#x7684;correspondence
                        TransformToStart(&(surfPointsFlat->points[i]), &pointSel);
                        // &#x5148;&#x5BFB;&#x627E;&#x4E0A;&#x4E00;&#x5E27;&#x8DDD;&#x79BB;&#x8FD9;&#x4E2A;&#x9762;&#x70B9;&#x6700;&#x8FD1;&#x7684;&#x9762;&#x70B9;
                        kdtreeSurfLast->nearestKSearch(pointSel, 1, pointSearchInd, pointSearchSqDis);

                        int closestPointInd = -1, minPointInd2 = -1, minPointInd3 = -1;
                        // &#x8DDD;&#x79BB;&#x5FC5;&#x987B;&#x5C0F;&#x4E8E;&#x7ED9;&#x5B9A;&#x9608;&#x503C;
                        if (pointSearchSqDis[0] < DISTANCE_SQ_THRESHOLD)
                        {
                            // &#x53D6;&#x51FA;&#x627E;&#x5230;&#x7684;&#x4E0A;&#x4E00;&#x5E27;&#x9762;&#x70B9;&#x7684;&#x7D22;&#x5F15;
                            closestPointInd = pointSearchInd[0];

                            // get closest point's scan ID
                            // &#x53D6;&#x51FA;&#x6700;&#x8FD1;&#x7684;&#x9762;&#x70B9;&#x5728;&#x4E0A;&#x4E00;&#x5E27;&#x7684;&#x7B2C;&#x51E0;&#x6839;scan&#x4E0A;&#x9762;
                            int closestPointScanID = int(laserCloudSurfLast->points[closestPointInd].intensity);
                            double minPointSqDis2 = DISTANCE_SQ_THRESHOLD, minPointSqDis3 = DISTANCE_SQ_THRESHOLD;
                            // &#x989D;&#x5916;&#x5728;&#x5BFB;&#x627E;&#x4E24;&#x4E2A;&#x70B9;&#xFF0C;&#x8981;&#x6C42;&#xFF0C;&#x4E00;&#x4E2A;&#x70B9;&#x548C;&#x6700;&#x8FD1;&#x70B9;&#x540C;&#x4E00;&#x4E2A;scan&#xFF0C;&#x53E6;&#x4E00;&#x4E2A;&#x662F;&#x4E0D;&#x540C;scan
                            // search in the direction of increasing scan line
                            // &#x6309;&#x7167;&#x589E;&#x91CF;&#x65B9;&#x5411;&#x5BFB;&#x627E;&#x5176;&#x4ED6;&#x9762;&#x70B9;
                            for (int j = closestPointInd + 1; j < (int)laserCloudSurfLast->points.size(); ++j)
                            {
                                // if not in nearby scans, end the loop
                                // &#x4E0D;&#x80FD;&#x548C;&#x5F53;&#x524D;&#x627E;&#x5230;&#x7684;&#x4E0A;&#x4E00;&#x5E27;&#x9762;&#x70B9;&#x7EBF;&#x675F;&#x8DDD;&#x79BB;&#x592A;&#x8FDC;
                                if (int(laserCloudSurfLast->points[j].intensity) > (closestPointScanID + NEARBY_SCAN))
                                    break;
                                // &#x8BA1;&#x7B97;&#x548C;&#x5F53;&#x524D;&#x5E27;&#x8BE5;&#x70B9;&#x8DDD;&#x79BB;
                                double pointSqDis = (laserCloudSurfLast->points[j].x - pointSel.x) *
                                                        (laserCloudSurfLast->points[j].x - pointSel.x) +
                                                    (laserCloudSurfLast->points[j].y - pointSel.y) *
                                                        (laserCloudSurfLast->points[j].y - pointSel.y) +
                                                    (laserCloudSurfLast->points[j].z - pointSel.z) *
                                                        (laserCloudSurfLast->points[j].z - pointSel.z);

                                // if in the same or lower scan line
                                // &#x5982;&#x679C;&#x662F;&#x540C;&#x4E00;&#x6839;scan&#x4E14;&#x8DDD;&#x79BB;&#x6700;&#x8FD1;
                                if (int(laserCloudSurfLast->points[j].intensity) <= closestpointscanid && pointsqdis < minpointsqdis2) { minpointsqdis2="pointSqDis;" minpointind2="j;" } if in the higher scan line 如果是其他线束点 else (int(lasercloudsurflast->points[j].intensity) > closestPointScanID && pointSqDis < minPointSqDis3)
                                {
                                    minPointSqDis3 = pointSqDis;
                                    minPointInd3 = j;
                                }
                            }

                            // search in the direction of decreasing scan line
                            // &#x540C;&#x6837;&#x7684;&#x65B9;&#x5F0F;&#xFF0C;&#x53BB;&#x6309;&#x7167;&#x964D;&#x5E8F;&#x65B9;&#x5411;&#x5BFB;&#x627E;&#x8FD9;&#x4E24;&#x4E2A;&#x70B9;
                            for (int j = closestPointInd - 1; j >= 0; --j)
                            {
                                // if not in nearby scans, end the loop
                                if (int(laserCloudSurfLast->points[j].intensity) < (closestPointScanID - NEARBY_SCAN))
                                    break;

                                double pointSqDis = (laserCloudSurfLast->points[j].x - pointSel.x) *
                                                        (laserCloudSurfLast->points[j].x - pointSel.x) +
                                                    (laserCloudSurfLast->points[j].y - pointSel.y) *
                                                        (laserCloudSurfLast->points[j].y - pointSel.y) +
                                                    (laserCloudSurfLast->points[j].z - pointSel.z) *
                                                        (laserCloudSurfLast->points[j].z - pointSel.z);

                                // if in the same or higher scan line
                                if (int(laserCloudSurfLast->points[j].intensity) >= closestPointScanID && pointSqDis < minPointSqDis2)
                                {
                                    minPointSqDis2 = pointSqDis;
                                    minPointInd2 = j;
                                }
                                else if (int(laserCloudSurfLast->points[j].intensity) < closestPointScanID && pointSqDis < minPointSqDis3)
                                {
                                    // find nearer point
                                    minPointSqDis3 = pointSqDis;
                                    minPointInd3 = j;
                                }
                            }
                            // &#x5982;&#x679C;&#x53E6;&#x5916;&#x627E;&#x5230;&#x7684;&#x4E24;&#x4E2A;&#x70B9;&#x662F;&#x6709;&#x6548;&#x70B9;&#xFF0C;&#x5C31;&#x53D6;&#x51FA;&#x4ED6;&#x4EEC;&#x7684;3d&#x5750;&#x6807;
                            if (minPointInd2 >= 0 && minPointInd3 >= 0)
                            {

                                Eigen::Vector3d curr_point(surfPointsFlat->points[i].x,
                                                            surfPointsFlat->points[i].y,
                                                            surfPointsFlat->points[i].z);
                                Eigen::Vector3d last_point_a(laserCloudSurfLast->points[closestPointInd].x,
                                                                laserCloudSurfLast->points[closestPointInd].y,
                                                                laserCloudSurfLast->points[closestPointInd].z);
                                Eigen::Vector3d last_point_b(laserCloudSurfLast->points[minPointInd2].x,
                                                                laserCloudSurfLast->points[minPointInd2].y,
                                                                laserCloudSurfLast->points[minPointInd2].z);
                                Eigen::Vector3d last_point_c(laserCloudSurfLast->points[minPointInd3].x,
                                                                laserCloudSurfLast->points[minPointInd3].y,
                                                                laserCloudSurfLast->points[minPointInd3].z);

                                double s;
                                if (DISTORTION)
                                    s = (surfPointsFlat->points[i].intensity - int(surfPointsFlat->points[i].intensity)) / SCAN_PERIOD;
                                else
                                    s = 1.0;
                                // &#x6784;&#x5EFA;&#x70B9;&#x5230;&#x9762;&#x7684;&#x7EA6;&#x675F;
                                ceres::CostFunction *cost_function = LidarPlaneFactor::Create(curr_point, last_point_a, last_point_b, last_point_c, s);
                                problem.AddResidualBlock(cost_function, loss_function, para_q, para_t);
                                plane_correspondence++;
                            }
                        }
                    }</=>

应用ceres 构建求解器:

                    printf("data association time %f ms \n", t_data.toc());
                    // &#x5982;&#x679C;&#x603B;&#x7684;&#x7EA6;&#x675F;&#x592A;&#x5C11;&#xFF0C;&#x5C31;&#x6253;&#x5370;&#x4E00;&#x4E0B;
                    if ((corner_correspondence + plane_correspondence) < 10)
                    {
                        printf("less correspondence! *************************************************\n");
                    }
                    // &#x8C03;&#x7528;ceres&#x6C42;&#x89E3;&#x5668;&#x6C42;&#x89E3;
                    TicToc t_solver;
                    ceres::Solver::Options options;
                    options.linear_solver_type = ceres::DENSE_QR;
                    options.max_num_iterations = 4;
                    options.minimizer_progress_to_stdout = false;
                    ceres::Solver::Summary summary;
                    ceres::Solve(options, &problem, &summary);
                    printf("solver time %f ms \n", t_solver.toc());
                }
                printf("optimization twice time %f \n", t_opt.toc());
                // &#x8FD9;&#x91CC;&#x7684;w_curr &#x5B9E;&#x9645;&#x4E0A;&#x662F; w_last
                t_w_curr = t_w_curr + q_w_curr * t_last_curr;
                q_w_curr = q_w_curr * q_last_curr;
            }

里程计数据打包与发布比较简单不再赘述,其中值得注意的是需要降频发给后端。

在求点到线和点到面函数中需要对点云数据就行运动补偿以去除运动畸变带来的估计误差,

运动补偿畸变函数LOAM假设激光雷达的运动为匀速模型,虽然实际中并不完全匹配,但是实际使用效果也基本没有问题。

无人驾驶学习笔记 - A-LOAM 算法代码解析总结

常用的做法是补偿到起始时刻,如果有IMU,我们通过IMU得到的雷达高频位姿,可以求出每个点相对起始点的位姿,就可以补偿回去。

如果没有IMU,我们可以使用匀速模型假设,使⽤上⼀个帧间⾥程记的结果作为当前两帧之间的运动,假设当前帧也是匀速运动,可以估计出每个点相对起始时刻的位姿。

最后,当前点云中的点相对第一个点去除因运动产生的畸变,效果相当于静止扫描得到的点云。下面是去除运动畸变的函数。

/ undistort lidar point
void TransformToStart(PointType const *const pi, PointType *const po)
{
    //interpolation ratio
    double s;
    // &#x7531;&#x4E8E;kitti&#x6570;&#x636E;&#x96C6;&#x4E0A;&#x7684;lidar&#x5DF2;&#x7ECF;&#x505A;&#x8FC7;&#x4E86;&#x8FD0;&#x52A8;&#x8865;&#x507F;&#xFF0C;&#x56E0;&#x6B64;&#x8FD9;&#x91CC;&#x5C31;&#x4E0D;&#x505A;&#x5177;&#x4F53;&#x8865;&#x507F;&#x4E86;
    if (DISTORTION)
        s = (pi->intensity - int(pi->intensity)) / SCAN_PERIOD;
    else
        s = 1.0;    // s = 1s&#x8BF4;&#x660E;&#x5168;&#x90E8;&#x8865;&#x507F;&#x5230;&#x70B9;&#x4E91;&#x7ED3;&#x675F;&#x7684;&#x65F6;&#x523B;
    //s = 1;
    // &#x6240;&#x6709;&#x70B9;&#x7684;&#x64CD;&#x4F5C;&#x65B9;&#x5F0F;&#x90FD;&#x662F;&#x4E00;&#x81F4;&#x7684;&#xFF0C;&#x76F8;&#x5F53;&#x4E8E;&#x4ECE;&#x7ED3;&#x675F;&#x65F6;&#x523B;&#x8865;&#x507F;&#x5230;&#x8D77;&#x59CB;&#x65F6;&#x523B;
    // &#x8FD9;&#x91CC;&#x76F8;&#x5F53;&#x4E8E;&#x662F;&#x4E00;&#x4E2A;&#x5300;&#x901F;&#x6A21;&#x578B;&#x7684;&#x5047;&#x8BBE;
    Eigen::Quaterniond q_point_last = Eigen::Quaterniond::Identity().slerp(s, q_last_curr);
    Eigen::Vector3d t_point_last = s * t_last_curr;
    Eigen::Vector3d point(pi->x, pi->y, pi->z);
    Eigen::Vector3d un_point = q_point_last * point + t_point_last;

    po->x = un_point.x();
    po->y = un_point.y();
    po->z = un_point.z();
    po->intensity = pi->intensity;
}

// transform all lidar points to the start of the next frame

void TransformToEnd(PointType const *const pi, PointType *const po)
{
    // undistort point first
    pcl::PointXYZI un_point_tmp;
    TransformToStart(pi, &un_point_tmp);

    Eigen::Vector3d un_point(un_point_tmp.x, un_point_tmp.y, un_point_tmp.z);
    Eigen::Vector3d point_end = q_last_curr.inverse() * (un_point - t_last_curr);

    po->x = point_end.x();
    po->y = point_end.y();
    po->z = point_end.z();

    //Remove distortion time info
    po->intensity = int(pi->intensity);
}

3.2、总结概括

该部分主要实现前端高频低精度的激光里程计。首先搞清楚几个坐标系之间的转换,然后对点云匹配前先去畸变。采用两部LM的方法分别求解点到线的距离,点到面的距离,应用ceres 构建求解器。构建损失函数,构建优化项代入ceres 求解器。该部分在LOAM 中,求解是相当麻烦

流程如下图

无人驾驶学习笔记 - A-LOAM 算法代码解析总结

代码流程图,同样参考知乎大佬

SLAM前端入门框架-A_LOAM源码解析 – 知乎

无人驾驶学习笔记 - A-LOAM 算法代码解析总结

上图已经将laserOdometry的流程描述的比较清楚了。下面就具体的说明一下可能会存在的实现上或者原理上的问题。
步骤1,主要就是配置订阅和发布节点,以及一些提前使用的变量的初始化。
步骤2,步骤3主要判断是否存在数据异常。
步骤4,用来判断是否有第一帧数据,如果没有第一帧数据,没有办法进行后续的匹配。所以需要进行一个初始化判断。
步骤5,是最关键的一个步骤,内部流程是将获取的每一个角点和面点(小集合)利用估计的映射,映射到上一时刻(这一帧雷达的启始时刻)(代码中有实现关于畸变去除的部分),通过利用历史帧构建的面KDTree和线KDTree(大集合)来匹配相似的点来构建点线和点面残差,利用ceres来求解较为精准的里程计。
线残差的计算方式,面残差的计算方式具体看下文,在lidarFactor中有具体的原理和涉及
步骤6,将这一帧的角点和面点(大集合)信息加入面KDTree和线KDTree中,以供下一帧匹配使用。
步骤7,发布激光里程计,点云以及角点和面点(大集合)。

4、laserMapping.cpp

laserMapping.cpp主要是通过已经获得的激光里程计信息来消除激光里程计和地图之间的误差(也就是累计的误差),使得最终的姿态都是关于世界坐标的。

前端里程计会周期向后端发送位姿T_odom,但是Mapping模块中,我们需要得到位姿是T_map_cur,因此mapping就需要估计出odom坐标系与map坐标系之间的相对位姿变换T_map_odm,这部分需要仔细推导一下。 T_map_cur = T_map_odom × T_odom_cur

4.1、代码注释

主函数

int main(int argc, char **argv)
{
    ros::init(argc, argv, "laserMapping");
    ros::NodeHandle nh;

    float lineRes = 0;     // &#x6B21;&#x6781;&#x5927;&#x8FB9;&#x7EBF;&#x70B9;&#x96C6;&#x4F53;&#x7D20;&#x6EE4;&#x6CE2;&#x5206;&#x8FA8;&#x7387;
    float planeRes = 0;    // &#x6B21;&#x6781;&#x5C0F;&#x5E73;&#x9762;&#x70B9;&#x96C6;&#x4F53;&#x7D20;&#x6EE4;&#x6CE2;&#x5206;&#x8FA8;&#x7387;
    nh.param<float>("mapping_line_resolution", lineRes, 0.4);
    nh.param<float>("mapping_plane_resolution", planeRes, 0.8);
    printf("line resolution %f plane resolution %f \n", lineRes, planeRes);
    downSizeFilterCorner.setLeafSize(lineRes, lineRes,lineRes);
    downSizeFilterSurf.setLeafSize(planeRes, planeRes, planeRes);

    // &#x4ECE;laserOdometry&#x8282;&#x70B9;&#x63A5;&#x6536;&#x6B21;&#x6781;&#x5927;&#x8FB9;&#x7EBF;&#x70B9;
    ros::Subscriber subLaserCloudCornerLast = nh.subscribe<sensor_msgs::pointcloud2>("/laser_cloud_corner_last", 100, laserCloudCornerLastHandler);
    // &#x4ECE;laserOdometry&#x8282;&#x70B9;&#x63A5;&#x6536;&#x6B21;&#x6781;&#x5C0F;&#x5E73;&#x9762;&#x70B9;
    ros::Subscriber subLaserCloudSurfLast = nh.subscribe<sensor_msgs::pointcloud2>("/laser_cloud_surf_last", 100, laserCloudSurfLastHandler);
    // &#x4ECE;laserOdometry&#x8282;&#x70B9;&#x63A5;&#x6536;&#x5230;&#x7684;&#x6700;&#x65B0;&#x5E27;&#x7684;&#x4F4D;&#x59FF;T_cur^w
    ros::Subscriber subLaserOdometry = nh.subscribe<nav_msgs::odometry>("/laser_odom_to_init", 100, laserOdometryHandler);
    // &#x4ECE;laserOdometry&#x8282;&#x70B9;&#x63A5;&#x6536;&#x5230;&#x7684;&#x539F;&#x59CB;&#x70B9;&#x4E91;&#xFF08;&#x53EA;&#x7ECF;&#x8FC7;&#x4E00;&#x6B21;&#x964D;&#x91C7;&#x6837;&#xFF09;
    ros::Subscriber subLaserCloudFullRes = nh.subscribe<sensor_msgs::pointcloud2>("/velodyne_cloud_3", 100, laserCloudFullResHandler);

    // submap&#x6240;&#x5728;cube&#x4E2D;&#x7684;&#x70B9;&#x4E91;  /  &#x53D1;&#x5E03;&#x5468;&#x56F4;5&#x5E27;&#x7684;&#x70B9;&#x4E91;&#x96C6;&#x5408;&#xFF08;&#x964D;&#x91C7;&#x6837;&#x4EE5;&#x540E;&#x7684;&#xFF09;
    pubLaserCloudSurround = nh.advertise<sensor_msgs::pointcloud2>("/laser_cloud_surround", 100);
    // &#x6240;&#x6B32;cube&#x4E2D;&#x7684;&#x70B9;&#x4E91;
    pubLaserCloudMap = nh.advertise<sensor_msgs::pointcloud2>("/laser_cloud_map", 100);
    // &#x539F;&#x59CB;&#x70B9;&#x4E91;
    pubLaserCloudFullRes = nh.advertise<sensor_msgs::pointcloud2>("/velodyne_cloud_registered", 100);
    // &#x7ECF;&#x8FC7;Scan to Map&#x7CBE;&#x4F30;&#x8BA1;&#x4F18;&#x5316;&#x540E;&#x7684;&#x5F53;&#x524D;&#x5E27;&#x4F4D;&#x59FF;
    pubOdomAftMapped = nh.advertise<nav_msgs::odometry>("/aft_mapped_to_init", 100);
        // &#x5C06;&#x91CC;&#x7A0B;&#x8BA1;&#x5750;&#x6807;&#x7CFB;&#x4F4D;&#x59FF;&#x8F6C;&#x5316;&#x5230;&#x4E16;&#x754C;&#x5750;&#x6807;&#x7CFB;&#x4F4D;&#x59FF;&#xFF08;&#x5730;&#x56FE;&#x5750;&#x6807;&#x7CFB;&#xFF09;&#xFF0C;&#x76F8;&#x5F53;&#x4E8E;&#x4F4D;&#x59FF;&#x4F18;&#x5316;&#x521D;&#x503C;
    pubOdomAftMappedHighFrec = nh.advertise<nav_msgs::odometry>("/aft_mapped_to_init_high_frec", 100);
    // &#x7ECF;&#x8FC7;Scan to Map&#x7CBE;&#x4F30;&#x8BA1;&#x4F18;&#x5316;&#x540E;&#x7684;&#x5F53;&#x524D;&#x5E27;&#x5E73;&#x79FB;
    pubLaserAfterMappedPath = nh.advertise<nav_msgs::path>("/aft_mapped_path", 100);

    /*
     * &#x91CD;&#x7F6E;&#x8FD9;&#x4E24;&#x4E2A;&#x6570;&#x7EC4;&#xFF0C;&#x8FD9;&#x4E24;&#x6570;&#x7EC4;&#x7528;&#x4E8E;&#x5B58;&#x50A8;&#x6240;&#x6709;&#x8FB9;&#x7EBF;&#x70B9;cube&#x548C;&#x5E73;&#x9762;&#x70B9;cube
     */
    for (int i = 0; i < laserCloudNum; i++)
    {
        laserCloudCornerArray[i].reset(new pcl::PointCloud<pointtype>());
        laserCloudSurfArray[i].reset(new pcl::PointCloud<pointtype>());
    }

    std::thread mapping_process{process};

    ros::spin();

    return 0;
}</pointtype></pointtype></nav_msgs::path></nav_msgs::odometry></nav_msgs::odometry></sensor_msgs::pointcloud2></sensor_msgs::pointcloud2></sensor_msgs::pointcloud2></sensor_msgs::pointcloud2></nav_msgs::odometry></sensor_msgs::pointcloud2></sensor_msgs::pointcloud2></float></float>

关于坐标转换的几个函数:

这里涉及到几个坐标系的相互转换,需要理清关系:雷达坐标系(每帧扫描到的点云点的坐标point_curr都在雷达坐标系中),里程计坐标系(由laseOdometry节点粗估计得到的LiDAR位姿wodom_curr对应的坐标系),地图坐标系(真实的世界坐标系)

// set initial guess&#xFF0C;&#x91CC;&#x7A0B;&#x8BA1;&#x4F4D;&#x59FF;&#x8F6C;&#x5316;&#x4E3A;&#x5730;&#x56FE;&#x4F4D;&#x59FF;&#xFF0C;&#x4F5C;&#x4E3A;&#x540E;&#x7AEF;&#x521D;&#x59CB;&#x4F30;&#x8BA1;
void transformAssociateToMap()
{
    // T_w_curr = T_w_last * T_last_curr(from lidar odom)
    q_w_curr = q_wmap_wodom * q_wodom_curr;
    t_w_curr = q_wmap_wodom * t_wodom_curr + t_wmap_wodom;
}

// &#x66F4;&#x65B0;odom&#x5230;map&#x4E4B;&#x95F4;&#x7684;&#x4F4D;&#x59FF;&#x53D8;&#x6362;
void transformUpdate()
{
    q_wmap_wodom = q_w_curr * q_wodom_curr.inverse();
    t_wmap_wodom = t_w_curr - q_wmap_wodom * t_wodom_curr;
}

//&#x96F7;&#x8FBE;&#x5750;&#x6807;&#x7CFB;&#x70B9;&#x8F6C;&#x5316;&#x4E3A;&#x5730;&#x56FE;&#x70B9;
void pointAssociateToMap(PointType const *const pi, PointType *const po)
{
    Eigen::Vector3d point_curr(pi->x, pi->y, pi->z);
    Eigen::Vector3d point_w = q_w_curr * point_curr + t_w_curr;
    po->x = point_w.x();
    po->y = point_w.y();
    po->z = point_w.z();
    po->intensity = pi->intensity;
    //po->intensity = 1.0;
}

//&#x5730;&#x56FE;&#x70B9;&#x8F6C;&#x5316;&#x5230;&#x96F7;&#x8FBE;&#x5750;&#x6807;&#x7CFB;&#x70B9;
void pointAssociateTobeMapped(PointType const *const pi, PointType *const po)
{
    Eigen::Vector3d point_w(pi->x, pi->y, pi->z);
    Eigen::Vector3d point_curr = q_w_curr.inverse() * (point_w - t_w_curr);
    po->x = point_curr.x();
    po->y = point_curr.y();
    po->z = point_curr.z();
    po->intensity = pi->intensity;
}

回调函数处理

// &#x56DE;&#x8C03;&#x51FD;&#x6570;&#x4E2D;&#x5C06;&#x6D88;&#x606F;&#x90FD;&#x662F;&#x9001;&#x5165;&#x5404;&#x81EA;&#x961F;&#x5217;&#xFF0C;&#x8FDB;&#x884C;&#x7EBF;&#x7A0B;&#x52A0;&#x9501;&#x548C;&#x89E3;&#x9501;
void laserCloudCornerLastHandler(const sensor_msgs::PointCloud2ConstPtr &laserCloudCornerLast2)
{
    mBuf.lock();
    cornerLastBuf.push(laserCloudCornerLast2);
    mBuf.unlock();
}

void laserCloudSurfLastHandler(const sensor_msgs::PointCloud2ConstPtr &laserCloudSurfLast2)
{
    mBuf.lock();
    surfLastBuf.push(laserCloudSurfLast2);
    mBuf.unlock();
}

void laserCloudFullResHandler(const sensor_msgs::PointCloud2ConstPtr &laserCloudFullRes2)
{
    mBuf.lock();
    fullResBuf.push(laserCloudFullRes2);
    mBuf.unlock();
}

//receive odomtry
//&#x91CC;&#x7A0B;&#x8BA1;&#x5750;&#x6807;&#x7CFB;&#x4F4D;&#x59FF;&#x8F6C;&#x5316;&#x4E3A;&#x5730;&#x56FE;&#x5750;&#x6807;&#x7CFB;&#x4F4D;&#x59FF;
void laserOdometryHandler(const nav_msgs::Odometry::ConstPtr &laserOdometry)
{
    mBuf.lock();
    odometryBuf.push(laserOdometry);
    mBuf.unlock();

    // high frequence publish
    Eigen::Quaterniond q_wodom_curr;
    Eigen::Vector3d t_wodom_curr;
    q_wodom_curr.x() = laserOdometry->pose.pose.orientation.x;
    q_wodom_curr.y() = laserOdometry->pose.pose.orientation.y;
    q_wodom_curr.z() = laserOdometry->pose.pose.orientation.z;
    q_wodom_curr.w() = laserOdometry->pose.pose.orientation.w;
    t_wodom_curr.x() = laserOdometry->pose.pose.position.x;
    t_wodom_curr.y() = laserOdometry->pose.pose.position.y;
    t_wodom_curr.z() = laserOdometry->pose.pose.position.z;

    //&#x91CC;&#x7A0B;&#x8BA1;&#x5750;&#x6807;&#x7CFB;&#x4F4D;&#x59FF;&#x8F6C;&#x6362;&#x4E3A;&#x5730;&#x56FE;&#x5750;&#x6807;&#x7CFB;&#x4F4D;&#x59FF;
    Eigen::Quaterniond q_w_curr = q_wmap_wodom * q_wodom_curr;
    Eigen::Vector3d t_w_curr = q_wmap_wodom * t_wodom_curr + t_wmap_wodom;

    nav_msgs::Odometry odomAftMapped;
    odomAftMapped.header.frame_id = "/camera_init";
    odomAftMapped.child_frame_id = "/aft_mapped";
    odomAftMapped.header.stamp = laserOdometry->header.stamp;
    odomAftMapped.pose.pose.orientation.x = q_w_curr.x();
    odomAftMapped.pose.pose.orientation.y = q_w_curr.y();
    odomAftMapped.pose.pose.orientation.z = q_w_curr.z();
    odomAftMapped.pose.pose.orientation.w = q_w_curr.w();
    odomAftMapped.pose.pose.position.x = t_w_curr.x();
    odomAftMapped.pose.pose.position.y = t_w_curr.y();
    odomAftMapped.pose.pose.position.z = t_w_curr.z();
    pubOdomAftMappedHighFrec.publish(odomAftMapped);
}

接下来就是核心函数,scan to map 位姿精匹配的实现。但是,如果完全使用所有区域的点云进行匹配,这样的效率会很低,而且内存空间可能会爆掉。LOAM论文中提到采用的是栅格(cube)地图的方法,将整个地图分成21×21×11个珊格,每个珊格是⼀个边⻓50m的正⽅体,当地图逐渐累加时,珊格之外的部分就被舍弃,这样可以保证内存空间不会随着程序的运⾏⽽爆掉,同时保证效率。

process 主线程中,首先对数据就行预处理,同步时间戳,转换为pcl格式,点云数据坐标转换,建立cube,并通过六个循环处理当前栅格,使其始终保持单钱栅格在中间位置,目的是为了保证当前帧不在局部地图的边缘,方便从地图中获取足够的特征点

// &#x4E3B;&#x5904;&#x7406;&#x7EBF;&#x7A0B;
void process()
{
    while(1)
    {
        // &#x5047;&#x5982;&#x56DB;&#x4E2A;&#x961F;&#x5217;&#x975E;&#x7A7A;&#xFF0C;&#x56DB;&#x4E2A;&#x961F;&#x5217;&#x5206;&#x522B;&#x5B58;&#x653E;&#x8FB9;&#x7EBF;&#x70B9;&#x3001;&#x5E73;&#x9762;&#x70B9;&#x3001;&#x5168;&#x90E8;&#x70B9;&#x3001;&#x548C;&#x91CC;&#x7A0B;&#x8BA1;&#x4F4D;&#x59FF;
        while (!cornerLastBuf.empty() && !surfLastBuf.empty() &&
            !fullResBuf.empty() && !odometryBuf.empty())
        {
            // laserOdometry&#x6A21;&#x5757;&#x5BF9;&#x672C;&#x8282;&#x70B9;&#x7684;&#x6267;&#x884C;&#x9891;&#x7387;&#x8FDB;&#x884C;&#x4E86;&#x63A7;&#x5236;&#xFF0C;laserOdometry&#x6A21;&#x5757;publish&#x7684;&#x4F4D;&#x59FF;&#x662F;10Hz&#xFF0C;&#x70B9;&#x4E91;&#x7684;publish&#x9891;&#x7387;&#x5219;&#x53EF;&#x4EE5;&#x6CA1;&#x8FD9;&#x4E48;&#x9AD8;
            // &#x4FDD;&#x8BC1;&#x5176;&#x4ED6;&#x5BB9;&#x5668;&#x7684;&#x6700;&#x65B0;&#x6D88;&#x606F;&#x4E0E;cornerLastBuf.front()&#x6700;&#x65B0;&#x6D88;&#x606F;&#x65F6;&#x95F4;&#x6233;&#x540C;&#x6B65;
            mBuf.lock();
            // &#x4EE5;cornerLastBuf&#x4E3A;&#x57FA;&#x51C6;&#xFF0C;&#x628A;&#x65F6;&#x95F4;&#x6233;&#x5C0F;&#x4E8E;&#x5176;&#x7684;&#x5168;&#x90E8;pop&#x51FA;&#x53BB;
            while (!odometryBuf.empty() && odometryBuf.front()->header.stamp.toSec() < cornerLastBuf.front()->header.stamp.toSec())
                odometryBuf.pop();
            if (odometryBuf.empty())
            {
                mBuf.unlock();
                break;
            }

            while (!surfLastBuf.empty() && surfLastBuf.front()->header.stamp.toSec() < cornerLastBuf.front()->header.stamp.toSec())
                surfLastBuf.pop();
            if (surfLastBuf.empty())
            {
                mBuf.unlock();
                break;
            }

            while (!fullResBuf.empty() && fullResBuf.front()->header.stamp.toSec() < cornerLastBuf.front()->header.stamp.toSec())
                fullResBuf.pop();
            if (fullResBuf.empty())
            {
                mBuf.unlock();
                break;
            }

            timeLaserCloudCornerLast = cornerLastBuf.front()->header.stamp.toSec();
            timeLaserCloudSurfLast = surfLastBuf.front()->header.stamp.toSec();
            timeLaserCloudFullRes = fullResBuf.front()->header.stamp.toSec();
            timeLaserOdometry = odometryBuf.front()->header.stamp.toSec();
            // &#x539F;&#x5219;&#x4E0A;&#x53D6;&#x51FA;&#x6765;&#x7684;&#x65F6;&#x95F4;&#x6233;&#x90FD;&#x662F;&#x4E00;&#x6837;&#x7684;&#xFF0C;&#x5982;&#x679C;&#x4E0D;&#x4E00;&#x5B9A;&#x8BF4;&#x660E;&#x6709;&#x95EE;&#x9898;&#x4E86;
            if (timeLaserCloudCornerLast != timeLaserOdometry ||
                timeLaserCloudSurfLast != timeLaserOdometry ||
                timeLaserCloudFullRes != timeLaserOdometry)
            {
                printf("time corner %f surf %f full %f odom %f \n", timeLaserCloudCornerLast, timeLaserCloudSurfLast, timeLaserCloudFullRes, timeLaserOdometry);
                printf("unsync messeage!");
                mBuf.unlock();
                break;
            }
            // &#x70B9;&#x4E91;&#x5168;&#x90E8;&#x8F6C;&#x6210;pcl&#x7684;&#x6570;&#x636E;&#x683C;&#x5F0F;
            laserCloudCornerLast->clear();
            pcl::fromROSMsg(*cornerLastBuf.front(), *laserCloudCornerLast);
            cornerLastBuf.pop();

            laserCloudSurfLast->clear();
            pcl::fromROSMsg(*surfLastBuf.front(), *laserCloudSurfLast);
            surfLastBuf.pop();

            laserCloudFullRes->clear();
            pcl::fromROSMsg(*fullResBuf.front(), *laserCloudFullRes);
            fullResBuf.pop();
            // lidar odom&#x7684;&#x7ED3;&#x679C;&#x8F6C;&#x6210;eigen&#x6570;&#x636E;&#x683C;&#x5F0F;
            q_wodom_curr.x() = odometryBuf.front()->pose.pose.orientation.x;
            q_wodom_curr.y() = odometryBuf.front()->pose.pose.orientation.y;
            q_wodom_curr.z() = odometryBuf.front()->pose.pose.orientation.z;
            q_wodom_curr.w() = odometryBuf.front()->pose.pose.orientation.w;
            t_wodom_curr.x() = odometryBuf.front()->pose.pose.position.x;
            t_wodom_curr.y() = odometryBuf.front()->pose.pose.position.y;
            t_wodom_curr.z() = odometryBuf.front()->pose.pose.position.z;
            odometryBuf.pop();
            // &#x8003;&#x8651;&#x5230;&#x5B9E;&#x65F6;&#x6027;&#xFF0C;&#x5C31;&#x628A;&#x961F;&#x5217;&#x91CC;&#x5176;&#x4ED6;&#x7684;&#x90FD;pop&#x51FA;&#x53BB;&#xFF0C;&#x4E0D;&#x7136;&#x53EF;&#x80FD;&#x51FA;&#x73B0;&#x5904;&#x7406;&#x5EF6;&#x65F6;&#x7684;&#x60C5;&#x51B5;
            // &#x5176;&#x4F59;&#x7684;&#x8FB9;&#x7EBF;&#x70B9;&#x6E05;&#x7A7A;
            // &#x4E3A;&#x4E86;&#x4FDD;&#x8BC1;LOAM&#x7B97;&#x6CD5;&#x6574;&#x4F53;&#x7684;&#x5B9E;&#x65F6;&#x6027;&#xFF0C;&#x56E0;Mapping&#x7EBF;&#x7A0B;&#x8017;&#x65F6;>100ms&#x5BFC;&#x81F4;&#x7684;&#x5386;&#x53F2;&#x7F13;&#x5B58;&#x90FD;&#x4F1A;&#x88AB;&#x6E05;&#x7A7A;
            while(!cornerLastBuf.empty())
            {
                cornerLastBuf.pop();
                printf("drop lidar frame in mapping for real time performance \n");
            }

            mBuf.unlock();

            TicToc t_whole;
            // &#x6839;&#x636E;&#x524D;&#x7AEF;&#x7ED3;&#x679C;&#xFF0C;&#x5F97;&#x5230;&#x540E;&#x7AEF;&#x7684;&#x4E00;&#x4E2A;&#x521D;&#x59CB;&#x4F30;&#x8BA1;&#x503C;
            transformAssociateToMap();

            TicToc t_shift;
            // &#x6839;&#x636E;&#x521D;&#x59CB;&#x4F30;&#x8BA1;&#x503C;&#x8BA1;&#x7B97;&#x5BFB;&#x627E;&#x5F53;&#x524D;&#x4F4D;&#x59FF;&#x5728;&#x5730;&#x56FE;&#x4E2D;&#x7684;&#x7D22;&#x5F15;&#xFF0C;&#x4E00;&#x4E2A;&#x5404;&#x81EA;&#x8FB9;&#x957F;&#x662F;50m
            // &#x540E;&#x7AEF;&#x7684;&#x5730;&#x56FE;&#x672C;&#x8D28;&#x4E0A;&#x662F;&#x4E00;&#x4E2A;&#x4EE5;&#x5F53;&#x524D;&#x70B9;&#x4E3A;&#x4E2D;&#x5FC3;&#xFF0C;&#x4E00;&#x4E2A;&#x73CA;&#x683C;&#x5730;&#x56FE;
            int centerCubeI = int((t_w_curr.x() + 25.0) / 50.0) + laserCloudCenWidth;
            int centerCubeJ = int((t_w_curr.y() + 25.0) / 50.0) + laserCloudCenHeight;
            int centerCubeK = int((t_w_curr.z() + 25.0) / 50.0) + laserCloudCenDepth;

            // &#x5982;&#x679C;&#x5C0F;&#x4E8E;25&#x5C31;&#x5411;&#x4E0B;&#x53D6;&#x6574;&#xFF0C;&#x76F8;&#x5F53;&#x4E8E;&#x56DB;&#x820D;&#x4E94;&#x5165;&#x7684;&#x4E00;&#x4E2A;&#x8FC7;&#x7A0B;
            // &#x7531;&#x4E8E;int(-2.1)=-2&#x662F;&#x5411;0&#x53D6;&#x6574;&#xFF0C;&#x5F53;&#x88AB;&#x6C42;&#x4F59;&#x6570;&#x4E3A;&#x8D1F;&#x6570;&#x65F6;&#x6C42;&#x4F59;&#x7ED3;&#x679C;&#x7EDF;&#x4E00;&#x5411;&#x5DE6;&#x504F;&#x79FB;&#x4E00;&#x4E2A;&#x5355;&#x4F4D;
            if (t_w_curr.x() + 25.0 < 0)
                centerCubeI--;
            if (t_w_curr.y() + 25.0 < 0)
                centerCubeJ--;
            if (t_w_curr.z() + 25.0 < 0)
                centerCubeK--;
            // &#x5982;&#x679C;&#x5F53;&#x524D;&#x73CA;&#x683C;&#x7D22;&#x5F15;&#x5C0F;&#x4E8E;3,&#x5C31;&#x8BF4;&#x660E;&#x5F53;&#x524D;&#x70B9;&#x5FEB;&#x63A5;&#x8FD1;&#x5730;&#x56FE;&#x8FB9;&#x754C;&#x4E86;&#xFF0C;&#x9700;&#x8981;&#x8FDB;&#x884C;&#x8C03;&#x6574;&#xFF0C;&#x76F8;&#x5F53;&#x4E8E;&#x5730;&#x56FE;&#x6574;&#x4F53;&#x5F80;x&#x6B63;&#x65B9;&#x5411;&#x79FB;&#x52A8;
            // &#x8C03;&#x6574;&#x53D6;&#x503C;&#x8303;&#x56F4;:3 < centerCubeI < 18&#xFF0C; 3 < centerCubeJ < 18, 3 < centerCubeK < 8
            // &#x5982;&#x679C;cube&#x5904;&#x4E8E;&#x8FB9;&#x754C;&#xFF0C;&#x5219;&#x5C06;cube&#x5411;&#x4E2D;&#x5FC3;&#x9760;&#x62E2;&#x4E00;&#x4E9B;&#xFF0C;&#x65B9;&#x4FBF;&#x540E;&#x7EED;&#x62D3;&#x5C55;cube
            while (centerCubeI < 3)
            {
                for (int j = 0; j < laserCloudHeight; j++)
                {
                    for (int k = 0; k < laserCloudDepth; k++)
                    {
                        int i = laserCloudWidth - 1;
                        // &#x4ECE;x&#x6700;&#x5927;&#x503C;&#x5F00;&#x59CB;
                        pcl::PointCloud<pointtype>::Ptr laserCloudCubeCornerPointer =
                            laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k];
                        pcl::PointCloud<pointtype>::Ptr laserCloudCubeSurfPointer =
                            laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k];
                        // &#x6574;&#x4F53;&#x53F3;&#x79FB;
                        for (; i >= 1; i--)
                        {
                            laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                                laserCloudCornerArray[i - 1 + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k];
                            laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                                laserCloudSurfArray[i - 1 + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k];
                        }
                        // &#x6B64;&#x65F6;i = 0,&#x4E5F;&#x5C31;&#x662F;&#x6700;&#x5DE6;&#x8FB9;&#x7684;&#x683C;&#x5B50;&#x8D4B;&#x503C;&#x4E86;&#x4E4B;&#x524D;&#x6700;&#x53F3;&#x8FB9;&#x7684;&#x683C;&#x5B50;
                        laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                            laserCloudCubeCornerPointer;
                        laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                            laserCloudCubeSurfPointer;
                        // &#x8BE5;&#x70B9;&#x4E91;&#x6E05;&#x96F6;&#xFF0C;&#x7531;&#x4E8E;&#x662F;&#x6307;&#x9488;&#x64CD;&#x4F5C;&#xFF0C;&#x76F8;&#x5F53;&#x4E8E;&#x6700;&#x5DE6;&#x8FB9;&#x7684;&#x683C;&#x5B50;&#x6E05;&#x7A7A;&#x4E86;
                        laserCloudCubeCornerPointer->clear();
                        laserCloudCubeSurfPointer->clear();
                    }
                }
                // &#x7D22;&#x5F15;&#x53F3;&#x79FB;
                centerCubeI++;
                laserCloudCenWidth++;
            }
            // &#x540C;&#x7406;x&#x5982;&#x679C;&#x62B5;&#x8FBE;&#x53F3;&#x8FB9;&#x754C;&#xFF0C;&#x5C31;&#x6574;&#x4F53;&#x5DE6;&#x79FB;
            while (centerCubeI >= laserCloudWidth - 3)
            {
                for (int j = 0; j < laserCloudHeight; j++)
                {
                    for (int k = 0; k < laserCloudDepth; k++)
                    {
                        int i = 0;
                        pcl::PointCloud<pointtype>::Ptr laserCloudCubeCornerPointer =
                            laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k];
                        pcl::PointCloud<pointtype>::Ptr laserCloudCubeSurfPointer =
                            laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k];
                        // &#x6574;&#x4F53;&#x5DE6;&#x79FB;
                        for (; i < laserCloudWidth - 1; i++)
                        {
                            laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                                laserCloudCornerArray[i + 1 + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k];
                            laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                                laserCloudSurfArray[i + 1 + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k];
                        }
                        laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                            laserCloudCubeCornerPointer;
                        laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                            laserCloudCubeSurfPointer;
                        laserCloudCubeCornerPointer->clear();
                        laserCloudCubeSurfPointer->clear();
                    }
                }

                centerCubeI--;
                laserCloudCenWidth--;
            }
            // y&#x548C;z&#x7684;&#x64CD;&#x4F5C;&#x540C;&#x7406;
            while (centerCubeJ < 3)
            {
                for (int i = 0; i < laserCloudWidth; i++)
                {
                    for (int k = 0; k < laserCloudDepth; k++)
                    {
                        int j = laserCloudHeight - 1;
                        pcl::PointCloud<pointtype>::Ptr laserCloudCubeCornerPointer =
                            laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k];
                        pcl::PointCloud<pointtype>::Ptr laserCloudCubeSurfPointer =
                            laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k];
                        for (; j >= 1; j--)
                        {
                            laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                                laserCloudCornerArray[i + laserCloudWidth * (j - 1) + laserCloudWidth * laserCloudHeight * k];
                            laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                                laserCloudSurfArray[i + laserCloudWidth * (j - 1) + laserCloudWidth * laserCloudHeight * k];
                        }
                        laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                            laserCloudCubeCornerPointer;
                        laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                            laserCloudCubeSurfPointer;
                        laserCloudCubeCornerPointer->clear();
                        laserCloudCubeSurfPointer->clear();
                    }
                }

                centerCubeJ++;
                laserCloudCenHeight++;
            }

            while (centerCubeJ >= laserCloudHeight - 3)
            {
                for (int i = 0; i < laserCloudWidth; i++)
                {
                    for (int k = 0; k < laserCloudDepth; k++)
                    {
                        int j = 0;
                        pcl::PointCloud<pointtype>::Ptr laserCloudCubeCornerPointer =
                            laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k];
                        pcl::PointCloud<pointtype>::Ptr laserCloudCubeSurfPointer =
                            laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k];
                        for (; j < laserCloudHeight - 1; j++)
                        {
                            laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                                laserCloudCornerArray[i + laserCloudWidth * (j + 1) + laserCloudWidth * laserCloudHeight * k];
                            laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                                laserCloudSurfArray[i + laserCloudWidth * (j + 1) + laserCloudWidth * laserCloudHeight * k];
                        }
                        laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                            laserCloudCubeCornerPointer;
                        laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                            laserCloudCubeSurfPointer;
                        laserCloudCubeCornerPointer->clear();
                        laserCloudCubeSurfPointer->clear();
                    }
                }

                centerCubeJ--;
                laserCloudCenHeight--;
            }

            while (centerCubeK < 3)
            {
                for (int i = 0; i < laserCloudWidth; i++)
                {
                    for (int j = 0; j < laserCloudHeight; j++)
                    {
                        int k = laserCloudDepth - 1;
                        pcl::PointCloud<pointtype>::Ptr laserCloudCubeCornerPointer =
                            laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k];
                        pcl::PointCloud<pointtype>::Ptr laserCloudCubeSurfPointer =
                            laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k];
                        for (; k >= 1; k--)
                        {
                            laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                                laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * (k - 1)];
                            laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                                laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * (k - 1)];
                        }
                        laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                            laserCloudCubeCornerPointer;
                        laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                            laserCloudCubeSurfPointer;
                        laserCloudCubeCornerPointer->clear();
                        laserCloudCubeSurfPointer->clear();
                    }
                }

                centerCubeK++;
                laserCloudCenDepth++;
            }

            while (centerCubeK >= laserCloudDepth - 3)
            {
                for (int i = 0; i < laserCloudWidth; i++)
                {
                    for (int j = 0; j < laserCloudHeight; j++)
                    {
                        int k = 0;
                        pcl::PointCloud<pointtype>::Ptr laserCloudCubeCornerPointer =
                            laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k];
                        pcl::PointCloud<pointtype>::Ptr laserCloudCubeSurfPointer =
                            laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k];
                        for (; k < laserCloudDepth - 1; k++)
                        {
                            laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                                laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * (k + 1)];
                            laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                                laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * (k + 1)];
                        }
                        laserCloudCornerArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                            laserCloudCubeCornerPointer;
                        laserCloudSurfArray[i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k] =
                            laserCloudCubeSurfPointer;
                        laserCloudCubeCornerPointer->clear();
                        laserCloudCubeSurfPointer->clear();
                    }
                }

                centerCubeK--;
                laserCloudCenDepth--;
            }
            // &#x4EE5;&#x4E0A;&#x64CD;&#x4F5C;&#x76F8;&#x5F53;&#x4E8E;&#x7EF4;&#x62A4;&#x4E86;&#x4E00;&#x4E2A;&#x5C40;&#x90E8;&#x5730;&#x56FE;&#xFF0C;&#x4FDD;&#x8BC1;&#x5F53;&#x524D;&#x5E27;&#x4E0D;&#x5728;&#x8FD9;&#x4E2A;&#x5C40;&#x90E8;&#x5730;&#x56FE;&#x7684;&#x8FB9;&#x7F18;&#xFF0C;&#x8FD9;&#x6837;&#x624D;&#x53EF;&#x4EE5;&#x4ECE;&#x5730;&#x56FE;&#x4E2D;&#x83B7;&#x53D6;&#x8DB3;&#x591F;&#x7684;&#x7EA6;&#x675F;
            int laserCloudValidNum = 0;
            int laserCloudSurroundNum = 0;
            // &#x4ECE;&#x5F53;&#x524D;&#x683C;&#x5B50;&#x4E3A;&#x4E2D;&#x5FC3;&#xFF0C;&#x9009;&#x51FA;&#x5730;&#x56FE;&#x4E2D;&#x4E00;&#x5B9A;&#x8303;&#x56F4;&#x7684;&#x70B9;&#x4E91;
            // &#x5411;IJ&#x5750;&#x6807;&#x8F74;&#x7684;&#x6B63;&#x8D1F;&#x65B9;&#x5411;&#x5404;&#x62D3;&#x5C55;2&#x4E2A;cube&#xFF0C;K&#x5750;&#x6807;&#x8F74;&#x7684;&#x6B63;&#x8D1F;&#x65B9;&#x5411;&#x5404;&#x62D3;&#x5C55;1&#x4E2A;cube
            // &#x5728;&#x6BCF;&#x4E00;&#x7EF4;&#x9644;&#x8FD1;5&#x4E2A;cube(&#x524D;2&#x4E2A;&#xFF0C;&#x540E;2&#x4E2A;&#xFF0C;&#x4E2D;&#x95F4;1&#x4E2A;)&#x91CC;&#x8FDB;&#x884C;&#x67E5;&#x627E;&#xFF08;&#x524D;&#x540E;250&#x7C73;&#x8303;&#x56F4;&#x5185;&#xFF0C;&#x603B;&#x5171;500&#x7C73;&#x8303;&#x56F4;&#xFF09;&#xFF0C;&#x4E09;&#x4E2A;&#x7EF4;&#x5EA6;&#x603B;&#x5171;125&#x4E2A;cube
            // &#x5728;&#x8FD9;125&#x4E2A;cube&#x91CC;&#x9762;&#x8FDB;&#x4E00;&#x6B65;&#x7B5B;&#x9009;&#x5728;&#x89C6;&#x57DF;&#x8303;&#x56F4;&#x5185;&#x7684;cube
            for (int i = centerCubeI - 2; i <= centercubei + 2; i++) { for (int j="centerCubeJ" - <="centerCubeJ" j++) k="centerCubeK" 1; k++) if (i>= 0 && i < laserCloudWidth &&
                            j >= 0 && j < laserCloudHeight &&
                            k >= 0 && k < laserCloudDepth)
                        {
                            // &#x628A;&#x5404;&#x81EA;&#x7684;&#x7D22;&#x5F15;&#x8BB0;&#x5F55;&#x4E0B;&#x6765;
                            laserCloudValidInd[laserCloudValidNum] = i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k;
                            laserCloudValidNum++;
                            laserCloudSurroundInd[laserCloudSurroundNum] = i + laserCloudWidth * j + laserCloudWidth * laserCloudHeight * k;
                            laserCloudSurroundNum++;
                        }
                    }
                }
            }

            // &#x5C06;&#x6709;&#x6548;index&#x7684;cube&#x4E2D;&#x7684;&#x70B9;&#x4E91;&#x53E0;&#x52A0;&#x5230;&#x4E00;&#x8D77;&#x7EC4;&#x6210;submap&#x7684;&#x7279;&#x5F81;&#x70B9;&#x4E91;
            laserCloudCornerFromMap->clear();
            laserCloudSurfFromMap->clear();
            // &#x5F00;&#x59CB;&#x6784;&#x5EFA;&#x7528;&#x6765;&#x8FD9;&#x4E00;&#x5E27;&#x4F18;&#x5316;&#x7684;&#x5C0F;&#x7684;&#x5C40;&#x90E8;&#x5730;&#x56FE;
            for (int i = 0; i < laserCloudValidNum; i++)
            {
                *laserCloudCornerFromMap += *laserCloudCornerArray[laserCloudValidInd[i]];
                *laserCloudSurfFromMap += *laserCloudSurfArray[laserCloudValidInd[i]];
            }
            int laserCloudCornerFromMapNum = laserCloudCornerFromMap->points.size();
            int laserCloudSurfFromMapNum = laserCloudSurfFromMap->points.size();

            // &#x4E3A;&#x4E86;&#x51CF;&#x5C11;&#x8FD0;&#x7B97;&#x91CF;&#xFF0C;&#x5BF9;&#x70B9;&#x4E91;&#x8FDB;&#x884C;&#x4E0B;&#x91C7;&#x6837;
            pcl::PointCloud<pointtype>::Ptr laserCloudCornerStack(new pcl::PointCloud<pointtype>());
            downSizeFilterCorner.setInputCloud(laserCloudCornerLast);
            downSizeFilterCorner.filter(*laserCloudCornerStack);
            int laserCloudCornerStackNum = laserCloudCornerStack->points.size();

            pcl::PointCloud<pointtype>::Ptr laserCloudSurfStack(new pcl::PointCloud<pointtype>());
            downSizeFilterSurf.setInputCloud(laserCloudSurfLast);
            downSizeFilterSurf.filter(*laserCloudSurfStack);
            int laserCloudSurfStackNum = laserCloudSurfStack->points.size();

            printf("map prepare time %f ms\n", t_shift.toc());
            printf("map corner num %d  surf num %d \n", laserCloudCornerFromMapNum, laserCloudSurfFromMapNum);</pointtype></pointtype></pointtype></pointtype></=></pointtype></pointtype></pointtype></pointtype></pointtype></pointtype></pointtype></pointtype></pointtype></pointtype></pointtype></pointtype>

scan to map 在submap的cube 与全部地图的cube匹配时,帧间匹配的方法不再适用,cube上的方法如下:

  1. 取当前帧的角点与面点

  2. 找到全部地图特征点中,当前特征点的5个最近邻点。

  3. 如果是角点,则以这五个点的均值点为中心,以5个点的主方向向量(类似于PCA方法)为方向,作一条直线,令该边线点与直线距离最短,构建非线性优化问题。

  4. 如果是平面点,则寻找五个点的法方向(反向的PCA方法),令这个平面点在法方向上与五个近邻点的距离最小,构建非线性优化问题。

  5. 优化变量是雷达位姿,求解能够让以上非线性问题代价函数最小的雷达位姿。

            // &#x6700;&#x7EC8;&#x7684;&#x6709;&#x6548;&#x70B9;&#x4E91;&#x6570;&#x76EE;&#x8FDB;&#x884C;&#x5224;&#x65AD;
            if (laserCloudCornerFromMapNum > 10 && laserCloudSurfFromMapNum > 50)
            {
                TicToc t_opt;
                TicToc t_tree;
                // &#x9001;&#x5165;kdtree&#x4FBF;&#x4E8E;&#x6700;&#x8FD1;&#x90BB;&#x641C;&#x7D22;
                kdtreeCornerFromMap->setInputCloud(laserCloudCornerFromMap);
                kdtreeSurfFromMap->setInputCloud(laserCloudSurfFromMap);
                printf("build tree time %f ms \n", t_tree.toc());
                // &#x5EFA;&#x7ACB;&#x5BF9;&#x5E94;&#x5173;&#x7CFB;&#x7684;&#x8FED;&#x4EE3;&#x6B21;&#x6570;&#x4E0D;&#x8D85;2&#x6B21; &#x4E24;&#x6B21;&#x975E;&#x7EBF;&#x6027;&#x4F18;&#x5316;
                for (int iterCount = 0; iterCount < 2; iterCount++)
                {
                    //ceres::LossFunction *loss_function = NULL;
                    // &#x5EFA;&#x7ACB;ceres&#x95EE;&#x9898;
                    ceres::LossFunction *loss_function = new ceres::HuberLoss(0.1);
                    ceres::LocalParameterization *q_parameterization =
                        new ceres::EigenQuaternionParameterization();
                    ceres::Problem::Options problem_options;

                    ceres::Problem problem(problem_options);
                    problem.AddParameterBlock(parameters, 4, q_parameterization);
                    problem.AddParameterBlock(parameters + 4, 3);

                    TicToc t_data;
                    int corner_num = 0;
                    // &#x6784;&#x5EFA;&#x89D2;&#x70B9;&#x76F8;&#x5173;&#x7684;&#x7EA6;&#x675F;
                    for (int i = 0; i < laserCloudCornerStackNum; i++)
                    {
                        pointOri = laserCloudCornerStack->points[i];
                        //double sqrtDis = pointOri.x * pointOri.x + pointOri.y * pointOri.y + pointOri.z * pointOri.z;
                        // &#x628A;&#x5F53;&#x524D;&#x70B9;&#x6839;&#x636E;&#x521D;&#x503C;&#x6295;&#x5230;&#x5730;&#x56FE;&#x5750;&#x6807;&#x7CFB;&#x4E0B;&#x53BB;
                        // submap&#x4E2D;&#x7684;&#x70B9;&#x4E91;&#x90FD;&#x662F;&#x5728;(map)world&#x5750;&#x6807;&#x7CFB;&#x4E0B;&#xFF0C;&#x800C;&#x63A5;&#x6536;&#x5230;&#x7684;&#x5F53;&#x524D;&#x5E27;&#x70B9;&#x4E91;&#x90FD;&#x662F;Lidar&#x5750;&#x6807;&#x7CFB;&#x4E0B;
                        // &#x6240;&#x4EE5;&#x5728;&#x641C;&#x5BFB;&#x6700;&#x8FD1;&#x90BB;&#x70B9;&#x65F6;&#xFF0C;&#x5148;&#x7528;&#x9884;&#x6D4B;&#x7684;Mapping&#x4F4D;&#x59FF;w_curr&#xFF0C;&#x5C06;Lidar&#x5750;&#x6807;&#x7CFB;&#x4E0B;&#x7684;&#x7279;&#x5F81;&#x70B9;&#x53D8;&#x6362;&#x5230;(map)world&#x5750;&#x6807;&#x7CFB;&#x4E0B;
                        pointAssociateToMap(&pointOri, &pointSel);
                        // &#x5730;&#x56FE;&#x4E2D;&#x5BFB;&#x627E;&#x548C;&#x8BE5;&#x70B9;&#x6700;&#x8FD1;&#x7684;5&#x4E2A;&#x70B9;
                        // &#x7279;&#x5F81;&#x70B9;&#x4E2D;&#xFF0C;&#x5BFB;&#x627E;&#x8DDD;&#x79BB;&#x5F53;&#x524D;&#x5E27;corner&#x7279;&#x5F81;&#x70B9;&#x6700;&#x8FD1;&#x7684;5&#x4E2A;&#x70B9;
                        kdtreeCornerFromMap->nearestKSearch(pointSel, 5, pointSearchInd, pointSearchSqDis);
                        // &#x5224;&#x65AD;&#x6700;&#x8FDC;&#x7684;&#x70B9;&#x8DDD;&#x79BB;&#x4E0D;&#x80FD;&#x8D85;&#x8FC7;1m&#xFF0C;&#x5426;&#x5219;&#x5C31;&#x662F;&#x65E0;&#x6548;&#x7EA6;&#x675F;
                        if (pointSearchSqDis[4] < 1.0)
                        {
                            // &#x8BA1;&#x7B97;&#x8FD9;&#x4E2A;5&#x4E2A;&#x6700;&#x8FD1;&#x90BB;&#x70B9;&#x7684;&#x4E2D;&#x5FC3;
                            std::vector<eigen::vector3d> nearCorners;
                            Eigen::Vector3d center(0, 0, 0);
                            for (int j = 0; j < 5; j++)
                            {
                                Eigen::Vector3d tmp(laserCloudCornerFromMap->points[pointSearchInd[j]].x,
                                                    laserCloudCornerFromMap->points[pointSearchInd[j]].y,
                                                    laserCloudCornerFromMap->points[pointSearchInd[j]].z);
                                center = center + tmp;
                                nearCorners.push_back(tmp);
                            }
                            // &#x8BA1;&#x7B97;&#x8FD9;&#x4E94;&#x4E2A;&#x70B9;&#x7684;&#x5747;&#x503C;
                            center = center / 5.0;

                            Eigen::Matrix3d covMat = Eigen::Matrix3d::Zero();
                            // &#x6784;&#x5EFA;5&#x4E2A;&#x6700;&#x8FD1;&#x90BB;&#x70B9;&#x7684;&#x534F;&#x65B9;&#x5DEE;&#x77E9;&#x9635;
                            for (int j = 0; j < 5; j++)
                            {
                                Eigen::Matrix<double, 1 3,> tmpZeroMean = nearCorners[j] - center;
                                covMat = covMat + tmpZeroMean * tmpZeroMean.transpose();
                            }
                            // &#x8FDB;&#x884C;&#x7279;&#x5F81;&#x503C;&#x5206;&#x89E3;
                            Eigen::SelfAdjointEigenSolver<eigen::matrix3d> saes(covMat);

                            // if is indeed line feature
                            // note Eigen library sort eigenvalues in increasing order
                            // &#x6839;&#x636E;&#x7279;&#x5F81;&#x503C;&#x5206;&#x89E3;&#x60C5;&#x51B5;&#x770B;&#x770B;&#x662F;&#x4E0D;&#x662F;&#x771F;&#x6B63;&#x7684;&#x7EBF;&#x7279;&#x5F81;
                            // &#x7279;&#x5F81;&#x5411;&#x91CF;&#x5C31;&#x662F;&#x7EBF;&#x7279;&#x5F81;&#x7684;&#x65B9;&#x5411;

                            // &#x8BA1;&#x7B97;&#x534F;&#x65B9;&#x5DEE;&#x77E9;&#x9635;&#x7684;&#x7279;&#x5F81;&#x503C;&#x548C;&#x7279;&#x5F81;&#x5411;&#x91CF;&#xFF0C;&#x7528;&#x4E8E;&#x5224;&#x65AD;&#x8FD9;5&#x4E2A;&#x70B9;&#x662F;&#x4E0D;&#x662F;&#x5448;&#x7EBF;&#x72B6;&#x5206;&#x5E03;&#xFF0C;&#x6B64;&#x4E3A;PCA&#x7684;&#x539F;&#x7406;
                            // &#x5982;&#x679C;5&#x4E2A;&#x70B9;&#x5448;&#x7EBF;&#x72B6;&#x5206;&#x5E03;&#xFF0C;&#x6700;&#x5927;&#x7684;&#x7279;&#x5F81;&#x503C;&#x5BF9;&#x5E94;&#x7684;&#x7279;&#x5F81;&#x5411;&#x91CF;&#x5C31;&#x662F;&#x8BE5;&#x7EBF;&#x7684;&#x65B9;&#x5411;&#x77E2;&#x91CF;
                            Eigen::Vector3d unit_direction = saes.eigenvectors().col(2);
                            Eigen::Vector3d curr_point(pointOri.x, pointOri.y, pointOri.z);
                            // &#x6700;&#x5927;&#x7279;&#x5F81;&#x503C;&#x5927;&#x4E8E;&#x6B21;&#x5927;&#x7279;&#x5F81;&#x503C;&#x7684;3&#x500D;&#x8BA4;&#x4E3A;&#x662F;&#x7EBF;&#x7279;&#x5F81;
                            // &#x5982;&#x679C;&#x6700;&#x5927;&#x7684;&#x7279;&#x5F81;&#x503C; >> &#x5176;&#x4ED6;&#x7279;&#x5F81;&#x503C;&#xFF0C;&#x5219;5&#x4E2A;&#x70B9;&#x786E;&#x5B9E;&#x5448;&#x7EBF;&#x72B6;&#x5206;&#x5E03;&#xFF0C;&#x5426;&#x5219;&#x8BA4;&#x4E3A;&#x76F4;&#x7EBF;&#x201C;&#x4E0D;&#x591F;&#x76F4;&#x201D;
                            if (saes.eigenvalues()[2] > 3 * saes.eigenvalues()[1])
                            {
                                Eigen::Vector3d point_on_line = center;
                                Eigen::Vector3d point_a, point_b;
                                // &#x6839;&#x636E;&#x62DF;&#x5408;&#x51FA;&#x6765;&#x7684;&#x7EBF;&#x7279;&#x5F81;&#x65B9;&#x5411;&#xFF0C;&#x4EE5;&#x5E73;&#x5747;&#x70B9;&#x4E3A;&#x4E2D;&#x5FC3;&#x6784;&#x5EFA;&#x4E24;&#x4E2A;&#x865A;&#x62DF;&#x70B9;
                                // &#x4ECE;&#x4E2D;&#x5FC3;&#x70B9;&#x6CBF;&#x7740;&#x65B9;&#x5411;&#x5411;&#x91CF;&#x5411;&#x4E24;&#x7AEF;&#x79FB;&#x52A8;0.1m&#xFF0C;&#x4F7F;&#x7528;&#x4E24;&#x4E2A;&#x70B9;&#x4EE3;&#x66FF;&#x4E00;&#x6761;&#x76F4;&#x7EBF;&#xFF0C;&#x8FD9;&#x6837;&#x8BA1;&#x7B97;&#x70B9;&#x5230;&#x76F4;&#x7EBF;&#x7684;&#x8DDD;&#x79BB;&#x7684;&#x5F62;&#x5F0F;&#x5C31;&#x8DDF;laserOdometry&#x76F8;&#x4F3C;
                                point_a = 0.1 * unit_direction + point_on_line;
                                point_b = -0.1 * unit_direction + point_on_line;
                                // &#x6784;&#x5EFA;&#x7EA6;&#x675F;&#xFF0C;&#x548C;lidar odom&#x7EA6;&#x675F;&#x4E00;&#x81F4;
                                ceres::CostFunction *cost_function = LidarEdgeFactor::Create(curr_point, point_a, point_b, 1.0);
                                problem.AddResidualBlock(cost_function, loss_function, parameters, parameters + 4);
                                corner_num++;
                            }
                        }
                        /*
                        else if(pointSearchSqDis[4] < 0.01 * sqrtDis)
                        {
                            Eigen::Vector3d center(0, 0, 0);
                            for (int j = 0; j < 5; j++)
                            {
                                Eigen::Vector3d tmp(laserCloudCornerFromMap->points[pointSearchInd[j]].x,
                                                    laserCloudCornerFromMap->points[pointSearchInd[j]].y,
                                                    laserCloudCornerFromMap->points[pointSearchInd[j]].z);
                                center = center + tmp;
                            }
                            center = center / 5.0;
                            Eigen::Vector3d curr_point(pointOri.x, pointOri.y, pointOri.z);
                            ceres::CostFunction *cost_function = LidarDistanceFactor::Create(curr_point, center);
                            problem.AddResidualBlock(cost_function, loss_function, parameters, parameters + 4);
                        }
                        */
                    }

                    int surf_num = 0;
                    // &#x6784;&#x5EFA;&#x9762;&#x70B9;&#x7EA6;&#x675F;
                    for (int i = 0; i < laserCloudSurfStackNum; i++)
                    {
                        pointOri = laserCloudSurfStack->points[i];
                        //double sqrtDis = pointOri.x * pointOri.x + pointOri.y * pointOri.y + pointOri.z * pointOri.z;
                        pointAssociateToMap(&pointOri, &pointSel);
                        kdtreeSurfFromMap->nearestKSearch(pointSel, 5, pointSearchInd, pointSearchSqDis);

                        Eigen::Matrix<double, 3 5,> matA0;
                        Eigen::Matrix<double, 1 5,> matB0 = -1 * Eigen::Matrix<double, 1 5,>::Ones();
                        // &#x6784;&#x5EFA;&#x5E73;&#x9762;&#x65B9;&#x7A0B;Ax + By +Cz + 1 = 0
                        // &#x901A;&#x8FC7;&#x6784;&#x5EFA;&#x4E00;&#x4E2A;&#x8D85;&#x5B9A;&#x65B9;&#x7A0B;&#x6765;&#x6C42;&#x89E3;&#x8FD9;&#x4E2A;&#x5E73;&#x9762;&#x65B9;&#x7A0B;
                        if (pointSearchSqDis[4] < 1.0)
                        {

                            for (int j = 0; j < 5; j++)
                            {
                                matA0(j, 0) = laserCloudSurfFromMap->points[pointSearchInd[j]].x;
                                matA0(j, 1) = laserCloudSurfFromMap->points[pointSearchInd[j]].y;
                                matA0(j, 2) = laserCloudSurfFromMap->points[pointSearchInd[j]].z;
                                //printf(" pts %f %f %f ", matA0(j, 0), matA0(j, 1), matA0(j, 2));
                            }
                            // find the norm of plane
                            // &#x8C03;&#x7528;eigen&#x63A5;&#x53E3;&#x6C42;&#x89E3;&#x8BE5;&#x65B9;&#x7A0B;&#xFF0C;&#x89E3;&#x5C31;&#x662F;&#x8FD9;&#x4E2A;&#x5E73;&#x9762;&#x7684;&#x6CD5;&#x5411;&#x91CF;
                            Eigen::Vector3d norm = matA0.colPivHouseholderQr().solve(matB0);
                            double negative_OA_dot_norm = 1 / norm.norm(); // &#x6CD5;&#x5411;&#x91CF;&#x957F;&#x5EA6;&#x7684;&#x5012;&#x6570;
                            // &#x6CD5;&#x5411;&#x91CF;&#x5F52;&#x4E00;&#x5316;
                            norm.normalize(); // &#x6CD5;&#x5411;&#x91CF;&#x5F52;&#x4E00;&#x5316;&#xFF0C;&#x76F8;&#x5F53;&#x4E8E;&#x76F4;&#x7EBF;Ax + By + Cz + 1 = 0&#x4E24;&#x8FB9;&#x90FD;&#x4E58;negative_OA_dot_norm

                            // Here n(pa, pb, pc) is unit norm of plane
                            bool planeValid = true;
                            // &#x6839;&#x636E;&#x6C42;&#x51FA;&#x6765;&#x7684;&#x5E73;&#x9762;&#x65B9;&#x7A0B;&#x8FDB;&#x884C;&#x6821;&#x9A8C;&#xFF0C;&#x770B;&#x770B;&#x662F;&#x4E0D;&#x662F;&#x7B26;&#x5408;&#x5E73;&#x9762;&#x7EA6;&#x675F;
                            for (int j = 0; j < 5; j++)
                            {
                                // if OX * n > 0.2, then plane is not fit well
                                // &#x8FD9;&#x91CC;&#x76F8;&#x5F53;&#x4E8E;&#x6C42;&#x89E3;&#x70B9;&#x5230;&#x5E73;&#x9762;&#x7684;&#x8DDD;&#x79BB;
                                // &#x70B9;(x0, y0, z0)&#x5230;&#x5E73;&#x9762;Ax + By + Cz + D = 0 &#x7684;&#x8DDD;&#x79BB;&#x516C;&#x5F0F; = fabs(Ax0 + By0 + Cz0 + D) / sqrt(A^2 + B^2 + C^2)
                                if (fabs(norm(0) * laserCloudSurfFromMap->points[pointSearchInd[j]].x +
                                         norm(1) * laserCloudSurfFromMap->points[pointSearchInd[j]].y +
                                         norm(2) * laserCloudSurfFromMap->points[pointSearchInd[j]].z + negative_OA_dot_norm) > 0.2)
                                {
                                    planeValid = false; // &#x70B9;&#x5982;&#x679C;&#x8DDD;&#x79BB;&#x5E73;&#x9762;&#x592A;&#x8FDC;&#xFF0C;&#x5C31;&#x8BA4;&#x4E3A;&#x8FD9;&#x662F;&#x4E00;&#x4E2A;&#x62DF;&#x5408;&#x7684;&#x4E0D;&#x597D;&#x7684;&#x5E73;&#x9762;
                                    break;
                                }
                            }
                            Eigen::Vector3d curr_point(pointOri.x, pointOri.y, pointOri.z);
                            // &#x5982;&#x679C;&#x5E73;&#x9762;&#x6709;&#x6548;&#x5C31;&#x6784;&#x5EFA;&#x5E73;&#x9762;&#x7EA6;&#x675F;
                            if (planeValid)
                            {
                                // &#x5229;&#x7528;&#x5E73;&#x9762;&#x65B9;&#x7A0B;&#x6784;&#x5EFA;&#x7EA6;&#x675F;&#xFF0C;&#x548C;&#x524D;&#x7AEF;&#x6784;&#x5EFA;&#x5F62;&#x5F0F;&#x7A0D;&#x6709;&#x4E0D;&#x540C;
                                ceres::CostFunction *cost_function = LidarPlaneNormFactor::Create(curr_point, norm, negative_OA_dot_norm);
                                problem.AddResidualBlock(cost_function, loss_function, parameters, parameters + 4);
                                surf_num++;
                            }
                        }
                        /*
                        else if(pointSearchSqDis[4] < 0.01 * sqrtDis)
                        {
                            Eigen::Vector3d center(0, 0, 0);
                            for (int j = 0; j < 5; j++)
                            {
                                Eigen::Vector3d tmp(laserCloudSurfFromMap->points[pointSearchInd[j]].x,
                                                    laserCloudSurfFromMap->points[pointSearchInd[j]].y,
                                                    laserCloudSurfFromMap->points[pointSearchInd[j]].z);
                                center = center + tmp;
                            }
                            center = center / 5.0;
                            Eigen::Vector3d curr_point(pointOri.x, pointOri.y, pointOri.z);
                            ceres::CostFunction *cost_function = LidarDistanceFactor::Create(curr_point, center);
                            problem.AddResidualBlock(cost_function, loss_function, parameters, parameters + 4);
                        }
                        */
                    }

                    //printf("corner num %d used corner num %d \n", laserCloudCornerStackNum, corner_num);
                    //printf("surf num %d used surf num %d \n", laserCloudSurfStackNum, surf_num);

                    printf("mapping data assosiation time %f ms \n", t_data.toc());
                    // &#x8C03;&#x7528;ceres&#x6C42;&#x89E3;
                    TicToc t_solver;
                    ceres::Solver::Options options;
                    options.linear_solver_type = ceres::DENSE_QR;
                    options.max_num_iterations = 4;
                    options.minimizer_progress_to_stdout = false;
                    options.check_gradients = false;
                    options.gradient_check_relative_precision = 1e-4;
                    ceres::Solver::Summary summary;
                    ceres::Solve(options, &problem, &summary);
                    printf("mapping solver time %f ms \n", t_solver.toc());

                    //printf("time %f \n", timeLaserOdometry);
                    //printf("corner factor num %d surf factor num %d\n", corner_num, surf_num);
                    //printf("result q %f %f %f %f result t %f %f %f\n", parameters[3], parameters[0], parameters[1], parameters[2],
                    //     parameters[4], parameters[5], parameters[6]);
                }
                printf("mapping optimization time %f \n", t_opt.toc());
            }
            else
            {
                ROS_WARN("time Map corner and surf num are not enough");
            }
            // &#x5B8C;&#x6210;&#xFF08;&#x8FED;&#x4EE3;2&#x6B21;&#xFF09;&#x7279;&#x5F81;&#x5339;&#x914D;&#x540E;&#xFF0C;&#x7528;&#x6700;&#x540E;&#x5339;&#x914D;&#x8BA1;&#x7B97;&#x51FA;&#x7684;&#x4F18;&#x5316;&#x53D8;&#x91CF;w_curr&#xFF0C;&#x66F4;&#x65B0;&#x589E;&#x91CF;wmap_wodom&#xFF0C;&#x8BA9;&#x4E0B;&#x4E00;&#x6B21;&#x7684;Mapping&#x521D;&#x503C;&#x66F4;&#x51C6;&#x786E;
            transformUpdate();</double,></double,></double,></eigen::matrix3d></double,></eigen::vector3d>

接下来就是一些后处理,即将当前帧的特征点加入到全部地图栅格中,对全部地图栅格中的点进行降采样,刷新附近点云地图,刷新全部点云地图,发布当前帧的精确位姿和平移估计。

            TicToc t_add;
            // &#x5C06;&#x4F18;&#x5316;&#x540E;&#x7684;&#x5F53;&#x524D;&#x5E27;&#x89D2;&#x70B9;&#x52A0;&#x5230;&#x5C40;&#x90E8;&#x5730;&#x56FE;&#x4E2D;&#x53BB;
            for (int i = 0; i < laserCloudCornerStackNum; i++)
            {
                // &#x8BE5;&#x70B9;&#x6839;&#x636E;&#x4F4D;&#x59FF;&#x6295;&#x5230;&#x5730;&#x56FE;&#x5750;&#x6807;&#x7CFB;
                pointAssociateToMap(&laserCloudCornerStack->points[i], &pointSel);
                // &#x7B97;&#x51FA;&#x8FD9;&#x4E2A;&#x70B9;&#x6240;&#x5728;&#x7684;&#x683C;&#x5B50;&#x7684;&#x7D22;&#x5F15;
                int cubeI = int((pointSel.x + 25.0) / 50.0) + laserCloudCenWidth;
                int cubeJ = int((pointSel.y + 25.0) / 50.0) + laserCloudCenHeight;
                int cubeK = int((pointSel.z + 25.0) / 50.0) + laserCloudCenDepth;
                // &#x540C;&#x6837;&#x56DB;&#x820D;&#x4E94;&#x5165;&#x4E00;&#x4E0B;
                if (pointSel.x + 25.0 < 0)
                    cubeI--;
                if (pointSel.y + 25.0 < 0)
                    cubeJ--;
                if (pointSel.z + 25.0 < 0)
                    cubeK--;
                // &#x5982;&#x679C;&#x8D85;&#x8FC7;&#x8FB9;&#x754C;&#x7684;&#x8BDD;&#x5C31;&#x7B97;&#x4E86;
                if (cubeI >= 0 && cubeI < laserCloudWidth &&
                    cubeJ >= 0 && cubeJ < laserCloudHeight &&
                    cubeK >= 0 && cubeK < laserCloudDepth)
                {
                    // &#x6839;&#x636E;xyz&#x7684;&#x7D22;&#x5F15;&#x8BA1;&#x7B97;&#x5728;&#x4E00;&#x4F4D;&#x6570;&#x7EC4;&#x4E2D;&#x7684;&#x7D22;&#x5F15;
                    int cubeInd = cubeI + laserCloudWidth * cubeJ + laserCloudWidth * laserCloudHeight * cubeK;
                    laserCloudCornerArray[cubeInd]->push_back(pointSel);
                }
            }
            // &#x9762;&#x70B9;&#x4E5F;&#x505A;&#x540C;&#x6837;&#x7684;&#x5904;&#x7406;
            for (int i = 0; i < laserCloudSurfStackNum; i++)
            {
                pointAssociateToMap(&laserCloudSurfStack->points[i], &pointSel);

                int cubeI = int((pointSel.x + 25.0) / 50.0) + laserCloudCenWidth;
                int cubeJ = int((pointSel.y + 25.0) / 50.0) + laserCloudCenHeight;
                int cubeK = int((pointSel.z + 25.0) / 50.0) + laserCloudCenDepth;

                if (pointSel.x + 25.0 < 0)
                    cubeI--;
                if (pointSel.y + 25.0 < 0)
                    cubeJ--;
                if (pointSel.z + 25.0 < 0)
                    cubeK--;

                if (cubeI >= 0 && cubeI < laserCloudWidth &&
                    cubeJ >= 0 && cubeJ < laserCloudHeight &&
                    cubeK >= 0 && cubeK < laserCloudDepth)
                {
                    int cubeInd = cubeI + laserCloudWidth * cubeJ + laserCloudWidth * laserCloudHeight * cubeK;
                    laserCloudSurfArray[cubeInd]->push_back(pointSel);
                }
            }
            printf("add points time %f ms\n", t_add.toc());

            TicToc t_filter;
            // &#x628A;&#x5F53;&#x524D;&#x5E27;&#x6D89;&#x53CA;&#x5230;&#x7684;&#x5C40;&#x90E8;&#x5730;&#x56FE;&#x7684;&#x73CA;&#x683C;&#x505A;&#x4E00;&#x4E2A;&#x4E0B;&#x91C7;&#x6837;
            for (int i = 0; i < laserCloudValidNum; i++)
            {
                int ind = laserCloudValidInd[i];

                pcl::PointCloud<pointtype>::Ptr tmpCorner(new pcl::PointCloud<pointtype>());
                downSizeFilterCorner.setInputCloud(laserCloudCornerArray[ind]);
                downSizeFilterCorner.filter(*tmpCorner);
                laserCloudCornerArray[ind] = tmpCorner;

                pcl::PointCloud<pointtype>::Ptr tmpSurf(new pcl::PointCloud<pointtype>());
                downSizeFilterSurf.setInputCloud(laserCloudSurfArray[ind]);
                downSizeFilterSurf.filter(*tmpSurf);
                laserCloudSurfArray[ind] = tmpSurf;
            }
            printf("filter time %f ms \n", t_filter.toc());

            TicToc t_pub;
            //publish surround map for every 5 frame
            // &#x6BCF;&#x9694;5&#x5E27;&#x5BF9;&#x5916;&#x53D1;&#x5E03;&#x4E00;&#x4E0B;
            if (frameCount % 5 == 0)
            {
                laserCloudSurround->clear();
                // &#x628A;&#x8BE5;&#x5F53;&#x524D;&#x5E27;&#x76F8;&#x5173;&#x7684;&#x5C40;&#x90E8;&#x5730;&#x56FE;&#x53D1;&#x5E03;&#x51FA;&#x53BB;
                for (int i = 0; i < laserCloudSurroundNum; i++)
                {
                    int ind = laserCloudSurroundInd[i];
                    *laserCloudSurround += *laserCloudCornerArray[ind];
                    *laserCloudSurround += *laserCloudSurfArray[ind];
                }

                sensor_msgs::PointCloud2 laserCloudSurround3;
                pcl::toROSMsg(*laserCloudSurround, laserCloudSurround3);
                laserCloudSurround3.header.stamp = ros::Time().fromSec(timeLaserOdometry);
                laserCloudSurround3.header.frame_id = "/camera_init";
                pubLaserCloudSurround.publish(laserCloudSurround3);
            }
            // &#x6BCF;&#x9694;20&#x5E27;&#x53D1;&#x5E03;&#x5168;&#x91CF;&#x7684;&#x5C40;&#x90E8;&#x5730;&#x56FE;
            if (frameCount % 20 == 0)
            {
                pcl::PointCloud<pointtype> laserCloudMap;
                // 21 &#xD7; 21 &#xD7; 11 = 4851
                for (int i = 0; i < 4851; i++)
                {
                    laserCloudMap += *laserCloudCornerArray[i];
                    laserCloudMap += *laserCloudSurfArray[i];
                }
                sensor_msgs::PointCloud2 laserCloudMsg;
                pcl::toROSMsg(laserCloudMap, laserCloudMsg);
                laserCloudMsg.header.stamp = ros::Time().fromSec(timeLaserOdometry);
                laserCloudMsg.header.frame_id = "/camera_init";
                pubLaserCloudMap.publish(laserCloudMsg);
            }

            int laserCloudFullResNum = laserCloudFullRes->points.size();
            // &#x628A;&#x5F53;&#x524D;&#x5E27;&#x53D1;&#x5E03;&#x51FA;&#x53BB;
            for (int i = 0; i < laserCloudFullResNum; i++)
            {
                pointAssociateToMap(&laserCloudFullRes->points[i], &laserCloudFullRes->points[i]);
            }

            sensor_msgs::PointCloud2 laserCloudFullRes3;
            pcl::toROSMsg(*laserCloudFullRes, laserCloudFullRes3);
            laserCloudFullRes3.header.stamp = ros::Time().fromSec(timeLaserOdometry);
            laserCloudFullRes3.header.frame_id = "/camera_init";
            pubLaserCloudFullRes.publish(laserCloudFullRes3);

            printf("mapping pub time %f ms \n", t_pub.toc());

            printf("whole mapping time %f ms +++++\n", t_whole.toc());
            // &#x53D1;&#x5E03;&#x5F53;&#x524D;&#x4F4D;&#x59FF;
            nav_msgs::Odometry odomAftMapped;
            odomAftMapped.header.frame_id = "/camera_init";
            odomAftMapped.child_frame_id = "/aft_mapped";
            odomAftMapped.header.stamp = ros::Time().fromSec(timeLaserOdometry);
            odomAftMapped.pose.pose.orientation.x = q_w_curr.x();
            odomAftMapped.pose.pose.orientation.y = q_w_curr.y();
            odomAftMapped.pose.pose.orientation.z = q_w_curr.z();
            odomAftMapped.pose.pose.orientation.w = q_w_curr.w();
            odomAftMapped.pose.pose.position.x = t_w_curr.x();
            odomAftMapped.pose.pose.position.y = t_w_curr.y();
            odomAftMapped.pose.pose.position.z = t_w_curr.z();
            pubOdomAftMapped.publish(odomAftMapped);
            // &#x53D1;&#x5E03;&#x5F53;&#x524D;&#x8F68;&#x8FF9;
            geometry_msgs::PoseStamped laserAfterMappedPose;
            laserAfterMappedPose.header = odomAftMapped.header;
            laserAfterMappedPose.pose = odomAftMapped.pose.pose;
            laserAfterMappedPath.header.stamp = odomAftMapped.header.stamp;
            laserAfterMappedPath.header.frame_id = "/camera_init";
            laserAfterMappedPath.poses.push_back(laserAfterMappedPose);
            pubLaserAfterMappedPath.publish(laserAfterMappedPath);
            // &#x53D1;&#x5E03;tf
            static tf::TransformBroadcaster br;
            tf::Transform transform;
            tf::Quaternion q;
            transform.setOrigin(tf::Vector3(t_w_curr(0),
                                            t_w_curr(1),
                                            t_w_curr(2)));
            q.setW(q_w_curr.w());
            q.setX(q_w_curr.x());
            q.setY(q_w_curr.y());
            q.setZ(q_w_curr.z());
            transform.setRotation(q);
            br.sendTransform(tf::StampedTransform(transform, odomAftMapped.header.stamp, "/camera_init", "/aft_mapped"));

            frameCount++;
        }
        std::chrono::milliseconds dura(2);
        std::this_thread::sleep_for(dura);
    }</pointtype></pointtype></pointtype></pointtype></pointtype>

4.2、总结概括

该部分主要实现低频率高精度的地图构建。该部分的算法流程如论文中可参考下图理解

无人驾驶学习笔记 - A-LOAM 算法代码解析总结

关于坐标之间的转换是理解代码的关键,关于三个坐标系之间的转换关系,可以参考知乎大佬的图

SLAM前端入门框架-A_LOAM源码解析 – 知乎

下面皆引自上述链接

无人驾驶学习笔记 - A-LOAM 算法代码解析总结

我们再来描述一下这个过程,首先每一帧的里程计坐标从上一个节点不断传来,起初里程计坐标和世界坐标下是没有偏差的,利用里程计坐标和两者之间的变化来求解世界坐标。优化后得到准确的世界坐标。利用这个准确的坐标和这一点的里程计坐标来求解,里程计坐标和世界坐标之间的变化,来给下一帧的激光里程计作为估计参考。
接下来是代码流程部分:
具体看下图:

无人驾驶学习笔记 - A-LOAM 算法代码解析总结

上述图已经描绘的很清楚了,我是按照代码片段和个人的一些判断分成了15个步骤,每一步骤都有具体解释。比较复杂的可能也就是两部分,一部分是关于大cube的构建和移动,另一部分就是利用特征值和特征向量来计算线和面了

首先是cube的构建:

无人驾驶学习笔记 - A-LOAM 算法代码解析总结

A_LOAM在实现的过程中构建了一个212111的大立方体。
在流程图的第1步初始化时给其分配了对应的空间。
在流程图第5步中计算当前世界坐标系下的位置,就是计算上图中红色五角星的位置。对应的蓝色cube也就是此刻世界坐标下的点云。
其次是调整cube和此刻的相对位置:
流程图第6步所做的事情就是将这个这个大cube(212111)的相对中间的位置为红色五角星(此刻点云在世界坐标系的位置),也就是通过6个while循环,移动到(3-18,3-18,3-11)的位置。具体移动一小格的示意图见下图

无人驾驶学习笔记 - A-LOAM 算法代码解析总结

5、lidarFactor.cpp

在laserOdometry和laserMapping中利用特征来计算位姿变化时,需要计算残差,这里就是来计算残差的。

下面介绍下ceres优化问题如何求解:定义一个模板类,重写优化问题,在这个类中定义残差,重载()运算符,()运算符函数前⼏个参数是参数块的起始指针,最后⼀个参数是残差块的指针,()运算符负责计算残差。下面是优化边线点的模板类程序。

线残差计算方式(引自 SLAM前端入门框架-A_LOAM源码解析 – 知乎

无人驾驶学习笔记 - A-LOAM 算法代码解析总结

点线特征模板程序

struct LidarEdgeFactor
{
    LidarEdgeFactor(Eigen::Vector3d curr_point_, Eigen::Vector3d last_point_a_,
                    Eigen::Vector3d last_point_b_, double s_)
        : curr_point(curr_point_), last_point_a(last_point_a_), last_point_b(last_point_b_), s(s_) {}

    template <typename t>
    bool operator()(const T *q, const T *t, T *residual) const
    {
        // &#x5C06;double&#x6570;&#x7EC4;&#x8F6C;&#x6210;eigen&#x7684;&#x6570;&#x636E;&#x7ED3;&#x6784;&#xFF0C;&#x6CE8;&#x610F;&#x8FD9;&#x91CC;&#x5FC5;&#x987B;&#x90FD;&#x5199;&#x6210;&#x6A21;&#x677F;
        Eigen::Matrix<t, 1 3,> cp{T(curr_point.x()), T(curr_point.y()), T(curr_point.z())};
        Eigen::Matrix<t, 1 3,> lpa{T(last_point_a.x()), T(last_point_a.y()), T(last_point_a.z())};
        Eigen::Matrix<t, 1 3,> lpb{T(last_point_b.x()), T(last_point_b.y()), T(last_point_b.z())};

        //Eigen::Quaternion<t> q_last_curr{q[3], T(s) * q[0], T(s) * q[1], T(s) * q[2]};
        Eigen::Quaternion<t> q_last_curr{q[3], q[0], q[1], q[2]};
        Eigen::Quaternion<t> q_identity{T(1), T(0), T(0), T(0)};
        // &#x8BA1;&#x7B97;&#x7684;&#x662F;&#x4E0A;&#x4E00;&#x5E27;&#x5230;&#x5F53;&#x524D;&#x5E27;&#x7684;&#x4F4D;&#x59FF;&#x53D8;&#x6362;&#xFF0C;&#x56E0;&#x6B64;&#x6839;&#x636E;&#x5300;&#x901F;&#x6A21;&#x578B;&#xFF0C;&#x8BA1;&#x7B97;&#x8BE5;&#x70B9;&#x5BF9;&#x5E94;&#x7684;&#x4F4D;&#x59FF;
        // &#x8FD9;&#x91CC;&#x6682;&#x65F6;&#x4E0D;&#x8003;&#x8651;&#x7578;&#x53D8;&#xFF0C;&#x56E0;&#x6B64;&#x8FD9;&#x91CC;&#x4E0D;&#x505A;&#x4EFB;&#x4F55;&#x53D8;&#x6362;
        q_last_curr = q_identity.slerp(T(s), q_last_curr);
        Eigen::Matrix<t, 1 3,> t_last_curr{T(s) * t[0], T(s) * t[1], T(s) * t[2]};

        Eigen::Matrix<t, 1 3,> lp;
        // &#x628A;&#x5F53;&#x524D;&#x70B9;&#x6839;&#x636E;&#x5F53;&#x524D;&#x8BA1;&#x7B97;&#x7684;&#x5E27;&#x95F4;&#x4F4D;&#x59FF;&#x53D8;&#x6362;&#x5230;&#x4E0A;&#x4E00;&#x5E27;
        lp = q_last_curr * cp + t_last_curr;

        Eigen::Matrix<t, 1 3,> nu = (lp - lpa).cross(lp - lpb); // &#x6A21;&#x662F;&#x4E09;&#x89D2;&#x5F62;&#x7684;&#x9762;&#x79EF;
        Eigen::Matrix<t, 1 3,> de = lpa - lpb;
        // &#x6B8B;&#x5DEE;&#x7684;&#x6A21;&#x662F;&#x8BE5;&#x70B9;&#x5230;&#x5E95;&#x8FB9;&#x7684;&#x5782;&#x7EBF;&#x957F;&#x5EA6;
        // &#x8FD9;&#x91CC;&#x611F;&#x89C9;&#x4E0D;&#x9700;&#x8981;&#x5B9A;&#x4E49;&#x4E09;&#x7EF4;
        residual[0] = nu.x() / de.norm();
        residual[1] = nu.y() / de.norm();
        residual[2] = nu.z() / de.norm();

        return true;
    }

    static ceres::CostFunction *Create(const Eigen::Vector3d curr_point_, const Eigen::Vector3d last_point_a_,
                                       const Eigen::Vector3d last_point_b_, const double s_)
    {
        return (new ceres::AutoDiffCostFunction<
                LidarEdgeFactor, 3, 4, 3>(
            new LidarEdgeFactor(curr_point_, last_point_a_, last_point_b_, s_)));
    }

    Eigen::Vector3d curr_point, last_point_a, last_point_b;
    double s;
};
</t,></t,></t,></t,></t></t></t></t,></t,></t,></typename>

面参数计算方法:

无人驾驶学习笔记 - A-LOAM 算法代码解析总结

面点残差模板

struct LidarPlaneFactor
{
    LidarPlaneFactor(Eigen::Vector3d curr_point_, Eigen::Vector3d last_point_j_,
                     Eigen::Vector3d last_point_l_, Eigen::Vector3d last_point_m_, double s_)
        : curr_point(curr_point_), last_point_j(last_point_j_), last_point_l(last_point_l_),
          last_point_m(last_point_m_), s(s_)
    {
        // &#x6C42;&#x51FA;&#x5E73;&#x9762;&#x5355;&#x4F4D;&#x6CD5;&#x5411;&#x91CF;
        ljm_norm = (last_point_j - last_point_l).cross(last_point_j - last_point_m);
        ljm_norm.normalize();
    }

    template <typename t>
    bool operator()(const T *q, const T *t, T *residual) const
    {

        Eigen::Matrix<t, 1 3,> cp{T(curr_point.x()), T(curr_point.y()), T(curr_point.z())};
        Eigen::Matrix<t, 1 3,> lpj{T(last_point_j.x()), T(last_point_j.y()), T(last_point_j.z())};
        //Eigen::Matrix<t, 1 3,> lpl{T(last_point_l.x()), T(last_point_l.y()), T(last_point_l.z())};
        //Eigen::Matrix<t, 1 3,> lpm{T(last_point_m.x()), T(last_point_m.y()), T(last_point_m.z())};
        Eigen::Matrix<t, 1 3,> ljm{T(ljm_norm.x()), T(ljm_norm.y()), T(ljm_norm.z())};

        //Eigen::Quaternion<t> q_last_curr{q[3], T(s) * q[0], T(s) * q[1], T(s) * q[2]};
        Eigen::Quaternion<t> q_last_curr{q[3], q[0], q[1], q[2]};
        Eigen::Quaternion<t> q_identity{T(1), T(0), T(0), T(0)};
        // &#x6839;&#x636E;&#x65F6;&#x95F4;&#x6233;&#x8FDB;&#x884C;&#x63D2;&#x503C;
        q_last_curr = q_identity.slerp(T(s), q_last_curr);
        Eigen::Matrix<t, 1 3,> t_last_curr{T(s) * t[0], T(s) * t[1], T(s) * t[2]};

        Eigen::Matrix<t, 1 3,> lp;
        lp = q_last_curr * cp + t_last_curr;
        // &#x70B9;&#x5230;&#x5E73;&#x9762;&#x7684;&#x8DDD;&#x79BB;
        residual[0] = (lp - lpj).dot(ljm);

        return true;
    }

    static ceres::CostFunction *Create(const Eigen::Vector3d curr_point_, const Eigen::Vector3d last_point_j_,
                                       const Eigen::Vector3d last_point_l_, const Eigen::Vector3d last_point_m_,
                                       const double s_)
    {
        return (new ceres::AutoDiffCostFunction<
                LidarPlaneFactor, 1, 4, 3>(
            new LidarPlaneFactor(curr_point_, last_point_j_, last_point_l_, last_point_m_, s_)));
    }

    Eigen::Vector3d curr_point, last_point_j, last_point_l, last_point_m;
    Eigen::Vector3d ljm_norm;
    double s;
};
</t,></t,></t></t></t></t,></t,></t,></t,></t,></typename>

关于ceres 优化器的使用,可参考 高博的《视觉slam十四讲》熟悉熟悉。推荐ceres官网(http://www.ceres-solver.org/

6、参考连接

SLAM前端入门框架-A_LOAM源码解析 – 知乎

从A_LOAM开始入门3D激光SLAM – 知乎

二十七-VIO-SLAM开源框架Vin-mono跑EuRoC数据集 – 知乎

LOAM细节分析 – 知乎

loam代码解析 – 知乎

Original: https://blog.csdn.net/p942005405/article/details/125039397
Author: ppipp1109
Title: 无人驾驶学习笔记 – A-LOAM 算法代码解析总结

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/648033/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球