LVI-SAM mapOptimization 代码解析

本篇文章来对图优化内容进行解析。如今,图优化和卡尔曼滤波已经成为了主流的后端优化方法。

头文件

// 头文件,和imuPreintegration差不多
#include "utility.h"
#include "lvi_sam/cloud_info.h"

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 

// 命名空间,这个作用就是不用写gtsam::了,就直接用就可以
using namespace gtsam;

// 这个意思就是X表示位姿(位置 + 姿态) 然后V表示速度,这个后面有赋值,例如V(0)这种的
using symbol_shorthand::X; // Pose3 (x,y,z,r,p,y)
using symbol_shorthand::V; // Vel   (xdot,ydot,zdot)
using symbol_shorthand::B; // Bias  (ax,ay,az,gx,gy,gz)
using symbol_shorthand::G; // GPS pose

首先这个头文件和imu预积分的内容差不多,其中有几个库是imu预积分没有的,当然imu预积分没有用上那些库,所以才没有include进去。然后下面是使用了gtsam命名空间,这样的话就可以不用谢gtsam::然后啥啥啥变量了,就直接写变量名即可。然后是XVBG,这些就是起了个名字,具体的用法的话,在我的印象里X(0),类似这种,进行赋值。

变量

/*
 * A point cloud type that has 6D pose info ([x,y,z,roll,pitch,yaw] intensity is time stamp)
 * 这里非常贴心的告诉阅读者 intensity里面装的是时间戳
 */
struct PointXYZIRPYT
{
    PCL_ADD_POINT4D // xyz + padding(填充的首选方法)
    PCL_ADD_INTENSITY;
    float roll;
    float pitch;
    float yaw;
    double time;
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW // 这个是eigen字节对齐用的,前面搜过
} EIGEN_ALIGN16;

// 注册为PCL点云格式 (PCL内有自己定义的一系列点云格式,用结构体扩展后需要注册)
POINT_CLOUD_REGISTER_POINT_STRUCT (PointXYZIRPYT,
                                   (float, x, x) (float, y, y)
                                   (float, z, z) (float, intensity, intensity)
                                   (float, roll, roll) (float, pitch, pitch) (float, yaw, yaw)
                                   (double, time, time))

typedef PointXYZIRPYT  PointTypePose; // 重命名

// 定义的类,还是公有继承ParamServer
class mapOptimization : public ParamServer
{

public:

    // gtsam
    NonlinearFactorGraph gtSAMgraph; // 包含非线性因子的因子图
    // Values 用于指定因子图中一组变量的值
    Values initialEstimate;
    Values optimizedEstimate;
    // 优化器
    ISAM2 *isam;
    Values isamCurrentEstimate;
    Eigen::MatrixXd poseCovariance; // 位置协方差

    // 发布
    // 下面是在ros作用域下面,也就是可能会显示到rviz里面
    ros::Publisher pubLaserCloudSurround; // 点云信息 发布关键帧的map的特征点云
    ros::Publisher pubOdomAftMappedROS; // odometry信息 发布激光里程计
    ros::Publisher pubKeyPoses; // 点云信息 发布关键位姿信息
    ros::Publisher pubPath; // 路径信息 发布路径,主要是给rivz用于展示

    ros::Publisher pubHistoryKeyFrames; // 历史点云信息 发布历史关键帧
    ros::Publisher pubIcpKeyFrames; // 点云信息 icp方法 配准的 对齐的 发布当前关键帧经过闭环优化后的位姿变换之后的特征点云
    ros::Publisher pubRecentKeyFrames; // 点云信息 发布局部map的降采样平面边重合
    ros::Publisher pubRecentKeyFrame; // 点云信息 发布历史帧的角点、平面点降采样集合
    ros::Publisher pubCloudRegisteredRaw; // 点云信息 校准之后的点云信息
    ros::Publisher pubLoopConstraintEdge; // MarkerArray信息 发布闭环边信息

    // 输入:激光点云信息、GPS信息、闭环信息
    ros::Subscriber subLaserCloudInfo; // 订阅当前激光帧点云信息,来自FeatureExtraction
    ros::Subscriber subGPS; // 订阅GPS里程计
    ros::Subscriber subLoopInfo; // 订阅来自外部闭环检测程序提供的闭环数据。(本程序没有提供)

    std::deque gpsQueue;
    lvi_sam::cloud_info cloudInfo; // 这个是作者团队自己定义的,可以在lvi_sam/cloud_info.h里面看一下

    // 历史所有关键帧的角点集合(降采样)
    vector::Ptr> cornerCloudKeyFrames;
    // 历史所有关键帧的平面点集合(降采样)
    vector::Ptr> surfCloudKeyFrames;
    
    // 历史关键帧位姿
    pcl::PointCloud::Ptr cloudKeyPoses3D;
    pcl::PointCloud::Ptr cloudKeyPoses6D;

    pcl::PointCloud::Ptr laserCloudCornerLast; // corner feature set from odoOptimization 来自优化里面的角点
    pcl::PointCloud::Ptr laserCloudSurfLast; // surf feature set from odoOptimization
    // 后面DS是downsampled 降采样的简称 是上面经过降采样后的数据
    pcl::PointCloud::Ptr laserCloudCornerLastDS; // downsampled corner featuer set from odoOptimization
    pcl::PointCloud::Ptr laserCloudSurfLastDS; // downsampled surf featuer set from odoOptimization

    pcl::PointCloud::Ptr laserCloudOri;
    pcl::PointCloud::Ptr coeffSel; // coeffSel : 系数选择

    std::vector laserCloudOriCornerVec; // corner point holder for parallel computation 用于并行计算的角点支架 
    std::vector coeffSelCornerVec;
    std::vector laserCloudOriCornerFlag;
    std::vector laserCloudOriSurfVec; // surf point holder for parallel computation
    std::vector coeffSelSurfVec;
    std::vector laserCloudOriSurfFlag;

    pcl::PointCloud::Ptr laserCloudCornerFromMap;
    pcl::PointCloud::Ptr laserCloudSurfFromMap;
    pcl::PointCloud::Ptr laserCloudCornerFromMapDS;
    pcl::PointCloud::Ptr laserCloudSurfFromMapDS;

    //局部关键帧构建的地图点云,对应kdtree,用于scan-to-map找相邻
    pcl::KdTreeFLANN::Ptr kdtreeCornerFromMap;
    pcl::KdTreeFLANN::Ptr kdtreeSurfFromMap;

    pcl::KdTreeFLANN::Ptr kdtreeSurroundingKeyPoses;
    pcl::KdTreeFLANN::Ptr kdtreeHistoryKeyPoses;

    pcl::PointCloud::Ptr latestKeyFrameCloud;
    pcl::PointCloud::Ptr nearHistoryKeyFrameCloud;

    //降采样
    pcl::VoxelGrid downSizeFilterCorner;
    pcl::VoxelGrid downSizeFilterSurf;
    pcl::VoxelGrid downSizeFilterICP;
    pcl::VoxelGrid downSizeFilterSurroundingKeyPoses; // for surrounding key poses of scan-to-map optimization
    
    ros::Time timeLaserInfoStamp;
    double timeLaserInfoCur;

    float transformTobeMapped[6];

    std::mutex mtx; // 这个是锁,用于共享内存数据读取的

    bool isDegenerate = false; // Degenerate:退化
    cv::Mat matP; // 这个是矩阵

    // 当前激光帧角点数量 还得是降采样的
    int laserCloudCornerLastDSNum = 0;
    // 当前激光帧平面点数量 降采样之后的
    int laserCloudSurfLastDSNum = 0;

    bool aLoopIsClosed = false;
    int imuPreintegrationResetId = 0;

    //一个关于运动路径的消息
    nav_msgs::Path globalPath;

    //当前帧位姿
    Eigen::Affine3f transPointAssociateToMap;

    map loopIndexContainer; // from new to old
    vector> loopIndexQueue;
    vector loopPoseQueue;
    vector loopNoiseQueue;

首先是定义了一个结构体,这个结构体里面包含了xyz RPY 还有时间戳这些非常基本的内容。然后这个EIGEN_MAKE_ALIGNED_OPERATOR_NEW是EIGEN字节对齐用的,这个不再重述了。然后下面是注册为点云格式,然后这个的话,就看看记一下格式模板即可。最后把定义的结构体重新命名一下,这样显得更加高端一些!下面就是mapOptimization类中定义的变量,首先它公有继承自ParamServer。然后下面的话就是一些列的值,这里就不过多讲解了,自己看注释。这里的话,分清作用域即可。然后下面有锁std::mutex mtx,还有一些下面函数需要单独使用的变量,哲理的话不讲解,没啥意思。

函数

    //构造函数
    mapOptimization()
    {
        ISAM2Params parameters; // ISAM2优化器的参数
        // 设置参数
        parameters.relinearizeThreshold = 0.1;
        parameters.relinearizeSkip = 1;
        // 初始化优化器
        isam = new ISAM2(parameters);

        // 发布历史关键帧里程计
        pubKeyPoses           = nh.advertise(PROJECT_NAME + "/lidar/mapping/trajectory", 1); // 后面这个1是消息接受的数量,就看成能接受几条就可以
        // 发布局部关键帧地图的特征点云
        pubLaserCloudSurround = nh.advertise(PROJECT_NAME + "/lidar/mapping/map_global", 1);
        // 发布激光里程计,rviz中表现为坐标轴
        pubOdomAftMappedROS   = nh.advertise      (PROJECT_NAME + "/lidar/mapping/odometry", 1);
        // 发布路径
        pubPath               = nh.advertise          (PROJECT_NAME + "/lidar/mapping/path", 1);

        // 订阅当前激光帧点云信息,来自featureExtraction
        subLaserCloudInfo     = nh.subscribe     (PROJECT_NAME + "/lidar/feature/cloud_info", 5, &mapOptimization::laserCloudInfoHandler, this, ros::TransportHints().tcpNoDelay());
        // 订阅GPS里程计 这个GPS在图优化部分可以先不考虑,这个加入GPS会更精确
        subGPS                = nh.subscribe      (gpsTopic,                                   50, &mapOptimization::gpsHandler, this, ros::TransportHints().tcpNoDelay());
        // 订阅来自外部闭环检测程序提供的闭环数据
        subLoopInfo           = nh.subscribe(PROJECT_NAME + "/vins/loop/match_frame", 5, &mapOptimization::loopHandler, this, ros::TransportHints().tcpNoDelay());

        // 发布闭环匹配关键帧局部地图
        pubHistoryKeyFrames   = nh.advertise(PROJECT_NAME + "/lidar/mapping/loop_closure_history_cloud", 1);
        // 发布当前关键帧经过闭环优化后的位姿变换以后的特征点云
        pubIcpKeyFrames       = nh.advertise(PROJECT_NAME + "/lidar/mapping/loop_closure_corrected_cloud", 1);
        // 发布闭环边,rviz中变现为闭环帧之间的连线
        pubLoopConstraintEdge = nh.advertise(PROJECT_NAME + "/lidar/mapping/loop_closure_constraints", 1);

        // 发布局部地图的降采样平面点集合
        pubRecentKeyFrames    = nh.advertise(PROJECT_NAME + "/lidar/mapping/map_local", 1);
        // 发布历史帧的角点、平面点降采样集合
        pubRecentKeyFrame     = nh.advertise(PROJECT_NAME + "/lidar/mapping/cloud_registered", 1);
        // 发布当前帧原始点云配准之后的点云
        pubCloudRegisteredRaw = nh.advertise(PROJECT_NAME + "/lidar/mapping/cloud_registered_raw", 1);

        // 设置每个体素的大小,分别为长宽高
        downSizeFilterCorner.setLeafSize(mappingCornerLeafSize, mappingCornerLeafSize, mappingCornerLeafSize); // 这个就相当于一个正方体了,前面也看到过 长 == 宽 == 高
        downSizeFilterSurf.setLeafSize(mappingSurfLeafSize, mappingSurfLeafSize, mappingSurfLeafSize);
        downSizeFilterICP.setLeafSize(mappingSurfLeafSize, mappingSurfLeafSize, mappingSurfLeafSize);
        // 用于扫描到贴图优化的周围关键姿势
        downSizeFilterSurroundingKeyPoses.setLeafSize(surroundingKeyframeDensity, surroundingKeyframeDensity, surroundingKeyframeDensity); // for surrounding key poses of scan-to-map optimization

        // 初始化变量
        allocateMemory();
    }

构造函数,设置了阈值的数值,然后下面的话就是订阅和发布的内容,一个advertise 一个subscribe。当然subscribe里面有函数句柄,在调用这个函数的时候,这个句柄就会自动启动,然后它们做的事情,下面来讲解。然后设置体素大小,这个没啥意思,我的理解的话还是你先搞个正方体,然后把装在正方体里面的点云用一个点来表示,这样实现降采样。开始第一个函数,分配内存。

// 分配内存
    void allocateMemory()
    {
        // 对定义的一系列关键帧变量进行初始化
        // reset 括号里面的内容 和前面的类型要对齐
        cloudKeyPoses3D.reset(new pcl::PointCloud());
        cloudKeyPoses6D.reset(new pcl::PointCloud());

        kdtreeSurroundingKeyPoses.reset(new pcl::KdTreeFLANN());
        kdtreeHistoryKeyPoses.reset(new pcl::KdTreeFLANN());

        laserCloudCornerLast.reset(new pcl::PointCloud()); // corner feature set from odoOptimization
        laserCloudSurfLast.reset(new pcl::PointCloud()); // surf feature set from odoOptimization
        laserCloudCornerLastDS.reset(new pcl::PointCloud()); // downsampled corner featuer set from odoOptimization
        laserCloudSurfLastDS.reset(new pcl::PointCloud()); // downsampled surf featuer set from odoOptimization

        laserCloudOri.reset(new pcl::PointCloud());
        coeffSel.reset(new pcl::PointCloud()); // 这个是系数选择,现在还不清楚是啥系数

        laserCloudOriCornerVec.resize(N_SCAN * Horizon_SCAN);
        coeffSelCornerVec.resize(N_SCAN * Horizon_SCAN);
        laserCloudOriCornerFlag.resize(N_SCAN * Horizon_SCAN);
        laserCloudOriSurfVec.resize(N_SCAN * Horizon_SCAN);
        coeffSelSurfVec.resize(N_SCAN * Horizon_SCAN);
        laserCloudOriSurfFlag.resize(N_SCAN * Horizon_SCAN);
        // 这里的N_SCAN 我的理解是横着的扫描线 总共有16条,然后运行代码的时候,可以找一个树的点云,然后发现他是一层一层的,感觉是验证了这个N_SCAN是横着的

        std::fill(laserCloudOriCornerFlag.begin(), laserCloudOriCornerFlag.end(), false); // 这里应该是都填充为false
        std::fill(laserCloudOriSurfFlag.begin(), laserCloudOriSurfFlag.end(), false);

        laserCloudCornerFromMap.reset(new pcl::PointCloud());
        laserCloudSurfFromMap.reset(new pcl::PointCloud());
        laserCloudCornerFromMapDS.reset(new pcl::PointCloud());
        laserCloudSurfFromMapDS.reset(new pcl::PointCloud());

        kdtreeCornerFromMap.reset(new pcl::KdTreeFLANN());
        kdtreeSurfFromMap.reset(new pcl::KdTreeFLANN());

        latestKeyFrameCloud.reset(new pcl::PointCloud());
        nearHistoryKeyFrameCloud.reset(new pcl::PointCloud());

        for (int i = 0; i < 6; ++i)
        {
            transformTobeMapped[i] = 0;
        }

        matP = cv::Mat(6, 6, CV_32F, cv::Scalar::all(0));
    }

这个没啥好说的 reset resize fill 等函数操作 都是给这个板块里的变量在清除里面可能存在的数值。哲理的话,我对N_SCAN * Horizon_SCAN的理解,就是行×列,这个雷达的扫描线就是一行行的,横着扫的。这个是我的理解,同时这个的话,也是转为一维坐标的关键,知道他是按行来一个个存储的就ok。还有这个fill函数,第一次遇到,一看名字就知道他是干啥的,但是里面具体的逻辑,再读一遍的时候,细细研究。这里没啥好说的,就后面括号里面的内容 类型,和前面的变量对齐就可以了。

    // 订阅激光帧点云信息 这个就是构造函数里面订阅函数里面的一个参数,就是一用到构造函数的那个变量,这个就开始启动了
    void laserCloudInfoHandler(const lvi_sam::cloud_infoConstPtr& msgIn)
    {
        // extract time stamp
        timeLaserInfoStamp = msgIn->header.stamp;
        timeLaserInfoCur = msgIn->header.stamp.toSec(); // 这两个都是一样的内容,不过形式不一样而已,一个是转换成秒的,上面那个是没转换的

        // extract info ana(and 我猜测的) feature cloud 提取当前激光帧角点、平面点集合
        cloudInfo = *msgIn;
        pcl::fromROSMsg(msgIn->cloud_corner,  *laserCloudCornerLast);
        pcl::fromROSMsg(msgIn->cloud_surface, *laserCloudSurfLast);

        // 设置锁(同步)
        std::lock_guard lock(mtx);

        static double timeLastProcessing = -1;
        // mapping执行频率控制
        // 如果当前激光帧与上一帧激光帧之间的差值大于某个值
        // mappingProcessInterval在utility.h中被初始化为0.15
        if (timeLaserInfoCur - timeLastProcessing >= mappingProcessInterval)
        {
            // 更新timeLastProcessing的值
            timeLastProcessing = timeLaserInfoCur;

            // 当前帧初始化
            // 1.如果是第一帧,用原始imu数据的RPY初始化当前帧位姿(旋转部分)
            // 2.后续帧,用imu里程计计算两帧之间的增量位姿变换,作用于前一帧的激光位姿,得到当前帧激光位姿
            // 我的理解:要不使用vins来更新,要不使用imu来更新,然后一系列的转换矩阵相乘,得到坐标
            updateInitialGuess();

            // 提取局部角点、平面点云集合,加入局部地图
            // 1.对最近的一帧关键帧,搜索时空纬度上相邻的关键帧集合,降采样一下
            // 2.对关键帧集合中的每一帧,提取对应的角点、平面点,加入局部地图中
            // 先nearby 再extractcloud 最后结束,这个就是对周围的关键帧做一个处理
            extractSurroundingKeyFrames();

            // 当前激光帧角点、平面点集合降采样
            // 重复了两边的降采样操作,这个没啥多解析的地方
            downsampleCurrentScan();

            // 1、要求当前帧特征点数量足够多,且匹配的点数够多,才执行优化
            
            // 2、迭代30次优化

            // 1)当前激光帧角点寻找局部map匹配点
            // a.更新当前帧位姿,将当前帧角点坐标变换到map系下,在局部map中查找5个最近点,距离小于1m,且5个点构成直线,
            //  则认为匹配上了(用距离中心点的协方差矩阵,特征值进行判断)
            // b.计算当前帧角点到直线的距离、垂线的单位向量,存储为角点参数

            // 2)当前激光帧平面点寻找局部map匹配点
            // a.更新当前帧位姿,将当前帧平面点坐标交换到map系下,在局部map中查找5个最近点,距离小于1米,且5个点构成平面
            // (最小二乘拟合平面),则认为匹配上了
            // b.计算当前帧平面点到平面的距离、垂线的单位向量,存储为平面点参数

            // 3)提取当前帧中与局部map匹配上了的角点、平面点,加入同一集合

            // 4)对匹配特征点计算Jacobian矩阵,观测值为特征点到直线、平面的距离,构建高斯牛顿方程,迭代优化当前位姿,存transformTobeMapped

            // 3、用imu原始RPY数据与scan-to-map优化后的位姿进行加权融合,更新当前帧位姿的roll pirch 约束z坐标
            scan2MapOptimization();

            // 因子图优化,更新所有关键帧位姿
            // 设置当前帧为关键帧并执行因子图优化
            // 计算当前帧与前一帧位姿变化,如果变化太小,不设为关键帧,反之设为关键帧
            // 添加激光里程计因子、GPS因子、闭环因子
            // 执行因子图优化
            // 得到当前帧优化后位姿,位姿协方差
            // 添加cloudKeyPoses3D,cloudKeyPoses6D,更新transformTobeMapped,添加当前关键帧的角点、平面点集合
            // 该优化是独立于scan-map的另一个优化
            saveKeyFramesAndFactor();

            // 更新因子图中所有变量节点的位姿,也就是所有历史关键帧的位姿,更新里程计轨迹
            correctPoses();

            // 发布激光里程计
            publishOdometry();

            // 发布里程计、点云、轨迹
            // 1、发布历史关键帧位姿集合
            // 2、发布局部地图降采样平面点集合
            // 3、发布历史帧(累加的)的角点、平面点降采样集合
            // 4、发布里程计轨迹
            publishFrames();
        }
    }

雷达点云的句柄,当然是subcribe雷达点云的时候启动的,一开始获取时间,然后从ROS作用域下获得点云的数据,然后上锁,下面的话,就是一系列函数,对于这些函数能干啥,都在代码里面注释了。当然这是学习了网上各路神仙以后总结下来的代码。

laserCloudInfoHandler:

1、updateInitialGuess();首先初始化 这个Guess可以想成高斯分布,完了这个我不确定,反正是初始化

2、extractSurroundingKeyFrames();这个提取周围的关键帧

3、downsampleCurrentScan(); 降采样,就是上一步可能提取的数据太多,计算机一下处理不了这么多

4、scan2MapOptimization(); 这个部分还不是很理解,大体内容的话就是cornerOptimization、surfOptimization然后combineOptimization,里面还有LMOptimization,这些优化的内容,前面的话感觉就是要找到角点和平面点,然后你算出来个coeff用在后面的优化中,后面两个的话就是不断地优化,然后他最后判断一个delta啥东西的,是两个!然后不停地迭代优化,找到最优解。就是里面的话一些变量不是很明白,我推测,应该论文原文会有,或者是一些基础的知识,但是我没看到。

5、saveKeyFramesAndFactor();这部分的话就是添加因子,然后保存关键帧的一些操作

6、correctPoses();这部分的话,没看懂的是aLoopIsClosed是啥意思,然后这个东西在if条件中用到,这个地方不是很理解,这个模块的话就是在修正位姿。

7、publishOdometry(); 发布激光里程计

8、publishFrames(); 发布帧 这两个的话可以具体到函数里面看看它到底发布了啥,这里不多讲了

    void gpsHandler(const nav_msgs::Odometry::ConstPtr& gpsMsg)
    {
        std::lock_guard lock(mtx);
        gpsQueue.push_back(*gpsMsg);
    }

    void pointAssociateToMap(PointType const * const pi, PointType * const po)
    {
        po->x = transPointAssociateToMap(0,0) * pi->x 
                +transPointAssociateToMap(0,1) * pi->y 
                +transPointAssociateToMap(0,2) * pi->z 
                +transPointAssociateToMap(0,3);
        
        po->y = transPointAssociateToMap(1,0) * pi->x 
                +transPointAssociateToMap(1,1) * pi->y 
                +transPointAssociateToMap(1,2) * pi->z 
                +transPointAssociateToMap(1,3);
        
        po->z = transPointAssociateToMap(2,0) * pi->x 
                +transPointAssociateToMap(2,1) * pi->y 
                +transPointAssociateToMap(2,2) * pi->z 
                +transPointAssociateToMap(2,3);
        
        po->intensity = pi->intensity;
    }

    pcl::PointCloud::Ptr transformPointCloud(pcl::PointCloud::Ptr cloudIn, PointTypePose* transformIn)
    {
        pcl::PointCloud::Ptr cloudOut(new pcl::PointCloud());

        PointType *pointFrom;

        int cloudSize = cloudIn->size();
        cloudOut->resize(cloudSize);

        Eigen::Affine3f transCur = pcl::getTransformation(transformIn->x, transformIn->y, transformIn->z, transformIn->roll, transformIn->pitch, transformIn->yaw);
        
        for (int i = 0; i < cloudSize; ++i)
        {
            pointFrom = &cloudIn->points[i];
            cloudOut->points[i].x = transCur(0,0) * pointFrom->x 
                                    +transCur(0,1) * pointFrom->y 
                                    +transCur(0,2) * pointFrom->z 
                                    +transCur(0,3);
            
            cloudOut->points[i].y = transCur(1,0) * pointFrom->x 
                                    +transCur(1,1) * pointFrom->y 
                                    +transCur(1,2) * pointFrom->z 
                                    +transCur(1,3);
            
            cloudOut->points[i].z = transCur(2,0) * pointFrom->x 
                                    +transCur(2,1) * pointFrom->y 
                                    +transCur(2,2) * pointFrom->z 
                                    +transCur(2,3);
            
            cloudOut->points[i].intensity = pointFrom->intensity;
        }
        return cloudOut;
    }

    gtsam::Pose3 affine3fTogtsamPose3(const Eigen::Affine3f& thisPose)
    {
        float x, y, z, roll, pitch, yaw;
        pcl::getTranslationAndEulerAngles(thisPose, x, y, z, roll, pitch, yaw);
        return gtsam::Pose3(gtsam::Rot3::RzRyRx(double(roll), double(pitch), double(yaw)),
                                  gtsam::Point3(double(x),    double(y),     double(z)));
    }

    gtsam::Pose3 pclPointTogtsamPose3(PointTypePose thisPoint)
    {
        return gtsam::Pose3(gtsam::Rot3::RzRyRx(double(thisPoint.roll), double(thisPoint.pitch), double(thisPoint.yaw)),
                                  gtsam::Point3(double(thisPoint.x),    double(thisPoint.y),     double(thisPoint.z)));
    }

    gtsam::Pose3 trans2gtsamPose(float transformIn[])
    {
        return gtsam::Pose3(gtsam::Rot3::RzRyRx(transformIn[0], transformIn[1], transformIn[2]), 
                                  gtsam::Point3(transformIn[3], transformIn[4], transformIn[5]));
    }

    Eigen::Affine3f pclPointToAffine3f(PointTypePose thisPoint)
    { 
        return pcl::getTransformation(thisPoint.x, thisPoint.y, thisPoint.z, thisPoint.roll, thisPoint.pitch, thisPoint.yaw);
    }

    Eigen::Affine3f trans2Affine3f(float transformIn[])
    {
        return pcl::getTransformation(transformIn[3], transformIn[4], transformIn[5], transformIn[0], transformIn[1], transformIn[2]);
    }

    PointTypePose trans2PointTypePose(float transformIn[])
    {
        PointTypePose thisPose6D;
        thisPose6D.x = transformIn[3];
        thisPose6D.y = transformIn[4];
        thisPose6D.z = transformIn[5];
        thisPose6D.roll  = transformIn[0];
        thisPose6D.pitch = transformIn[1];
        thisPose6D.yaw   = transformIn[2];
        return thisPose6D;
    }

一些转换的函数,可以看看里面到底谁赋值给了谁,这里我还没看,再看一遍的时候,再来注意这个地方。

    void visualizeGlobalMapThread()
    {
        ros::Rate rate(0.2); // 调整ros系统运行的快慢的
        while (ros::ok())
        {
            rate.sleep();
            publishGlobalMap();
        }

        if (savePCD == false)
            return;

        cout << "****************************************************" << endl;
        cout << "Saving map to pcd files ..." << endl;
        // create directory and remove old files;
        savePCDDirectory = std::getenv("HOME") + savePCDDirectory;
        int unused = system((std::string("exec rm -r ") + savePCDDirectory).c_str());
        unused = system((std::string("mkdir ") + savePCDDirectory).c_str()); ++unused;
        // save key frame transformations
        pcl::io::savePCDFileASCII(savePCDDirectory + "trajectory.pcd", *cloudKeyPoses3D);
        pcl::io::savePCDFileBinary(savePCDDirectory + "transformations.pcd", *cloudKeyPoses6D);
        // extract global point cloud map        
        pcl::PointCloud::Ptr globalCornerCloud(new pcl::PointCloud());
        pcl::PointCloud::Ptr globalSurfCloud(new pcl::PointCloud());
        pcl::PointCloud::Ptr globalMapCloud(new pcl::PointCloud());
        for (int i = 0; i < (int)cloudKeyPoses3D->size(); i++) 
        {
            // clip cloud
            pcl::PointCloud::Ptr cornerTemp(new pcl::PointCloud());
            pcl::PointCloud::Ptr cornerTemp2(new pcl::PointCloud());
            *cornerTemp = *transformPointCloud(cornerCloudKeyFrames[i],  &cloudKeyPoses6D->points[i]);
            for (int j = 0; j < (int)cornerTemp->size(); ++j)
            {
                if (cornerTemp->points[j].z > cloudKeyPoses6D->points[i].z && cornerTemp->points[j].z < cloudKeyPoses6D->points[i].z + 5)
                    cornerTemp2->push_back(cornerTemp->points[j]);
            }
            pcl::PointCloud::Ptr surfTemp(new pcl::PointCloud());
            pcl::PointCloud::Ptr surfTemp2(new pcl::PointCloud());
            *surfTemp = *transformPointCloud(surfCloudKeyFrames[i],  &cloudKeyPoses6D->points[i]);
            for (int j = 0; j < (int)surfTemp->size(); ++j)
            {
                if (surfTemp->points[j].z > cloudKeyPoses6D->points[i].z && surfTemp->points[j].z < cloudKeyPoses6D->points[i].z + 5)
                    surfTemp2->push_back(surfTemp->points[j]);
            }
            *globalCornerCloud += *cornerTemp2;
            *globalSurfCloud   += *surfTemp2;

            // origin cloud
            *globalCornerCloud += *transformPointCloud(cornerCloudKeyFrames[i],  &cloudKeyPoses6D->points[i]);
            *globalSurfCloud   += *transformPointCloud(surfCloudKeyFrames[i],    &cloudKeyPoses6D->points[i]);
            cout << "\r" << std::flush << "Processing feature cloud " << i << " of " << cloudKeyPoses6D->size() << " ...";
        }
        // down-sample and save corner cloud
        downSizeFilterCorner.setInputCloud(globalCornerCloud);
        pcl::io::savePCDFileASCII(savePCDDirectory + "cloudCorner.pcd", *globalCornerCloud);
        // down-sample and save surf cloud
        downSizeFilterSurf.setInputCloud(globalSurfCloud);
        pcl::io::savePCDFileASCII(savePCDDirectory + "cloudSurf.pcd", *globalSurfCloud);
        // down-sample and save global point cloud map
        *globalMapCloud += *globalCornerCloud;
        *globalMapCloud += *globalSurfCloud;
        downSizeFilterSurf.setInputCloud(globalMapCloud);
        pcl::io::savePCDFileASCII(savePCDDirectory + "cloudGlobal.pcd", *globalMapCloud);
        cout << "****************************************************" << endl;
        cout << "Saving map to pcd files completed" << endl;
    }

    void publishGlobalMap()
    {
        if (pubLaserCloudSurround.getNumSubscribers() == 0)
            return;

        if (cloudKeyPoses3D->points.empty() == true)
            return;

        pcl::KdTreeFLANN::Ptr kdtreeGlobalMap(new pcl::KdTreeFLANN());;
        pcl::PointCloud ::Ptr globalMapKeyPoses(new pcl::PointCloud());
        pcl::PointCloud ::Ptr globalMapKeyPosesDS(new pcl::PointCloud());
        pcl::PointCloud ::Ptr globalMapKeyFrames(new pcl::PointCloud());
        pcl::PointCloud ::Ptr globalMapKeyFramesDS(new pcl::PointCloud());

        // kd-tree to find near key frames to visualize
        std::vector pointSearchIndGlobalMap;
        std::vector pointSearchSqDisGlobalMap;
        // search near key frames to visualize
        mtx.lock();
        kdtreeGlobalMap->setInputCloud(cloudKeyPoses3D);
        kdtreeGlobalMap->radiusSearch(cloudKeyPoses3D->back(), globalMapVisualizationSearchRadius, pointSearchIndGlobalMap, pointSearchSqDisGlobalMap, 0);
        mtx.unlock();

        for (int i = 0; i < (int)pointSearchIndGlobalMap.size(); ++i)
            globalMapKeyPoses->push_back(cloudKeyPoses3D->points[pointSearchIndGlobalMap[i]]);
        // downsample near selected key frames
        pcl::VoxelGrid downSizeFilterGlobalMapKeyPoses; // for global map visualization
        downSizeFilterGlobalMapKeyPoses.setLeafSize(globalMapVisualizationPoseDensity, globalMapVisualizationPoseDensity, globalMapVisualizationPoseDensity); // for global map visualization
        downSizeFilterGlobalMapKeyPoses.setInputCloud(globalMapKeyPoses);
        downSizeFilterGlobalMapKeyPoses.filter(*globalMapKeyPosesDS);

        // extract visualized and downsampled key frames
        for (int i = 0; i < (int)globalMapKeyPosesDS->size(); ++i){
            if (pointDistance(globalMapKeyPosesDS->points[i], cloudKeyPoses3D->back()) > globalMapVisualizationSearchRadius)
                continue;
            int thisKeyInd = (int)globalMapKeyPosesDS->points[i].intensity;
            *globalMapKeyFrames += *transformPointCloud(cornerCloudKeyFrames[thisKeyInd],  &cloudKeyPoses6D->points[thisKeyInd]);
            *globalMapKeyFrames += *transformPointCloud(surfCloudKeyFrames[thisKeyInd],    &cloudKeyPoses6D->points[thisKeyInd]);
        }
        // downsample visualized points
        pcl::VoxelGrid downSizeFilterGlobalMapKeyFrames; // for global map visualization
        downSizeFilterGlobalMapKeyFrames.setLeafSize(globalMapVisualizationLeafSize, globalMapVisualizationLeafSize, globalMapVisualizationLeafSize); // for global map visualization
        downSizeFilterGlobalMapKeyFrames.setInputCloud(globalMapKeyFrames);
        downSizeFilterGlobalMapKeyFrames.filter(*globalMapKeyFramesDS);
        publishCloud(&pubLaserCloudSurround, globalMapKeyFramesDS, timeLaserInfoStamp, "odom");    
    }

    void loopHandler(const std_msgs::Float64MultiArray::ConstPtr& loopMsg)
    {
        // control loop closure frequency
        static double last_loop_closure_time = -1;
        {
            std::lock_guard lock(mtx);
            if (timeLaserInfoCur - last_loop_closure_time < 5.0)
                return;
            else
                last_loop_closure_time = timeLaserInfoCur;
        }

        performLoopClosure(*loopMsg);
    }

    void performLoopClosure(const std_msgs::Float64MultiArray& loopMsg)
    {
        pcl::PointCloud::Ptr copy_cloudKeyPoses6D(new pcl::PointCloud());
        {
            std::lock_guard lock(mtx);
            *copy_cloudKeyPoses6D = *cloudKeyPoses6D;
        }

        // get lidar keyframe id
        int key_cur = -1; // latest lidar keyframe id
        int key_pre = -1; // previous lidar keyframe id
        {
            loopFindKey(loopMsg, copy_cloudKeyPoses6D, key_cur, key_pre);
            if (key_cur == -1 || key_pre == -1 || key_cur == key_pre) || abs(key_cur - key_pre) < 25)
                return;
        }

        // check if loop added before
        {
            // if image loop closure comes at high frequency, many image loop may point to the same key_cur
            auto it = loopIndexContainer.find(key_cur);
            if (it != loopIndexContainer.end())
                return;
        }
        
        // get lidar keyframe cloud
        pcl::PointCloud::Ptr cureKeyframeCloud(new pcl::PointCloud());
        pcl::PointCloud::Ptr prevKeyframeCloud(new pcl::PointCloud());
        {
            loopFindNearKeyframes(copy_cloudKeyPoses6D, cureKeyframeCloud, key_cur, 0);
            loopFindNearKeyframes(copy_cloudKeyPoses6D, prevKeyframeCloud, key_pre, historyKeyframeSearchNum);
            if (cureKeyframeCloud->size() < 300 || prevKeyframeCloud->size() < 1000)
                return;
            if (pubHistoryKeyFrames.getNumSubscribers() != 0)
                publishCloud(&pubHistoryKeyFrames, prevKeyframeCloud, timeLaserInfoStamp, "odom");
        }

        // get keyframe pose
        Eigen::Affine3f pose_cur;
        Eigen::Affine3f pose_pre;
        Eigen::Affine3f pose_diff_t; // serves as initial guess
        {
            pose_cur = pclPointToAffine3f(copy_cloudKeyPoses6D->points[key_cur]);
            pose_pre = pclPointToAffine3f(copy_cloudKeyPoses6D->points[key_pre]);

            Eigen::Vector3f t_diff;
            t_diff.x() = - (pose_cur.translation().x() - pose_pre.translation().x());
            t_diff.y() = - (pose_cur.translation().y() - pose_pre.translation().y());
            t_diff.z() = - (pose_cur.translation().z() - pose_pre.translation().z());
            if (t_diff.norm() < historyKeyframeSearchRadius)
                t_diff.setZero();
            pose_diff_t = pcl::getTransformation(t_diff.x(), t_diff.y(), t_diff.z(), 0, 0, 0);
        }

        // transform and rotate cloud for matching
        pcl::IterativeClosestPoint icp;
        // pcl::GeneralizedIterativeClosestPoint icp;
        icp.setMaxCorrespondenceDistance(historyKeyframeSearchRadius * 2);
        icp.setMaximumIterations(100);
        icp.setRANSACIterations(0);
        icp.setTransformationEpsilon(1e-3);
        icp.setEuclideanFitnessEpsilon(1e-3);

        // initial guess cloud
        pcl::PointCloud::Ptr cureKeyframeCloud_new(new pcl::PointCloud());
        pcl::transformPointCloud(*cureKeyframeCloud, *cureKeyframeCloud_new, pose_diff_t);

        // match using icp
        icp.setInputSource(cureKeyframeCloud_new);
        icp.setInputTarget(prevKeyframeCloud);
        pcl::PointCloud::Ptr unused_result(new pcl::PointCloud());
        icp.align(*unused_result);

        if (pubIcpKeyFrames.getNumSubscribers() != 0)
        {
            pcl::PointCloud::Ptr closed_cloud(new pcl::PointCloud());
            pcl::transformPointCloud(*cureKeyframeCloud_new, *closed_cloud, icp.getFinalTransformation());
            publishCloud(&pubIcpKeyFrames, closed_cloud, timeLaserInfoStamp, "odom");
        }

        // add graph factor
        if (icp.getFitnessScore() < historyKeyframeFitnessScore && icp.hasConverged() == true)
        {
            // get gtsam pose
            gtsam::Pose3 poseFrom = affine3fTogtsamPose3(Eigen::Affine3f(icp.getFinalTransformation()) * pose_diff_t * pose_cur);
            gtsam::Pose3 poseTo   = pclPointTogtsamPose3(copy_cloudKeyPoses6D->points[key_pre]);
            // get noise
            float noise = icp.getFitnessScore();
            gtsam::Vector Vector6(6);
            Vector6 << noise, noise, noise, noise, noise, noise;
            noiseModel::Diagonal::shared_ptr constraintNoise = noiseModel::Diagonal::Variances(Vector6);
            // save pose constraint
            mtx.lock();
            loopIndexQueue.push_back(make_pair(key_cur, key_pre));
            loopPoseQueue.push_back(poseFrom.between(poseTo));
            loopNoiseQueue.push_back(constraintNoise);
            mtx.unlock();
            // add loop pair to container
            loopIndexContainer[key_cur] = key_pre;
        }

        // visualize loop constraints
        if (!loopIndexContainer.empty())
        {
            visualization_msgs::MarkerArray markerArray;
            // loop nodes
            visualization_msgs::Marker markerNode;
            markerNode.header.frame_id = "odom";
            markerNode.header.stamp = timeLaserInfoStamp;
            markerNode.action = visualization_msgs::Marker::ADD;
            markerNode.type = visualization_msgs::Marker::SPHERE_LIST;
            markerNode.ns = "loop_nodes";
            markerNode.id = 0;
            markerNode.pose.orientation.w = 1;
            markerNode.scale.x = 0.3; markerNode.scale.y = 0.3; markerNode.scale.z = 0.3; 
            markerNode.color.r = 0; markerNode.color.g = 0.8; markerNode.color.b = 1;
            markerNode.color.a = 1;
            // loop edges
            visualization_msgs::Marker markerEdge;
            markerEdge.header.frame_id = "odom";
            markerEdge.header.stamp = timeLaserInfoStamp;
            markerEdge.action = visualization_msgs::Marker::ADD;
            markerEdge.type = visualization_msgs::Marker::LINE_LIST;
            markerEdge.ns = "loop_edges";
            markerEdge.id = 1;
            markerEdge.pose.orientation.w = 1;
            markerEdge.scale.x = 0.1;
            markerEdge.color.r = 0.9; markerEdge.color.g = 0.9; markerEdge.color.b = 0;
            markerEdge.color.a = 1;

            for (auto it = loopIndexContainer.begin(); it != loopIndexContainer.end(); ++it)
            {
                int key_cur = it->first;
                int key_pre = it->second;
                geometry_msgs::Point p;
                p.x = copy_cloudKeyPoses6D->points[key_cur].x;
                p.y = copy_cloudKeyPoses6D->points[key_cur].y;
                p.z = copy_cloudKeyPoses6D->points[key_cur].z;
                markerNode.points.push_back(p);
                markerEdge.points.push_back(p);
                p.x = copy_cloudKeyPoses6D->points[key_pre].x;
                p.y = copy_cloudKeyPoses6D->points[key_pre].y;
                p.z = copy_cloudKeyPoses6D->points[key_pre].z;
                markerNode.points.push_back(p);
                markerEdge.points.push_back(p);
            }

            markerArray.markers.push_back(markerNode);
            markerArray.markers.push_back(markerEdge);
            pubLoopConstraintEdge.publish(markerArray);
        }
    }

    void loopFindNearKeyframes(const pcl::PointCloud::Ptr& copy_cloudKeyPoses6D,
                               pcl::PointCloud::Ptr& nearKeyframes, 
                               const int& key, const int& searchNum)
    {
        // extract near keyframes
        nearKeyframes->clear();
        int cloudSize = copy_cloudKeyPoses6D->size();
        for (int i = -searchNum; i <= searchNum; ++i)
        {
            int key_near = key + i;
            if (key_near < 0 || key_near >= cloudSize )
                continue;
            *nearKeyframes += *transformPointCloud(cornerCloudKeyFrames[key_near], ©_cloudKeyPoses6D->points[key_near]);
            *nearKeyframes += *transformPointCloud(surfCloudKeyFrames[key_near],   ©_cloudKeyPoses6D->points[key_near]);
        }

        if (nearKeyframes->empty())
            return;

        // downsample near keyframes
        pcl::PointCloud::Ptr cloud_temp(new pcl::PointCloud());
        downSizeFilterICP.setInputCloud(nearKeyframes);
        downSizeFilterICP.filter(*cloud_temp);
        *nearKeyframes = *cloud_temp;
    }

    void loopFindKey(const std_msgs::Float64MultiArray& loopMsg, 
                     const pcl::PointCloud::Ptr& copy_cloudKeyPoses6D,
                     int& key_cur, int& key_pre)
    {
        if (loopMsg.data.size() != 2)
            return;

        double loop_time_cur = loopMsg.data[0];
        double loop_time_pre = loopMsg.data[1];

        if (abs(loop_time_cur - loop_time_pre) < historyKeyframeSearchTimeDiff)
            return;

        int cloudSize = copy_cloudKeyPoses6D->size();
        if (cloudSize < 2)
            return;

        // latest key
        key_cur = cloudSize - 1;
        for (int i = cloudSize - 1; i >= 0; --i)
        {
            if (copy_cloudKeyPoses6D->points[i].time > loop_time_cur)
                key_cur = round(copy_cloudKeyPoses6D->points[i].intensity);
            else
                break;
        }

        // previous key
        key_pre = 0;
        for (int i = 0; i < cloudSize; ++i)
        {
            if (copy_cloudKeyPoses6D->points[i].time < loop_time_pre)
                key_pre = round(copy_cloudKeyPoses6D->points[i].intensity);
            else
                break;
        }
    }

    void loopClosureThread()
    {
        if (loopClosureEnableFlag == false)
            return;

        ros::Rate rate(0.5);
        while (ros::ok())
        {
            rate.sleep();
            performLoopClosureDetection();
        }
    }

    void performLoopClosureDetection()
    {
        std::vector pointSearchIndLoop;
        std::vector pointSearchSqDisLoop;

        int key_cur = -1;
        int key_pre = -1;

        double loop_time_cur = -1;
        double loop_time_pre = -1;

        // find latest key and time 对于latest的理解就是最新的那个,就是当前的,感觉差不多
        {
            std::lock_guard lock(mtx); // 先上锁

            if (cloudKeyPoses3D->empty())
                return;
            
            kdtreeHistoryKeyPoses->setInputCloud(cloudKeyPoses3D);
            kdtreeHistoryKeyPoses->radiusSearch(cloudKeyPoses3D->back(), historyKeyframeSearchRadius, pointSearchIndLoop, pointSearchSqDisLoop, 0);

            key_cur = cloudKeyPoses3D->size() - 1;
            loop_time_cur = cloudKeyPoses6D->points[key_cur].time;
        }

        // find previous key and time 从小到大去循环
        {
            for (int i = 0; i < (int)pointSearchIndLoop.size(); ++i)
            {
                int id = pointSearchIndLoop[i];
                if (abs(cloudKeyPoses6D->points[id].time - loop_time_cur) > historyKeyframeSearchTimeDiff)
                {
                    key_pre = id;
                    loop_time_pre = cloudKeyPoses6D->points[key_pre].time;
                    break;
                }
            }
        }

        // 下面if条件中 都是不正常的情况
        if (key_cur == -1 || key_pre == -1 || key_pre == key_cur || loop_time_cur < 0 || loop_time_pre < 0)
            return;

        std_msgs::Float64MultiArray match_msg;
        match_msg.data.push_back(loop_time_cur);
        match_msg.data.push_back(loop_time_pre);
        performLoopClosure(match_msg);
    }

上面是一些关于线程的内容,这个是在最下面的main函数里面出现的,这个main函数相比其他的话,多了两个线程。

    void updateInitialGuess()
    {        
        // 前一帧的位姿,这里指lidar的位姿
        // Affine3f 仿射变换矩阵(移向量 + 旋转变换组合而成,可以同时实现旋转,缩放,平移等空间变换)
        static Eigen::Affine3f lastImuTransformation;
        // system initialization 系统初始化
        // 如果关键帧集合为空,继续进行初始化
        if (cloudKeyPoses3D->points.empty())
        {
            // 大小为6的数组,当前帧位姿的旋转部分,用激光帧信息中的RPY(来自IMU原始数据)初始化
            // 这里前三个变量分别记录了roll pitch yaw三个初始化的值
            transformTobeMapped[0] = cloudInfo.imuRollInit;
            transformTobeMapped[1] = cloudInfo.imuPitchInit;
            transformTobeMapped[2] = cloudInfo.imuYawInit;

            // 在utility.h中定义
            if (!useImuHeadingInitialization)
                transformTobeMapped[2] = 0;

            lastImuTransformation = pcl::getTransformation(0, 0, 0, cloudInfo.imuRollInit, cloudInfo.imuPitchInit, cloudInfo.imuYawInit); // save imu before return;
            return;
        }

        // use VINS odometry estimation for pose guess
        // 用当前帧和前一帧对应的imu里程计计算相对位姿变换,再用前一帧的位姿与相对变换,计算当前帧的位姿,存transformTobeMapped
        static int odomResetId = 0;
        static bool lastVinsTransAvailable = false;
        static Eigen::Affine3f lastVinsTransformation;
        if (cloudInfo.odomAvailable == true && cloudInfo.odomResetId == odomResetId)
        {
            ROS_INFO("Using VINS initial guess");
            if (lastVinsTransAvailable == false)
            {
                ROS_INFO("Initializing VINS initial guess");
                // 如果是首次积分,则将lastVinsTransformation赋值为根据odom的xyz,RPY转换得到的transform
                lastVinsTransformation = pcl::getTransformation(cloudInfo.odomX,    cloudInfo.odomY,     cloudInfo.odomZ, 
                                                                cloudInfo.odomRoll, cloudInfo.odomPitch, cloudInfo.odomYaw);
                lastVinsTransAvailable = true;
            } 
            else 
            {
                ROS_INFO("Obtaining VINS incremental guess");
                // 首先从odom转换成transform,获得当前transform在lastVinsTransformation下的位置
                // 当前帧的初始化估计位姿(来自IMU里程计),后面用来计算增量位姿变换
                Eigen::Affine3f transBack = pcl::getTransformation(cloudInfo.odomX,    cloudInfo.odomY,     cloudInfo.odomZ, 
                                                                   cloudInfo.odomRoll, cloudInfo.odomPitch, cloudInfo.odomYaw);
                // inverse 逆矩阵
                // 当前帧相对于前一帧的位姿变换--imu里程计计算得到
                Eigen::Affine3f transIncre = lastVinsTransformation.inverse() * transBack;

                // 前一帧的位姿
                Eigen::Affine3f transTobe = trans2Affine3f(transformTobeMapped);
                // 当前帧的位姿
                Eigen::Affine3f transFinal = transTobe * transIncre;
                // 更新当前帧位姿transformTobeMapped
                // 这里的0 1 2位置代表了RPY 3 4 5 位置代表了xyz,这里的顺序换了一下 小细节
                pcl::getTranslationAndEulerAngles(transFinal, transformTobeMapped[3], transformTobeMapped[4], transformTobeMapped[5], 
                                                              transformTobeMapped[0], transformTobeMapped[1], transformTobeMapped[2]);

                // 将视觉惯性里程计的transform赋值为odom的位姿
                lastVinsTransformation = pcl::getTransformation(cloudInfo.odomX,    cloudInfo.odomY,     cloudInfo.odomZ, 
                                                                cloudInfo.odomRoll, cloudInfo.odomPitch, cloudInfo.odomYaw);

                // 保存当前时态的imu位姿
                lastImuTransformation = pcl::getTransformation(0, 0, 0, cloudInfo.imuRollInit, cloudInfo.imuPitchInit, cloudInfo.imuYawInit); // save imu before return;
                return;
                // transformTobeMapped存储激光雷达位姿
            }
        } 
        else 
        {
            ROS_WARN("VINS failure detected.");
            // 没有odom信息,或者是第一帧进入的时候
            lastVinsTransAvailable = false;
            odomResetId = cloudInfo.odomResetId;
        }

        // 当odo不可用 或者 lastVinsTransAvailable == false
        // use imu incremental estimation for pose guess (only rotation)
        // 只在第一帧调用(注意上面的return),用imu累计估计位姿(只估计旋转的)
        if (cloudInfo.imuAvailable == true)
        {
            ROS_INFO("Using IMU initial guess");
            // 首先从imu转换成transform,获得当前transform在lastImuTransformation下的位置
            // 当前帧的姿态角(来自原始imu数据)
            Eigen::Affine3f transBack = pcl::getTransformation(0, 0, 0, cloudInfo.imuRollInit, cloudInfo.imuPitchInit, cloudInfo.imuYawInit);
            //当前帧相对于前一帧的姿态变换
            Eigen::Affine3f transIncre = lastImuTransformation.inverse() * transBack;

            // 将上一时态的imu的transform点乘两个imu时态间的位姿变换,将其赋值给transformTobeMapped数组
            // 前一帧的位姿
            Eigen::Affine3f transTobe = trans2Affine3f(transformTobeMapped);
            Eigen::Affine3f transFinal = transTobe * transIncre;
            pcl::getTranslationAndEulerAngles(transFinal, transformTobeMapped[3], transformTobeMapped[4], transformTobeMapped[5], 
                                                          transformTobeMapped[0], transformTobeMapped[1], transformTobeMapped[2]);

            lastImuTransformation = pcl::getTransformation(0, 0, 0, cloudInfo.imuRollInit, cloudInfo.imuPitchInit, cloudInfo.imuYawInit); // save imu before return;
            return;
        }
    }

一个初始化的操作,没啥好讲的

    void extractNearby()
    {
        pcl::PointCloud::Ptr surroundingKeyPoses(new pcl::PointCloud());
        pcl::PointCloud::Ptr surroundingKeyPosesDS(new pcl::PointCloud());
        std::vector pointSearchInd;
        std::vector pointSearchSqDis;

        // extract all the nearby key poses and downsample them 提取所有附近的关键点位姿并对其进行降采样
        // kdtree的输入,全局关键帧位姿集合(历史所有关键帧集合)
        // kdtree作为一个搜索的工具
        kdtreeSurroundingKeyPoses->setInputCloud(cloudKeyPoses3D); // create kd-tree
        // surroundingKeyframeSearchRadius默认值为50.0,scan-to-map优化的距离
        // 对最近的一帧关键帧,在半径区域内搜索空间区域上相邻的关键帧集合
        // 搜索的结果存储在 pointSearchInd 和 pointSearchSqDis中
        // 对于back就是点云里的最后一个点
        // 这个函数的第一个参数 传入一个点在kd树中,搜索这个所有到这个点的距离小于某个值的点
        kdtreeSurroundingKeyPoses->radiusSearch(cloudKeyPoses3D->back(), (double)surroundingKeyframeSearchRadius, pointSearchInd, pointSearchSqDis);
        for (int i = 0; i < (int)pointSearchInd.size(); ++i)
        {
            // 将搜索到的每个点,通过id在cloudKeyPose3D中找到对应的关键帧(点云)并加入
            int id = pointSearchInd[i];
            // 加入相邻关键帧位姿集合中
            surroundingKeyPoses->push_back(cloudKeyPoses3D->points[id]);
        }

        // 降采样
        downSizeFilterSurroundingKeyPoses.setInputCloud(surroundingKeyPoses);
        // 降采样后的结果存储在DS中
        downSizeFilterSurroundingKeyPoses.filter(*surroundingKeyPosesDS);

        // also extract some latest key frames in case the robot rotates in one position 同时提取一些最新的关键帧,防止机器人在一个位置旋转
        // 加入时间上相邻的一些关键帧,比如当载体在原地转圈,这些帧加进来是合理的
        // Question : 原地旋转为什么不算作空间上相邻的关键帧
        /*
        暂时的答案(3.7)
        3D点云集合中只保存位置信息(xyz),6D点云中保存位置 + 旋转信息(xyz + rpy)
        当原地旋转时,关键帧的xyz应该是保持不变的,所以连续的若干帧会在3D点云集合中只保存一个坐标(是不是这样需要后续看cloudKeyPose3D是怎么添加帧的)
        所以使用3D点云集合搜索空间领域时,会丢失旋转前后的帧(xyz相同,rpy不同),所以需要重新对事件领域进行添加
        这便解释下面if语句中使用的是6D的时间戳
        */
        int numPoses = cloudKeyPoses3D->size();
        for (int i = numPoses-1; i >= 0; --i)
        {
            if (timeLaserInfoCur - cloudKeyPoses6D->points[i].time < 10.0)
                surroundingKeyPosesDS->push_back(cloudKeyPoses3D->points[i]);
            else
                break;
        }

        // 将相邻关键帧集合对应的角点、平面点,加入到局部map中,作为scan-to-map匹配的局部点云地图
        extractCloud(surroundingKeyPosesDS);
    }

    // 相邻关键帧集合对应的角点、平面点,加入到map中
    // 称之为局部map,后面进行scan-to-map匹配,所用到的map就是这里的相邻关键帧对应点云集合
    void extractCloud(pcl::PointCloud::Ptr cloudToExtract)
    {
        std::vector> laserCloudCornerSurroundingVec;
        std::vector> laserCloudSurfSurroundingVec;

        laserCloudCornerSurroundingVec.resize(cloudToExtract->size()); // 这个是重新设置大小的
        laserCloudSurfSurroundingVec.resize(cloudToExtract->size());

        // extract surrounding map
        // 遍历当前帧(实际是取最近的一个关键帧来找它相邻的关键帧集合)时空唯独上相邻的关键帧集合
        #pragma omp parallel for num_threads(numberOfCores) //并行编程
        // #pragma omp parallel for num_threads(numberOfCores)
        for (int i = 0; i < (int)cloudToExtract->size(); ++i)
        {
            // 相邻关键帧索引
            // intensity也可以放索引:index
            int thisKeyInd = (int)cloudToExtract->points[i].intensity;
            // 提取50米范围内的点,transformPointCloud作用是返回输入点云乘以输入的transform
            // 自定义函数,计算两个点之间的距离
            if (pointDistance(cloudKeyPoses3D->points[thisKeyInd], cloudKeyPoses3D->back()) > surroundingKeyframeSearchRadius)
                continue;
            // 相邻关键帧对应的角点、平面点云,通过6D位姿变换到世界坐标系下
            laserCloudCornerSurroundingVec[i]  = *transformPointCloud(cornerCloudKeyFrames[thisKeyInd],  &cloudKeyPoses6D->points[thisKeyInd]);
            laserCloudSurfSurroundingVec[i]    = *transformPointCloud(surfCloudKeyFrames[thisKeyInd],    &cloudKeyPoses6D->points[thisKeyInd]);
        }

        // fuse the map
        // 赋值线和面特征点击,并且进行下采样
        laserCloudCornerFromMap->clear();
        laserCloudSurfFromMap->clear(); 
        for (int i = 0; i < (int)cloudToExtract->size(); ++i)
        {
            // 加入局部map
            *laserCloudCornerFromMap += laserCloudCornerSurroundingVec[i];
            *laserCloudSurfFromMap   += laserCloudSurfSurroundingVec[i];
        }

        // Downsample the surrounding corner key frames (or map) 降采样 DS后缀downsample
        downSizeFilterCorner.setInputCloud(laserCloudCornerFromMap);
        downSizeFilterCorner.filter(*laserCloudCornerFromMapDS);
        // Downsample the surrounding surf key frames (or map)
        downSizeFilterSurf.setInputCloud(laserCloudSurfFromMap);
        downSizeFilterSurf.filter(*laserCloudSurfFromMapDS);
        // downSizeFilterSurf.setInputCloud(laserCloudSurfFromMap);
        // downSizeFilterSurf.filter(*laserCloudSurfFromMapDS);
    }
    void extractSurroundingKeyFrames()
    {
        // 关键帧集合为空,直接返回
        if (cloudKeyPoses3D->points.empty() == true)
            return; 
        
        extractNearby();
    }

这个地方的逻辑很简单:使用extractSurroundingKeyFrames ->extractNearBy -> extractCloud就可以了,这个函数具体内容可以看看里面,这里就不浪费笔墨了。

    // 当前激光帧角点,平面点集合降采样
    void downsampleCurrentScan()
    {
        // Downsample cloud from current scan
        // 对第二部分得到的角点和平面点集合进行进一步降采样
        // 3.7实验,这个只能写两个,多一个少一个都不行,直接图上的xyz飞了
        laserCloudCornerLastDS->clear();
        downSizeFilterCorner.setInputCloud(laserCloudCornerLast);
        downSizeFilterCorner.filter(*laserCloudCornerLastDS);
        laserCloudCornerLastDSNum = laserCloudCornerLastDS->size();

        laserCloudSurfLastDS->clear();
        downSizeFilterSurf.setInputCloud(laserCloudSurfLast);
        downSizeFilterSurf.filter(*laserCloudSurfLastDS);
        laserCloudSurfLastDSNum = laserCloudSurfLastDS->size();

        // laserCloudSurfLastDS->clear();
        // downSizeFilterSurf.setInputCloud(laserCloudSurfLast);
        // downSizeFilterSurf.filter(*laserCloudSurfLastDS);
        // laserCloudSurfLastDSNum = laserCloudCornerLastDS->size();
    }

降采样内容,一开始想再加一个重复的步骤,但是没编译过,不知道效果怎么样,但是作者用了两个的话,应该会有用两个的说法。

    void updatePointAssociateToMap()
    {
        transPointAssociateToMap = trans2Affine3f(transformTobeMapped);
    }

    void cornerOptimization()
    {
        // 仿射变换,更新当前位姿与地图间位姿变换
        updatePointAssociateToMap();

        #pragma omp parallel for num_threads(numberOfCores) // 并行计算
        // 遍历点云,构建点到直线的约束
        for (int i = 0; i < laserCloudCornerLastDSNum; i++)
        {
            PointType pointOri, pointSel, coeff;
            std::vector pointSearchInd;
            std::vector pointSearchSqDis;

            // 角点(lidar系的)
            pointOri = laserCloudCornerLastDS->points[i];
            // 将点从lidar系转换到map坐标系,一个变换的操作,矩阵乘法
            pointAssociateToMap(&pointOri, &pointSel);
            // 在局部角点map中查找当前角点相邻的5个角点 这里的5可以做个对比实验
            kdtreeCornerFromMap->nearestKSearch(pointSel, 5, pointSearchInd, pointSearchSqDis);

            // cv库中来存储矩阵的
            cv::Mat matA1(3, 3, CV_32F, cv::Scalar::all(0));
            cv::Mat matD1(1, 3, CV_32F, cv::Scalar::all(0));
            cv::Mat matV1(3, 3, CV_32F, cv::Scalar::all(0));
                    
            // 只有最近的点都在一定的阈值内(这里是1m)才进行计算 这里的第5号位置没看明白
            if (pointSearchSqDis[4] < 1.0)
            {
                float cx = 0, cy = 0, cz = 0;
                for (int j = 0; j < 5; j++) 
                {
                    // 计算5个点的均值坐标,记为中心点
                    cx += laserCloudCornerFromMapDS->points[pointSearchInd[j]].x;
                    cy += laserCloudCornerFromMapDS->points[pointSearchInd[j]].y;
                    cz += laserCloudCornerFromMapDS->points[pointSearchInd[j]].z;
                }
                cx /= 5; cy /= 5;  cz /= 5;

                // 根据均值计算协方差(得到一个协方差矩阵 3*3的)
                float a11 = 0, a12 = 0, a13 = 0, a22 = 0, a23 = 0, a33 = 0;
                for (int j = 0; j < 5; j++)
                {
                    //计算点与中心点之间的距离
                    float ax = laserCloudCornerFromMapDS->points[pointSearchInd[j]].x - cx;
                    float ay = laserCloudCornerFromMapDS->points[pointSearchInd[j]].y - cy;
                    float az = laserCloudCornerFromMapDS->points[pointSearchInd[j]].z - cz;

                    a11 += ax * ax; a12 += ax * ay; a13 += ax * az;
                    a22 += ay * ay; a23 += ay * az;
                    a33 += az * az;
                }
                a11 /= 5; a12 /= 5; a13 /= 5; a22 /= 5; a23 /= 5; a33 /= 5;

                // 构建协方差矩阵,这种构造方式非常无脑,直接填内容
                matA1.at(0, 0) = a11; matA1.at(0, 1) = a12; matA1.at(0, 2) = a13;
                matA1.at(1, 0) = a12; matA1.at(1, 1) = a22; matA1.at(1, 2) = a23;
                matA1.at(2, 0) = a13; matA1.at(2, 1) = a23; matA1.at(2, 2) = a33;

                // eigen库,进行特征值分解,求协方差矩阵的特征值(matD1)和特征向量(matV1)
                cv::eigen(matA1, matD1, matV1);
                // 如果最大的特征值相比次大特征值 大很多 则认为构成了线,角点是合格的

                // 个人理解:第一大的特征值 比 次大特征值的3倍还要大,执行下面的if条件语句
                if (matD1.at(0, 0) > 3 * matD1.at(0, 1)) 
                {
                    // 当前帧角点坐标(map系下)
                    float x0 = pointSel.x;
                    float y0 = pointSel.y;
                    float z0 = pointSel.z;
                    // 局部map对应中心角点,沿着特征向量的方向,前后各取一个点
                    float x1 = cx + 0.1 * matV1.at(0, 0);
                    float y1 = cy + 0.1 * matV1.at(0, 1);
                    float z1 = cz + 0.1 * matV1.at(0, 2);
                    float x2 = cx - 0.1 * matV1.at(0, 0);
                    float y2 = cy - 0.1 * matV1.at(0, 1);
                    float z2 = cz - 0.1 * matV1.at(0, 2);

                    // 这个就是外积,表示三角形的面积
                    // 这个就是外积,用向量表示三角形的两条边,然后用这两个向量来计算外积,从而得到三角形面积。
                    float a012 = sqrt(((x0 - x1)*(y0 - y2) - (x0 - x2)*(y0 - y1)) * ((x0 - x1)*(y0 - y2) - (x0 - x2)*(y0 - y1)) 
                                    + ((x0 - x1)*(z0 - z2) - (x0 - x2)*(z0 - z1)) * ((x0 - x1)*(z0 - z2) - (x0 - x2)*(z0 - z1)) 
                                    + ((y0 - y1)*(z0 - z2) - (y0 - y2)*(z0 - z1)) * ((y0 - y1)*(z0 - z2) - (y0 - y2)*(z0 - z1)));

                    // 计算1和2点构成线段的长度
                    float l12 = sqrt((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2) + (z1 - z2)*(z1 - z2));

                    // 这里的la lb lc应该是文献中定义的东西,这个没看出来表示啥,要读论文
                    // 这个是和下面算系数的
                    float la = ((y1 - y2)*((x0 - x1)*(y0 - y2) - (x0 - x2)*(y0 - y1)) 
                              + (z1 - z2)*((x0 - x1)*(z0 - z2) - (x0 - x2)*(z0 - z1))) / a012 / l12;

                    float lb = -((x1 - x2)*((x0 - x1)*(y0 - y2) - (x0 - x2)*(y0 - y1)) 
                               - (z1 - z2)*((y0 - y1)*(z0 - z2) - (y0 - y2)*(z0 - z1))) / a012 / l12;

                    float lc = -((x1 - x2)*((x0 - x1)*(z0 - z2) - (x0 - x2)*(z0 - z1)) 
                               + (y1 - y2)*((y0 - y1)*(z0 - z2) - (y0 - y2)*(z0 - z1))) / a012 / l12;

                    //三角形的高,点到直线的距离
                    float ld2 = a012 / l12;

                    //涉及到一个鲁棒核函数,距离越大,s越小,使用距离惩罚因子(权重)
                    float s = 1 - 0.9 * fabs(ld2);

                    coeff.x = s * la;
                    coeff.y = s * lb;
                    coeff.z = s * lc;
                    coeff.intensity = s * ld2; // intensity的新存储内容,那么这个intensity就非常灵活,对于不同的部分,它会存储不同意义的数值
                    
                    // 距离越小,这个因子的影响就越小(距离越大,加权后的影响就越大)
                    if (s > 0.1)
                    {
                        // s大于0.1 当前激光帧角点,加入匹配集合中
                        laserCloudOriCornerVec[i] = pointOri;
                        // 角点的参数
                        coeffSelCornerVec[i] = coeff;
                        laserCloudOriCornerFlag[i] = true;
                    }
                }
            }
        }
    }

    // 当前激光帧平面点寻找局部map匹配点
    void surfOptimization()
    {
        updatePointAssociateToMap(); // 先更新点

        #pragma omp parallel for num_threads(numberOfCores) // 并行计算,这里只使用了2个核
        
        for (int i = 0; i < laserCloudSurfLastDSNum; i++)
        {
            PointType pointOri, pointSel, coeff;
            std::vector pointSearchInd;
            std::vector pointSearchSqDis;

            pointOri = laserCloudSurfLastDS->points[i];
            // 这里的sel的话,就是ori经过函数以后的一个输出
            pointAssociateToMap(&pointOri, &pointSel); 
            kdtreeSurfFromMap->nearestKSearch(pointSel, 5, pointSearchInd, pointSearchSqDis); // 一个快速搜索

            // 这个地方应该是Ax = B
            Eigen::Matrix matA0;
            Eigen::Matrix matB0;
            Eigen::Vector3f matX0;

            // 一个初始化 不过多解释
            matA0.setZero();
            matB0.fill(-1);
            matX0.setZero();

            // 这个第5号位置 存疑,应该在下面的代码里面
            if (pointSearchSqDis[4] < 1.0) 
            {
                for (int j = 0; j < 5; j++)
                {
                    // 将前5个点存入matA
                    matA0(j, 0) = laserCloudSurfFromMapDS->points[pointSearchInd[j]].x;
                    matA0(j, 1) = laserCloudSurfFromMapDS->points[pointSearchInd[j]].y;
                    matA0(j, 2) = laserCloudSurfFromMapDS->points[pointSearchInd[j]].z;
                }

                // Ax=B,根据这五个点求解平面方程,进行QR分解,获得平面方程解
                matX0 = matA0.colPivHouseholderQr().solve(matB0);

                // 平面方程的系数,也是法向量的分量
                float pa = matX0(0, 0);
                float pb = matX0(1, 0);
                float pc = matX0(2, 0);
                float pd = 1; // 这个就是平面方程的常数项

                // 将matx归一化,得到单位法向量
                float ps = sqrt(pa * pa + pb * pb + pc * pc);
                pa /= ps; pb /= ps; pc /= ps; pd /= ps;

                bool planeValid = true;
                // 检查平面是否合格,如果5个点中有点到平面的距离超过0.2米,那么认为这些点太分散了,不构成平面
                // 这里的if条件没看明白,应该是一个特定的判别式子,距离的话验证一下?
                for (int j = 0; j < 5; j++)
                {
                    if (fabs(pa * laserCloudSurfFromMapDS->points[pointSearchInd[j]].x +
                             pb * laserCloudSurfFromMapDS->points[pointSearchInd[j]].y +
                             pc * laserCloudSurfFromMapDS->points[pointSearchInd[j]].z + pd) > 0.2)
                    {
                        planeValid = false;
                        break;
                    }
                }

                // 平面合格
                if (planeValid)
                {
                    // 当前激光帧点到平面距离
                    float pd2 = pa * pointSel.x + pb * pointSel.y + pc * pointSel.z + pd;

                    // 可以理解成权值 也可以说是鲁邦核函数,也可以说是惩罚因子
                    float s = 1 - 0.9 * fabs(pd2) / sqrt(sqrt(pointSel.x * pointSel.x
                            + pointSel.y * pointSel.y + pointSel.z * pointSel.z));

                    // 系数赋值
                    coeff.x = s * pa;
                    coeff.y = s * pb;
                    coeff.z = s * pc;
                    coeff.intensity = s * pd2; // 这里的intensity赋值为pd2 * s 具体公式参见上面内容

                    // 这个if条件里面的内容 大体是说 找到了平面匹配点,然后给下面三个变量赋值(加入到集合中)
                    if (s > 0.1)
                    {
                        laserCloudOriSurfVec[i] = pointOri;
                        coeffSelSurfVec[i] = coeff;
                        laserCloudOriSurfFlag[i] = true;
                    }
                }
            }
        }
    }

    // 提取当前帧中与局部map匹配上了的角点、平面点,加入同一集合
    void combineOptimizationCoeffs()
    {
        // combine corner coeffs
        // 遍历当前帧角点集合,提取出与局部map匹配上了的角点
        for (int i = 0; i < laserCloudCornerLastDSNum; ++i)
        {
            if (laserCloudOriCornerFlag[i] == true)
            {
                laserCloudOri->push_back(laserCloudOriCornerVec[i]);
                coeffSel->push_back(coeffSelCornerVec[i]);
            }
        }
        // combine surf coeffs
        // 遍历当前帧平面点集合,提取出与局部map匹配上了的平面点
        for (int i = 0; i < laserCloudSurfLastDSNum; ++i)
        {
            if (laserCloudOriSurfFlag[i] == true)
            {
                laserCloudOri->push_back(laserCloudOriSurfVec[i]);
                coeffSel->push_back(coeffSelSurfVec[i]);
            }
        }
        // 上面两个for循环以后,laserCloudOri里面加了角点和平面点 然后coeffsel里面是角点和平面点的系数,这个上面有明确公式

        // reset flag for next iteration
        // 清空标记 这个判断条件用在了上面两个for循环的if条件中
        std::fill(laserCloudOriCornerFlag.begin(), laserCloudOriCornerFlag.end(), false);
        std::fill(laserCloudOriSurfFlag.begin(), laserCloudOriSurfFlag.end(), false);
    }

    // LM优化
    bool LMOptimization(int iterCount)
    {
        // This optimization is from the original loam_velodyne, need to cope with coordinate transformation
        // 这个优化来自于原来的loam_velodyne,需要进行坐标变换 
        // lidar <- camera      ---     camera <- lidar
        // x = z                ---     x = y
        // y = x                ---     y = z
        // z = y                ---     z = x
        // roll = yaw           ---     roll = pitch
        // pitch = roll         ---     pitch = yaw
        // yaw = pitch          ---     yaw = roll
        // 这个是相机到雷达系的转换          这个是雷达到相机系的转换
        // 就是后面的值 赋值给前面 举个例子:相机的x赋值给雷达的y 那么雷达的y就要赋值给相机的x 这样就能对应起来了
        // 这个在此代码中是这样的 其他的代码 再检查就可以。

        // lidar -> camera
        // 雷达到相机变换的三轴方向的正弦 和 余弦
        float srx = sin(transformTobeMapped[1]);
        float crx = cos(transformTobeMapped[1]);
        float sry = sin(transformTobeMapped[2]);
        float cry = cos(transformTobeMapped[2]);
        float srz = sin(transformTobeMapped[0]);
        float crz = cos(transformTobeMapped[0]);

        int laserCloudSelNum = laserCloudOri->size();
        // 当前帧匹配特征点数太少
        if (laserCloudSelNum < 50)
        {
            return false;
        }

        // 第一个参数是行 第二个参数是列 At就是转置的意思
        // CV_32F是浮点型的-像素可以具有0-1.0之间的任何值
        // cv::Scalar::all(0)全部初始化为0
        cv::Mat matA(laserCloudSelNum, 6, CV_32F, cv::Scalar::all(0));
        cv::Mat matAt(6, laserCloudSelNum, CV_32F, cv::Scalar::all(0));
        cv::Mat matAtA(6, 6, CV_32F, cv::Scalar::all(0));
        cv::Mat matB(laserCloudSelNum, 1, CV_32F, cv::Scalar::all(0));
        cv::Mat matAtB(6, 1, CV_32F, cv::Scalar::all(0));
        cv::Mat matX(6, 1, CV_32F, cv::Scalar::all(0));

        PointType pointOri, coeff;

        // 遍历匹配特征点,构建Jacobian矩阵
        for (int i = 0; i < laserCloudSelNum; i++)
        {
            // lidar -> camera
            // 参照一开始的转换关系进行转换
            pointOri.x = laserCloudOri->points[i].y;
            pointOri.y = laserCloudOri->points[i].z;
            pointOri.z = laserCloudOri->points[i].x;
            // lidar -> camera
            // coeff为系数,在上面有具体的公式
            coeff.x = coeffSel->points[i].y;
            coeff.y = coeffSel->points[i].z;
            coeff.z = coeffSel->points[i].x;
            coeff.intensity = coeffSel->points[i].intensity;
            // in camera
            // 啊 。。。 这个应该是很专业的知识了,有些欠缺,这部分要进行补充
            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;
            
            // lidar -> camera
            matA.at(i, 0) = arz;
            matA.at(i, 1) = arx;
            matA.at(i, 2) = ary;
            matA.at(i, 3) = coeff.z;
            matA.at(i, 4) = coeff.x;
            matA.at(i, 5) = coeff.y;
            
            //点到直线距离、平面距离,作为观测值。个人认为,这也不能说是距离,反正是个数值
            matB.at(i, 0) = -coeff.intensity;
        }

        cv::transpose(matA, matAt);
        matAtA = matAt * matA;
        matAtB = matAt * matB;
        
        //高斯牛顿方程,进行解决,用到QR,这个和上面平面方程求解有联系
        cv::solve(matAtA, matAtB, matX, cv::DECOMP_QR);

        //首次迭代,检查近似Hession矩阵,看是否退化,或者称为奇异,也就是它的行列式的值为0
        if (iterCount == 0) 
        {
            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));

            // 猜测matE是特征值 matV是特征向量 是对的,网上搜过
            cv::eigen(matAtA, matE, matV);
            matV.copyTo(matV2); // 这里V2应该是和V的值一样

            isDegenerate = false; // 这个是判断是否退化的,这个退化可以理解为能否求出来它的特征值
            float eignThre[6] = {100, 100, 100, 100, 100, 100}; // 这里是设置了个阈值,然后通过的话,v2矩阵列赋值为0,并且isdegenerate赋值为true
            for (int i = 5; i >= 0; i--)
            {
                if (matE.at(0, i) < eignThre[i])
                {
                    for (int j = 0; j < 6; j++)
                    {
                        matV2.at(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;
        }

        transformTobeMapped[0] += matX.at(0, 0);
        transformTobeMapped[1] += matX.at(1, 0);
        transformTobeMapped[2] += matX.at(2, 0);
        transformTobeMapped[3] += matX.at(3, 0);
        transformTobeMapped[4] += matX.at(4, 0);
        transformTobeMapped[5] += matX.at(5, 0);

        // rad2deg 把弧度转换为角度
        float deltaR = sqrt(pow(pcl::rad2deg(matX.at(0, 0)), 2) + pow(pcl::rad2deg(matX.at(1, 0)), 2) + pow(pcl::rad2deg(matX.at(2, 0)), 2));
        float deltaT = sqrt(pow(matX.at(3, 0) * 100, 2) + pow(matX.at(4, 0) * 100, 2) + pow(matX.at(5, 0) * 100, 2));

        if (deltaR < 0.05 && deltaT < 0.05)
        {
            return true; // converged 收敛的 能求出来解的
        }
        return false; // keep optimizing
    }

    // scan-to-map优化
    void scan2MapOptimization()
    {
        // 如果关键帧点云数据为空,则直接返回 就是一个鲁棒性判断
        if (cloudKeyPoses3D->points.empty())
            return;

        // 关键点数量 这里是降采样之后的 大于阈值,边为10,面为100
        if (laserCloudCornerLastDSNum > edgeFeatureMinValidNum && laserCloudSurfLastDSNum > surfFeatureMinValidNum)
        {
            // 输入为局部map点云
            kdtreeCornerFromMap->setInputCloud(laserCloudCornerFromMapDS); // 我的理解,后面的内容是作为输入的内容
            kdtreeSurfFromMap->setInputCloud(laserCloudSurfFromMapDS);

            // 设置的一个迭代次数 30
            for (int iterCount = 0; iterCount < 30; iterCount++)
            {
                // 每次迭代清空特征点集合
                // 当前帧与局部map匹配上了的角点,平面点,加入同一集合,后面是对应点的参数,这个参数重点关注
                laserCloudOri->clear();
                coeffSel->clear();

                // 当前激光帧角点寻找局部map匹配点 这个要看函数内部定义
                // 这里面有一个coeff 具体的计算公式可以点击这个函数的定义,还有la lb lc是什么含义需要解决
                cornerOptimization();
                // 当前激光帧平面点寻找局部map匹配点
                surfOptimization();
                // 经过上面两个以后 会得到角点和平面点的coeff 用在下面的LM优化中

                // 提取当前帧中与局部map匹配上了的角点、平面点,加入同一集合
                combineOptimizationCoeffs();

                // scan-to-map优化
                // 对匹配特征点计算雅克比矩阵,观测值为特征点到直线、平面的距离,构建高斯牛顿方程,迭代优化当前位姿,存transformTobeMapped
                if (LMOptimization(iterCount) == true)
                    break;              
            }
            transformUpdate();
        }
        else
        {
            ROS_WARN("Not enough features! Only %d edge and %d planar features available.", laserCloudCornerLastDSNum, laserCloudSurfLastDSNum);
        }
    }

    // 用imu原始RPY数据与scan-to-map优化后的位姿进行加权融合,更新当前帧位姿的roll pitch 约束z坐标
    void transformUpdate()
    {
        if (cloudInfo.imuAvailable == true)
        {
            // 俯仰角小于1.4
            if (std::abs(cloudInfo.imuPitchInit) < 1.4)
            {
                double imuWeight = 0.01;
                tf::Quaternion imuQuaternion;
                tf::Quaternion transformQuaternion;
                double rollMid, pitchMid, yawMid;

                // slerp roll
                // roll角求加权均值,用scan-to-map优化得到的位姿与imu原始RPY数据,进行加权平均
                transformQuaternion.setRPY(transformTobeMapped[0], 0, 0);
                imuQuaternion.setRPY(cloudInfo.imuRollInit, 0, 0);
                // slerp函数,利用求面差值得到介于两个已知旋转之间的近似值
                // 转到函数的定义,简要返回四元数,它是该四元数和另一个四元数之间的球面线性插值的结果
                tf::Matrix3x3(transformQuaternion.slerp(imuQuaternion, imuWeight)).getRPY(rollMid, pitchMid, yawMid);
                transformTobeMapped[0] = rollMid;

                // slerp pitch
                transformQuaternion.setRPY(0, transformTobeMapped[1], 0);
                imuQuaternion.setRPY(0, cloudInfo.imuPitchInit, 0);
                tf::Matrix3x3(transformQuaternion.slerp(imuQuaternion, imuWeight)).getRPY(rollMid, pitchMid, yawMid);
                transformTobeMapped[1] = pitchMid;
            }
        }

        transformTobeMapped[0] = constraintTransformation(transformTobeMapped[0], rotation_tollerance);
        transformTobeMapped[1] = constraintTransformation(transformTobeMapped[1], rotation_tollerance);
        transformTobeMapped[5] = constraintTransformation(transformTobeMapped[5], z_tollerance);
    }

这里就是涉及到了那4个优化的函数,里面涉及到了一些数学公式的计算,这个还没搞懂,然后coeff到底赋值了些啥,上面都写得很清楚,但是intensity部分的赋值还没看懂。前两个优化,就相当于把角点和平面点匹配到map上面,然后后两个的话就是迭代找最优的X解。这里在LM优化函数里面有雷达到相机 和 相机到雷达的转换关系,这个可以适当记一下。

    float constraintTransformation(float value, float limit)
    {
        if (value < -limit)
            value = -limit;
        if (value > limit)
            value = limit;

        return value;
    }

    bool saveFrame()
    {
        if (cloudKeyPoses3D->points.empty())
            return true;

        Eigen::Affine3f transStart = pclPointToAffine3f(cloudKeyPoses6D->back());
        Eigen::Affine3f transFinal = pcl::getTransformation(transformTobeMapped[3], transformTobeMapped[4], transformTobeMapped[5], 
                                                            transformTobeMapped[0], transformTobeMapped[1], transformTobeMapped[2]);
        Eigen::Affine3f transBetween = transStart.inverse() * transFinal;
        float x, y, z, roll, pitch, yaw;
        pcl::getTranslationAndEulerAngles(transBetween, x, y, z, roll, pitch, yaw);

        if (abs(roll)  < surroundingkeyframeAddingAngleThreshold &&
            abs(pitch) < surroundingkeyframeAddingAngleThreshold && 
            abs(yaw)   < surroundingkeyframeAddingAngleThreshold &&
            sqrt(x*x + y*y + z*z) < surroundingkeyframeAddingDistThreshold)
            return false;

        return true;
    }

没怎么看上面这两个函数,应该比较简单。

    // 激光里程计因子
    void addOdomFactor()
    {
        // 检查是否有关键帧点
        if (cloudKeyPoses3D->points.empty())
        {
            // 第一帧初始化先验因子
            noiseModel::Diagonal::shared_ptr priorNoise = noiseModel::Diagonal::Variances((Vector(6) << 1e-2, 1e-2, M_PI*M_PI, 1e8, 1e8, 1e8).finished()); // rad*rad, meter*meter
            gtSAMgraph.add(PriorFactor(0, trans2gtsamPose(transformTobeMapped), priorNoise));
            // 变量节点设置初始值
            initialEstimate.insert(0, trans2gtsamPose(transformTobeMapped));
        }
        else
        {
            // 添加激光里程计因子
            noiseModel::Diagonal::shared_ptr odometryNoise = noiseModel::Diagonal::Variances((Vector(6) << 1e-6, 1e-6, 1e-6, 1e-4, 1e-4, 1e-4).finished());
            gtsam::Pose3 poseFrom = pclPointTogtsamPose3(cloudKeyPoses6D->points.back());
            gtsam::Pose3 poseTo   = trans2gtsamPose(transformTobeMapped);
            // 添加关键帧最近的两个位姿
            // 参数:前一帧id,当前帧id,第一帧与当前帧的位姿变换(作为观测值),噪声协方差
            gtSAMgraph.add(BetweenFactor(cloudKeyPoses3D->size()-1, cloudKeyPoses3D->size(), poseFrom.between(poseTo), odometryNoise));
            // 变量节点设置初始值
            // 下标
            initialEstimate.insert(cloudKeyPoses3D->size(), poseTo);
            
            // if (isDegenerate)
            // {
            //     // 如果是退化的
            //     // adding VINS constraints is deleted as benefits are not obvious, disable for now
            //     // 添加VINS约束被删除,因为好处不明显,暂时禁用 
            //     gtSAMgraph.add(BetweenFactor(cloudKeyPoses3D->size()-1, cloudKeyPoses3D->size(), vinsPoseFrom.between(vinsPoseTo), odometryNoise));
            // }
        }
    }

    // 添加GPS因子
    void addGPSFactor()
    {
        // 一开始都要有鲁棒条件判断
        if (gpsQueue.empty())
            return; // 直接返回

        // wait for system initialized and settles down 等待系统初始化并稳定下来 
        if (cloudKeyPoses3D->points.empty())
            return;
        else if (pointDistance(cloudKeyPoses3D->front(), cloudKeyPoses3D->back()) < 5.0)
            return;

        // pose covariance small, no need to correct 位姿协方差小,无需校正
        // 这个4行4列 和 5行5列存储的内容 需要验证
        if (poseCovariance(3,3) < poseCovThreshold && poseCovariance(4,4) < poseCovThreshold)
            return;

        // last gps position
        static PointType lastGPSPoint;

        while (!gpsQueue.empty())
        {
            if (gpsQueue.front().header.stamp.toSec() < timeLaserInfoCur - 0.2)
            {
                // message too old 意思是时间间隔超过0.2,说明这个数据有点旧了,就pop出去,双端队列
                gpsQueue.pop_front();
            }
            else if (gpsQueue.front().header.stamp.toSec() > timeLaserInfoCur + 0.2)
            {
                // message too new
                break; // 直接结束while循环
            }
            else
            {
                nav_msgs::Odometry thisGPS = gpsQueue.front(); // 得到队首元素
                gpsQueue.pop_front();

                // GPS too noisy, skip
                float noise_x = thisGPS.pose.covariance[0];
                float noise_y = thisGPS.pose.covariance[7];
                float noise_z = thisGPS.pose.covariance[14];
                if (noise_x > gpsCovThreshold || noise_y > gpsCovThreshold) // 如果大于阈值,结束下面部分
                    continue;

                float gps_x = thisGPS.pose.pose.position.x;
                float gps_y = thisGPS.pose.pose.position.y;
                float gps_z = thisGPS.pose.pose.position.z;
                if (!useGpsElevation)
                {
                    gps_z = transformTobeMapped[5];
                    noise_z = 0.01;
                }

                // GPS not properly initialized (0,0,0) GPS未正确初始化 
                if (abs(gps_x) < 1e-6 && abs(gps_y) < 1e-6)
                    continue; // 跳过下面的内容

                // Add GPS every a few meters 每隔几米添加 GPS 
                PointType curGPSPoint;
                curGPSPoint.x = gps_x;
                curGPSPoint.y = gps_y;
                curGPSPoint.z = gps_z;
                if (pointDistance(curGPSPoint, lastGPSPoint) < 5.0)
                    continue; // 这个5,在前面出现过很多次,是作者团队自己定义的
                else
                    lastGPSPoint = curGPSPoint;

                gtsam::Vector Vector3(3);
                Vector3 << max(noise_x, 1.0f), max(noise_y, 1.0f), max(noise_z, 1.0f);
                noiseModel::Diagonal::shared_ptr gps_noise = noiseModel::Diagonal::Variances(Vector3); // 这个算方差的
                gtsam::GPSFactor gps_factor(cloudKeyPoses3D->size(), gtsam::Point3(gps_x, gps_y, gps_z), gps_noise);
                gtSAMgraph.add(gps_factor);

                aLoopIsClosed = true;

                break;
            }
        }
    }

    // 闭环因子
    void addLoopFactor()
    {
        // 常规鲁棒判断
        if (loopIndexQueue.empty())
            return;

        for (size_t i = 0; i < loopIndexQueue.size(); ++i)
        {
            // 添加相邻两个回环位姿
            int indexFrom = loopIndexQueue[i].first;
            int indexTo = loopIndexQueue[i].second;
            gtsam::Pose3 poseBetween = loopPoseQueue[i];
            gtsam::noiseModel::Diagonal::shared_ptr noiseBetween = loopNoiseQueue[i];
            // 添加到因子图中 添加的内容:两个index 一个pose 一个noise
            gtSAMgraph.add(BetweenFactor(indexFrom, indexTo, poseBetween, noiseBetween));
        }

        // 清空所有的回环数据队列
        loopIndexQueue.clear();
        loopPoseQueue.clear();
        loopNoiseQueue.clear();
        aLoopIsClosed = true;
    }

添加因子的函数,哲理的话我感觉他们添加因子就是使用了gtSAMgraph.add()函数,然后最后一个参数就是噪声协方差,当然这个协方差越小越好。GPS一开始是注释掉的,等接下来把注释去掉,看看会什么样子。然后是闭环因子,这里的参数是index index pose noise。这里aLoopIsClosed被赋值为true,是不是这个和加入闭环因子有关,或者想多了,就直接理解成表面意思,感觉也可以。

    // 因子图优化,更新所有关键帧位姿
    void saveKeyFramesAndFactor()
    {
        // 计算当前帧与前一帧位姿变换,如果变化太小,不设为关键帧,反之设为关键帧
        if (saveFrame() == false)
            return; // 直接返回

        // odom factor
        // 激光里程计因子
        addOdomFactor();

        // 这个gps因子没有加入,这个地方以后研究
        // gps factor
        //addGPSFactor();

        // loop factor
        // 闭环检测因子,这个需要VIS先看见,然后用LIS来进一步优化,论文中是这么说的
        addLoopFactor();

        // update iSAM
        // iSAM就是来优化的
        isam->update(gtSAMgraph, initialEstimate);
        isam->update();
        
        // update以后要清空一下保存的因子图,note!历史数据不会清掉,ISAM保存起来了
        gtSAMgraph.resize(0);
        initialEstimate.clear();

        // save key poses
        PointType thisPose3D;
        PointTypePose thisPose6D;
        Pose3 latestEstimate;

        // 优化结果
        isamCurrentEstimate = isam->calculateEstimate();
        latestEstimate = isamCurrentEstimate.at(isamCurrentEstimate.size()-1);
        cout << "****************************************************" << endl;
        isamCurrentEstimate.print("Current estimate: ");

        // cloudKeyPoses3D 加入当前帧位姿
        thisPose3D.x = latestEstimate.translation().x();
        thisPose3D.y = latestEstimate.translation().y();
        thisPose3D.z = latestEstimate.translation().z();
        thisPose3D.intensity = cloudKeyPoses3D->size(); // this can be used as index 这个强度不用来定位,而是用来分类,可以百度
        cloudKeyPoses3D->push_back(thisPose3D);

        // cloudKeyPoses6D 加入当前帧位姿
        // 6D中存有时间戳
        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().roll();
        thisPose6D.pitch = latestEstimate.rotation().pitch();
        thisPose6D.yaw   = latestEstimate.rotation().yaw();
        thisPose6D.time = timeLaserInfoCur;
        cloudKeyPoses6D->push_back(thisPose6D); // 加入到cloudKeyPoses6D中

        cout << "****************************************************" << endl;
        cout << "Pose covariance:" << endl;
        cout << isam->marginalCovariance(isamCurrentEstimate.size()-1) << endl << endl;
        //位姿协方差
        poseCovariance = isam->marginalCovariance(isamCurrentEstimate.size()-1); // 姿态协方差

        // save updated transform
        // transformTobeMapped中更新当前帧位姿,注意每个位置对应的RPY 和 xyz
        // 这个顺序比较特殊,需要留心
        transformTobeMapped[0] = latestEstimate.rotation().roll();
        transformTobeMapped[1] = latestEstimate.rotation().pitch();
        transformTobeMapped[2] = latestEstimate.rotation().yaw();
        transformTobeMapped[3] = latestEstimate.translation().x();
        transformTobeMapped[4] = latestEstimate.translation().y();
        transformTobeMapped[5] = latestEstimate.translation().z();

        // save all the received edge and surf points
        // 当前帧激光角点、平面点,降采样集合
        pcl::PointCloud::Ptr thisCornerKeyFrame(new pcl::PointCloud());
        pcl::PointCloud::Ptr thisSurfKeyFrame(new pcl::PointCloud());
        pcl::copyPointCloud(*laserCloudCornerLastDS,  *thisCornerKeyFrame); // 下面这两行,就一个复制
        pcl::copyPointCloud(*laserCloudSurfLastDS,    *thisSurfKeyFrame);

        // save key frame cloud
        // 保存特征点降采样集合
        cornerCloudKeyFrames.push_back(thisCornerKeyFrame);
        surfCloudKeyFrames.push_back(thisSurfKeyFrame);

        // save path for visualization
        // 更新里程计轨迹,将thisPose6D的位姿加入到/lidar/mapping/path中去
        updatePath(thisPose6D);
    }

这个没啥好说的,就是加因子的操作。

    // 更新因子图中所有变量节点的位姿,也就是所有历史关键帧的位姿,更新里程计轨迹
    void correctPoses()
    {
        // 日常鲁棒性判断
        if (cloudKeyPoses3D->points.empty())
            return;

        if (aLoopIsClosed == true)
        {
            // clear path
            // 清空里程计轨迹
            globalPath.poses.clear();

            // update key poses
            int numPoses = isamCurrentEstimate.size();
            // 更新因子图中所有变量节点的位姿,也就是所有历史关键帧的位姿
            for (int i = 0; i < numPoses; ++i)
            {
                // 这里3d表示xyz,6d表示xyz+rpy,代码一目了然
                cloudKeyPoses3D->points[i].x = isamCurrentEstimate.at(i).translation().x();
                // cloudKeyPoses3D->points[i].x = isamCurrentEstimate.at(i).translation().x();
                cloudKeyPoses3D->points[i].y = isamCurrentEstimate.at(i).translation().y();
                cloudKeyPoses3D->points[i].z = isamCurrentEstimate.at(i).translation().z();

                cloudKeyPoses6D->points[i].x = cloudKeyPoses3D->points[i].x;
                cloudKeyPoses6D->points[i].y = cloudKeyPoses3D->points[i].y;
                cloudKeyPoses6D->points[i].z = cloudKeyPoses3D->points[i].z;
                cloudKeyPoses6D->points[i].roll  = isamCurrentEstimate.at(i).rotation().roll();
                cloudKeyPoses6D->points[i].pitch = isamCurrentEstimate.at(i).rotation().pitch();
                cloudKeyPoses6D->points[i].yaw   = isamCurrentEstimate.at(i).rotation().yaw();

                // 将修正好的位姿逐一再添加到globalPath中去
                updatePath(cloudKeyPoses6D->points[i]);
            }

            aLoopIsClosed = false; // 这个含义没看懂
            // ID for reseting IMU pre-integration
            ++imuPreintegrationResetId; // 这个在imu预积分里面见过,好了,知道在这里增加值了
        }
    }

    // 发布激光里程计
    void publishOdometry()
    {
        // Publish odometry for ROS
        nav_msgs::Odometry laserOdometryROS;
        laserOdometryROS.header.stamp = timeLaserInfoStamp;
        laserOdometryROS.header.frame_id = "odom";
        laserOdometryROS.child_frame_id = "odom_mapping";
        // 猜测transformTobeMapped的123为rpy,事实论证是对的
        laserOdometryROS.pose.pose.position.x = transformTobeMapped[3];
        laserOdometryROS.pose.pose.position.y = transformTobeMapped[4];
        laserOdometryROS.pose.pose.position.z = transformTobeMapped[5];
        laserOdometryROS.pose.pose.orientation = tf::createQuaternionMsgFromRollPitchYaw(transformTobeMapped[0], transformTobeMapped[1], transformTobeMapped[2]); // 用四元数
        laserOdometryROS.pose.covariance[0] = double(imuPreintegrationResetId);
        pubOdomAftMappedROS.publish(laserOdometryROS);
        // Publish TF
        // 发布TF,odom->lidar
        static tf::TransformBroadcaster br;
        tf::Transform t_odom_to_lidar = tf::Transform(tf::createQuaternionFromRPY(transformTobeMapped[0], transformTobeMapped[1], transformTobeMapped[2]),
                                                      tf::Vector3(transformTobeMapped[3], transformTobeMapped[4], transformTobeMapped[5]));
        tf::StampedTransform trans_odom_to_lidar = tf::StampedTransform(t_odom_to_lidar, timeLaserInfoStamp, "odom", "lidar_link");
        br.sendTransform(trans_odom_to_lidar);
    }

    // 更新里程计轨迹,将thisPose6D的位姿加入到/lidar/mapping/path 中去
    // 更新路径(位置坐标 + 旋转四元数)
    void updatePath(const PointTypePose& pose_in)
    {
        geometry_msgs::PoseStamped pose_stamped;
        pose_stamped.header.stamp = ros::Time().fromSec(pose_in.time);
        pose_stamped.header.frame_id = "odom";
        pose_stamped.pose.position.x = pose_in.x;
        pose_stamped.pose.position.y = pose_in.y;
        pose_stamped.pose.position.z = pose_in.z;
        tf::Quaternion q = tf::createQuaternionFromRPY(pose_in.roll, pose_in.pitch, pose_in.yaw);
        pose_stamped.pose.orientation.x = q.x();
        pose_stamped.pose.orientation.y = q.y();
        pose_stamped.pose.orientation.z = q.z();
        pose_stamped.pose.orientation.w = q.w();

        globalPath.poses.push_back(pose_stamped);
    }

    //发布里程计、点云、轨迹
    void publishFrames()
    {
        if (cloudKeyPoses3D->points.empty())
            return;
        // publish key poses
        // 发布历史关键帧位姿集合
        publishCloud(&pubKeyPoses, cloudKeyPoses6D, timeLaserInfoStamp, "odom");
        // Publish surrounding key frames
        // 发布局部map的降采样特征点集合
        publishCloud(&pubRecentKeyFrames, laserCloudSurfFromMapDS, timeLaserInfoStamp, "odom");
        // publish registered key frame
        // 发布历史帧(累加的)的角点,平面点降采样集合
        if (pubRecentKeyFrame.getNumSubscribers() != 0)
        {
            pcl::PointCloud::Ptr cloudOut(new pcl::PointCloud());
            PointTypePose thisPose6D = trans2PointTypePose(transformTobeMapped);
            // transformPointCloud作用是返回输入点云乘以输入的transform
            *cloudOut += *transformPointCloud(laserCloudCornerLastDS,  &thisPose6D);
            *cloudOut += *transformPointCloud(laserCloudSurfLastDS,    &thisPose6D);
            publishCloud(&pubRecentKeyFrame, cloudOut, timeLaserInfoStamp, "odom");
        }
        // publish registered high-res raw cloud
        if (pubCloudRegisteredRaw.getNumSubscribers() != 0)
        {
            pcl::PointCloud::Ptr cloudOut(new pcl::PointCloud());
            pcl::fromROSMsg(cloudInfo.cloud_deskewed, *cloudOut);
            PointTypePose thisPose6D = trans2PointTypePose(transformTobeMapped);
            *cloudOut = *transformPointCloud(cloudOut,  &thisPose6D);
            publishCloud(&pubCloudRegisteredRaw, cloudOut, timeLaserInfoStamp, "odom");
        }
        // publish path
        // 发布路径
        if (pubPath.getNumSubscribers() != 0)
        {
            globalPath.header.stamp = timeLaserInfoStamp;
            globalPath.header.frame_id = "odom";
            pubPath.publish(globalPath);
        }
    }
};

这些也没啥好说的感觉,就直接看代码就可以。

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

    mapOptimization MO;

    ROS_INFO("\033[1;32m----> Lidar Map Optimization Started.\033[0m");
    
    // 这两个线程,重点看
    std::thread loopDetectionthread(&mapOptimization::loopClosureThread, &MO);
    std::thread visualizeMapThread(&mapOptimization::visualizeGlobalMapThread, &MO);

    ros::spin();

    // 线程加入到主线程
    loopDetectionthread.join();
    visualizeMapThread.join();

    return 0;
}

然后最后这个主函数,没啥好说的,前面差不多说过了,这里多了两个线程,一个是闭环的,另一个是可视化地图的,没啥好讲的,这两个线程还不是很熟悉,再读一遍代码的时候认真研究。然后spin(),然后线程的join操作,最后返回0.这个没啥好说的,本篇结束!

接下来的话,会进行视觉部分的代码解析。这个部分的话,问题好像有,但是忘记了,记得有个变量不清楚里面赋值是啥,但是后面读着读着就忘记这个变量是啥了。。。 好了 再读一遍的时候立刻解决问题!!!

你可能感兴趣的:(SLAM+计算机视觉,c++)