自学内容网 自学内容网

《自动驾驶与机器人中的SLAM技术》ch10:自动驾驶车辆的实时定位系统

        本章,我们来关注实时的激光雷达定位系统。在点云地图基础之上,我们可以把当前激光扫描数据与地图进行匹配,从而获得车辆自身的位置,再与 IMU 等传感器进行滤波器融合。然而,点云定位并不像 RTK 那样可以直接给出物理世界坐标,而必须先给出一个大致的位置点,再引导点云配准算法收敛。因此,点云定位在实际使用时,会遇到一些特有逻辑问题。本章将使用第 9 章构建的点云地图,展示点云定位的使用方法,并演示一个基于 ESKF 的实时定位方案。

        1 点云融合定位的设计方案

        从融合手段上来看,点云融合定位可以与传统组合导航一样,使用 ESKF 进行融合;也可以像现有的 SLAM 系统一样,使用位姿图优化进行融合。

  • ①卡尔曼方案从设计到实现都比较简单,其结果往往比较光滑,但可能收敛到错误的解,导致定位跑偏,而且不容易被修正回来。
  • ②图优化方法容易检查各种因子与定位状态的偏差,从而在逻辑层面处理各种异常情况,但其定位平滑性不容易保障(除非我们和第 8 章一样进行手动的边缘化处理,或者使用 GTSAM 这种自带边缘化的优化库)。

        点云融合定位算法的框图如下所示。本章将演示基于 ESKF 的定位方案。由于卡尔曼滤波器原理已经在第 3 章中展开介绍过了,本章重点关注点云定位与卡尔曼滤波器的融合部分。点云定位需要一个预测的车辆位置作为搜索的起点,因此设计了一个初始化流程。当滤波器尚未计算出自身位置时,利用第一个有效 RTK 信号来控制点云定位的搜索范围。又由于 NCLT 数据集中的 RTK 并不含有姿态信息,我们需要通过网格搜索来确定车辆的朝向。当卡尔曼滤波器收敛以后,我们再通过滤波器的预测值来作为点云定位的初值进行配准。

        2 点云融合定位的流程

  • 1. 将 ROS 包中的 RTK 数据提取出来,用第一个有效的 RTK 数据减去地图原点(建图时第一个有效的 RTK 位姿,定位时从配置文件中加载即可)后加载点云地图,并使用该 RTK 数据进行初始化网格搜索确定车辆在点云地图中的初始位姿。确定位姿后使用该位姿初始化 ESKF 状态
  • 2. 使用 IMU 数据开始 ESKF 的预测过程,和 《自动驾驶与机器人中的SLAM技术》ch3:惯性导航与组合导航 中一样。
  • 3.先使用 ESKF 预测得到的位姿加载点云地图,将当前 LIDAR 数据作为 scan 和 点云地图进行 scan to map 的 NDT 配准(使用 PCL 库中的 NDT 配准函数),迭代后得到优化位姿。使用该优化位姿进行 ESKF 的更新过程。

void Fusion::ProcessMeasurements(const MeasureGroup& meas) {
    measures_ = meas;

    if (imu_need_init_) {
        TryInitIMU();
        return;
    }

    /// 以下三步与LIO一致,只是align完成地图匹配工作
    if (status_ == Status::WORKING) {
        Predict();
        Undistort();
    } else {
        scan_undistort_ = measures_.lidar_;
    }

    Align();
}

void Fusion::Align() {
    FullCloudPtr scan_undistort_trans(new FullPointCloudType);
    pcl::transformPointCloud(*scan_undistort_, *scan_undistort_trans, TIL_.matrix());
    scan_undistort_ = scan_undistort_trans;
    current_scan_ = ConvertToCloud<FullPointType>(scan_undistort_);
    current_scan_ = VoxelCloud(current_scan_, 0.5);

    if (status_ == Status::WAITING_FOR_RTK) {
        // 若存在最近的RTK信号,则尝试初始化
        if (last_gnss_ != nullptr) {
            if (SearchRTK()) {
                status_ == Status::WORKING;
                ui_->UpdateScan(current_scan_, eskf_.GetNominalSE3());
                ui_->UpdateNavState(eskf_.GetNominalState());
            }
        }
    } else {
        LidarLocalization();
        ui_->UpdateScan(current_scan_, eskf_.GetNominalSE3());
        ui_->UpdateNavState(eskf_.GetNominalState());
    }
}

        3 算法实现

        3.1 RTK 初始搜索

        1. 将 ROS 包中的 RTK 数据提取出来,用第一个有效的 RTK 数据减去地图原点(建图时第一个有效的 RTK 位姿,定位时从配置文件中加载即可)后加载点云地图,并使用该 RTK 数据进行初始化网格搜索确定车辆在点云地图中的初始位姿。确定位姿后使用该位姿初始化 ESKF 状态

        网格搜索的结构:

    /// 网格搜索时的结构
    struct GridSearchResult {
        SE3 pose_;           // 按 10 度递增的网格搜索的初始位姿
        SE3 result_pose_;    // NDT 配准优化后的位姿
        double score_ = 0.0; // NDT 配准得分
    };

        由于 RTK 不带角度,按 10 度的固定步长进行 360 度搜索,需要搜索 36 次。将按 10 度递增的网格搜索的初始位姿送入 AlignForGrid() 函数中进行多分辨率 NDT 配准(10.0, 5.0, 4.0, 3.0。map 按照 NDT 尺寸乘以 0.1 降采样,scan 不额外进行降采样),求解配准后的位姿及其对应的配准得分,如果最大配准得分大于阈值(4.5)则认为获得了正确的初始位姿。 确定位姿后使用该位姿初始化 ESKF 状态。

bool Fusion::SearchRTK() {
    if (init_has_failed_) {
        if ((last_gnss_->utm_pose_.translation() - last_searched_pos_.translation()).norm() < 20.0) {
            LOG(INFO) << "skip this position";
            return false;
        }
    }

    // 由于RTK不带姿态,我们必须先搜索一定的角度范围
    std::vector<GridSearchResult> search_poses;
    LoadMap(last_gnss_->utm_pose_);

    /// 由于RTK不带角度,这里按固定步长扫描RTK角度
    double grid_ang_range = 360.0, grid_ang_step = 10;  // 角度搜索范围与步长
    for (double ang = 0; ang < grid_ang_range; ang += grid_ang_step) {
        SE3 pose(SO3::rotZ(ang * math::kDEG2RAD), Vec3d(0, 0, 0) + last_gnss_->utm_pose_.translation());
        GridSearchResult gr;
        gr.pose_ = pose;
        search_poses.emplace_back(gr);
    }

    LOG(INFO) << "grid search poses: " << search_poses.size();
    std::for_each(std::execution::par_unseq, search_poses.begin(), search_poses.end(),
                  [this](GridSearchResult& gr) { AlignForGrid(gr); });

    // 选择最优的匹配结果
    auto max_ele = std::max_element(search_poses.begin(), search_poses.end(),
                                    [](const auto& g1, const auto& g2) { return g1.score_ < g2.score_; });
    LOG(INFO) << "max score: " << max_ele->score_ << ", pose: \n" << max_ele->result_pose_.matrix();
    if (max_ele->score_ > rtk_search_min_score_) {
        LOG(INFO) << "初始化成功, score: " << max_ele->score_ << ">" << rtk_search_min_score_;
        status_ = Status::WORKING;

        /// 重置滤波器状态
        auto state = eskf_.GetNominalState();
        state.R_ = max_ele->result_pose_.so3();
        state.p_ = max_ele->result_pose_.translation();
        state.v_.setZero();
        eskf_.SetX(state, eskf_.GetGravity());

        ESKFD::Mat18T cov;
        cov = ESKFD::Mat18T::Identity() * 1e-4;
        cov.block<12, 12>(6, 6) = Eigen::Matrix<double, 12, 12>::Identity() * 1e-6;
        eskf_.SetCov(cov);

        return true;
    }

    init_has_failed_ = true;
    last_searched_pos_ = last_gnss_->utm_pose_;
    return false;
}

void Fusion::AlignForGrid(sad::Fusion::GridSearchResult& gr) {
    /// 多分辨率
    pcl::NormalDistributionsTransform<PointType, PointType> ndt;
    ndt.setTransformationEpsilon(0.05);
    ndt.setStepSize(0.7);
    ndt.setMaximumIterations(40);

    ndt.setInputSource(current_scan_);
    auto map = ref_cloud_;

    CloudPtr output(new PointCloudType);
    std::vector<double> res{10.0, 5.0, 4.0, 3.0};
    Mat4f T = gr.pose_.matrix().cast<float>();
    for (auto& r : res) {
        auto rough_map = VoxelCloud(map, r * 0.1);
        ndt.setInputTarget(rough_map);
        ndt.setResolution(r);
        ndt.align(*output, T);
        T = ndt.getFinalTransformation();
    }

    gr.score_ = ndt.getTransformationProbability();
    gr.result_pose_ = Mat4ToSE3(ndt.getFinalTransformation());
}

        3.2 使用 IMU 数据进行 ESKF 的预测过程

         2.使用 IMU 数据开始 ESKF 的预测过程,和 《自动驾驶与机器人中的SLAM技术》ch3:惯性导航与组合导航 中一样。

void Fusion::Predict() {
    imu_states_.clear();
    imu_states_.emplace_back(eskf_.GetNominalState());

    /// 对IMU状态进行预测
    for (auto& imu : measures_.imu_) {
        eskf_.Predict(*imu);
        imu_states_.emplace_back(eskf_.GetNominalState());
    }
}

        3.3 使用 LIDAR 数据 NDT 配准位姿进行 ESKF 的更新过程 

        3.先使用 ESKF 预测得到的位姿加载点云地图,将当前 LIDAR 数据作为 scan 和 点云地图进行 scan to map 的 NDT 配准(使用 PCL 库中的 NDT 配准函数),迭代后得到优化位姿。使用该优化位姿进行 ESKF 的更新过程。       

bool Fusion::LidarLocalization() {
    SE3 pred = eskf_.GetNominalSE3();
    LoadMap(pred);

    ndt_.setInputCloud(current_scan_);
    CloudPtr output(new PointCloudType);
    ndt_.align(*output, pred.matrix().cast<float>());

    SE3 pose = Mat4ToSE3(ndt_.getFinalTransformation());
    eskf_.ObserveSE3(pose, 1e-1, 1e-2);

    LOG(INFO) << "lidar loc score: " << ndt_.getTransformationProbability();

    return true;
}

         3.4 点云地图的加载和卸载

        在定位过程中,初始化阶段在 Fusion::Init() 函数中调用 LoadMapIndex() 函数加载了点云地图的索引文件。后续在 2 个地方调用了 Fusion::LoadMap() 函数加载和卸载点云地图。分别在 RTK 初始搜索函数 Fusion::SearchRTK() 和 ESKF 更新函数 Fusion::LidarLocalization() 中

        点云定位层面,我们使用第 9 章切分好的点云进行定位。第 9 章中,我们把点云按边长 100米的范围进行了网格划分,所以本章控制点云地图的载入范围为车辆周边 9 格(如下图所示)。同时,为了防止车辆在某个区块边缘处运动导致频繁加载与卸载,我们也设定一个卸载范围,卸载范围稍大于加载范围,卸载范围取和当前测量距离大于,超出该范围的点云才会被卸载。

        代码实现如下所示。这里使用了 std::set<Vec2i, less_vec<2>> 存储地图索引、使用std::map<Vec2i, CloudPtr, less_vec<2>> 存储地图数据。即使用 C++ STL 容器中的 std::setstd::map 来管理和加载地图数据。

 

void Fusion::LoadMap(const SE3& pose) {
    int gx = floor((pose.translation().x() - 50.0) / 100);
    int gy = floor((pose.translation().y() - 50.0) / 100);
    Vec2i key(gx, gy);

    // 一个区域的周边地图,我们认为9个就够了
    std::set<Vec2i, less_vec<2>> surrounding_index{
        key + Vec2i(0, 0), key + Vec2i(-1, 0), key + Vec2i(-1, -1), key + Vec2i(-1, 1), key + Vec2i(0, -1),
        key + Vec2i(0, 1), key + Vec2i(1, 0),  key + Vec2i(1, -1),  key + Vec2i(1, 1),
    };

    // 加载必要区域
    bool map_data_changed = false;
    int cnt_new_loaded = 0, cnt_unload = 0;
    for (auto& k : surrounding_index) {
        if (map_data_index_.find(k) == map_data_index_.end()) {
            // 该地图数据不存在
            continue;
        }

        if (map_data_.find(k) == map_data_.end()) {
            // 加载这个区块
            CloudPtr cloud(new PointCloudType);
            pcl::io::loadPCDFile(data_path_ + std::to_string(k[0]) + "_" + std::to_string(k[1]) + ".pcd", *cloud);
            map_data_.emplace(k, cloud);
            map_data_changed = true;
            cnt_new_loaded++;
        }
    }

    // 卸载不需要的区域,这个稍微加大一点,不需要频繁卸载
    for (auto iter = map_data_.begin(); iter != map_data_.end();) {
        if ((iter->first - key).cast<float>().norm() > 3.0) {
            // 卸载本区块
            iter = map_data_.erase(iter);
            cnt_unload++;
            map_data_changed = true;
        } else {
            iter++;
        }
    }

    LOG(INFO) << "new loaded: " << cnt_new_loaded << ", unload: " << cnt_unload;
    if (map_data_changed) {
        // rebuild ndt target map
        ref_cloud_.reset(new PointCloudType);
        for (auto& mp : map_data_) {
            *ref_cloud_ += *mp.second;
        }

        LOG(INFO) << "rebuild global cloud, grids: " << map_data_.size();
        ndt_.setInputTarget(ref_cloud_);
    }

    ui_->UpdatePointCloudGlobal(map_data_);
}

        4 算法结果

        点云定位通常比 RTK 定位具有更好的鲁棒性。只要点云地图本身建的足够准确,在大部分地图区域内它都可以正常工作,定位系统不必依赖室外 RTK 信号的好坏。

        在测试时,可以使用 4 月份的数据进行建图,然后用 5 月、6 月的数据测试定位,这样可以包含一部分动态物体的情况。

wu@WP:~/slam_in_autonomous_driving/bin$ ./run_fusion_offline

I0114 22:58:41.102108 436026 cloud_convert.cc:193] Using Velodyne 32 Lidar
I0114 22:59:04.802245 436026 io_utils.cc:77] running in /home/wu/slam_in_autonomous_driving/data/nclt/20130110.bag, reg process func: 3
Failed to find match for field 'intensity'.
Failed to find match for field 'intensity'.
Failed to find match for field 'intensity'.
Failed to find match for field 'intensity'.
I0114 22:59:05.155918 436026 static_imu_init.cc:86] mean acce: -0.133286 0.0166332 009.80364
I0114 22:59:05.155944 436026 static_imu_init.cc:109] IMU 初始化成功,初始化时间= 9.9985, bg = 00.00159973 0000.003007 -0.00239681, ba = 07.40016e-05 -9.23492e-06 0-0.00544309, gyro sq = 00.00067222 3.29092e-06 3.63085e-05, acce sq = 7.68385e-06 00.00197847 1.57072e-06, grav = 0000.13336 -0.0166424 00-9.80908, norm: 9.81
I0114 22:59:05.155958 436026 static_imu_init.cc:113] mean gyro: 00.00159973 0000.003007 -0.00239681 acce: 07.40016e-05 -9.23492e-06 0-0.00544309
imu try init true time:1357847247.28515291
I0114 22:59:05.155977 436026 fusion.cc:88] IMU初始化成功
Failed to find match for field 'intensity'.
I0114 22:59:06.284561 436026 fusion.cc:287] new loaded: 7, unload: 0
I0114 22:59:06.293992 436026 fusion.cc:295] rebuild global cloud, grids: 7
I0114 22:59:06.323706 436026 fusion.cc:172] grid search poses: 36
I0114 22:59:07.356271 436026 fusion.cc:179] max score: 9.63038, pose: 
00-0.989445 0000.141687 0-0.0303793 00-0.941667
00-0.141768 0000-0.9899 0.000527289 0000.593236
0-0.0299978 00.00482854 0000.999538 0000.682519
00000000000 00000000000 00000000000 00000000001
I0114 22:59:07.356317 436026 fusion.cc:181] 初始化成功, score: 9.63038>4.5
Failed to find match for field 'intensity'.
---IMU 预测
I0114 22:59:07.377723 436026 eskf.hpp:233] skip this imu because dt_ = 1.35785e+09
---IMU 预测
---IMU 预测
---IMU 预测
---IMU 预测
---IMU 预测
---IMU 预测
---IMU 预测
---IMU 预测
---IMU 预测
I0114 22:59:07.378505 436026 fusion.cc:287] new loaded: 0, unload: 0
---SE3 观测
I0114 22:59:07.442410 436026 fusion.cc:240] lidar loc score: 2.03312
Failed to find match for field 'intensity'.
---IMU 预测
---IMU 预测
---IMU 预测
---IMU 预测
---IMU 预测
---IMU 预测
---IMU 预测
---IMU 预测
---IMU 预测
---IMU 预测
I0114 22:59:07.443528 436026 fusion.cc:287] new loaded: 0, unload: 0
---SE3 观测
I0114 22:59:07.449277 436026 fusion.cc:240] lidar loc score: 2.18825

 

        5 本章小结

        本章以第 9 章的点云建图结果为基础,演示了如何在已有激光点云地图的基础上,进行融合定位的案例。点云定位的几个优势和劣势可以概括如下:
1. 点云定位结果与场景结构相关,而与天气、遮挡关系无关,通常比 RTK 具有更好的环境适应性。点云定位既可以用于室外,也可以用于室内,没有太多限制因素。
2. 点云定位结果可以和 RTK 一样,融入 ESKF 滤波器中,也可以像第 4 章那样组成一个图优化系统。
3. 如果在较大的场景中使用,应该先对地图点云切分,每次加载周围一小块区域用于定位。
4. 与 RTK 不同的时,点云定位需要事先指定一个大致的位置,而不是像 RTK 那直接给出定位结果。如果初始位置精度不够,我们还需要对附近的位置和角度进行网格搜索,才能确定自身位置。
5. 点云定位对动态场景有一定的适应性。只要主体结构(通常是建筑物)变化不大,一些小型的动态物体(人群、车辆等等)通常不会对点云定位产生很大的干扰。
6. 如果定位场景属于开阔场地,我们还可以对点云地图进行压缩,形成 2.5D 地图 。这种压缩之后的地图可以大幅减少所需的存储空间,对自动驾驶应用更加友好。     


原文地址:https://blog.csdn.net/m0_49384824/article/details/145144611

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!