LeGO_LOAM的mapOptimization函数

mapOptimization函数

transformAssociateToMap函数(细节剖析)

**transformSum:**为当前时刻里程计的估计的姿态变换
**transformBefMapped:**为上一时刻里程计估计的姿态变换
**transformAftMapped:**为上一时刻优化后的全局姿态变换
**transformTobeMapped:**为当前时刻待优化的全局姿态变换
这四个变量都是在世界坐标系下的表示——>下面这页纸是分别运用里程计估计和建图优化推算出的上一时刻lidar pose经过姿态变换得到当前时刻lidar pose的两种不同的推算过程。
通过transformAssociateToMap函数能够估计出一个未经优化的当前时刻的经过建图后的全局姿态变换的估计
在这里插入图片描述参考链接 很重要
参考链接
在这里插入图片描述

    void transformAssociateToMap()
    {//transform delta pose from world frame to current frame
        std::cout << "carry out transformAssociateToMap thread"<<std::endl;//yaw T
        //using the odometry to estimate the transition in current frame
        // (transformSum is the rotation from world to current frame)
        float x1 = cos(transformSum[1]) * (transformBefMapped[3] - transformSum[3]) 
                 - sin(transformSum[1]) * (transformBefMapped[5] - transformSum[5]);
        float y1 = transformBefMapped[4] - transformSum[4];
        float z1 = sin(transformSum[1]) * (transformBefMapped[3] - transformSum[3]) 
                 + cos(transformSum[1]) * (transformBefMapped[5] - transformSum[5]);

        float x2 = x1;//pitch T
        float y2 = cos(transformSum[0]) * y1 + sin(transformSum[0]) * z1;
        float z2 = -sin(transformSum[0]) * y1 + cos(transformSum[0]) * z1;
        // for the estimation of the transition using odometry information0
        transformIncre[3] = cos(transformSum[2]) * x2 + sin(transformSum[2]) * y2;
        transformIncre[4] = -sin(transformSum[2]) * x2 + cos(transformSum[2]) * y2;
        transformIncre[5] = z2;//roll T

        float sbcx = sin(transformSum[0]);
        float cbcx = cos(transformSum[0]);
        float sbcy = sin(transformSum[1]);
        float cbcy = cos(transformSum[1]);
        float sbcz = sin(transformSum[2]);
        float cbcz = cos(transformSum[2]);//current LO estimated pose

        float sblx = sin(transformBefMapped[0]);
        float cblx = cos(transformBefMapped[0]);
        float sbly = sin(transformBefMapped[1]);
        float cbly = cos(transformBefMapped[1]);
        float sblz = sin(transformBefMapped[2]);
        float cblz = cos(transformBefMapped[2]);//last LO estimated pose

        float salx = sin(transformAftMapped[0]);
        float calx = cos(transformAftMapped[0]);
        float saly = sin(transformAftMapped[1]);
        float caly = cos(transformAftMapped[1]);
        float salz = sin(transformAftMapped[2]);
        float calz = cos(transformAftMapped[2]);
//for the estimation of the current after mapped pose(before updated) store in transformTobeMapped
//first estimate the tobe updated euler angle
        float srx = -sbcx*(salx*sblx + calx*cblx*salz*sblz + calx*calz*cblx*cblz)
                  - cbcx*sbcy*(calx*calz*(cbly*sblz - cblz*sblx*sbly)
                  - calx*salz*(cbly*cblz + sblx*sbly*sblz) + cblx*salx*sbly)
                  - cbcx*cbcy*(calx*salz*(cblz*sbly - cbly*sblx*sblz)
                  - calx*calz*(sbly*sblz + cbly*cblz*sblx) + cblx*cbly*salx);
        transformTobeMapped[0] = -asin(srx);

        float srycrx = sbcx*(cblx*cblz*(caly*salz - calz*salx*saly)
                     - cblx*sblz*(caly*calz + salx*saly*salz) + calx*saly*sblx)
                     - cbcx*cbcy*((caly*calz + salx*saly*salz)*(cblz*sbly - cbly*sblx*sblz)
                     + (caly*salz - calz*salx*saly)*(sbly*sblz + cbly*cblz*sblx) - calx*cblx*cbly*saly)
                     + cbcx*sbcy*((caly*calz + salx*saly*salz)*(cbly*cblz + sblx*sbly*sblz)
                     + (caly*salz - calz*salx*saly)*(cbly*sblz - cblz*sblx*sbly) + calx*cblx*saly*sbly);

        float crycrx = sbcx*(cblx*sblz*(calz*saly - caly*salx*salz)
                     - cblx*cblz*(saly*salz + caly*calz*salx) + calx*caly*sblx)
                     + cbcx*cbcy*((saly*salz + caly*calz*salx)*(sbly*sblz + cbly*cblz*sblx)
                     + (calz*saly - caly*salx*salz)*(cblz*sbly - cbly*sblx*sblz) + calx*caly*cblx*cbly)
                     - cbcx*sbcy*((saly*salz + caly*calz*salx)*(cbly*sblz - cblz*sblx*sbly)
                     + (calz*saly - caly*salx*salz)*(cbly*cblz + sblx*sbly*sblz) - calx*caly*cblx*sbly);
        transformTobeMapped[1] = atan2(srycrx / cos(transformTobeMapped[0]), 
                                       crycrx / cos(transformTobeMapped[0]));
        
        float srzcrx = (cbcz*sbcy - cbcy*sbcx*sbcz)*(calx*salz*(cblz*sbly - cbly*sblx*sblz)
                     - calx*calz*(sbly*sblz + cbly*cblz*sblx) + cblx*cbly*salx)
                     - (cbcy*cbcz + sbcx*sbcy*sbcz)*(calx*calz*(cbly*sblz - cblz*sblx*sbly)
                     - calx*salz*(cbly*cblz + sblx*sbly*sblz) + cblx*salx*sbly)
                     + cbcx*sbcz*(salx*sblx + calx*cblx*salz*sblz + calx*calz*cblx*cblz);
        float crzcrx = (cbcy*sbcz - cbcz*sbcx*sbcy)*(calx*calz*(cbly*sblz - cblz*sblx*sbly)
                     - calx*salz*(cbly*cblz + sblx*sbly*sblz) + cblx*salx*sbly)
                     - (sbcy*sbcz + cbcy*cbcz*sbcx)*(calx*salz*(cblz*sbly - cbly*sblx*sblz)
                     - calx*calz*(sbly*sblz + cbly*cblz*sblx) + cblx*cbly*salx)
                     + cbcx*cbcz*(salx*sblx + calx*cblx*salz*sblz + calx*calz*cblx*cblz);
        transformTobeMapped[2] = atan2(srzcrx / cos(transformTobeMapped[0]), 
                                       crzcrx / cos(transformTobeMapped[0]));
        //current to world frame (yaw pitch roll)  transformIncre is transition in current frame
        x1 = cos(transformTobeMapped[2]) * transformIncre[3] - sin(transformTobeMapped[2]) * transformIncre[4];
        y1 = sin(transformTobeMapped[2]) * transformIncre[3] + cos(transformTobeMapped[2]) * transformIncre[4];
        z1 = transformIncre[5];

        x2 = x1;
        y2 = cos(transformTobeMapped[0]) * y1 - sin(transformTobeMapped[0]) * z1;
        z2 = sin(transformTobeMapped[0]) * y1 + cos(transformTobeMapped[0]) * z1;
        //used the last after maped transition - tobeupdated transition for current tobemapped transition
        transformTobeMapped[3] = transformAftMapped[3] 
                               - (cos(transformTobeMapped[1]) * x2 + sin(transformTobeMapped[1]) * z2);
        transformTobeMapped[4] = transformAftMapped[4] - y2;
        transformTobeMapped[5] = transformAftMapped[5] 
                               - (-sin(transformTobeMapped[1]) * x2 + cos(transformTobeMapped[1]) * z2);
    }

TransformToEnd函数

我是这么理解的:先将当前帧的laser point (由于点的扫描是连续的),这是通过s * transformCur[0],假设匀速运动模型,将点云转换到统一的first lidar (last imu)对应的frame下,然后,在通过transformCur变换,得到当前点云在first lidar下做的变换,得到当前帧点云在first lidar 下得到的姿态更新,然后在通过从first lidar(first)->end lidar(imu)提供的变换,将last end frame 的点云的姿态更新位姿,转到当前时刻的end 姿态更新的位姿

first laser scan map optimization执行内容

  1. 执行transformAssociateToMap函数,利用通过laserOdometryHandler接收到的经过雷达里程计估计出来的pose信息,对即将被mapping updated的优化位姿做估计预测。并将估计的mapping updated 信息存储到transformTobeMapped中
  2. 由于当前帧还未提取出关键帧点云,所以extractSurroundingKeyFrames函数不执行,而是执行downsampleCurrentScan对当前帧点云的corner 以及 surf feature point 进行降采样。[降采样的点云分别存储在laserCloudCornerLastDSNum,laserCloudSurfLastDSNum,laserCloudOutlierLastDSNum中,并通过laserCloudSurfTotalLast以及laserCloudSurfTotalLast存储每一帧点云的特征点信息(关注一下后面这个怎么调整)]
  3. scan2MapOptimization中因为还没有插入到global map中的feature point 因此 不能利用 map 中的关键特征点信息进行scan to map 的优化。
  4. saveKeyFramesAndFactor 中,首先明确当前帧的初始robot pose is 0.
currentRobotPosPoint.x = transformAftMapped[3];//for first laser scan 0
        currentRobotPosPoint.y = transformAftMapped[4];
        currentRobotPosPoint.z = transformAftMapped[5];

但是关注这一行代码:


        if (saveThisKeyFrame == false && !cloudKeyPoses3D->points.empty())
        	return;

不退出当前的saveKeyFramesAndFactor函数,因为需要从第一帧点云提取关键帧用于后面的scan2mapOptimization.
将第一次得到的优化里程计的估计位姿信息transformAssociateToMap得到的transformTobeMapped估计的位姿预测值加入到factor graph中作为initialEstimate——初始估计,并将初始的mapping后的位姿预测值作为上一次的transform结果传递给transformLast(我推测是用来记录上一次transform 优化后的pose 信息的),对第一帧点云的估计位姿信息执行isam的优化更新。

 isam->update(gtSAMgraph, initialEstimate);
        isam->update();

将优化更新后的pose姿态信息作为一个三维点云信息,存储在keypose中作为关键帧输出!!(这里不止存储3Dpose还存储6Dpose)
这里的巧妙之处在于将利用更新的pose信息,作为一个关键帧点信息存储在keypose中

        isamCurrentEstimate = isam->calculateEstimate();
        latestEstimate = isamCurrentEstimate.at<Pose3>(isamCurrentEstimate.size()-1);
        //key pose storation
        thisPose3D.x = latestEstimate.translation().y();
        thisPose3D.y = latestEstimate.translation().z();
        thisPose3D.z = latestEstimate.translation().x();
        thisPose3D.intensity = cloudKeyPoses3D->points.size(); // this can be used as index
        cloudKeyPoses3D->push_back(thisPose3D);
        //for the estimation of the 6 pose
        thisPose6D.x = thisPose3D.x;
        thisPose6D.y = thisPose3D.y;
        thisPose6D.z = thisPose3D.z;
        thisPose6D.intensity = thisPose3D.intensity; // this can be used as index
        thisPose6D.roll  = latestEstimate.rotation().pitch();
        thisPose6D.pitch = latestEstimate.rotation().yaw();
        thisPose6D.yaw   = latestEstimate.rotation().roll(); // in camera frame
        thisPose6D.time = timeLaserOdometry;
        cloudKeyPoses6D->push_back(thisPose6D);

将优化更新的位姿进行存储transformAftMapped作为updated后的pose 信息,这样也能明白 transformLast[i] = transformAftMapped[i];中transformLast记录的是前一帧优化更新后的optimized pose,并将下一帧的带估计mapped的pose的预测值初始化为前一帧优化后的optimized pose

 pcl::PointCloud<PointType>::Ptr thisCornerKeyFrame(new pcl::PointCloud<PointType>());
        pcl::PointCloud<PointType>::Ptr thisSurfKeyFrame(new pcl::PointCloud<PointType>());
        pcl::PointCloud<PointType>::Ptr thisOutlierKeyFrame(new pcl::PointCloud<PointType>());

        pcl::copyPointCloud(*laserCloudCornerLastDS,  *thisCornerKeyFrame);
        pcl::copyPointCloud(*laserCloudSurfLastDS,    *thisSurfKeyFrame);
        pcl::copyPointCloud(*laserCloudOutlierLastDS, *thisOutlierKeyFrame);//for the last feature point as the key pose

        cornerCloudKeyFrames.push_back(thisCornerKeyFrame);
        surfCloudKeyFrames.push_back(thisSurfKeyFrame);
        outlierCloudKeyFrames.push_back(thisOutlierKeyFrame);//for the storation of the key pose

当前帧被选择为关键帧后,其corner feature 以及surf feature作为关键帧点云,存储在CloudKeyFrames(用来保存所有被选作为关键帧的点云数据信息)中,记录所有作为关键帧的点云。
5. 通过publishTF函数,分别将mapping优化后的pose以及transformBefMapped==这个transformBefMapped代表什么?==的角速度和加速度信息存储到odomAftMapped基于mapping 优化后的odo pose,并进行发布。同时aftMappedTrans发布基于camera到aftermapped 的基于mapping update 的优化位姿变换。
6. 最后通过publishKeyPosesAndFrames函数,发布关键帧点云以及基于优化估计的位姿transformTobeMapped,发布基于里程计的特征点云的数据信息

对于非起始点云

  1. 通过transformAssociateToMap函数根据当前里程计数据transformSumtransformBefMapped前一时刻里程计数据transformBefMapped 前一个时刻的优化全局位姿估计出transformTobeMapped 待优化的当前时刻的优化位姿的预测值
  2. 通过extractSurroundingKeyFrames函数,由于前一个时刻存储了之前所有时刻提取出来的关键帧cloudKeyPoses3D信息,通过kdtree 查找基于上一个优化机器人的pose information currentRobotPosPoint进行最近邻搜索radiusSearch,同时返回所有在上一个优化的机器人关键帧位姿的全部特征关键帧(基于旧关键帧 取提取附近的关键帧 用于提供 scan to map 的匹配),判断基于上一帧优化的关键帧数据其提取出的可用于优化的关键帧的特征信息,是否在surroundingKeyPosesDS中存在,对于不存在的特征 (表示之前存在的老的关键帧特征数据已经不能支持scan 2 map 基于前一帧的优化 采集到的特征 提供新的特征关键帧了 所以 需要删除旧关键帧 以及插入新的关键帧 )这里需要注意surroundingExistingKeyPosesID以及surroundingCornerCloudKeyFrames;surroundingSurfCloudKeyFrames;surroundingOutlierCloudKeyFrames均为基于扫描线的顺序存储,在for 循环插入新的关键帧数据时,通过上一次update 的pose 数据,利用transformPointCloud函数,将插入的新的关键帧数据 transform到world 坐标系,然后在surroundingExistingKeyPoses和Frame中插入,分别将已经经过更新的存在的特征数据插入MaplaserCloudCornerFromMap 以及laserCloudSurfFromMap 和laserCloudSurfFromMap中 用于 构建全局特征地图这个全局地图怎么用?
  3. scan2MapOptimization基于当前帧的特征数据和kdtreeFeatureFromMap中存储的已经存在的特征数据,执行迭代更新(基于特征)cornerOptimization,surfOptimization
  4. 大致的更新流程如下:cornerOptimization实则利用updatePointAssociateToMapSinCos函数,根据利用transformAssociateToMap函数基于上一帧的优化位姿和上一帧和当前帧的里程计优化位姿 ,计算得出当前帧未基于mapping 优化前的estimated 位姿计算出当前机器人位姿相对于world 的欧拉角,基于当前帧的特征点信息laserCloudCornerLastDSNum通过pointAssociateToMap函数将当前帧的first imu位置的信息转到world frame,然后在kdtree中搜索**这里注意kdtree 的feature point 信息已经通过transformPointCloud函数,将之前扫描的关键帧特征信息,通过上一次优化的位姿updateTransformPointCloudSinCos函数,更新了最新的一次优化的pose 信息,利用yaw -> pitch ->roll转到了world frame****实现了在global map 以及 current scan中的feature point 均在world坐标系中进行匹配**搜索5个最近邻,计算协方差,然后求出直线的主要的方向向量。这里有一个巧妙的设计(为了在LMOptimization函数中同时使用点面特征进行计算,此时在corneroptimization中两次运用叉乘,计算出surf的法向量 转换成店面特征)
    点线特征判断 入1>3入2
    在这里插入图片描述
  5. 实现点面特征匹配**surfOptimization:**先投影到world坐标系,然后利用估计的待优化位姿 通过pointAssociateToMap函数将current scan feature point 转到world frame 与exist feature point进行匹配。!!!
    这里需要理解一个流程。

先通过cv::solve函数利用neighbour point信息,运用超定方程的解算策略,AX = B的形式 解算出平面surf的法向量信息,然后判断平面的拟合是否有效,在根据有效的平面与面点对应,存储构建的有效的surf 特征的法向量。
在这里插入图片描述
在这里插入图片描述参考公式原理

  1. 利用LMOptimization函数,对yaw -> pitch -> roll的旋转矩阵求偏导数,
  2. 在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    对于旋转矩阵求偏导数
    在这里插入图片描述
    关于平移向量的偏导数
    在这里插入图片描述
 float arx = (crx*sry*srz*pointOri.x + crx*crz*sry*pointOri.y - srx*sry*pointOri.z) * coeff.x
                      + (-srx*srz*pointOri.x - crz*srx*pointOri.y - crx*pointOri.z) * coeff.y
                      + (crx*cry*srz*pointOri.x + crx*cry*crz*pointOri.y - cry*srx*pointOri.z) * coeff.z;

            float ary = ((cry*srx*srz - crz*sry)*pointOri.x 
                      + (sry*srz + cry*crz*srx)*pointOri.y + crx*cry*pointOri.z) * coeff.x
                      + ((-cry*crz - srx*sry*srz)*pointOri.x 
                      + (cry*srz - crz*srx*sry)*pointOri.y - crx*sry*pointOri.z) * coeff.z;

            float arz = ((crz*srx*sry - cry*srz)*pointOri.x + (-cry*crz-srx*sry*srz)*pointOri.y)*coeff.x
                      + (crx*crz*pointOri.x - crx*srz*pointOri.y) * coeff.y
                      + ((sry*srz + cry*crz*srx)*pointOri.x + (crz*sry-cry*srx*srz)*pointOri.y)*coeff.z;

            matA.at<float>(i, 0) = arx;//for the df / drotation
            matA.at<float>(i, 1) = ary;
            matA.at<float>(i, 2) = arz;
            matA.at<float>(i, 3) = coeff.x;//for df /dtransition
            matA.at<float>(i, 4) = coeff.y;
            matA.at<float>(i, 5) = coeff.z;
            matB.at<float>(i, 0) = -coeff.intensity;

A矩阵的构成
在这里插入图片描述
高斯牛顿法的优化公式
在这里插入图片描述

 cv::transpose(matA, matAt);
        matAtA = matAt * matA;
        matAtB = matAt * matB;
        cv::solve(matAtA, matAtB, matX, cv::DECOMP_QR);//jtj detax = - jt b

以及判断是否退化,最后更新优化的位姿transformTobeMapped。并判断迭代停止的条件

if (iterCount == 0) {//for the detection of the degenerate
            cv::Mat matE(1, 6, CV_32F, cv::Scalar::all(0));
            cv::Mat matV(6, 6, CV_32F, cv::Scalar::all(0));
            cv::Mat matV2(6, 6, CV_32F, cv::Scalar::all(0));

            cv::eigen(matAtA, matE, matV);
            matV.copyTo(matV2);

            isDegenerate = false;
            float eignThre[6] = {100, 100, 100, 100, 100, 100};
            for (int i = 5; i >= 0; i--) {
                if (matE.at<float>(0, i) < eignThre[i]) {
                    for (int j = 0; j < 6; j++) {
                        matV2.at<float>(i, j) = 0;
                    }
                    isDegenerate = true;
                } else {
                    break;
                }
            }
            matP = matV.inv() * matV2;
        }

        if (isDegenerate) {
            cv::Mat matX2(6, 1, CV_32F, cv::Scalar::all(0));
            matX.copyTo(matX2);
            matX = matP * matX2;//using the un distorted estimation to update the delta
        }

        transformTobeMapped[0] += matX.at<float>(0, 0);
        transformTobeMapped[1] += matX.at<float>(1, 0);
        transformTobeMapped[2] += matX.at<float>(2, 0);
        transformTobeMapped[3] += matX.at<float>(3, 0);
        transformTobeMapped[4] += matX.at<float>(4, 0);
        transformTobeMapped[5] += matX.at<float>(5, 0);//update the estimated pose using the mapping optimization

        float deltaR = sqrt(
                            pow(pcl::rad2deg(matX.at<float>(0, 0)), 2) +
                            pow(pcl::rad2deg(matX.at<float>(1, 0)), 2) +
                            pow(pcl::rad2deg(matX.at<float>(2, 0)), 2));
        float deltaT = sqrt(
                            pow(matX.at<float>(3, 0) * 100, 2) +
                            pow(matX.at<float>(4, 0) * 100, 2) +
                            pow(matX.at<float>(5, 0) * 100, 2));

        if (deltaR < 0.05 && deltaT < 0.05) {
            return true;//terminate the iteration
        }
        return false;

迭代更新的算法公式

  1. 基于LM高斯牛顿实现的scan2map的迭代优化后,通过transformUpdate函数基于当前里程计的时找出与之关联的imu信息,运用下面这两行信息,将更新的位姿通过调整权重的方式,在优化更新的情况下,考虑IMU数据带来的影响,至此scan2MapOptimization基于扫描scan到局部地图map的匹配完成
	    transformTobeMapped[0] = 0.998 * transformTobeMapped[0] + 0.002 * imuPitchLast;
		    transformTobeMapped[2] = 0.998 * transformTobeMapped[2] + 0.002 * imuRollLast;//weighted fusion

  1. saveKeyFramesAndFactor函数基于LM优化结果更新机器人位姿

        currentRobotPosPoint.x = transformAftMapped[3];//for first laser scan 0
        currentRobotPosPoint.y = transformAftMapped[4];//for not first laser is the scan2map updated pose
        currentRobotPosPoint.z = transformAftMapped[5];

判断更新的优化位姿是否满足创建关键帧的条件。如果满足,先将当前帧数据,作为上一个关键帧数据进行赋值,然后利用上一帧的优化信息transformLast和当前帧的优化位姿transformAftMapped创建
参考数学推导链接

//
           gtsam::Pose3 poseFrom = Pose3(Rot3::RzRyRx(transformLast[2], transformLast[0], transformLast[1]),
                                          Point3(transformLast[5], transformLast[3], transformLast[4]));//last updated pose
            gtsam::Pose3 poseTo   = Pose3(Rot3::RzRyRx(transformAftMapped[2], transformAftMapped[0], transformAftMapped[1]),
                                          Point3(transformAftMapped[5], transformAftMapped[3], transformAftMapped[4]));//current updated pose
            gtSAMgraph.add(BetweenFactor<Pose3>(cloudKeyPoses3D->points.size()-1, cloudKeyPoses3D->points.size(),
                                                poseFrom.between(poseTo), odometryNoise));//for the between factor id is size - 1 to size (key)
            initialEstimate.insert(cloudKeyPoses3D->points.size(), Pose3(Rot3::RzRyRx(transformAftMapped[2], transformAftMapped[0], transformAftMapped[1]),//当前基于约束优化的更新关键帧的pose 信息为基于corner 与surf optimization 经过LM优化后的pose 来作为initial pose 去进行isam的增量图约束优化。
                                                                         Point3(transformAftMapped[5], transformAftMapped[3], transformAftMapped[4])));
            //add the size which need updated pose in the initialEstimate with the initial guesses is last updated pose

上面这函数poseFrom为上一个时刻更新的pose,poseTo为当前时刻基于LM更新的pose,通过add函数,将两个关键帧之间构建约束。
BetweenFactor用于表示两个状态变量之间的相对约束。描述了这两个变量之间的相对关系。这种因子在优化过程中提供了关于两个状态之间相对位置或姿态的约束。** poseFrom.between(poseTo)为Ra_w * Rw_b计算出Ra_b的相对旋转**,基于isam优化更新的factor graph信息,提取出优化后的transformation信息,存储为cloudKeyPoses3D以及cloudKeyPoses6D,并进行增量更新优化的保存。**transformLast为last isam 增量优化的pose 以及transformTobeMapped也通过上一次的last optimized 进行初始化 **

for (int i = 0; i < 6; ++i){
                transformLast[i] = transformAftMapped[i];//last after mapping updated pose store
                transformTobeMapped[i] = transformAftMapped[i];
                //after mapped but with no next frame pose use the current aftmapped estimation to update the next need mapped estimation
            }
  1. publishTF函数用来发布基于优化后的位姿的转换
        odomAftMapped.header.frame_id = "/camera_init";
        odomAftMapped.child_frame_id = "/aft_mapped";

tf::TransformBroadcaster broadcaster;的作用是创建一个tf::TransformBroadcaster类的对象,用来广播坐标系base_link -> base_laser的变换关系。sendTransform函数用来广播parent frame ->child frame之间的坐标变换关系。

针对于visualizeGlobalMapThread与main线程

visualizeGlobalMapThread线程通过ros::Rate rate(0.2)每5s执行一次global map的构建过程,而基于LO里程计的优化,通过 ros::Rate rate(200)每0.05s执行一次里程计的优化过程,所以针对于map Optimization函数,LO 的里程计优化执行的频率更高,global map的发布则需要积累足够的key pose去发布。

整体main函数

执行是while结束-》threadloopthread结束-》visualizeGlobalMapThre结束

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

    for(int i = 0; i<argc; i++)
    {
        std::cout<<"argv:"<<argv[i]<<std::endl;
    }

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

    mapOptimization MO;

    std::thread loopthread(&mapOptimization::loopClosureThread, &MO);
    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;
}
在当今数字化教育蓬勃发展的背景下,校园网络作为教学与科研的关键基础设施,其重要性日益凸显。本文旨在探讨小型校园网络的规划与设计,以满足网络实验教学的需求,为相关专业师生提供一个高效、稳定且功能完备的网络实验环境,助力教学活动顺利开展,提升学生的实践能力和创新思维。 网络实验教学要求校园网络具备高度的灵活性与可扩展性。学生需在实验过程中模拟各种网络拓扑结构、配置不同网络设备参数,这就要求网络能够快速调整资源分配,适应多样化的实验场景。同时,为保证实验数据的准确性和实验过程的稳定性,网络的高可靠性与低延迟特性不可或缺。此外,考虑到校园内多用户同时接入的场景,网络还需具备良好的并发处理能力,确保每位用户都能流畅地进行实验操作。 采用层次化结构构建小型校园网络,分为核心层、汇聚层与接入层。核心层选用高性能交换机,负责高速数据转发与关键路由决策,保障网络主干的稳定运行;汇聚层连接不同教学区域,实现数据的汇聚与初步处理,通过划分虚拟局域网(VLAN)对不同专业或班级的实验流量进行隔离,避免相互干扰;接入层则直接连接学生终端设备,提供充足的接入端口,满足大量用户同时接入的需求,并通过端口安全策略限制非法设备接入,保障网络安全。 在设备选型上,核心层交换机需具备高吞吐量、低延迟以及丰富的路由协议支持能力,以满足复杂网络流量的转发需求;汇聚层交换机则注重VLAN划分与管理功能,以及对链路聚合的支持,提升网络的可靠性和带宽利用率;接入层交换机则需具备高密度端口、灵活的端口配置以及完善的用户认证功能。配置方面,通过静态路由与动态路由协议相结合的方式,确保网络路径的最优选择;在汇聚层与接入层设备上启用VLAN Trunk技术,实现不同VLAN间的数据交换;同时,利用网络管理软件对设备进行集中监控与管理,实时掌握网络运行状态,及时发现并解决潜在问题。 网络安全是校园网络规划的关键环节。在接入层设置严
管理后台HTML页面是Web开发中一种常见的实践,主要用于构建企业或组织内部的管理界面,具备数据监控、用户管理、内容编辑等功能。本文将探讨一套美观易用的二级菜单目录设计,帮助开发者创建高效且直观的后台管理系统。 HTML5:作为超文本标记语言的最新版本,HTML5增强了网页的互动性和可访问性,提供了更多语义元素,如<header>、<nav>、<section>、<article>等,有助于清晰地定义网页结构。在管理后台中,HTML5可用于构建页面布局,划分功能区域,并集成多媒体内容,如图像、音频和视频。 界面设计:良好的管理后台界面应具备清晰的导航、一致的布局和易于理解的图标。二级菜单目录设计能够有效组织信息,主菜单涵盖大类功能,次级菜单则提供更具体的操作选项,通过展开和折叠实现层次感,降低用户认知负担。 CSS:CSS是用于控制网页外观和布局的语言,可对HTML元素进行样式设置,包括颜色、字体、布局等。在管理后台中,CSS能够实现响应式设计,使页面在不同设备上具有良好的显示效果。借助CSS预处理器(如Sass或Less),可以编写更高效、模块化的样式代码,便于维护。 文件结构: guanli.html:可能是管理页面的主入口,包含后台的主要功能和布局。 xitong.html:可能是系统设置或配置页面,用于管理员调整系统参数。 denglu.html:登录页面,通常包含用户名和密码输入框、登录按钮,以及注册或忘记密码的链接。 image文件夹:存放页面使用的图片资源,如图标、背景图等。 css文件夹:包含后台系统的样式文件,如全局样式表style.css或按模块划分的样式文件。 响应式设计:在移动设备普及的背景下,管理后台需要支持多种屏幕尺寸。通过媒体查询(Media Queries)和流式布局(Fluid Grids),可以确保后台在桌面、平板和手机上都能良好展示。
标题Python基于Hadoop的租房数据分析系统的设计与实现AI更换标题第1章引言介绍租房数据分析的重要性,以及Hadoop和Python在数据分析领域的应用优势。1.1研究背景与意义分析租房市场的现状,说明数据分析在租房市场中的重要作用。1.2国内外研究现状概述Hadoop和Python在数据分析领域的应用现状及发展趋势。1.3论文研究内容与方法阐述论文的研究目标、主要研究内容和所采用的技术方法。第2章相关技术理论详细介绍Hadoop和Python的相关技术理论。2.1Hadoop技术概述解释Hadoop的基本概念、核心组件及其工作原理。2.2Python技术概述阐述Python在数据处理和分析方面的优势及相关库函数。2.3Hadoop与Python的结合应用讨论Hadoop与Python在数据处理和分析中的结合方式及优势。第3章租房数据分析系统设计详细描述基于Hadoop的租房数据分析系统的设计思路和实现方案。3.1系统架构设计给出系统的整体架构设计,包括数据采集、存储、处理和分析等模块。3.2数据采集与预处理介绍数据的来源、采集方式和预处理流程。3.3数据存储与管理阐述数据在Hadoop平台上的存储和管理方式。第4章租房数据分析系统实现详细介绍租房数据分析系统的实现过程,包括关键代码和算法。4.1数据分析算法实现给出数据分析算法的具体实现步骤和关键代码。4.2系统界面设计与实现介绍系统界面的设计思路和实现方法,包括前端和后端的交互方式。4.3系统测试与优化对系统进行测试,发现并解决问题,同时对系统进行优化以提高性能。第5章实验结果与分析对租房数据分析系统进行实验验证,并对实验结果进行详细分析。5.1实验环境与数据集介绍实验所采用的环境和数据集,包括数据来源和规模等。5.2实验方法与步骤给出实验的具体方法和步骤,包括数据预处理、模型训练和测试等。5.3实验结果分析从多
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值