LeGO-LOAM算法分析之mapOptmization

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

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
<think>好的,用户现在遇到了CMake构建错误,具体是“目标模式不含有‘%’”的问题,涉及到的项目包括SC-LeGO-LOAMmapOptmization、LIO-SAM的Makefile。我需要结合用户提供的引用信息和之前的对话历史来分析和解决这个问题。 首先,用户之前的问题是关于LIO-SAM编译时的错误,涉及Boost库链接失败和Makefile目标格式错误。之前的回答中已经提供了Boost库安装、CMake路径设置、清理编译环境等方法。现在用户的新问题虽然还是关于CMake构建错误,但错误信息不同,是“目标模式不含有‘%’”,同时用户引用了两个新的错误信息:一个是关于opencv/cv.h找不到,另一个是GTSAM和Eigen版本冲突的问题。 首先,我需要理解“目标模式不含有‘%’”这个错误的具体原因。根据经验,这个错误通常是由于Makefile中的规则语法错误导致的,尤其是在使用模式规则(Pattern Rules)时,比如%匹配符使用不当。可能的场景包括变量赋值错误、路径中包含空格或特殊字符,或者Makefile生成过程中出现异常。 接下来,用户提供的引用1中的错误信息显示在编译mapOptmization.cpp和featureAssociation.cpp时出现opencv/cv.h找不到的问题。而引用2中用户提到GTSAM和Eigen版本冲突的问题,并给出了修改GTSAM的CMakeLists.txt的解决方案。这可能意味着用户的项目中同时存在多个依赖库版本冲突的问题,比如Eigen、OpenCV、GTSAM等。 结合用户当前的错误,可能的原因有几个: 1. **Makefile生成错误**:CMake在生成Makefile时可能由于某些配置问题导致生成的目标规则不正确,特别是涉及模式规则(如%.o: %.cpp)的部分。这可能是由于CMakeLists.txt中的错误配置,或者环境变量影响导致的。 2. **路径或文件名问题**:如果文件路径中包含空格、特殊字符或者中文,可能导致Makefile解析错误,进而出现目标模式问题。 3. **依赖库版本冲突**:用户提到的GTSAM和Eigen版本冲突可能导致编译过程中的错误,进而影响Makefile的正确生成。同样,OpenCV头文件路径错误也可能导致编译失败,从而间接引发后续的Makefile错误。 4. **环境变量或缓存问题**:之前的编译残留或CMake缓存中的旧配置可能导致生成错误的Makefile。 接下来,我需要综合这些可能性,给出解决方案的步骤: 1. **检查CMakeLists.txt的语法**:确保没有错误的目标定义,特别是在使用自定义构建规则或外部依赖时。例如,检查是否有错误的条件语句或变量引用。 2. **处理OpenCV头文件路径问题**:用户引用1中出现的opencv/cv.h缺失,可能是因为OpenCV版本较新,该头文件路径已变更。需要将#include <opencv/cv.h>改为#include <opencv2/opencv.hpp>,并确保CMake正确找到OpenCV库。 3. **解决GTSAM和Eigen的版本冲突**:根据引用2中的方法,修改GTSAM的CMakeLists.txt,强制使用系统Eigen,避免版本不一致。 4. **清理和重新生成构建环境**:删除旧的build和devel目录,重新运行catkin_make,确保没有残留的旧配置影响新构建。 5. **检查文件路径和权限**:确保所有相关文件的路径不含特殊字符或空格,并且用户有足够的权限进行编译操作。 6. **验证CMake生成步骤**:手动运行CMake并检查输出的Makefile,查看是否有异常的目标规则,特别是涉及%的部分。 在解决方案中,需要分步骤指导用户进行排查,并针对可能的错误点提供具体的命令或代码修改建议。例如,修改头文件引用、调整CMake配置、清理构建目录等。同时,需要提醒用户在修改后重新配置和编译,以确保变更生效。 此外,考虑到用户之前的问题涉及Boost库和GTSAM,可能需要综合这些依赖项的调整,确保所有库的版本兼容,避免连锁错误。同时,用户可能是在ROS环境下进行编译,因此需要注意ROS的依赖管理和工作空间配置是否正确。 最后,生成相关问题,帮助用户进一步排查可能遇到的其他问题,例如如何确认OpenCV安装是否正确、如何检查CMake变量等,以增强用户自主解决问题的能力。</think>#### 一、错误根源分析 "目标模式不含有‘%’"错误属于Makefile语法解析错误,常见于以下三种情况: 1. **环境变量污染**:终端会话残留旧的PATH或LD_LIBRARY_PATH设置 2. **路径特殊字符**:包含空格或非法字符的工程路径导致规则解析失败 3. **Makefile生成异常**:CMake生成的中间文件损坏(特别是使用ROS catkin_make时) #### 二、系统级修复方案 **1. 基础环境清理** ```bash # 清理所有构建产物 cd ~/catkin_ws rm -rf build devel .catkin_tools # 重置环境变量 unset CMAKE_PREFIX_PATH source /opt/ros/noetic/setup.bash # 根据实际ROS版本调整 ``` **2. 关键依赖验证** ```bash # 检查OpenCV路径(解决引用[1]的opencv/cv.h错误) sudo updatedb locate cv.h | grep opencv # 典型修复方案:修改utility.h头文件引用 # 原错误代码:#include <opencv/cv.h> # 修正为:#include <opencv2/opencv.hpp> ``` **3. 强制重建Makefile** ```cmake # 在CMakeLists.txt首部添加(解决GTSAM eigen冲突) set(GTSAM_USE_SYSTEM_EIGEN ON) # 引用[2]的解决方案 set(CMAKE_CXX_STANDARD 14) # 强制C++14标准 ``` #### 三、分步操作指南 **步骤1:修复路径配置** ```bash # 检查当前路径合法性 echo $PWD | grep -E '[[:space:]]' # 若输出非空则存在空格 # 推荐使用纯英文路径(示例) mv ~/livo_ws ~/lio_ws # 移除路径中的中文 ``` **步骤2:重建GTSAM依赖(关键步骤)** ```bash cd ~/libs/gtsam mkdir -p build && cd build cmake -DGTSAM_BUILD_WITH_MARCH_NATIVE=OFF \ -DGTSAM_USE_SYSTEM_EIGEN=ON .. # 引用[2]增强 make -j$(nproc) sudo make install ``` **步骤3:修正OpenCV引用(解决引用[1]错误)** ```diff // 修改文件:LeGO-LOAM/include/utility.h - #include <opencv/cv.h> + #include <opencv2/opencv.hpp> ``` **步骤4:完整构建流程** ```bash cd ~/lio_ws catkin clean -y catkin config --cmake-args -DCMAKE_BUILD_TYPE=Release catkin build lio_sam --no-status ``` #### 四、验证方法 ```bash # 检查Makefile语法完整性 find build -name "*.make" | xargs grep "%" # 预期输出应包含合法模式规则: %.o: %.cpp @echo Building target: $@ ``` #### 五、版本兼容对照表 | 组件 | 要求版本 | 验证命令 | |------------|------------------|--------------------| | Eigen | ≥3.3.7 | pkg-config --modversion eigen3 | | OpenCV | 3.4.x/4.5.x | opencv_version | | GTSAM | 4.1.0+ | gtsam.version | | ROS | Noetic/Melodic | rosversion -d |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值