LeGO-LOAM将当前帧扫描到点云特征(线特征和面特征)与当前帧周围的点云地图进行匹配,进行位姿优化。
代码分析
从主函数开始分析,主函数分为2个主要的功能,一是启动单独的线程进行回环检测,二是在循环中执行主流程。
int main(int argc, char** argv)
{
ros::init(argc, argv, "lego_loam");
ROS_INFO("\033[1;32m---->\033[0m Map Optimization Started.");
mapOptimization MO;
// std::thread 构造函数,将MO作为参数传入构造的线程中使用
// 进行闭环检测与闭环的功能
std::thread loopthread(&mapOptimization::loopClosureThread, &MO);
// 该线程中进行的工作是publishGlobalMap(),将数据发布到ros中,可视化
std::thread visualizeMapThread(&mapOptimization::visualizeGlobalMapThread, &MO);
ros::Rate rate(200);
while (ros::ok())
{
ros::spinOnce();
MO.run();
rate.sleep();
}
loopthread.join();
visualizeMapThread.join();
return 0;
}
主流程
run函数
void run(){
if (newLaserCloudCornerLast && std::abs(timeLaserCloudCornerLast - timeLaserOdometry) < 0.005 &&
newLaserCloudSurfLast && std::abs(timeLaserCloudSurfLast - timeLaserOdometry) < 0.005 &&
newLaserCloudOutlierLast && std::abs(timeLaserCloudOutlierLast - timeLaserOdometry) < 0.005 &&
newLaserOdometry)
{
newLaserCloudCornerLast = false; newLaserCloudSurfLast = false; newLaserCloudOutlierLast = false; newLaserOdometry = false;
std::lock_guard<std::mutex> lock(mtx);
if (timeLaserOdometry - timeLastProcessing >= mappingProcessInterval) {
timeLastProcessing = timeLaserOdometry;
//转换到map坐标系下
transformAssociateToMap();
//提取周围的关键帧
extractSurroundingKeyFrames();
//下采样当前帧
downsampleCurrentScan();
// 当前扫描进行边缘优化,图优化以及进行LM优化的过程
scan2MapOptimization();
//保存关键帧和因子
saveKeyFramesAndFactor();
//校正位姿
correctPoses();
//发布坐标变换
publishTF();
//发布关键帧和因子
publishKeyPosesAndFrames();
//清除点云
clearCloud();
}
}
}
提取周围关键帧
如果使能了回环检测,则添加过去50个关键帧,如果没有使能回环检测,则添加离当前欧式距离最近的50个关键帧,然后拼接成点云。
void extractSurroundingKeyFrames(){
if (cloudKeyPoses3D->points.empty() == true)
return;
// loopClosureEnableFlag 这个变量另外只在loopthread这部分中有用到
if (loopClosureEnableFlag == true){
// recentCornerCloudKeyFrames保存的点云数量太少,则清空后重新塞入新的点云直至数量够
if (recentCornerCloudKeyFrames.size() < surroundingKeyframeSearchNum){
recentCornerCloudKeyFrames. clear();
recentSurfCloudKeyFrames. clear();
recentOutlierCloudKeyFrames.clear();
int numPoses = cloudKeyPoses3D->points.size();
for (int i = numPoses-1; i >= 0; --i){
// cloudKeyPoses3D的intensity中存的是索引值?
// 保存的索引值从1开始编号;
int thisKeyInd = (int)cloudKeyPoses3D->points[i].intensity;
PointTypePose thisTransformation = cloudKeyPoses6D->points[thisKeyInd];
updateTransformPointCloudSinCos(&thisTransformation);
// 依据上面得到的变换thisTransformation,对cornerCloudKeyFrames,surfCloudKeyFrames,surfCloudKeyFrames
// 进行坐标变换
recentCornerCloudKeyFrames. push_front(transformPointCloud(cornerCloudKeyFrames[thisKeyInd]));
recentSurfCloudKeyFrames. push_front(transformPointCloud(surfCloudKeyFrames[thisKeyInd]));
recentOutlierCloudKeyFrames.push_front(transformPointCloud(outlierCloudKeyFrames[thisKeyInd]));
if (recentCornerCloudKeyFrames.size() >= surroundingKeyframeSearchNum)
break;
}
}else{
// recentCornerCloudKeyFrames中点云保存的数量较多
// pop队列最前端的一个,再push后面一个
if (latestFrameID != cloudKeyPoses3D->points.size() - 1){
recentCornerCloudKeyFrames. pop_front();
recentSurfCloudKeyFrames. pop_front();
recentOutlierCloudKeyFrames.pop_front();
// 为什么要把recentCornerCloudKeyFrames最前面第一个元素弹出?
// (1个而不是好几个或者是全部)?
latestFrameID = cloudKeyPoses3D->points.size() - 1;
PointTypePose thisTransformation = cloudKeyPoses6D->points[latestFrameID];
updateTransformPointCloudSinCos(&thisTransformation);
recentCornerCloudKeyFrames. push_back(transformPointCloud(cornerCloudKeyFrames[latestFrameID]));
recentSurfCloudKeyFrames. push_back(transformPointCloud(surfCloudKeyFrames[latestFrameID]));
recentOutlierCloudKeyFrames.push_back(transformPointCloud(outlierCloudKeyFrames[latestFrameID]));
}
}
for (int i = 0; i < recentCornerCloudKeyFrames.size(); ++i){
// 两个pcl::PointXYZI相加?
// 注意这里把recentOutlierCloudKeyFrames也加入到了laserCloudSurfFromMap
*laserCloudCornerFromMap += *recentCornerCloudKeyFrames[i];
*laserCloudSurfFromMap += *recentSurfCloudKeyFrames[i];
*laserCloudSurfFromMap += *recentOutlierCloudKeyFrames[i];
}
}else{
// 下面这部分是没有闭环的代码
//
surroundingKeyPoses->clear();
surroundingKeyPosesDS->clear();
kdtreeSurroundingKeyPoses->setInputCloud(cloudKeyPoses3D);
// 进行半径surroundingKeyframeSearchRadius内的邻域搜索,
// currentRobotPosPoint:需要查询的点,
// pointSearchInd:搜索完的邻域点对应的索引
// pointSearchSqDis:搜索完的每个领域点点与传讯点之间的欧式距离
// 0:返回的邻域个数,为0表示返回全部的邻域点
kdtreeSurroundingKeyPoses->radiusSearch(currentRobotPosPoint, (double)surroundingKeyframeSearchRadius, pointSearchInd, pointSearchSqDis, 0);
for (int i = 0; i < pointSearchInd.size(); ++i)
surroundingKeyPoses->points.push_back(cloudKeyPoses3D->points[pointSearchInd[i]]);
downSizeFilterSurroundingKeyPoses.setInputCloud(surroundingKeyPoses);
downSizeFilterSurroundingKeyPoses.filter(*surroundingKeyPosesDS);
int numSurroundingPosesDS = surroundingKeyPosesDS->points.size();
for (int i = 0; i < surroundingExistingKeyPosesID.size(); ++i){
bool existingFlag = false;
for (int j = 0; j < numSurroundingPosesDS; ++j){
// 双重循环,不断对比surroundingExistingKeyPosesID[i]和surroundingKeyPosesDS的点的index
// 如果能够找到一样的,说明存在相同的关键点(因为surroundingKeyPosesDS从cloudKeyPoses3D中筛选而来)
if (surroundingExistingKeyPosesID[i] == (int)surroundingKeyPosesDS->points[j].intensity){
existingFlag = true;
break;
}
}
if (existingFlag == false){
// 如果surroundingExistingKeyPosesID[i]对比了一轮的已经存在的关键位姿的索引后(intensity保存的就是size())
// 没有找到相同的关键点,那么把这个点从当前队列中删除
// 否则的话,existingFlag为true,该关键点就将它留在队列中
surroundingExistingKeyPosesID. erase(surroundingExistingKeyPosesID. begin() + i);
surroundingCornerCloudKeyFrames. erase(surroundingCornerCloudKeyFrames. begin() + i);
surroundingSurfCloudKeyFrames. erase(surroundingSurfCloudKeyFrames. begin() + i);
surroundingOutlierCloudKeyFrames.erase(surroundingOutlierCloudKeyFrames.begin() + i);
--i;
}
}
// 上一个两重for循环主要用于删除数据,此处的两重for循环用来添加数据
for (int i = 0; i < numSurroundingPosesDS; ++i) {
bool existingFlag = false;
for (auto iter = surroundingExistingKeyPosesID.begin(); iter != surroundingExistingKeyPosesID.end(); ++iter){
// *iter就是不同的cloudKeyPoses3D->points.size(),
// 把s

本文详细分析了LeGO-LOAM(Lightweight and Ground-Optimized LOAM)算法的主要流程,包括关键帧提取、线面特征优化和LM优化等步骤。在主函数中,算法启动回环检测线程和可视化线程,并在循环中执行主要的LOAM流程,如位姿优化和地图构建。通过线特征和面特征匹配,结合LM优化,实现精确的机器人位姿估计。此外,还介绍了如何在检测到回环时进行闭环修正,确保地图一致性。
最低0.47元/天 解锁文章
235

被折叠的 条评论
为什么被折叠?



