欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

深蓝学院激光SLAM第二期学习笔记

程序员文章站 2022-04-17 17:04:35
...

第二章传感器数据处理

 

第1节 里程计运动模型及标定

 

根据相对位置求绝对位姿:

若为二维点,[x, y, 1] = [R, t; 0, 1]*[del_x, del_y, 1],这里的xy表示的点是没有方向的

若为带方向的三维点,上述公式是没法用的,不能用向量而应该用位姿矩阵来表示T = T0*delta_T;

记住:带方向的位姿,一定先转化成T再进行运算

[R, t; 0, 1]  <=> [RT, -RT*t; 0, 1]

 

运动模型:从w_l, w_r获得v, w

航迹推算:从上一时刻绝对坐标和局部的相对坐标,推算下一时刻绝对坐标

线性最小二乘:超定方程Ax=b,A=[a1 a2 ... an]向量空间无法表示b,无解只有比较好的近似解,Ax*-b与该空间垂直,与任意一个a垂直,建立方程,推导得通解(ATA)逆(AT)b.

 

里程计标定(使用激光雷达):1、直接法。假设里程计给出的相对位移与激光之间存在线性的变换,求解该变换的9个参数;2、模型法。标定的是模型中的参数,两个*的半径、轮间距。

 

作业:找不到nav_core头文件,这个是navigation stack的一部分,sudo apt-get install ros-kinectic-navigation;

找不到CSM,The C(anonical) Scan Matcher (CSM) is a pure C implementation of a very fast variation of ICP using a point-to-line metric optimized for range-finder scan matching. 解决方案见https://github.com/AndreaCensi/csm sudo apt-get install ros-kinetic-csm

 

第2节 激光雷达畸变校正

 

雷达模型

光束模型:击中、被挡住提前返回、无穷远、完全随机,需要多次划线算法,杂乱环境下不好用,位姿(主要是角度)的微小变化导致期望的巨大变化

似然场模型:对障碍物膨胀,实际上csm就是

雷达畸变:

靠近墙壁时,墙壁畸变旋转的方向与雷达旋转方向相同;原理时,方向相反。

 

畸变校正方法:纯估计方法、里程计辅助方法、融合法

1、ICP,不知道匹配关系,因而不能一步到位求解出R和t,需要迭代<=>EM期望最大化;VICP基于匀速运动假设。位姿的线性插值,LOAM也是这种思想;状态估计和去畸变耦合;

2、IMU和里程计的选择,IMU线性太差,里程计位置和角度都比较好。实际上就是用IMU为激光插值,先选一部分直接插值,其他的激光再按照匀速运动插值,相当于分段函数;

3、融合:里程计->VICP->里程计->...

 

作业:主要思想,beam与beam之间时间间隔足够大,分段(start, end),两个断点的位姿通过里程计(高频)插值得到,假设该段内有n个点(包含两端),那么分割成n-1个段。插值得到每一个beam对应的机器人位姿后,将该beam的<r, theta>变成欧式坐标,再投影到世界坐标系下,再投影到base坐标系(整帧的第一个beam)下,再转换成极坐标,这样就将所有的beam修正到了base的极坐标系下。

tf::Stamped<tf::Pose> visualPose;

visualPose.getRotation()获得四元数头发::Quaternion,有.slerp(end, i*step)求解角度插值的方法

上述n个点分成n-1段,分别插值为start.slerp(end, 0*(1/n-1)), start.slerp(end, 1*(1/n-1)),...., start.slerp(end, 1)共n个点

double visualYaw = tf::getYaw(visualPose.getRotation());//可以进一步从四元数获得yaw角

tf::Vector3 = visualPose.getOrigin();//获得位置坐标

visualPose.getOrigin().getX();//等同于[0]索引

current_pose = start_pose.lerp(end_pose, step*i);//位置的插值

对于坐标变换T*v不知道怎么实现,这里是直接将T拆分开求解的

此外还有tfFuzzyZero()函数,tfNormalizeAngle()函数

如何定义自己的message格式,这个还没有看,有时间学习一下

 

第3节 前端1

 

ICP、PL-ICP、NICP、IMLS-ICP精度越来越高,计算量越来越大

1、如果已知匹配,实际上可以一步到位求解R和t,就是因为不知道,所以需要迭代

ICP在求解R和t的过程中可以求出解析解,速度要比迭代求解快

2、PLICP,对于直线,上一次看到一个点,由于离散性,下一次看到两侧的两个点。二阶收敛,精度高,特别是结构化环境中,对初值敏感,因而一般与里程计或者CSM结合使用。好像有解析解?

3、NICP匹配中除了欧式距离,还考虑法向量、曲率等曲面特征,误差项也考虑了法向量的角度差,方法就是把点从p扩展维度到<p, n>。法向量的求解,最小特征值对应的特征向量的方向,曲率lamda1/(lamda1+lambda2)。没有解析解,需要用LM迭代求解。

4、IMLS-ICP。PLICP相当于是分段光滑,这里相当于拟合出了曲线。隐式,求不出来点,但可以知道距离。

对于每一个点q,寻找附近点pi的集合P,它们可以拟合出一个面,为了求解q在面上的投影点,一个很自然的想法就是要知道高度h和投影点处的法向n。对于法向可以直接用最近的点的法向代替。对于高度,可以根据每一个pi及ni拟合出一个小平面,计算q与每一个小平面的距离hi,但是需要加权,权重就是pi在以q为中心的一个正态分布上的值,也就是说离q越近,权重越高,因此只有q一定范围内pi是有意义的,也就是moving的含义。至此,就求解出来了高度h和法向n,很容易反向求解出q在隐式曲面上的投影点。

 

作业:

根据点云估计法向,推导简化,可以减小计算量。涉及特征分解:

/**

* @brief IMLSICPMatcher::ComputeNormal

* 计算法向量

* @param nearPoints 某个点周围的所有激光点

* @return

*/

Eigen::Vector2d IMLSICPMatcher::ComputeNormal(std::vector<Eigen::Vector2d> &nearPoints)

{

Eigen::Vector2d normal;



//TODO

Eigen::Vector2d mean = Eigen::Vector2d::Zero();

Eigen::Matrix2d sigma = Eigen::Matrix2d::Zero();

for(auto& p : nearPoints){

mean += p;

sigma += p*p.transpose();//把vector严格视为n行1列的matrix即可

}

mean /= nearPoints.size();

sigma = sigma/nearPoints.size()-mean*mean.transpose();//不确定这里的推导有没有问题,看结果

Eigen::EigenSolver<Eigen::Matrix2d> es(sigma);//Eigen::EigenSolver是一个模板,需要指定矩阵的类型

auto eigen_value = es.pseudoEigenvalueMatrix();//矩阵可逆效果与.eigenvalues()一样;不可逆只有这个可以得到正确结果

auto eigen_vectors = es.pseudoEigenvectors();

if(eigen_value(0,0) < eigen_value(1, 1)){//此外还有.minCoeff()求最小值(也可以返回索引),.rowwise()可以降维。

normal = eigen_vectors.col(0);

}else{

normal = eigen_vectors.col(1);

}

//end of TODO

return normal;

}

 

height = projSum/(weightSum+0.000000001);//安全

 

第4节 前端2

基于优化的方法=基于势场的方法,不需要匹配点

高斯牛顿、NDT、CSM+BB(Branch Bound)

1、高斯牛顿

拉格朗日插值法,把插值多项式表示成基函数的线性组合

双线性插值,两个变量u, v

本质就是,非线性方程无法求解,但可以推导出在局部有意义的导数。为了最小化误差函数,需要求解一个最优的deltaT,给定该点处导数的数值解,高斯牛顿法就可以给出该处出发合适的步长和方向,即deltaT。误差可以不用二次方么?

2、NDT

牛顿法:求解f(x)的最小值,令g(x)=f'(x)=0去求解x,但是x也求不出来解析解,只能迭代:

g(x+delta)=g(x)+g'(x)*delta=0,然后求解出了delta,叠加到x上,这时的x在局部看来,可以让g(x)=0,即f(x)最小。由于非线性,需要再继续迭代,不断寻找新的最优位姿。这里的g'(x)即f(x)导数的导数,即H矩阵。

深蓝学院激光SLAM第二期学习笔记

3、CSM

 

作业总结:

1、已知odom1、odom2、laser1的位姿,估计laser1的位姿。这里有个假设,即laser与odom的中心是重合的,这样laser2相对于laser1的位姿,即odom2相对于odometer1的位姿。如此,根据odom之间的相对位姿,估计出来一个不错的,laser之间的相对位姿。这里有个数学上的逻辑,在对有方向的三维点进行坐标变换时有两种方式:

(1)

          [cos, -sin, x,        [cosd, -sind, xd,

           sin, cos, y,    *    sind, cosd, yd,

           0,     0,    1]         0,      0,        1]

这种方式得到了位姿矩阵,非常方便。

(2)

         [cos, -sin, 0,        [xd,               [x

           sin, cos, 0,    *    yd,         +     y

           0,     0,    1]         thetad]        theta]

这种方式将位姿向量转换成位姿向量,当需要使用向量时非常方便。反过来变换也是很明显的。

//进行优化.
        //初始解为上一帧激光位姿+运动增量
        Eigen::Vector3d deltaPose = nowPose - m_odomPath.back();
        deltaPose(2) = GN_NormalizationAngle(deltaPose(2));//现在一直odom之间的变化量,想求laser之间的变化量

        Eigen::Matrix3d R_laser = Eigen::Matrix3d::Zero();
        double theta = m_prevLaserPose(2);
        R_laser << cos(theta), -sin(theta), 0, 
                   sin(theta),  cos(theta), 0,
                        0,          0,      1;

        Eigen::Matrix3d R_odom = Eigen::Matrix3d::Zero();
        theta = m_odomPath.back()(2);
        R_odom << cos(theta), -sin(theta), 0, 
                  sin(theta),  cos(theta), 0,
                       0,          0,      1;
        Eigen::Vector3d finalPose = m_prevLaserPose + R_laser * R_odom.transpose() * deltaPose;//这里应该假设了odom和laser的中心重合
        finalPose(2) = GN_NormalizationAngle(finalPose(2));
        //final为根据odom估计处的laser的位姿
        GaussianNewtonOptimization(map,finalPose,nowPts);

解释一下,先求解odom2与odom1在世界坐标系下的差值,再将xy投影到odom1坐标系下,即求解得到了odom2在odom1下的坐标向量delta。基于odom与laser重合的假设,laser2相对于laser1的坐标也是delta,因此再进行一次变换就得到了laser2的世界坐标。这也是高斯牛顿法优化的起点。

2、作业中地图的逻辑是,世界坐标系和地图坐标系的(0, 0)均位于左下角。(x, y)处占用概率由四舍五入得到地图坐标查表得到,周围整数坐标由floor函数取整得到。地图问题需要再深入考虑下。在地图上求解答到导数的时候,要注意这里的导数单位是/格,需要除以resolution才能得到世界坐标系意义下的导数;地图需要不断判断是否越界;

3、1*2的vecor乘以2*3的matrix好像不可以,应该是数据结构上的问题,都是用matrix就没问题了。matrix*vector好像没问题。

4、

now_pose[2] = GN_NormalizationAngle(now_pose[2]);

在对位姿进行叠加的时候,需要注意判断角度的范围,控制在[-pi, pi]之间。

 

第5节 后端

 

激光SLAM中优化的节点一般只包含机器人位姿,不包含环境特征。

后端是一个标准的非线性最小二乘问题:

[z1,  z2, z3, ..., zn] = f([x1, x2, x3, ..., xn])

我觉得很重要的一点在于要认识到所有的x不是割裂的,他们都是整个系统状态的一部分,而不是n个独立的自变量,即状态向量的概念。

而每一次的观测zi是对整个状态向量的不完全观测

总体的误差函数,f(x)=sigma fi(x),因此对x的雅克比矩阵自然而言也应该是fi(x)对x的雅克比矩阵之和。

作业总结:

1、搞清楚矩阵的维度,特别是H, b, 最后要求解的是H * delx = -b,千万别忽略了这里的负号。

另外线性方程的求解,使用:

dx = -H.colPivHouseholderQr.solve(b);

没能成功,原因在于colPivHouseholderQr是函数,而不是属性,这点一定要注意!应为:

dx = -H.colPivHouseholderQr().solve(b);或者

dx = -H.lu().solve(b);

H.inverse()*b没有优势,原因在于H的维度高,而且稀疏性没有利用起来;

2、感性认知,需要知道每一个constraint对应的节点xi, yi坐标,以及观测zi和sigma,才能求解相应的误差ei,及ei的雅克比Ai, Bi,而他们又是H和b更新的主要组成部分;

3、代码中有几点需要后面学习:(1)读文本(2)发布markarray

 

 

 

 

 

 

 

 

 

 

 

 

相关标签: 激光SLAM