本篇文章只梳理了自己角度的代码逻辑,具体代码和逻辑可以结合上一篇博客查看,至于代码具体的注释版本可以在github上自行查找(有很多)。
项目工程四个文件分别是scanRegistration.cpp、laserOdometry.cpp、laserMapping.cpp、和lidarFactor,分别对应于激光数据处理、低精度里程计和高精度建图,还有就是ceres实现的优化。
接受lidar数据,进行数据处理和特征提取。
A-LOAM里面没有实现,在LIO-SAM里
void markOccludedPoints()
{
int cloudSize = extractedCloud->points.size();
// 标记遮挡点和平行光束点
for (int i = 5; i < cloudSize - 6; ++i)
{
// 获取相邻两个点距离信息
float depth1 = cloudInfo.pointRange[i];
float depth2 = cloudInfo.pointRange[i + 1];
// 获取相邻两个有效点之间的列索引差
int columnDiff = std::abs(int(cloudInfo.pointColInd[i + 1] - cloudInfo.pointColInd[i]));
// 如果列索引差较小,即两点在扫描角度上靠的很近,这样才有意义
if (columnDiff < 10)
{
// 如果深度值差的较大,则将相邻的前后5个点认为是遮挡点,并标记为已选择过,后面不会再对这些点进行特征提取
if (depth1 - depth2 > 0.3)
{
cloudNeighborPicked[i - 5] = 1;
cloudNeighborPicked[i - 4] = 1;
cloudNeighborPicked[i - 3] = 1;
cloudNeighborPicked[i - 2] = 1;
cloudNeighborPicked[i - 1] = 1;
cloudNeighborPicked[i] = 1;
}
else if (depth2 - depth1 > 0.3)
{
cloudNeighborPicked[i + 1] = 1;
cloudNeighborPicked[i + 2] = 1;
cloudNeighborPicked[i + 3] = 1;
cloudNeighborPicked[i + 4] = 1;
cloudNeighborPicked[i + 5] = 1;
cloudNeighborPicked[i + 6] = 1;
}
}
// 获取当前点与左右相邻点之间的深度差
float diff1 = std::abs(float(cloudInfo.pointRange[i - 1] - cloudInfo.pointRange[i]));
float diff2 = std::abs(float(cloudInfo.pointRange[i + 1] - cloudInfo.pointRange[i]));
// 如果相邻深度差都较大,则认为当前点为平行光束点(即激光近似水平射向反射平面),并标记为已选择过,后面不会对这些点进行特征提取
if (diff1 > 0.02 * cloudInfo.pointRange[i] && diff2 > 0.02 * cloudInfo.pointRange[i])
cloudNeighborPicked[i] = 1;
}
}
这个文件主要就是实现特征的匹配,然后调用ceres库实现转换位姿的求解运算。
每次迭代具体流程:
实现过程
在前端,是通过当前帧的线面特征分别和上一帧的线和面特征进行匹配,构建约束从而进行优化求解。由于机械式激光雷达的性质,这里面存在线束的约束。
而在后端的当前帧与地图匹配的时候,需要从地图中寻找线特征和面特征的约束时,就没有了线束信息,所以需要额外的操作来判断其是否符合线和面特征的给定约束。
具体流程:
而对于面特征来说,原则上也可以通过PCA分解方式得到特征,但代码里面采用的是平面拟合。先由找到的最近五个面点拟合出一个平面,然后看这五个点到这个平面的距离远不远,如果超过一定距离的话,就不行。最后算点到面距离构建优化。
但这里需要注意的就是,线特征优化中使用的因子优化和前端一致,但面特征则已经不是采用的向量方法了,而是直接使用点到面的距离公式进行计算:
点 ( x 0 , y 0 , z 0 ) 到平面 A x + B y + C z + D = 0 的距离 = f a b s ( A ∗ x 0 + B ∗ y 0 + C ∗ z 0 + D ) / s q r t ( A 2 + B 2 + C 2 ) ,因为法向量( A , B , C )已经归一化了,所以距离公式可以简写为:距离 = f a b s ( A ∗ x 0 + B ∗ y 0 + C ∗ z 0 + D ) ,即: 点(x0, y0, z0)到平面Ax + By + Cz + D = 0 的距离 = fabs(A*x_0 + B*y_0 + C*z_0 + D) / sqrt(A^2 + B^2 + C^2),因为法向量(A, B, C)已经归一化了,所以距离公式可以简写为:距离 = fabs(A*x_0 + B*y_0 + C*z_0 + D) ,即: 点(x0,y0,z0)到平面Ax+By+Cz+D=0的距离=fabs(A∗x0+B∗y0+C∗z0+D)/sqrt(A2+B2+C2),因为法向量(A,B,C)已经归一化了,所以距离公式可以简写为:距离=fabs(A∗x0+B∗y0+C∗z0+D),即:
residual[0] = norm.dot(point_w) + T(negative_OA_dot_norm);
void transformUpdate()
{
q_wmap_wodom = q_w_curr * q_wodom_curr.inverse();
t_wmap_wodom = t_w_curr - q_wmap_wodom * t_wodom_curr;
}
把map坐标系和odom坐标系之间的T更新后,并且把本帧位姿叠加保存,形成轨迹。
把当前帧对应角点点云和面点点云全部投到对应的cube里面,然后对所有的cube进行一次下采样(如果没有新增地图点就不用)
最后可以按一定的频率对外发布
因篇幅问题不能全部显示,请点此查看更多更全内容