• aloam 代码阅读与总结


    Aloam

    • A-LOAMLOAM的高级实现,它使用 Eigen 和 Ceres Solver 来简化代码结构。 此代码由 LOAMLOAM_NOTEA 修改而来。 这段代码简洁明了,没有复杂的数学推导和冗余操作。 对于 SLAM 初学者来说是一本很好的学习资料。

    • 节点示意图:

      • 在这里插入图片描述
    • KITTI Example

      • 下载 KITTI Odometry dataset 数据集到 你数据集文件夹,设置 dataset_foldersequence_number 参数。
      • 请注意,您还可以通过在 kitti_helper.launch 中设置适当的参数来将 KITTI 数据集转换为包文件以便于使用。

    Summary

    优点:

    • 代码确实简洁了不少,如果有loam基础在看这个,确实一目了然。

    建议:

    1. 计算曲率时,每一线间的转变未考虑进去,不过不影响,因为提取特征时都跳过了每一线的起始多少个点

    2. 基于曲率大小筛选特征时,因为点云已经基于曲率排过序了

      1. 角点:if 判断曲率需大于0.1成立,否则直接break不好吗,一直遍历完不浪费时间
      2. 平面点:if 判断曲率需小于0.1成立,否则直接break不好吗,一直遍历完不浪费时间
    3. 找临近后确定 直线和平面时 感觉没lio-sam中精度高

    4. ceres优化,来个手动求导不好吗

    rosbag read_write

    • 读写rosbag 用到 bag 类,具体使用见下例子:
    // 定义读写对象,并申明
    rosbag::Bag i_bag, o_bag;
    i_bag.open(src_bag, rosbag::bagmode::Read);
    o_bag.open(new_bag, rosbag::bagmode::Write);
    
    // 定义一个数组
    std::vector<std::string> topics;
    topics.push_back(std::string(imu_topic));
    topics.push_back(std::string(pcd_topic));
    // 申明 rosbag::View对象
    rosbag::View view(i_bag, rosbag::TopicQuery(topics));
    
    ///<  读取方法一:
    for (auto m : view) {  // 遍历view
        sensor_msgs::Imu::ConstPtr imu = m.instantiate<sensor_msgs::Imu>();
        if (imu == nullptr) {  // 是否有imu
            std::cerr << "imu null " << std::endl;
        } else {
            std::cout << "imu stamp:" << imu->header.stamp << std::endl;
            o_bag.write(imu_topic, imu->header.stamp, imu);
        }
        sensor_msgs::PointCloud2::ConstPtr pcd =
            m.instantiate<sensor_msgs::PointCloud2>();
        if (pcd == nullptr) {
            std::cerr << "pcd null " << std::endl;
        } else {
            std::cout << "pcd stamp:" << pcd->header.stamp << std::endl;
            o_bag.write(pcd2_topic, pcd->header.stamp, pcd);
        }
    }
    
    ///< 读取方法二:
    rosbag::View view(bag);
    for (const rosbag::ConnectionInfo* c : view.getConnections()) {
    
        const std::string& topic = c->topic;
        if (topic_to_publisher.count(topic) == 0) {
            ros::AdvertiseOptions options(c->topic, kQueueSize, c->md5sum,
                                          c->datatype, c->msg_def);
            topic_to_publisher[topic] = node_handle.advertise(options);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    scanRegistration

    主函数:

    • Ros节点
      • ros 节点初始化 scanRegistration
      • 读取参数:scan_lineminimum_range
    • 只支持 16,32,64 线的 velodyne,不对则 return
    • 订阅话题:
      • /velodyne_points laserCloudHandler
    • 发布话题:
      • sensor_msgs::PointCloud2>("/velodyne_cloud_2", 100);
      • sensor_msgs::PointCloud2>("/laser_cloud_sharp", 100);
      • sensor_msgs::PointCloud2>("/laser_cloud_less_sharp", 100);
      • sensor_msgs::PointCloud2>("/laser_cloud_flat", 100);
      • sensor_msgs::PointCloud2>("/laser_cloud_less_flat", 100);
      • sensor_msgs::PointCloud2>("/laser_remove_points", 100);
    • 基于参数 确定是否发布每条线 正常不执行

    laserCloudHandler

    • 上来一个系统初始化,就是单纯跳过多少帧
    • 点云转 pcl格式,pcl::removeNaNFromPointCloud
    • 移除最近点,removeClosedPointCloud
      • 遍历每个点,求 点到 激光的距离,若距离小于阈值,则 continue
      • 否则将点放入新一个容器中
    • 起始角度计算:
      • 开始角:startOri = -atan2(pointp[0].y,pointp[0].x)
      • 结束角:endOri = -atan2(pointp[size-1].y,pointp[size-1].x)+2π
      • 若 endOri-startOri > 3π endOri -= 2π
      • 若 endOri-startOri < 3π endOri += 2π
    • 计算 scanID + relTime
      • scanID 哪一线,好计算
      • relTime 与初始点的时间差 算与初始点的水平角*周期/总角度
    • 计算每个点的曲率
      • 跟 loam计算一样
      • 每一线间的转变未考虑进去,不过不影响,因为提取特征时都跳过了每一线的起始多少个点
    • 遍历 多线N_SCANS,对于每一线,将点云均分成6份:
      • 计算每一份的起始下标: sp,ep
      • 将每一份的点云按曲率从小到大排序 std::sort 函数
      • 找角点,反向遍历已排好序的点云
        • 若曲率大于 0.1 且 周围点可以为特征点 时:
          • 找曲率最大的2 点放入 cornerPointsSharp 点云标记:2
          • 找曲率 最大的20个放入 cornerPointsLessSharp 点云标记:1
          • 若该点标为特征点时, 周围的5个点不可以为特征点
      • 找平面点,正向遍历已排好序的点云
        • 若曲率小于 0.1 且 周围点可以为特征点 时:
          • 找曲率最大的4 点放入 surfPointsFlat 点云标记:-1
          • 若该点标为特征点时, 周围的5个点不可以为特征点
      • 若 标记为-1 则放入 surfPointsLessFlatScan

    laserOdometry

    主函数:

    • Ros节点
      • ros 节点初始化 laserOdometry
      • 读取参数:mapping_skip_frame 默认2
    • 订阅话题:
      • /velodyne_cloud_2 laserCloudFullResHandler
        • 这个点云就是去了最近点和 nan的
        • 这个回调也比较简单,将 数据放入队列中
      • 四种特征,角点,角点less,平面点,平面点less
        • 这四个回调都比较简单,就把数据放入队列中而已
    • 发布:
      • 角点 和 平面点
      • velodyne_cloud_3
      • laser_odom_to_init
      • laser_odom_path
    • while 主线程,ros::spin0nce,100hz
      • 上面5种数据都到 且 取第一个数据时间都一致时
        • 当5种数据都到后,时间肯定一致,不一致ros报错
      • 5种数据进行 pcl数据转换
      • 若 systemInited== flase时:
        • 直接赋值 systemInited=true
      • 否则,优化两次,对于每一次都执行下列操作:
        • 优化变量为 位姿 para_q,para_t,4+3个自由度
        • 遍历角点特征:
          • 将角点转换到里程计坐标系,TransformToStart
          • 通过kdTree找到 直线
            • 通过kdTree 找到上一帧中的最近角点下标 pointSearchInd
            • 该点下标 上下两range中最近的一个点作为 另一个点
            • 两点构成一条直线
          • 通过 LidarEdgeFactor 构造ceres直接约束
          • 并添加残差
        • 遍历平面特征:
          • 将平面点转换到里程计坐标系,TransformToStart
          • 通过kdTree找到 平面
            • 通过kdTree 找到上一帧中的最近角点下标 pointSearchInd
            • 该点下标 上下两range中最近的一个点作为 另一个点
            • 同一ring中 往前伸一个 平面特征作为 第三个点
          • 通过 LidarPlaneFactor 构造ceres直接约束
          • 并添加残差
      • 构建求解器:迭代4次,QR分解,得到解
      • 更新 q_w_currt_w_curr
      • 更新 上一帧 角点和平面点kdTree

    TransformToStart

    • 记录了上一次里程计坐标系的关系,直接按上次的关系进行转换
    • 即认为 当前帧初值与上一帧初值一样,方便计算

    LidarEdgeFactor

    • 点到直线的距离,自动求导

    LidarPlaneFactor

    • 点到平安的距离,自动求导

    laserMapping

    主函数:

    • ros节点初始化
      • 定义节点 laserMapping
      • 读取参数:
        • mapping_line_resolution 0.4
        • mapping_plane_resolution 0.8
    • 订阅话题:
      • /laser_cloud_corner_last laserCloudCornerLastHandler
      • /laser_cloud_surf_last laserCloudSurfLastHandler
      • /laser_odom_to_init laserOdometryHandler
      • /velodyne_cloud_3 laserCloudFullResHandler
    • 发布话题:
    • 主线程 mapping_process process

    callback

    • 3个激光回调都将数据放入各自的队列中,有互斥锁
    • 激光里程计回调除了将数据放入队列中后,还基于里程计漂移将数据从世界坐标系发出

    process

    while 1循环,2s执行一次

    • 数据同步,并转换成pcl格式
      • 若 3种点云数据 和 里程计数据都不为空时:
        • 若某一种据比cornerLastBuf.front() 数据早时,pop
      • 取各个数据的第一个数据,应该 这四种数据的时间一致
        • 同一节点发的,且四种数据的时间戳给的一样
      • 将3种点云数据通过pcl 转换为各自对象,并从队列中pop_front
      • 若 cornerLastBuf非空时, pop其数据,保证实时性
    • 得到当前帧的世界坐标系位姿 transformAssociateToMap()
      • L W T = O W T ∗ L O T { _L^WT=_O^WT*_L^OT} LWT=OWTLOT
    • // 明天补上,开会了
  • 相关阅读:
    Redis初步学习
    大火的4D Radar数据集及基线模型汇总
    力扣每日一题:最接近目标价格的甜点成【dfs 暴力搜索】
    k8s之图形界面DashBoard【九】
    NOIP2023模拟2联测23 害怕
    QtCreator配置代码字体和颜色
    SRS视频服务器-docker部署srs4.0:带SRT功能
    定时清理7天前的Tomcat日志脚本
    阿里P9大牛徒手编写的这份十亿级并发手册,教你彻底玩懂高并发,赶紧收藏
    递推+模拟---想好如何存储?
  • 原文地址:https://blog.csdn.net/xiaoma_bk/article/details/125547920