国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

LeGo-LOAM 源碼解析

這篇具有很好參考價(jià)值的文章主要介紹了LeGo-LOAM 源碼解析。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

A lightweight and ground optimized lidar odometry and mapping (LeGO-LOAM) system for ROS compatible UGVs. The system takes in point cloud from a Velodyne VLP-16 Lidar (placed horizontal) and optional IMU data as inputs. It outputs 6D pose estimation in real-time.

LeGO-LOAM(激光SLAM,IMU+LiDAR),以LOAM為基礎(chǔ),實(shí)現(xiàn)與其同等的精度同時(shí)大大提高了速度。利用點(diǎn)云分割區(qū)分噪聲,提取平面與邊特征,使用兩步LM優(yōu)化方法求解位姿,并且添加回環(huán)檢測減小漂移。

github:https://github.com/RobustFieldAutonomyLab/LeGO-LOAM

0、整體框架

LeGo-LOAM 源碼解析,ROS,lego-loam

首先看一下代碼結(jié)構(gòu),cloud_msgs、launch、include、src(核心程序),接下來具體看一下:

  • CMakeList.txt
...
find_package(GTSAM REQUIRED QUIET)
find_package(PCL REQUIRED QUIET)
find_package(OpenCV REQUIRED QUIET)
...

依賴庫:GTSAM、PCL、OPenCV

GTSAM 是一個在機(jī)器人領(lǐng)域和計(jì)算機(jī)視覺領(lǐng)域用于平滑(smoothing)和建圖(mapping)的C++庫,采用因子圖(factor graphs)和貝葉斯網(wǎng)絡(luò)(Bayes networks)的方式最大化后驗(yàn)概率。

  • launch/run.launch

節(jié)點(diǎn):rviz、camera_init_to_map、base_link_to_camera、imageProjectionfeatureAssociation、mapOptmizationtransformFusion

主要節(jié)點(diǎn)就是后4個,也是對應(yīng)src目錄下的核心程序。

  • include/utility.h

定義數(shù)據(jù)結(jié)構(gòu):點(diǎn)云、話題、激光雷達(dá)參數(shù)、圖像分割參數(shù)、特征提取參數(shù)、優(yōu)化參數(shù)、建圖參數(shù)

...
/*
    * A point cloud type that has "ring" channel
    */
struct PointXYZIR
{
   
    PCL_ADD_POINT4D;
    PCL_ADD_INTENSITY;
    uint16_t ring;
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW
} EIGEN_ALIGN16;

POINT_CLOUD_REGISTER_POINT_STRUCT (PointXYZIR,  
                                   (float, x, x) (float, y, y)
                                   (float, z, z) (float, intensity, intensity)
                                   (uint16_t, ring, ring)
)
...

這里定義了一種包含 ring_id 的點(diǎn)云數(shù)據(jù)類型,velodyne 的激光雷達(dá)是有這個數(shù)據(jù)的,其它廠商的不一定有;區(qū)別于laser_id,ring_id是指從最下面的激光線依次向上遞增的線號(0-15 for VLP-16)。

...
// Using velodyne cloud "ring" channel for image projection (other lidar may have different name for this channel, change "PointXYZIR" below)
extern const bool useCloudRing = false; // if true, ang_res_y and ang_bottom are not used

// VLP-16
extern const int N_SCAN = 16;//number of lasers
extern const int Horizon_SCAN = 1800;//number of points from one laser of one scan
extern const float ang_res_x = 0.2;//angle resolution of horizon
extern const float ang_res_y = 2.0;//angle resolution of vertical
extern const float ang_bottom = 15.0+0.1;
extern const int groundScanInd = 7;//表示地面需要的線圈數(shù);
...

針對不同激光雷達(dá)的參數(shù)設(shè)置,要根據(jù)自己的實(shí)際情況去調(diào)整,假如使用數(shù)據(jù)集的數(shù)據(jù),比如KITTI,就需要針對HDL-64E進(jìn)行調(diào)整,而且由于該雷達(dá)垂直角分辨率不是均勻的,還需要自己編寫后續(xù)的投影程序。

LeGo-LOAM 源碼解析,ROS,lego-loam

1、imageProjection —— 點(diǎn)云分割

將激光點(diǎn)云處理成圖像

總體流程:訂閱點(diǎn)云數(shù)據(jù)回調(diào)處理 -> 點(diǎn)云轉(zhuǎn)換到pcl預(yù)處理 -> 截取一幀激光數(shù)據(jù) -> 投影映射到圖像 -> 地面移除 -> 點(diǎn)云分割 -> 發(fā)布點(diǎn)云數(shù)據(jù) -> 重置參數(shù);

0. main()

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

    ros::init(argc, argv, "lego_loam");

    ImageProjection IP;

    ROS_INFO("\033[1;32m---->\033[0m Image Projection Started.");

    ros::spin();
    return 0;
}

在 main 函數(shù)中,只是實(shí)例化了一個對象 IP ,所以 一旦創(chuàng)建了一個對象,就進(jìn)入到了 Class 中去執(zhí)行構(gòu)造函數(shù)。

      ImageProjection() : nh("~")
    {
   
		// 訂閱原始的激光點(diǎn)云
        subLaserCloud = nh.subscribe<sensor_msgs::PointCloud2>(pointCloudTopic, 1, &ImageProjection::cloudHandler, this);
		
        // 轉(zhuǎn)換成圖片的點(diǎn)云
        pubFullCloud = nh.advertise<sensor_msgs::PointCloud2>("/full_cloud_projected", 1);
         // 轉(zhuǎn)換成圖片的并帶有距離信息的點(diǎn)云
        pubFullInfoCloud = nh.advertise<sensor_msgs::PointCloud2>("/full_cloud_info", 1);
		
        // 發(fā)布提取的地面特征
        pubGroundCloud = nh.advertise<sensor_msgs::PointCloud2>("/ground_cloud", 1);
        
        // 發(fā)布已經(jīng)分割的點(diǎn)云
        pubSegmentedCloud = nh.advertise<sensor_msgs::PointCloud2>("/segmented_cloud", 1);
         // 具有幾何信息的分割點(diǎn)云
        pubSegmentedCloudPure = nh.advertise<sensor_msgs::PointCloud2>("/segmented_cloud_pure", 1);
        pubSegmentedCloudInfo = nh.advertise<cloud_msgs::cloud_info>("/segmented_cloud_info", 1);
          
         // 含有異常信息的點(diǎn)云
        pubOutlierCloud = nh.advertise<sensor_msgs::PointCloud2>("/outlier_cloud", 1);

        nanPoint.x = std::numeric_limits<float>::quiet_NaN();
        nanPoint.y = std::numeric_limits<float>::quiet_NaN();
        nanPoint.z = std::numeric_limits<float>::quiet_NaN();
        nanPoint.intensity = -1;

        allocateMemory();
        resetParameters();
    }

class 中的構(gòu)造函數(shù)中,訂閱了原始的激光點(diǎn)云數(shù)據(jù)(subLaserCloud)然后就進(jìn)入到了 回調(diào)函數(shù)(ImageProjection::cloudHandler)對點(diǎn)云數(shù)據(jù)進(jìn)行處理。

1. cloudHandler()

訂閱激光雷達(dá)點(diǎn)云信息之后的回調(diào)函數(shù)

    void cloudHandler(const sensor_msgs::PointCloud2ConstPtr& laserCloudMsg){
   
        // 1. Convert ros message to pcl point cloud    將ros消息轉(zhuǎn)換為pcl點(diǎn)云
        copyPointCloud(laserCloudMsg);	
        // 2. Start and end angle of a scan     掃描的起始和結(jié)束角度
        findStartEndAngle();
        // 3. Range image projection        距離圖像投影
        projectPointCloud();
        // 4. Mark ground points        標(biāo)記地面點(diǎn)
        groundRemoval();
        // 5. Point cloud segmentation      點(diǎn)云分割
        cloudSegmentation();
        // 6. Publish all clouds        發(fā)布點(diǎn)云
        publishCloud();
        // 7. Reset parameters for next iteration       重置下一次迭代的參數(shù)
        resetParameters();
    }

回調(diào)函數(shù)中按照以上 7 個邏輯步驟對點(diǎn)云進(jìn)行處理,主要的邏輯步驟通過一個個封裝好的函數(shù)呈現(xiàn)出來。

2. copyPointCloud()

由于激光雷達(dá)點(diǎn)云消息在傳遞過程中使用的是ROS 類型的消息,所以在處理的過程中統(tǒng)一需要對消息類型轉(zhuǎn)換,通常的辦法就是使用 PCL 庫

    // 復(fù)制點(diǎn)云   將ros消息轉(zhuǎn)換為pcl點(diǎn)云
    // 使用pcl庫函數(shù);
    
    void copyPointCloud(const sensor_msgs::PointCloud2ConstPtr& laserCloudMsg){
   
         // 1. 讀取ROS點(diǎn)云轉(zhuǎn)換為PCL點(diǎn)云
        cloudHeader = laserCloudMsg->header;
        // cloudHeader.stamp = ros::Time::now(); // Ouster lidar users may need to uncomment this line
        pcl::fromROSMsg(*laserCloudMsg, *laserCloudIn);

        //2.移除無效的點(diǎn)云 Remove Nan points
        std::vector<int> indices;
        pcl::removeNaNFromPointCloud(*laserCloudIn, *laserCloudIn, indices);

        // 3. have "ring" channel in the cloud or not
        // 如果點(diǎn)云有"ring"通過,則保存為laserCloudInRing
        // 判斷是不是使用了 velodyne 的激光雷達(dá)
        if (useCloudRing == true){
   
            pcl::fromROSMsg(*laserCloudMsg, *laserCloudInRing);
            if (laserCloudInRing->is_dense == false) {
   
                ROS_ERROR("Point cloud is not in dense format, please remove NaN points first!");
                ros::shutdown();
            }  
        }
    }

?這個函數(shù)主體內(nèi)容分為三個步驟,其中的第三步注意區(qū)分自己使用的激光雷達(dá)是否存在 CloudRing,沒有這個的激光雷達(dá)接下來的步驟都不會去執(zhí)行,所以在實(shí)際跑這個框架的時(shí)候一定要注意這個地方

3. findStartEndAngle()

    // 找到開始結(jié)束角度
    // 1.一圈數(shù)據(jù)的角度差,使用atan2計(jì)算;
    // 2.注意計(jì)算結(jié)果的范圍合理性
    
    void findStartEndAngle(){
   
        // start and end orientation of this cloud
        // 1.開始點(diǎn)和結(jié)束點(diǎn)的航向角 (負(fù)號表示順時(shí)針旋轉(zhuǎn))   
        segMsg.startOrientation = -atan2(laserCloudIn->points[0].y, laserCloudIn->points[0].x);

        // 加 2 * M_PI 表示已經(jīng)轉(zhuǎn)轉(zhuǎn)了一圈
        segMsg.endOrientation   = -atan2(laserCloudIn->points[laserCloudIn->points.size() - 1].y,
                                                     laserCloudIn->points[laserCloudIn->points.size() - 1].x) + 2 * M_PI;
        
        // 2.保證所有角度落在 [M_PI , 3M_PI] 上
        if (segMsg.endOrientation - segMsg.startOrientation > 3 * M_PI) {
   
            segMsg.endOrientation -= 2 * M_PI;
        } else if (segMsg.endOrientation - segMsg.startOrientation < M_PI)
            segMsg.endOrientation += 2 * M_PI;
        segMsg.orientationDiff = segMsg.endOrientation - segMsg.startOrientation;
    }

這個函數(shù)在 LOAM 代碼里面也存在。
首先通過一個反正切函數(shù),找到一幀激光點(diǎn)云起始時(shí)刻和結(jié)束時(shí)刻的航向角,第二步的作用是將一幀激光點(diǎn)云的航向角度限制在 [pi , 3pi] 之間,方便后續(xù)計(jì)算出一幀激光點(diǎn)云的時(shí)間。

4. projectPointCloud()

距離圖像投影
將3D point cloud投影映射到2D range image
將激光雷達(dá)數(shù)據(jù)投影成一個16x1800(依雷達(dá)角分辨率而定)的點(diǎn)云陣列

    // 距離圖像投影
    // 將3D point cloud投影映射到2D range image
    // 將激光雷達(dá)數(shù)據(jù)投影成一個16x1800(依雷達(dá)角分辨率而定)的點(diǎn)云陣列
    void projectPointCloud(){
   
        // range image projection
        float verticalAngle, horizonAngle, range;
        size_t rowIdn, columnIdn, index, cloudSize; 
        PointType thisPoint;

        cloudSize = laserCloudIn->points.size();

        // 遍歷整個點(diǎn)云 
        for (size_t i = 0; i < cloudSize; ++i){
   
            // 提取點(diǎn)云中 x y z 坐標(biāo)數(shù)值
            thisPoint.x = laserCloudIn->points[i].x;
            thisPoint.y = laserCloudIn->points[i].y;
            thisPoint.z = laserCloudIn->points[i].z;

            // find the row and column index in the iamge for this point
            // 判斷是不是使用了 velodyne 的雷達(dá)
            if (useCloudRing == true){
   
                // 提取激光雷達(dá)線束到 rowIdn 
                rowIdn = laserCloudInRing->points[i].ring;
            }
            //  是其他的雷達(dá) 就通過俯仰角 確定當(dāng)前的激光點(diǎn)是來自哪個線束 index 
            else{
   
                verticalAngle = atan2(thisPoint.z, sqrt(thisPoint.x * thisPoint.x + thisPoint.y * thisPoint.y)) * 180 / M_PI;
                rowIdn = (verticalAngle + ang_bottom) / ang_res_y;  //確定行索引
            }
            if (rowIdn < 0 || rowIdn >= N_SCAN)
                continue;

            horizonAngle = atan2(thisPoint.x, thisPoint.y) * 180 / M_PI;

            // 保證 columnIdn 的大小在 [0 , 1800)
            columnIdn = -round((horizonAngle-90.0)/ang_res_x) + Horizon_SCAN/2; //確定列索引
            if (columnIdn >= Horizon_SCAN)
                columnIdn -= Horizon_SCAN;

            if (columnIdn < 0 || columnIdn >= Horizon_SCAN)
                continue;

            // 如果距離小于 1米 則過濾掉 通常是過濾自身(距離傳感器比較近的點(diǎn))
            range = sqrt(thisPoint.x * thisPoint.x + thisPoint.y * thisPoint.y + thisPoint.z * thisPoint.z);    //確定深度
            if (range < sensorMinimumRange)
                continue;
            
            // 將計(jì)算下來的距離傳感器的 數(shù)值保存到 rangeMat 中 
            // 這是一個 16 * 1800 的矩陣 rowIdn為線束數(shù)值  columnIdn 是 一圈圓形 灘平之后的數(shù)值
            // range  是特征點(diǎn)云點(diǎn)到原點(diǎn)的數(shù)值
            // 這樣就將一個三維的坐標(biāo) 轉(zhuǎn)換到一個 矩陣中了.
            rangeMat.at<float>(rowIdn, columnIdn) = range;

            // 將 index 和 橫坐標(biāo)存儲在 intensity 中 整數(shù)部分是線束數(shù)值  小數(shù)部分是方向角度
            thisPoint.intensity = (float)rowIdn + (float)columnIdn / 10000.0;

            //深度圖的索引值  index = 列號 +  行號 * 1800 
            index = columnIdn  + rowIdn * Horizon_SCAN;

            // fullCloud 存放的是坐標(biāo)信息(二維的)
            // fullInfoCloud 增加了點(diǎn)到傳感器的距離信息(三維的) 
            fullCloud->points[index] = thisPoint;
            fullInfoCloud->points[index] = thisPoint;

            // intensity 中存放的是點(diǎn)云點(diǎn)到傳感器的距離
            fullInfoCloud->points[index].intensity = range; // the corresponding range of a point is saved as "intensity"
        }
    }

此處對激光點(diǎn)云的處理是 LEGO-LOAM 和 LOAM 的第一個區(qū)別。這里將一幀激光點(diǎn)云處理成一個 16 * 1800 像素的圖片。 16代表的是激光雷達(dá)的線束,1800代表的是激光掃描一圈時(shí)候是 間隔0.5度發(fā)射一個激光束 所以 360 * 0.5 = 1800

? 再者,rangeMat.at(rowIdn, columnIdn) 這里調(diào)用了一個 opencv 的多維數(shù)組,數(shù)組中的行與列 對應(yīng)的就是圖片的長和寬,然后數(shù)組中存放的數(shù)值就是圖片相應(yīng)位置上面的點(diǎn)到激光雷達(dá)的距離。

? 最后將點(diǎn)云的坐標(biāo)信息存儲在 intensity 中,采用的方法是:整數(shù)+小數(shù) 存儲。整數(shù)部分存儲的是激光線束數(shù)值,小數(shù)部分存儲的水平航向值。

LeGo-LOAM 源碼解析,ROS,lego-loam

atan2(y,x);

反正切的角度等于X軸與通過原點(diǎn)和給定坐標(biāo)點(diǎn)(x, y)的直線之間的夾角,結(jié)果為正表示從X軸逆時(shí)針旋轉(zhuǎn)的角度,結(jié)果為負(fù)表示從X軸順時(shí)針旋轉(zhuǎn)的角度;

LeGo-LOAM 源碼解析,ROS,lego-loam

  1. 確定行索引

    根據(jù)ring id或者根據(jù)坐標(biāo)計(jì)算:
    LeGo-LOAM 源碼解析,ROS,lego-loam

  2. 確定列索引([ ? π , π ] 轉(zhuǎn)換為 [ 0 , 1800 ]):
    LeGo-LOAM 源碼解析,ROS,lego-loam

  3. 確定深度值:(注意數(shù)據(jù)的有效范圍)
    LeGo-LOAM 源碼解析,ROS,lego-loam

5. groundRemoval()

首先。判斷地面點(diǎn)的標(biāo)志就是相鄰兩個激光線束掃描到點(diǎn)的坐標(biāo),如果兩個坐標(biāo)之間的差值 或者兩個坐標(biāo)之間的斜率大于一個設(shè)定的值,則將該點(diǎn)

判斷是地面點(diǎn)。所以此處用到了激光點(diǎn)云圖片的深度信息

    // 移除地面點(diǎn)
    // 根據(jù)上下兩線之間點(diǎn)的位置計(jì)算兩線之間俯仰角判斷,小于10度則為地面點(diǎn)
    
    void groundRemoval(){
   
        size_t lowerInd, upperInd;
        float diffX, diffY, diffZ, angle;
        
        // groundMat
        // -1, no valid info to check if ground of not  沒有有效的信息確認(rèn)是不是地面
        //  0, initial value, after validation, means not ground    確認(rèn)不是地面點(diǎn)
        //  1, ground
        for (size_t j = 0; j < Horizon_SCAN; ++j){
   
            //  前7個激光雷達(dá)掃描線束足夠滿足地面點(diǎn)的檢測 所以只遍歷 7 次
            for (size_t i = 0; i < groundScanInd; ++i){
   
                //groundScanInd定義打到地面上激光線的數(shù)目
                lowerInd = j + ( i )*Horizon_SCAN;
                upperInd = j + (i+1)*Horizon_SCAN;

                // 如果之前計(jì)算過的 intensity 是 -1 則直接默認(rèn)為是一個無效點(diǎn)
                if (fullCloud->points[lowerInd].intensity == -1 ||
                    fullCloud->points[upperInd].intensity == -1){
   
                    // no info to check, invalid points  對這個無效點(diǎn)直接進(jìn)行貼標(biāo)簽
                    groundMat.at<int8_t>(i,j) = -1;
                    continue;
                }
                    
                diffX = fullCloud->points[upperInd].x - fullCloud->points[lowerInd].x;
                diffY = fullCloud->points[upperInd].y - fullCloud->points[lowerInd].y;
                diffZ = fullCloud->points[upperInd].z - fullCloud->points[lowerInd].z;

                // 計(jì)算相鄰兩個線束之間的夾角 
                angle = atan2(diffZ, sqrt(diffX*diffX + diffY*diffY) ) * 180 / M_PI;

                 //垂直方向相鄰兩點(diǎn)俯仰角小于10度就判定為地面點(diǎn);相鄰掃描圈
                if (abs(angle - sensorMountAngle) <= 10){
   
                    groundMat.at<int8_t>(i,j) = 1;
                    groundMat.at<int8_t>(i+1,j) = 1;
                }
            }
        }

        // extract ground cloud (groundMat == 1)
        // mark entry that doesn't need to label (ground and invalid point) for segmentation
        // note that ground remove is from 0~N_SCAN-1, need rangeMat for mark label matrix for the 16th scan

        // 給地面點(diǎn) 標(biāo)記一個符號 為 -1 
        for (size_t i = 0; i < N_SCAN; ++i){
   
            for (size_t j = 0; j < Horizon_SCAN; ++j){
   
                if (groundMat.at<int8_t>(i,j) == 1 || rangeMat.at<float>(i,j) == FLT_MAX){
   
                    labelMat.at<int>(i,j) = -1;
                }
            }
        }

        // 發(fā)布點(diǎn)云信息,只發(fā)布地面點(diǎn)云信息
        if (pubGroundCloud.getNumSubscribers() != 0){
   
            for (size_t i = 0; i <= groundScanInd; ++i){
   
                for (size_t j = 0; j < Horizon_SCAN; ++j){
   
                    if (groundMat.at<int8_t>(i,j) == 1)
                        groundCloud->push_back(fullCloud->points[j + i*Horizon_SCAN]);
                }
            }
        }
    }

LeGo-LOAM 源碼解析,ROS,lego-loam

垂直相鄰兩點(diǎn)俯仰角小于10度就判定為地面點(diǎn)。

6. cloudSegmentation()

?從論文中可以看到,分割函數(shù)的來源參考了兩篇論文 ( Effificient Online Segmentation for Sparse 3D Laser Scans ,另外一篇 Fast Range Image-Based Segmentation of Sparse 3D Laser Scans for Online Operation)的方法 。方法的原理可以參考知乎文章 《地面點(diǎn)障礙物快速分割聚類》 。

    // 點(diǎn)云分割
    // 首先對點(diǎn)云進(jìn)行聚類標(biāo)記,根據(jù)標(biāo)簽進(jìn)行對應(yīng)點(diǎn)云塊存儲;
    
    void cloudSegmentation(){
   
        // segmentation process
        for (size_t i = 0; i < N_SCAN; ++i)     // 16線的 一個線束一個的遍歷
            for (size_t j = 0; j < Horizon_SCAN; ++j)       // 水平 的 1800
                if (labelMat.at<int>(i,j) == 0)     // 對非地面點(diǎn)進(jìn)行點(diǎn)云分割
                    labelComponents(i, j);      //  調(diào)用這個函數(shù)對點(diǎn)云進(jìn)行分割聚類

        int sizeOfSegCloud = 0;

        // extract segmented cloud for lidar odometry
        // 提取分割點(diǎn)云 用于激光雷達(dá)里程計(jì)
        for (size_t i = 0; i < N_SCAN; ++i) {
   

            segMsg.startRingIndex[i] = sizeOfSegCloud-1 + 5;

            for (size_t j = 0; j < Horizon_SCAN; ++j) {
   
                if (labelMat.at<int>(i,j) > 0 || groundMat.at<int8_t>(i,j) == 1){
   
                    // outliers that will not be used for optimization (always continue)
                    // 勾勒出優(yōu)化過程中不被使用的值

                    // 1. 如果label為999999則跳過
                    if (labelMat.at<int>(i,j) == 999999){
   
                        if (i > groundScanInd && j % 5 == 0){
   
                            outlierCloud->push_back(fullCloud->points[j + i*Horizon_SCAN]);
                            continue;
                        }else{
   
                            continue;
                        }
                    }

                    // majority of ground points are skipped
                    // 2. 如果為地,跳過index不是5的倍數(shù)的點(diǎn)
                    if (groundMat.at<int8_t>(i,j) == 1){
   
                        if (j%5!=0 && j>5 && j<Horizon_SCAN-5)
                            continue;
                    }
                    // mark ground points so they will not be considered as edge features later
                    segMsg.segmentedCloudGroundFlag[sizeOfSegCloud] = (groundMat.at<int8_t>(i,j) == 1);
                    // mark the points' column index for marking occlusion later
                    segMsg.segmentedCloudColInd[sizeOfSegCloud] = j;
                    // save range info
                    segMsg.segmentedCloudRange[sizeOfSegCloud]  = rangeMat.at<float>(i,j);
                    // save seg cloud
                    segmentedCloud->push_back(fullCloud->points[j + i*Horizon_SCAN]);
                    // size of seg cloud
                    ++sizeOfSegCloud;
                }
            }

            segMsg.endRingIndex[i] = sizeOfSegCloud-1 - 5;
        }
        
        // extract segmented cloud for visualization
        if (pubSegmentedCloudPure.getNumSubscribers() != 0){
   
            for (size_t i = 0; i < N_SCAN; ++i){
   
                for (size_t j = 0; j < Horizon_SCAN; ++j){
   
                    if (labelMat.at<int>(i,j) > 0 && labelMat.at<int>(i,j) != 999999){
   
                        segmentedCloudPure->push_back(fullCloud->points[j + i*Horizon_SCAN]);
                        segmentedCloudPure->points.back().intensity = labelMat.at<int>(i,j);
                    }
                }
            }
        }
    }

7. labelComponents()

    // 標(biāo)簽組件
    // 局部特征檢測聚類
    // 平面點(diǎn)與邊緣點(diǎn)
    
    void labelComponents(int row, int col){
   
        // use std::queue std::vector std::deque will slow the program down greatly
        float d1, d2, alpha, angle;
        int fromIndX, fromIndY, thisIndX, thisIndY; 
        bool lineCountFlag[N_SCAN] = {
   false};

        // 傳進(jìn)來的兩個參數(shù),按照坐標(biāo)不同分別給他們放到 X 與 Y 的數(shù)組中
        queueIndX[0] = row;
        queueIndY[0] = col;
        int queueSize = 1;      // 需要計(jì)算角度的點(diǎn)的數(shù)量
        int queueStartInd = 0;
        int queueEndInd = 1;

        allPushedIndX[0] = row;
        allPushedIndY[0] = col;
        int allPushedIndSize = 1;
        
        while(queueSize > 0){
   
            // Pop point
            fromIndX = queueIndX[queueStartInd];
            fromIndY = queueIndY[queueStartInd];
            --queueSize;
            ++queueStartInd;
            // Mark popped point
            labelMat.at<int>(fromIndX, fromIndY) = labelCount;
            // Loop through all the neighboring grids of popped grid

            // 遍歷整個點(diǎn)云,遍歷前后左右四個鄰域點(diǎn),求點(diǎn)之間的角度數(shù)值 
            for (auto iter = neighborIterator.begin(); iter != neighborIterator.end(); ++iter){
   
                // new index
                thisIndX = fromIndX + (*iter).first;
                thisIndY = fromIndY + (*iter).second;
                // index should be within the boundary
                if (thisIndX < 0 || thisIndX >= N_SCAN)
                    continue;
                // at range image margin (left or right side)
                if (thisIndY < 0)
                    thisIndY = Horizon_SCAN - 1;
                if (thisIndY >= Horizon_SCAN)
                    thisIndY = 0;
                // prevent infinite loop (caused by put already examined point back)
                if (labelMat.at<int>(thisIndX, thisIndY) != 0)
                    continue;

                 //比較得出較大深度與較小深度
                d1 = std::max(rangeMat.at<float>(fromIndX, fromIndY), 
                              rangeMat.at<float>(thisIndX, thisIndY));
                d2 = std::min(rangeMat.at<float>(fromIndX, fromIndY), 
                              rangeMat.at<float>(thisIndX, thisIndY));

                if ((*iter).first == 0)
                    alpha = segmentAlphaX;
                else
                    alpha = segmentAlphaY;

                angle = atan2(d2*sin(alpha), (d1 -d2*cos(alpha)));      //平面聚類公式,角度越大兩點(diǎn)越趨向于平面
                //segmentTheta=60度
                if (angle > segmentTheta){
   

                    queueIndX[queueEndInd] = thisIndX;
                    queueIndY[queueEndInd] = thisIndY;
                    ++queueSize;
                    ++queueEndInd;

                    labelMat.at<int>(thisIndX, thisIndY) = labelCount;
                    lineCountFlag[thisIndX] = true;

                    allPushedIndX[allPushedIndSize] = thisIndX;
                    allPushedIndY[allPushedIndSize] = thisIndY;
                    ++allPushedIndSize;
                }
            }
        }

        // check if this segment is valid
        bool feasibleSegment = false;

        // 如果是 allPushedIndSize 累加的數(shù)量增加到了30 個 則判斷這部分點(diǎn)云屬于一個聚類
        if (allPushedIndSize >= 30)
            feasibleSegment = true;     //面點(diǎn)

        // 如果垂直 方向上點(diǎn)的數(shù)量大于 5個 默認(rèn)是一個有效的聚類
        else if (allPushedIndSize >= segmentValidPointNum){
   
            int lineCount = 0;
            for (size_t i = 0; i < N_SCAN; ++i)
                if (lineCountFlag[i] == true)
                    ++lineCount;
            if (lineCount >= segmentValidLineNum)
                feasibleSegment = true;     //邊點(diǎn)  
        }
        
        // segment is valid, mark these points
        if (feasibleSegment == true){
   
            ++labelCount;
        }else{
    // segment is invalid, mark these points
            for (size_t i = 0; i < allPushedIndSize; ++i){
   
                labelMat.at<int>(allPushedIndX[i], allPushedIndY[i]) = 999999;
            }
        }
    }

取去除地面的 labelMat,逐點(diǎn)開始計(jì)算鄰域4點(diǎn)(鄰域的索引要注意有效范圍),與中心點(diǎn)比較出距離的最大值與最小值,通過下面公式結(jié)合閾值π / 3,大于該值則構(gòu)成平面特征為平面點(diǎn),隊(duì)列存儲索引,labelMat 存儲標(biāo)簽值,同時(shí) lineCountFlag 置 true,直到不滿足閾值跳出進(jìn)行下步。

LeGo-LOAM 源碼解析,ROS,lego-loam
LeGo-LOAM 源碼解析,ROS,lego-loam
聚類,超過30個點(diǎn)聚為一類,labelCount++;
小于30超過5,統(tǒng)計(jì)垂直方向聚類點(diǎn)數(shù),超過3個也標(biāo)記為一類;
若都不滿足,則賦值999999表示需要舍棄的聚類點(diǎn)。

8. publishCloud()

發(fā)布各類點(diǎn)云數(shù)據(jù)

9. resetParameters()

參數(shù)重置

LeGO-LOAM首先進(jìn)行地面分割,找到地面之后,對地面之上的點(diǎn)進(jìn)行聚類。聚類的算法如下圖,主要是根據(jù)斜率對物體做切割,最后得到地面和分割后的點(diǎn)云。上述步驟的關(guān)鍵是要理解如何進(jìn)行地面分割和點(diǎn)云分割。
LeGo-LOAM 源碼解析,ROS,lego-loam

點(diǎn)云分割完成之后,接下來對分割后的點(diǎn)云提取特征,提取的特征的目的是進(jìn)行點(diǎn)云配準(zhǔn),從而得出當(dāng)前位姿。

2、featureAssociation —— 特征提取與匹配

訂閱分割點(diǎn)云、外點(diǎn)、imu數(shù)據(jù),總體流程:

  • (特征提取部分)訂閱傳感器數(shù)據(jù) -> 運(yùn)動畸變?nèi)コ?-> 曲率計(jì)算 -> 去除瑕點(diǎn) -> 提取邊、平面特征 -> 發(fā)布特征點(diǎn)云;
  • (特征關(guān)聯(lián)部分)將IMU信息作為先驗(yàn) -> 根據(jù)線特征、面特征計(jì)算位姿變換 -> 聯(lián)合IMU數(shù)據(jù)進(jìn)行位姿更新 -> 發(fā)布里程計(jì)信息

0. main()

主函數(shù)內(nèi)部做了兩件事情,一個是實(shí)例化一個對象,一個是循環(huán)執(zhí)行類方法

int main(int argc, char** argv)
{
   
    ros::init(argc, argv, "lego_loam");

    ROS_INFO("\033[1;32m---->\033[0m Feature Association Started.");

     // 1. 在構(gòu)造函數(shù)中訂閱消息和消息回調(diào)函數(shù)
    FeatureAssociation FA;

    ros::Rate rate(200);
    // 2. 循環(huán)調(diào)用runFeatureAssociation,函數(shù)的主流程所在
    while (ros::ok())
    {
   
        ros::spinOnce();

        FA.runFeatureAssociation();

        rate.sleep();
    }
    
    ros::spin();
    return 0;
}

主函數(shù)里面首選實(shí)例化了一個對象 FA ,所以和之前的ImageProjection 處理過程一樣,執(zhí)行構(gòu)造函數(shù)的內(nèi)容,所以在構(gòu)造函數(shù)中會訂閱上一個cpp 文件中 pub 出來的消息。然后轉(zhuǎn)到回調(diào)函數(shù)中處理數(shù)據(jù),此處的回調(diào)函數(shù)都是將相應(yīng)的信息存儲到buff 中,所以不做詳細(xì)分析。

消息回調(diào)主要是接收上一個步驟(點(diǎn)云分割)中分割好的點(diǎn)云消息。文章來源地址http://www.zghlxwxcb.cn/news/detail-707404.html

    FeatureAssociation():
        nh("~")
        {
   
        // 1. 接收消息,上一步分割好的點(diǎn)云
        // 訂閱了分割之后的點(diǎn)云
        subLaserCloud = nh.subscribe<sensor_msgs::PointCloud2>("/segmented_cloud", 1, &FeatureAssociation::laserCloudHandler, this);
        
        // 訂閱的分割點(diǎn)云含有圖像的深度信息
        subLaserCloudInfo = nh.subscribe<cloud_msgs::cloud_info>("/segmented_cloud_info", 1, &FeatureAssociation::laserCloudInfoHandler, this);
        
        // 非聚類的點(diǎn)
        subOutlierCloud = nh.subscribe<sensor_msgs::PointCloud2>("/outlier_cloud", 1, &FeatureAssociation::outlierCloudHandler, this);
        
        // IMU傳感器的消息
        subImu = nh.subscribe<sensor_msgs::Imu>(imuTopic, 50, &FeatureAssociation::imuHandler, this);
		
		// 2. 發(fā)布消息
        // 發(fā)布面特征點(diǎn) 和 邊特征點(diǎn)
        pubCornerPointsSharp = nh.advertise<sensor_msgs::PointCloud2>("/laser_cloud_sharp", 1);
        pubCornerPointsLessSharp = nh.advertise<sensor_msgs::PointCloud2>("/laser_cloud_less_sharp", 1);
        pubSurfPointsFlat = nh.advertise<sensor_msgs::PointCloud2>("/laser_cloud_flat", 1);
        pubSurfPointsLessFlat = nh.advertise<sensor_msgs::PointCloud2>("/laser_cloud_less_flat", 1);

        pubLaserCloudCornerLast = nh.advertise<sensor_msgs::PointCloud2>("/l

到了這里,關(guān)于LeGo-LOAM 源碼解析的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 多傳感器融合SLAM --- 5.Lego-LOAM論文解讀及運(yùn)行

    目錄 1 Lego LOAM框架簡介 2 論文解讀 2.1 摘要部分 2.2 INTRODUCTION部分 2.3 系統(tǒng)描述

    2024年02月08日
    瀏覽(27)
  • Ubuntu20.04下運(yùn)行LOAM系列:A-LOAM、LeGO-LOAM、SC-LeGO-LOAM、LIO-SAM 和 LVI-SAM

    Ubuntu20.04下運(yùn)行LOAM系列:A-LOAM、LeGO-LOAM、SC-LeGO-LOAM、LIO-SAM 和 LVI-SAM

    在我第一篇博文Ubuntu 20.04配置ORB-SLAM2和ORB-SLAM3運(yùn)行環(huán)境+ROS實(shí)時(shí)運(yùn)行ORB-SLAM2+Gazebo仿真運(yùn)行ORB-SLAM2+各種相關(guān)庫的安裝的基礎(chǔ)環(huán)境下跑通LOAM系列 首先按照上一篇文章已經(jīng)安裝好了ROS noetic、Eigen3.4.0、OpenCV4.2.0和PCL1.10等三方庫,它們的安裝不再贅述,另外文章中 使用的數(shù)據(jù) 已經(jīng)在

    2024年02月06日
    瀏覽(183)
  • Ubuntu20.04安裝LeGO-LOAM和LIO-SAM

    Ubuntu20.04安裝LeGO-LOAM和LIO-SAM

    Ubuntu20.04安裝LIO-SAM真是挺折磨人的,填了一路的坑,在此記錄分享一下,為大家安裝編譯算法提供一個全面的參考。 目錄 1. GTSAM安裝 1.1 GTSAM安裝準(zhǔn)備 1.1.1 目錄/usr/local/lib下清理gatsam 1.1.2 目錄/opt/ros/noetic/lib/下清理gtsam 1.2 GTSAM安裝過程 2. LIO-SAM算法依賴項(xiàng)安裝 3. LeGO-LOAM算法編

    2024年04月28日
    瀏覽(43)
  • 編譯LeGo-LOAM,并且采用速騰聚創(chuàng)激光雷達(dá)與之相連

    編譯LeGo-LOAM,并且采用速騰聚創(chuàng)激光雷達(dá)與之相連

    參考鏈接:實(shí)車部署采用速騰聚創(chuàng)RS16激光雷達(dá)的LeGo-LOAM LeGO-LOAM初探:原理,安裝和測試 1.gtsam安裝(install的過程比較慢,需要耐心等待) 2.下載并編譯LeGO-LOAM 3.數(shù)據(jù)集試運(yùn)行 數(shù)據(jù)集的百度云地址:https://pan.baidu.com/s/1SkrqfN82il1m6jhkLZT-WA 密碼: oqo8 打開 LeGO-LOAM/LeGO-LOAM/launch/run.

    2024年02月08日
    瀏覽(24)
  • 3D激光SLAM:LeGO-LOAM論文解讀---激光雷達(dá)里程計(jì)與建圖

    3D激光SLAM:LeGO-LOAM論文解讀---激光雷達(dá)里程計(jì)與建圖

    激光雷達(dá)里程計(jì)模塊的功能就是 :估計(jì)相鄰幀之間的位姿變換。 估計(jì)的方式 :在相鄰幀之間做點(diǎn)到線的約束和點(diǎn)到面的約束 具體的方式和LOAM一樣 針對LOAM的改進(jìn) 1 基于標(biāo)簽的匹配 在特征提取部分提取的特征點(diǎn)都會有個標(biāo)簽(在點(diǎn)云分割時(shí)分配的) 因此在找對應(yīng)點(diǎn)時(shí),標(biāo)簽

    2023年04月09日
    瀏覽(28)
  • 從零入門激光SLAM(五)——手把手帶你編譯運(yùn)行Lego_loam

    從零入門激光SLAM(五)——手把手帶你編譯運(yùn)行Lego_loam

    大家好呀,我是一個SLAM方向的在讀博士,深知SLAM學(xué)習(xí)過程一路走來的坎坷,也十分感謝各位大佬的優(yōu)質(zhì)文章和源碼。隨著知識的越來越多,越來越細(xì),我準(zhǔn)備整理一個自己的激光SLAM學(xué)習(xí)筆記專欄,從0帶大家快速上手激光SLAM,也方便想入門SLAM的同學(xué)和小白學(xué)習(xí)參考,相信看

    2024年01月17日
    瀏覽(295)
  • LOAM、Lego-liom、Lio-sam軌跡保存,與Kitti數(shù)據(jù)集真值進(jìn)行評估

    LOAM、Lego-liom、Lio-sam軌跡保存,與Kitti數(shù)據(jù)集真值進(jìn)行評估

    ????????首先需要保存軌跡,軌跡保存參考下面的代碼,最好自己 添加一個節(jié)點(diǎn) (如下圖),用新節(jié)點(diǎn)來訂閱和保存軌跡至txt文件,因?yàn)橹苯釉谒惴ǖ木€程中加入此步驟我試了好像保存不了,好像是在不同線程間的參數(shù)傳遞格式的問題(也可能是我個人的問題)。 ????

    2023年04月08日
    瀏覽(211)
  • lego_loam、lio_sam運(yùn)行kitti(完成kitti2bag、evo測試)

    lego_loam、lio_sam運(yùn)行kitti(完成kitti2bag、evo測試)

    目錄 一、工作空間的創(chuàng)建,功能包的編譯等等 二、lego_loam運(yùn)行、記錄traj軌跡 三、evo對比使用 四、kitti2bag轉(zhuǎn)換 五、lio_sam https://blog.csdn.net/qq_40528849/article/details/124705983 1.運(yùn)行 launch 文件 roslaunch lego_loam run.launch 注意:參數(shù)“/ use_sim_time”,對于模擬則設(shè)置為“true”,對于使用

    2024年02月05日
    瀏覽(87)
  • a-loam源碼圖文詳解

    a-loam源碼圖文詳解

    目錄 1?a-loam流程簡介 1.1 節(jié)點(diǎn)圖 1.2 代碼整體框架 2 特征點(diǎn)提取及均勻化 2.1論文原理 2.2 代碼詳解 3 異常點(diǎn)篩除 3.1 論文原理 3.2 代碼詳解 4 激光雷達(dá)畸變及運(yùn)動補(bǔ)償 4.1?畸變及補(bǔ)償原理 4.2 代碼詳解 5 幀間里程計(jì)運(yùn)動估計(jì) 5.1?幀間里程計(jì)原理 5.2 代碼詳解 6 局部地圖構(gòu)建 6.1?局

    2024年02月06日
    瀏覽(48)
  • 【3D激光SLAM】LOAM源代碼解析--laserOdometry.cpp

    【3D激光SLAM】LOAM源代碼解析--laserOdometry.cpp

    ·【3D激光SLAM】LOAM源代碼解析–scanRegistration.cpp ·【3D激光SLAM】LOAM源代碼解析–laserOdometry.cpp ·【3D激光SLAM】LOAM源代碼解析–laserMapping.cpp ·【3D激光SLAM】LOAM源代碼解析–transformMaintenance.cpp 本系列文章將對LOAM源代碼進(jìn)行講解,在講解過程中,涉及到論文中提到的部分, 會結(jié)

    2024年02月11日
    瀏覽(55)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包