激光SLAM:点云配准方法--ICP--原理推导及代码证明

本文详细介绍了激光SLAM中的ICP(迭代最近点)算法原理及其代码实现过程。ICP作为点云配准的重要手段,被广泛应用于深度相机和激光雷达等领域。文中深入探讨了ICP的基本数学模型,并通过代码验证了理论的有效性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

ICP全称 Iterative Closest Point ,翻译过来就是迭代最近点.ICP在点云配准领域应用的非常广泛,因此基于深度相机\激光雷达的算法使用ICP的频率比较高.

ICP的目的是用来求解两个点云集合转换关系,也是目前的最通用的方法。

最基本的ICP是通过对应点去求解点云的对应关系,后期演变出点线ICP\点面ICP等等众多的方法.

本篇博客主要来探究下ICP家族的师祖,点点ICP的数学模型,及推导.最后用代码证明.

ICP数学模型

给定两个点云集合:
在这里插入图片描述
xi和pi表示点云的坐标;
Nx和Np表示点云的数量.
ICP的核心思想是:
求解旋转矩阵R和平移向量t,使得下式最小:
在这里插入图片描述

ICP求解方法

在已知对应点的情况下.就是说上面的x1和p1对应,x2和p2对应…

进行两个点云的去中心化

求x点云的几何中心坐标
在这里插入图片描述
求p点云的几何中心坐标
在这里插入图片描述
两个点云分别每个点减去各自的点云几何中心坐标,形成新的点云
在这里插入图片描述

求W矩阵并进行SVD分解

将上面的新形成的两个点云相乘(其中一个先转置)得到的矩阵为W矩阵
然后对W矩阵进行SVD分解,得到U和V矩阵
在这里插入图片描述

计算R和t

通过U和V即可计算出R
然后通过两个点云的中心坐标及R可算出t
在这里插入图片描述

求解方法公式推导

从上面说的核心公式开始
在这里插入图片描述
引入点云的几何中心ux和up,由于核心公式里pi前乘了R,所以up前也乘R才好合并.下式红色部分为0
在这里插入图片描述
把中心点移到前面,剩下的和t组成最后括号里的
在这里插入图片描述
将平方开出来,形成,下面的式子
在这里插入图片描述
这个式子中红色框是为0的.蓝色框是需要继续向下求的
在这里插入图片描述
第一个蓝框仅与R有关
第二个蓝框中,对于任意R,总能得到t = ux − Rup ,使它等于0

那么原核心公式可以转换为:
在这里插入图片描述
将去中心化的点云表达式引入:
在这里插入图片描述
开平方得到下式
在这里插入图片描述
中间项的RTR就是E了,也就没R了,将有R的提出了,无R的向后放
在这里插入图片描述
上式中画横线的是一个固定值,与R无关,所以就成了求第一项的最小值
也就是
在这里插入图片描述
的最大值
在这里插入图片描述
在这里插入图片描述
注意第一个式子和第二个的对角线元素对应一致,所以
在这里插入图片描述
到现在核心公式转为了求下面的式子
在这里插入图片描述
对H进行SVD分解,即
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
XH为正定对称矩阵.所以
在这里插入图片描述
当R=X时,RH的值最大.所以R的解为
在这里插入图片描述

代码证明

Code

int main (int argc, char** argv)
{

        /*自己构建点云*/
        pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_in (new pcl::PointCloud<pcl::PointXYZ>);//声明源点云
        pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_out (new pcl::PointCloud<pcl::PointXYZ>);//声明目标点云
        /*自己构建的点云 */
        /*填充源点云*/
        cloud_in->width    = 100;
        cloud_in->height   = 1;
        cloud_in->is_dense = false;
        cloud_in->points.resize (cloud_in->width * cloud_in->height);
        for (size_t i = 0; i < cloud_in->points.size (); ++i)
        {   
            cloud_in->points[i].x = 1024 * rand () / (RAND_MAX + 1.0f);
            cloud_in->points[i].y = 1024 * rand () / (RAND_MAX + 1.0f);
            //cloud_in->points[i].z = 1024 * rand () / (RAND_MAX + 1.0f);
            //先测试二维数据
            cloud_in->points[i].z = 1;
        }

首先构建一个原点云.里面设置100个点,然后随机取值

        /*构建变换矩阵*/
        float d_yaw = 30;//变化航向角度 单位度
        float d_x = 1;//变换平移x
        float d_y = 2;//变换平移y

        float s = sin(d_yaw*M_PI/180);
        float c = cos(d_yaw*M_PI/180);
        Eigen::Matrix3f T;//变换矩阵
        T<< c , -s , d_x,
            s , c  , d_y,
            0 , 0  , 1;

构建变换矩阵

        //将源点云赋值给目标点云
        *cloud_out = *cloud_in;

        /*将源点云经过变换矩阵,获得目标点云*/
        for (size_t i = 0; i < cloud_in->points.size (); ++i)
        {
            Eigen::Vector3f point_in,point_out;
            point_in << cloud_in->points[i].x,cloud_in->points[i].y,cloud_in->points[i].z;
            point_out= T * point_in;
            cloud_out->points[i].x = point_out[0];
            cloud_out->points[i].y = point_out[1];
            cloud_out->points[i].z = point_out[2];
        }

将原点云经过变换矩阵得到目标点云

            /*求两个点云的几何中心*/
        int num_points = cloud_in->points.size ();
        Eigen::Vector3f sum_point_in = Eigen::Vector3f::Zero();
        Eigen::Vector3f sum_point_out = Eigen::Vector3f::Zero();
        for(size_t i=0;i<num_points;++i)
        {
            sum_point_in[0] = sum_point_in[0] + cloud_in->points[i].x ; 
            sum_point_in[1] = sum_point_in[1] + cloud_in->points[i].y ;
            sum_point_in[2] = sum_point_in[2] + cloud_in->points[i].z ;

            sum_point_out[0] = sum_point_out[0] + cloud_out->points[i].x ; 
            sum_point_out[1] = sum_point_out[1] + cloud_out->points[i].y ;
            sum_point_out[2] = sum_point_out[2] + cloud_out->points[i].z ;
        }
        Eigen::Vector3f u_point_in,u_point_out;
        //源点云几何中心
        u_point_in[0] = sum_point_in[0]/num_points;
        u_point_in[1] = sum_point_in[1]/num_points;
        u_point_in[2] = sum_point_in[2]/num_points;
        //目标点云几何中心
        u_point_out[0] = sum_point_out[0]/num_points;
        u_point_out[1] = sum_point_out[1]/num_points;
        u_point_out[2] = sum_point_out[2]/num_points;


        /*点云去中心化*/
        for(size_t i=0;i<num_points;++i)
        {
            //源点云去中心化
            cloud_in->points[i].x = cloud_in->points[i].x - u_point_in[0] ;
            cloud_in->points[i].y = cloud_in->points[i].y - u_point_in[1] ;
            cloud_in->points[i].z = cloud_in->points[i].z - u_point_in[2] ;
            //cloud_in->points[i].z = 1 ;
            //目标点云去中心化
            cloud_out->points[i].x = cloud_out->points[i].x - u_point_out[0] ;
            cloud_out->points[i].y = cloud_out->points[i].y - u_point_out[1] ;
            cloud_out->points[i].z = cloud_out->points[i].z - u_point_out[2] ;
            //cloud_out->points[i].z = 1 ;
        }

求两个点云的几何中心,然后去中心化


        /*求W矩阵*/
        Eigen::Matrix3f W =Eigen::Matrix3f::Zero();//声明W矩阵
        for(size_t i=0;i<num_points;++i)
        {        
            Eigen::Vector3f point_in,point_out;
            point_in << cloud_in->points[i].x,cloud_in->points[i].y,cloud_in->points[i].z;
            point_out << cloud_out->points[i].x,cloud_out->points[i].y,cloud_out->points[i].z;

            W = W + point_out*point_in.transpose();//累加求和
           
        }

求W矩阵

        //对W矩阵进行SVD分解
        Eigen::JacobiSVD<Eigen::MatrixXf> svd(W, Eigen::ComputeThinU | Eigen::ComputeThinV );
        //求V和U
	    Eigen::Matrix3f V = svd.matrixV(), U = svd.matrixU();

将W矩阵进行SVD分解,然后得到U和V

        /*求R和t*/
        Eigen::Matrix3f R = U*V.transpose();//这里公式和课件里不一致
        Eigen::Vector3f t = u_point_out - R*u_point_in;
        std::cout << "R"  << std::endl<< R << std::endl;
        std::cout << "t" << std::endl << t << std::endl;

求R和t

Result

在这里插入图片描述
在这里插入图片描述
求解的结果和代码里构建的变换矩阵是一致的

<think>嗯,用户让我介绍一下A-LOAM算法。我需要先回忆一下这个算法的基本概念和背景。A-LOAM是Advanced Lidar Odometry and Mapping的缩写,应该是在LOAM的基础上改进的吧。LOAM我记得是激光雷达里程计与建图方面的经典算法,由Zhang Ji在2014年提出的。A-LOAM可能是在此基础上进行了优化,比如代码结构的改进或者计算效率的提升。 首先,我应该介绍A-LOAM的基本情况,比如它是什么,主要用途是什么。然后需要分解它的核心组成部分,比如特征提取、里程计估计、建图等部分。特征提取可能涉及到从点云中提取边缘点和平面点,这部分需要解释清楚为什么选择这些特征,以及如何提取。 接下来是里程计估计,这里可能涉及到帧间匹,比如使用ICP或者类似的方法,但LOAM和A-LOAM可能用的是基于特征的匹,比如计算点到线、点到面的距离,然后通过优化来求解位姿变化。这里需要说算法是如何通过最小化误差函数来优化位姿的,可能涉及到非线性优化方法,比如高斯-牛顿或LM算法。 然后是建图模块,这部分可能将多帧点云融合到全局地图中,并优化全局一致性。可能需要提到如何使用滑动窗口或者关键帧技术来管理地图,以及如何进行全局优化,比如闭环检测或者后端优化,不过LOAM本身可能没有闭环,主要依靠前端的高精度里程计和局部地图优化。 另外,A-LOAM相对于LOAM的改进点是什么?可能是在代码实现上的优化,比如使用更高效的数学库,或者简化了某些步骤,使得算法更易于理解和实现。比如原版LOAM可能用了更复杂的特征筛选方法,而A-LOAM可能简化了这部分,同时保持了较好的性能。 还需要提到A-LOAM的优缺点。优点可能包括较高的实时性、较低的计算资源消耗,适合嵌入式平台;缺点可能是没有闭环检测,长期运行可能有累积误差,或者在动态环境中表现不佳。此外,A-LOAM可能依赖高精度激光雷达,比如旋转式雷达,对低线数雷达可能效果差。 应用场景方面,可能用于自动驾驶的定位与建图、机器人导航、无人机等领域。另外,作为开源项目,A-LOAM在研究和工业界中被广泛使用,作为SLAM的入门学习项目。 可能需要注意用户是否了解LOAM,是否需要先简要介绍LOAM再讲A-LOAM的改进。不过根据问题,用户直接问的是A-LOAM,所以应该先提LOAM,再说A-LOAM的优化点。 另外,用户可能希望了解算法的大致流程,比如每个模块如何协作,输入输出是什么。需要分步骤说,比如特征提取、计算位姿、更新地图等步骤。同时,数学公式部分需要符合系统指令,使用正确的LaTeX格式,行内公式用$...$,独立公式用$$...$$,并且单独成段。 可能还需要注意代码结构方面,A-LOAM是否使用了C++和ROS,以及依赖的库比如PCL、Eigen等,这些对用户来说可能有帮助,特别是如果用户想实际使用的话。 最后,确保回答结构清晰,分点说,逐步引导用户理解A-LOAM的各个部分,并保持语言简洁,技术细节确。如果有不确定的地方,比如A-LOAM是否包含闭环检测,需要确认资料,避免错误。根据之前的了解,A-LOAM主要专注于前端里程计和建图,闭环部分可能没有,所以需要确说这点。</think>A-LOAM(Advanced Lidar Odometry and Mapping)是一种基于激光雷达(LiDAR)的实时定位与建图算法,是经典算法**LOAM**(Lidar Odometry and Mapping)的优化版本。其核心思想是通过提取激光点云中的几何特征,结合帧间匹和优化方法,实现高精度的位姿估计和地图构建。以下分步介绍其核心原理与流程: --- ### 一、核心模块 #### 1. **特征提取** - **输入**:原始激光点云- **方法**:计算每个点的曲率(粗糙度),筛选出两类特征: - **边缘点**(Edge Points):曲率高的点,对应场景中的边缘或棱角(如墙角)。 - **平面点**(Planar Points):曲率低的点,对应平面区域(如墙面、地面)。 - **数学表示**: 曲率计算公式为: $$c = \frac{1}{|S| \cdot \|r\|} \sum_{i \in S, i \ne j} \left( r_j - r_i \right)^2$$ 其中$S$为邻域点集,$r_j$为当前点坐标。 #### 2. **里程计估计(Odometry)** - **目标**:通过相邻帧的特征匹,计算雷达的位姿变化(旋转矩阵$R$和平移向量$t$)。 - **匹方法**: - **边缘点**:匹到上一帧中最近的边缘线(点到线距离最小化)。 - **平面点**:匹到上一帧中最近的平面(点到面距离最小化)。 - **优化模型**:构建非线性最小二乘问题,使用**LM算法**或**高斯-牛顿法**求解: $$\min_{R,t} \sum \left( d_{\text{edge}} + d_{\text{planar}} \right)$$ #### 3. **建图(Mapping)** - **输入**:多帧点云及其位姿。 - **方法**:将局部点云到全局地图,并通过体素滤波降采样,保持地图稀疏性。 - **优化**:联合多帧数据,优化全局一致性(类似Bundle Adjustment)。 --- ### 二、A-LOAM对LOAM的改进 1. **代码优化**: - 使用**Eigen**库加速矩阵运算,替代LOAM中的手动优化。 - 简化特征提取逻辑,提高可读性和可维护性。 2. **数学推导简化**: - 采用四元数或欧拉角直接表示旋转,避免LOAM中复杂的李群/李代数推导。 3. **工程友好性**: - 基于**ROS**框架实现,便于与其他传感器(如IMU)集成。 --- ### 三、优缺点分析 #### 优点: - **高精度**:特征匹策略在结构化场景中表现优异。 - **低计算开销**:特征筛选显著减少数据量,适合实时应用。 - **鲁棒性**:对雷达运动畸变有较好补偿能力。 #### 缺点: - **依赖场景结构**:在无显著几何特征的环境(如隧道、草地)中易失效。 - **无闭环检测**:长期运行会导致累积误差。 --- ### 四、应用场景 - **自动驾驶**:实时定位与高精地图构建。 - **机器人导航**:室内外环境下的SLAM任务。 - **学术研究**:作为激光SLAM的基线算法(开源地址:[A-LOAM GitHub](https://github.com/HKUST-Aerial-Robotics/A-LOAM))。 --- ### 五、数学补充:帧间匹公式 对边缘点的点到线距离计算: $$d_{\text{edge}} = \frac{|(p_i - p_b) \times (p_i - p_a)|}{|p_a - p_b|}$$ 其中$p_a, p_b$为上一帧中构成直线的两个点,$p_i$为当前点。 --- 通过以上步骤,A-LOAM实现了高效、可靠的激光SLAM功能,是学习3D SLAM的经典案例。实际应用中,可结合IMU或GPS进一步优化轨迹精度。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

月照银海似蛟龙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值