3D激光SLAM:ALOAM---后端 lasermapping构建角点约束与面点约束

简介: 后端的构建约束问题和前端不一样。原因就是前端从上一帧上去找,而后端是在局部地图上找,点要多很多,并且没有了线束信息,所以原理上不一样了。**线特征的提取**通过kdtree在局部地图中找到5个最近的线特征,为了判断他们是否符合线特征的特性,需要对5个点构成的协方差矩阵进行特征值分解,当上述5个点在一条直线上时,他们只有一个主方向,也就是特征值是一个大特征值,以及两个小特征值,大特征值对应的特征向量就是对应直线的方向向量。**面特征的提取**通过kdtree在地图中找到最近的面特征也是5个, 理论上也可以通过特种值分解的方式,最小的特征值对应的特征向量就是平面的法向量, 不过代码里选

前言

上一篇博客中地图栅格化处理与提取完成了从整个地图中提取出要和当前帧做匹配的局部地图

下面要做的就是要构建角点约束和面点约束

后端的构建约束问题和前端不一样。原因就是前端从上一帧上去找,而后端是在局部地图上找,点要多很多,并且没有了线束信息,所以原理上不一样了。

线特征的提取
通过kdtree在局部地图中找到5个最近的线特征,为了判断他们是否符合线特征的特性,需要对5个点构成的协方差矩阵进行特征值分解,当上述5个点在一条直线上时,他们只有一个主方向,也就是特征值是一个大特征值,以及两个小特征值,大特征值对应的特征向量就是对应直线的方向向量。

面特征的提取
通过kdtree在地图中找到最近的面特征也是5个, 理论上也可以通过特种值分解的方式,最小的特征值对应的特征向量就是平面的法向量, 不过代码里选用的是平面拟合的方式。

平面方程为 Ax+By+Cz+D=0,D可以当成1(相当于等号两边分别乘1/D),也就是3个未知数,5个方程。写成矩阵的形式,可以构成5*3的矩阵,线性方程组,可以求出解。得到结果后,验证的方法为分别求出5个点到平面的距离,如果太远,则说明平面拟合不成功。

代码分析

代码在lasermapping.cpp中
上一篇博客中地图栅格化处理与提取,得到了局部地图,继续代码的分析

            if (laserCloudCornerFromMapNum > 10 && laserCloudSurfFromMapNum > 50)//角点和面点个数满足要求
            {

局部地图中的有效点云数目进行判断
局部地图中的角点和面点不能太少,要求

  • 角点不少于10个
  • 面点不少于50个
                kdtreeCornerFromMap->setInputCloud(laserCloudCornerFromMap);
                kdtreeSurfFromMap->setInputCloud(laserCloudSurfFromMap);
                printf("build tree time %f ms \n", t_tree.toc());//打印建立kd-tree的时间

把局部地图的角点和面点 送入KD tree 之后进行最近邻搜索
通过pcl建立kd-tree 还是比较耗时的

之后进入优化的迭代

                for (int iterCount = 0; iterCount < 2; iterCount++)//迭代两次
                {

建立对应关系的迭代次数不超过2次

                    ceres::LossFunction *loss_function = new ceres::HuberLoss(0.1);//生成损失函数
                    ceres::LocalParameterization *q_parameterization =
                        new ceres::EigenQuaternionParameterization();// 添加四元数得参数模块
                    ceres::Problem::Options problem_options;//声明ceres的 优化问题

                    ceres::Problem problem(problem_options);// 构建问题
                    problem.AddParameterBlock(parameters, 4, q_parameterization);//添加四元数得参数模块
                    problem.AddParameterBlock(parameters + 4, 3);//平移的参数块,parameters+4就是上面四元数后的指针便宜,3就是平移的参数个数

建立ceres问题
建立参数块
这边和帧间里程计类似,就是参数块的建立有些区别,帧间是用了四元数和平移两个数组,这边用的parameters一个数组。
前4个是四元数,后三个是平移
在这里插入图片描述
下面开始构建角点约束

构建角点约束

                    for (int i = 0; i < laserCloudCornerStackNum; i++)
                    {

循环上一帧(滤波后)的每个角点

                        pointOri = laserCloudCornerStack->points[i];//取出该点
                        // 把当前点根据初值投影到地图坐标系下去
                        pointAssociateToMap(&pointOri, &pointSel);

取出该点
把当前点根据初值投影到地图坐标系下去
投影的方法就是用当前的初始位姿估计,做位姿变换(乘四元数加平移)
在这里插入图片描述

                        // 地图中寻找和该点最近的5个点
                        kdtreeCornerFromMap->nearestKSearch(pointSel, 5, pointSearchInd, pointSearchSqDis); 

通过kd-tree在局部地图中寻找和该点最近的5个点
点的索引在 pointSearchInd 数组中
点的距离在 pointSearchSqDis 数组中

                        // 判断最远的点的距离不能超过1m,否则就是无效约束
                        if (pointSearchSqDis[4] < 1.0)
                        { 

判断最远的点的距离不能超过1m,否则就是无效约束

                            std::vector<Eigen::Vector3d> nearCorners;//5个最近点构成的数组
                            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);//构建5个点的数组
                            }
                            //计算这5个点的均值
                            center = center / 5.0;

由这5个点构成一个数组,并计算中心点

                            //构建协方差矩阵
                            for (int j = 0; j < 5; j++)
                            {
                                Eigen::Matrix<double, 3, 1> tmpZeroMean = nearCorners[j] - center;//每个点减去均值
                                covMat = covMat + tmpZeroMean * tmpZeroMean.transpose();
                            }

构建协方差矩阵
方法就是:

  • 每个点减去均值
  • 各自点向量乘上各自点向量的转置,然后加在一起
                            //进行特征值分解
                            Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> saes(covMat);

通过Eigen的特征值分解器进行特征值分解

          Eigen::Vector3d unit_direction = saes.eigenvectors().col(2);

分解后特征向量是根据特征值大小,由大到小排列,这里取出最后一列的特征向量,即线的方向

                            if (saes.eigenvalues()[2] > 3 * saes.eigenvalues()[1])
                            { 

最大特征值大于次特征值的3倍,认为该5个点构成的是一条线,满足线特征

                                Eigen::Vector3d point_on_line = center;
                                Eigen::Vector3d point_a, point_b;
                                // 根据拟合出来的线特征方向,以平均点为中心,建立两个虚拟点
                                point_a = 0.1 * unit_direction + point_on_line;
                                point_b = -0.1 * unit_direction + point_on_line;
                                // 构建角点约束,和lidar odom 约束一致

根据拟合出来的线特征方向,以平均点为中心,建立两个虚拟点
在这里插入图片描述

                                // 构建角点约束,和lidar odom 约束一致
                                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++;    

构建角点约束,和lidar odom 约束一致

构建面点约束

下面是构建面点约束

                    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);//从kd-tree中找到5个最近邻的面点

把该投影到局部地图中,从kd-tree中找到5个最近邻的面点

下面开始构建方程,方程如下:
在这里插入图片描述
在这里插入图片描述

                        Eigen::Matrix<double, 5, 3> matA0;//等号左边5*3的矩阵
                        Eigen::Matrix<double, 5, 1> matB0 = -1 * Eigen::Matrix<double, 5, 1>::Ones();//等号右边5*1的矩阵

声明等号左边和右边的矩阵

                        //构建 Ax+By+Cz+1=0 的平面方程
                        // 通过构建超定方程来求解这个平面方程
                        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
                            Eigen::Vector3d norm = matA0.colPivHouseholderQr().solve(matB0);
                            // 法向量模的倒数
                            double negative_OA_dot_norm = 1 / norm.norm();
                            //法向量归一化
                            norm.normalize();

最远的面点满足条件后,开始构建 Ax+By+Cz+1=0 的平面方程
然后通过构建超定方程来求解这个平面方程

用的Eigen 的colPivHouseholderQr 求解方法,结果 norm 就是[A B C ]
也就是平面的法向量

下面通过点到平面的距离来看是否符合平面约束
在这里插入图片描述

                            bool planeValid = true;
                            // 根据求出来的平面方程进行校验,看看是否符合平面约束
                            for (int j = 0; j < 5; j++)
                            {
                                // if OX * n > 0.2, then plane is not fit well
                                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;
                                    break;
                                }
                            }

代码里好像和公式不太一样,其实是一样的
首先法向量已经被归一化了,所以分母部分已经是1了
D就成了上面求得法向量模的倒数
然后距离和0.2m做判断,大于0.2m则认为平面拟合不成功

                            Eigen::Vector3d curr_point(pointOri.x, pointOri.y, pointOri.z);//取出该帧的点
                            //如果平面有效,则添加面点的平面约束
                            if (planeValid)
                            {
                                //利用平面方程构建约束,和前端有所不同
                                ceres::CostFunction *cost_function = LidarPlaneNormFactor::Create(curr_point, norm, negative_OA_dot_norm);
                                problem.AddResidualBlock(cost_function, loss_function, parameters, parameters + 4);
                                surf_num++;
                            }

如果平面有效,则添加面点的平面约束
利用平面方程构建约束,和前端有所不同

总结

  • 对局部地图的角度和面点个数进行判断(是满足优化条件)
  • 局部地图构建KD-tree
  • 构建ceres问题与参数块
  • 构建角点约束

a通过kd-tree找到5个最近邻角点
b通过特征值分解求线的方向
c通过最大特征值与次特征值的大小判断是否满足线特征
d以中心点与线的方向构建2个虚拟点
e构建角点约束(当前点和2个虚拟点)

  • 构建面点约束

a通过kd-tree找到5个最近邻面点
b通过求解平面方程得到法向量
c通过5个点到平面的距离判断是否满足面特征
d构建面点约束

相关文章
|
9天前
|
缓存 API 持续交付
构建高效后端服务的实践之路
【10月更文挑战第34天】在数字化时代的浪潮中,后端服务是支撑整个互联网的脊梁。本文将深入探讨如何构建一个高效的后端服务,涵盖从选择合适的编程语言和框架,到设计高性能的数据库交互、实现缓存机制、优化API设计,以及确保服务的高可用性和可扩展性。我们将通过实际代码示例,展示如何在面对复杂业务逻辑时,依然能够保持后端服务的高效与稳定。无论你是后端开发的新手,还是希望提升现有服务性能的资深开发者,这篇文章都将为你提供宝贵的实践经验和启示。
|
27天前
|
SQL 负载均衡 关系型数据库
构建高效的后端服务:从设计到部署
【10月更文挑战第16天】 在当今的数字化时代,后端服务的效率和可靠性对于任何成功的在线业务至关重要。本文将探讨如何设计和部署一个高效的后端服务,包括选择合适的技术栈、优化数据库性能、实现负载均衡以及确保安全性。我们将通过具体的案例分析,展示这些策略如何在实际中应用,并提供一些实用的技巧和最佳实践。
105 50
|
7天前
|
存储 SQL API
探索后端开发:构建高效API与数据库交互
【10月更文挑战第36天】在数字化时代,后端开发是连接用户界面和数据存储的桥梁。本文深入探讨如何设计高效的API以及如何实现API与数据库之间的无缝交互,确保数据的一致性和高性能。我们将从基础概念出发,逐步深入到实战技巧,为读者提供一个清晰的后端开发路线图。
|
22天前
|
Kubernetes 负载均衡 Docker
构建高效后端服务:微服务架构的探索与实践
【10月更文挑战第20天】 在数字化时代,后端服务的构建对于任何在线业务的成功至关重要。本文将深入探讨微服务架构的概念、优势以及如何在实际项目中有效实施。我们将从微服务的基本理念出发,逐步解析其在提高系统可维护性、扩展性和敏捷性方面的作用。通过实际案例分析,揭示微服务架构在不同场景下的应用策略和最佳实践。无论你是后端开发新手还是经验丰富的工程师,本文都将为你提供宝贵的见解和实用的指导。
|
15天前
|
JavaScript 中间件 关系型数据库
构建高效的后端服务:Node.js 与 Express 的实践指南
在后端开发领域,Node.js 与 Express 的组合因其轻量级和高效性而广受欢迎。本文将深入探讨如何利用这一组合构建高性能的后端服务。我们将从 Node.js 的事件驱动和非阻塞 I/O 模型出发,解释其如何优化网络请求处理。接着,通过 Express 框架的简洁 API,展示如何快速搭建 RESTful API。文章还将涉及中间件的使用,以及如何结合 MySQL 数据库进行数据操作。最后,我们将讨论性能优化技巧,包括异步编程模式和缓存策略,以确保服务的稳定性和扩展性。
|
13天前
|
存储 监控 NoSQL
构建高效后端服务:从理论到实践
【10月更文挑战第30天】在数字化时代,后端服务是支撑起整个互联网的基石。一个高效、稳定且可扩展的后端系统对于任何在线业务都是至关重要的。本文将带你了解如何从零开始构建一个高效的后端服务,涵盖了设计思路、关键技术选型、开发流程以及性能优化等方面。我们将通过实际的代码示例和案例分析,深入探讨如何实现一个既快速又可靠的后端系统。无论你是后端开发的新手还是有经验的开发者,这篇文章都将为你提供宝贵的参考和启示。
31 3
|
17天前
|
前端开发 关系型数据库 API
深入浅出后端开发——从零到一构建RESTful API
本文旨在为初学者提供一个关于后端开发的全面指南,特别是如何从零开始构建一个RESTful API。我们将探讨后端开发的基本概念、所需技术栈、以及通过实际案例展示如何设计和实现一个简单的RESTful API。无论你是完全的新手还是有一定编程基础的开发者,这篇文章都将为你提供实用的知识和技巧,帮助你在后端开发的道路上迈出坚实的一步。
|
21天前
|
监控 API 持续交付
构建高效后端服务:微服务架构的深度探索
【10月更文挑战第20天】 在数字化时代,后端服务的构建对于支撑复杂的业务逻辑和海量数据处理至关重要。本文深入探讨了微服务架构的核心理念、实施策略以及面临的挑战,旨在为开发者提供一套构建高效、可扩展后端服务的方法论。通过案例分析,揭示微服务如何帮助企业应对快速变化的业务需求,同时保持系统的稳定性和灵活性。
46 9
|
19天前
|
存储 SQL 缓存
构建高效后端服务:从理论到实践
在当今的软件开发领域,后端服务扮演着至关重要的角色。它不仅支撑着应用程序的核心功能,还影响着系统的性能、可扩展性和用户体验。本文将深入探讨如何构建一个高效的后端服务,涵盖从需求分析到架构设计,再到技术选型和性能优化的全过程。我们将通过实际案例,展示如何在保证数据一致性和安全性的前提下,实现高并发处理和快速响应。无论你是后端开发的新手还是有经验的工程师,这篇文章都将为你提供宝贵的见解和实用的建议。
|
18天前
|
缓存 负载均衡 安全
后端开发的艺术:构建高效、可扩展的API
在现代软件开发中,后端开发扮演着至关重要的角色。它不仅负责处理数据存储、业务逻辑和安全性,还需要提供高效、可扩展的API供前端和其他服务使用。本文将深入探讨后端开发的关键概念和技术,帮助读者了解如何构建高效、可扩展的API,并提供一些实用的建议和最佳实践。

热门文章

最新文章