apollo自动驾驶-感知-点云地面检测:pointcloud_ground_detection

功能介绍

该组件是检测打到地面的激光点云,将上一步过滤得到的ROI点云的基础上,再细分为地面点和非地面点两部分。

虽然代码有些难读,其实整个实现过程并不难。以下针对实现的细节做个较为详细的介绍。
1)首先将在感兴趣的距离(80m)范围内等距离划分出粗分(256)方格和细分(32)方格;如下图所示,粗分方格中包含细分方格。
请添加图片描述
2)将ROI点云根据实际位置分发到不同的方格中,同一个激光点,在粗分方格和细分方格都有对应的位置。为增加点云的处理速度,这里使用了SSE2指令集。
3)地面候选点云过滤。以细分方格为单位进行地面过滤,把细分方格的过滤结果存储在隶属于同一粗分方格的方格内。细分方格地面点云过滤时,满足以下条件的点云可作为地面的候选点:

  • 距离范围在激光雷达80m内;
  • 低于激光雷达1m到3m的点云,此范围的点云会有分布特性(注意这个条件需要根据激光雷达的高度设置);
  • 体素方格中的点与此分布范围的点高度值相差太大,也不能将该点加入候选点。

4)通过ransac算法,以粗分方格为单位,对每个粗分方格拟合平面。ransac算法主要针对噪声较大的场景,用在这里比较合适。这里分为几小步:

  • 获取本粗分体素周围8个相邻粗分体素方格的索引,并滤除体素中的z值超出相邻体素z值的均值的±1.0的点云;对于体素方格中含有较多的候选点的体素方格,需要执行下采样。
  • 对每个粗分体素方格,随机抽取3个候选点,求取这3个点决定的平面,再求取候选点到此平面的距离小于设定阈值(位于0.1~0.2)的点的个数。循环执行48次,存储每次的结果,作为候选平面。另外还需要将相邻8个平面的拟合结果也加入候选平面。
  • 从这56个候选平面中,选取候选点到此平面的距离小于设定阈值(位于0.1~0.2)的点的个数最多的平面作为最优平面。如果候选点一样,则选择与相邻平面夹角最小的作为拟合结果。
  • 针对最优平面附近的候选点(位于0.1~0.2范围),使用最小二乘法,重新拟合成一个平面,最为最终的拟合结果。
  • 拟合后的平面需要与相邻平面做平滑处理,针对没有被拟合到的体素方格,需要利用前后左右四个方格做平滑计算,针对被拟合到的体素方格,会连同前后左右四个方格一同做平滑计算。平滑过程用到欧几里得坐标系与球体坐标系之间的转换。
  • 针对含有拟合平面的粗分体素方格,求取该体素方格内每个激光点相对于拟合平面的高度;如果粗分体素方格没有拟合平面(候选点太少或拟合失败),则置高度值为一个很大的数。

5)配置文件中设定一个阈值0.25,按照上一步求取的粗分体素方格的每个激光点的高度值,低于该阈值的认为是地面点,否则认为是非地面点。

核心代码解读

若您想了解更多关于此模块的内容,请参考上方与该文章配套的资源,里面含有源码详细的注释和打印,以方便大家理解 。

如何实现将点云分配到不同的体素方格中

以细分体素方格为例,下面的函数为整个实现的过程。为了处理快速,使用了SSE2指令集。求取激光点云在x和y方向的方格索引后,以y为列,x为行展开成一维索引存储在voxels_容器中。

bool VoxelGridXY<T>::SetS(const float *data, unsigned int nr_points, unsigned int nr_point_element) {
    if (!data || !nr_points || !nr_point_element || !nr_voxel_x_ || !nr_voxel_y_ || nr_voxel_z_ != 1) {
        initialized_ = false;
        return initialized_;
    }

    data_ = data;
    nr_points_ = nr_points;
    nr_point_element_ = nr_point_element;
    // 以细分体素网格为例说明
    // nr_voxel_x_和nr_voxel_y_,对应配置文件中的 big_grid_size: 256
    // 分隔成256*256个方格
    unsigned int nr_voxel = (nr_voxel_x_ * nr_voxel_y_);
    unsigned int i, n = 0;
    int id;

    // 细分步长的倒数
    float voxel_width_x_rec = IRec(static_cast<float>(voxel_dim_[0]));
    float voxel_width_y_rec = IRec(static_cast<float>(voxel_dim_[1]));
    // SSE2(Streaming SIMD Extensions 2)指令集
    // (256,0,256,0)
    __m128i iv_nr_voxel_x = _mm_setr_epi32(static_cast<int>(nr_voxel_x_), 0, static_cast<int>(nr_voxel_x_), 0);
    // 四个值都设置为255
    __m128i iv_nr_voxel_x_m1 = _mm_set1_epi32(static_cast<int>(nr_voxel_x_ - 1));
    __m128i iv_nr_voxel_y_m1 = _mm_set1_epi32(static_cast<int>(nr_voxel_y_ - 1));

    // 四个值都设置为细分步长的倒数
    __m128 v_width_x_rec = _mm_set_ps1(voxel_width_x_rec);
    __m128 v_width_y_rec = _mm_set_ps1(voxel_width_y_rec);

    // 设置感兴趣的距离范围,配置文件中是80m
    __m128 v_x_min = _mm_set_ps1(dim_x_[0]);
    __m128 v_x_max = _mm_set_ps1(dim_x_[1]);
    __m128 v_y_min = _mm_set_ps1(dim_y_[0]);
    __m128 v_y_max = _mm_set_ps1(dim_y_[1]);
    __m128 v_z_min = _mm_set_ps1(dim_z_[0]);
    __m128 v_z_max = _mm_set_ps1(dim_z_[1]);

    __m128 v_cmp_x, v_cmp_y, v_cmp_z, v_in_roi;
    v_in_roi = _mm_setr_ps(1.0, 1.0, 1.0, 1.0);

    __m128 v_xs, v_ys, v_zs;
    __m128i iv_indices, iv_x_indices, iv_y_indices, iv_v_indices_02, iv_v_indices_13;

    for (i = 0; i < nr_voxel; ++i) {
        voxels_[i].indices_.clear();
    }

    unsigned int nr_loops = (nr_points >> 2);
    unsigned int nr_fast_processed = (nr_loops << 2);
    // 求未处理的剩余的点云
    unsigned int remainder = nr_points - nr_fast_processed;

    // d1=3;d2=6;d3=9;d4=12
    unsigned int d1 = nr_point_element;
    unsigned int d2 = (nr_point_element << 1);
    unsigned int d3 = d1 + d2;
    unsigned int d4 = (nr_point_element << 2);

    // xyz are required to be stored in continuous memory
    // 点云数据,分别提取的是xyz坐标
    const float *cptr_x = data;
    const float *cptr_y = data + 1;
    const float *cptr_z = data + 2;
    const float *cptr_remainder = data + (nr_fast_processed * nr_point_element);

    for (i = 0; i < nr_loops; ++i, n += 4) {
        // Set memory
        // 提取的是连续4个点x坐标
        v_xs = _mm_setr_ps(cptr_x[0], cptr_x[d1], cptr_x[d2], cptr_x[d3]);
        // 提取的是连续4个点y坐标
        v_ys = _mm_setr_ps(cptr_y[0], cptr_y[d1], cptr_y[d2], cptr_y[d3]);
        v_zs = _mm_setr_ps(cptr_z[0], cptr_z[d1], cptr_z[d2], cptr_z[d3]);

        // compare range:
        // v_in_roi为全1,则证明点在感兴趣的范围内
        v_cmp_x = _mm_and_ps(_mm_cmpge_ps(v_xs, v_x_min), _mm_cmple_ps(v_xs, v_x_max));
        v_cmp_y = _mm_and_ps(_mm_cmpge_ps(v_ys, v_y_min), _mm_cmple_ps(v_ys, v_y_max));
        v_cmp_z = _mm_and_ps(_mm_cmpge_ps(v_zs, v_z_min), _mm_cmple_ps(v_zs, v_z_max));
        v_in_roi = _mm_and_ps(_mm_and_ps(v_cmp_x, v_cmp_y), v_cmp_z);

        // vector operations, cast into signed integers
        // x坐标减去最小值(-80)
        v_xs = _mm_sub_ps(v_xs, v_x_min);
        // 除以细分步长,可以得到细分方格的索引
        v_xs = _mm_mul_ps(v_xs, v_width_x_rec);
        // 转换为int类型
        iv_x_indices = _mm_cvttps_epi32(v_xs);  // truncate towards zero
        // 索引值不能大于255,否则等于255
        iv_x_indices = _mm_min_epi32(iv_nr_voxel_x_m1, iv_x_indices);

        // vector operations, cast into signed integers
        v_ys = _mm_sub_ps(v_ys, v_y_min);
        v_ys = _mm_mul_ps(v_ys, v_width_y_rec);
        iv_y_indices = _mm_cvttps_epi32(v_ys);  // truncate towards zero
        iv_y_indices = _mm_min_epi32(iv_nr_voxel_y_m1, iv_y_indices);
        // 示例:mem_iv_x_indices_:121,121,122,122,mem_iv_y_indices_:125,124,124,123
        _mm_store_si128(reinterpret_cast<__m128i *>(mem_iv_x_indices_), iv_x_indices);
        _mm_store_si128(reinterpret_cast<__m128i *>(mem_iv_y_indices_), iv_y_indices);
        // AWARN << "autodrv-VoxelGridXY<T>::SetS-mem_iv_x_indices_:" << mem_iv_x_indices_[0] << ","
        //       << mem_iv_x_indices_[1] << "," << mem_iv_x_indices_[2] << "," << mem_iv_x_indices_[3]
        //       << ",mem_iv_y_indices_:" << mem_iv_y_indices_[0] << "," << mem_iv_y_indices_[1] << ","
        //       << mem_iv_y_indices_[2] << "," << mem_iv_y_indices_[3];
        // y方格索引(125,124,124,123)与(256,0,256,0)乘积得到(32000,0,31744,0)
        iv_v_indices_02 = _mm_mullo_epi32(iv_y_indices, iv_nr_voxel_x);
        // y方格索引执行位置交换得到(124,125,123,124)
        iv_y_indices = _mm_shuffle_epi32(iv_y_indices, _MM_SHUFFLE(2, 3, 0, 1));

        _mm_store_si128(reinterpret_cast<__m128i *>(mem_iv_nr_voxel_x_), iv_nr_voxel_x);
        _mm_store_si128(reinterpret_cast<__m128i *>(mem_iv_v_indices_02_), iv_v_indices_02);
        _mm_store_si128(reinterpret_cast<__m128i *>(mem_iv_y_indices2_), iv_y_indices);
        // 示例:mem_iv_nr_voxel_x_:256,0,256,0,mem_iv_v_indices_02_:32000,0,31744,0,mem_iv_y_indices2_:124,125,123,124
        // AWARN << "autodrv-VoxelGridXY<T>::SetS-mem_iv_nr_voxel_x_:" << mem_iv_nr_voxel_x_[0] << ","
        //       << mem_iv_nr_voxel_x_[1] << "," << mem_iv_nr_voxel_x_[2] << "," << mem_iv_nr_voxel_x_[3]
        //       << ",mem_iv_v_indices_02_:" << mem_iv_v_indices_02_[0] << "," << mem_iv_v_indices_02_[1] << ","
        //       << mem_iv_v_indices_02_[2] << "," << mem_iv_v_indices_02_[3]
        //       << ",mem_iv_y_indices2_:" << mem_iv_y_indices2_[0] << "," << mem_iv_y_indices2_[1] << ","
        //       << mem_iv_y_indices2_[2] << "," << mem_iv_y_indices2_[3];
        // 将交换后的y方格索引(124,125,123,124)与(256,0,256,0)乘积得到(31744,0,31488,0)
        iv_v_indices_13 = _mm_mullo_epi32(iv_y_indices, iv_nr_voxel_x);
        // iv_v_indices_13执行位置交换得到(0,31744,0,31488)
        iv_v_indices_13 = _mm_shuffle_epi32(iv_v_indices_13, _MM_SHUFFLE(2, 3, 0, 1));
        // 变量相加后得到(32000,31744,31744,31488)
        iv_indices = _mm_add_epi32(iv_v_indices_02, iv_v_indices_13);
        // (32000,31744,31744,31488)再与x方格索引(121,121,122,122)相加得到(32121,31865,31866,31610)
        // 从而得到连续4个激光点云在voxels_中的位置索引
        iv_indices = _mm_add_epi32(iv_indices, iv_x_indices);

        // store values from registers to memory
        // address 16byte-aligned
        _mm_store_ps(mem_aligned16_f32_, v_in_roi);
        // address 16byte-aligned
        _mm_store_si128(reinterpret_cast<__m128i *>(mem_aligned16_i32_), iv_indices);

        // 示例:mem_aligned16_i32_:32121,31865,31866,31610
        // AWARN << "autodrv-VoxelGridXY<T>::SetS-mem_aligned16_i32_:" << mem_aligned16_i32_[0] << ","
        //       << mem_aligned16_i32_[1] << "," << mem_aligned16_i32_[2] << "," << mem_aligned16_i32_[3] << ",n:" << n;
        // voxels_中存储的是每个细分方格中点云索引的集合,分开来看就是一个0.625*0.625的方格有多少个点云
        // voxels_是一个容器,将y行和x列的方格展开成一维后,与voxels_的位置对应
        if (mem_aligned16_f32_[0] != 0) {
            voxels_[mem_aligned16_i32_[0]].indices_.push_back(n);
        }
        if (mem_aligned16_f32_[1] != 0) {
            voxels_[mem_aligned16_i32_[1]].indices_.push_back(n + 1);
        }
        if (mem_aligned16_f32_[2] != 0) {
            voxels_[mem_aligned16_i32_[2]].indices_.push_back(n + 2);
        }
        if (mem_aligned16_f32_[3] != 0) {
            voxels_[mem_aligned16_i32_[3]].indices_.push_back(n + 3);
        }
        cptr_x += d4;
        cptr_y += d4;
        cptr_z += d4;
    }

    // handling remaining points
    // 上面是按照4的倍数处理的点云,有可能还会剩下1、2或3个点未处理,下面是对剩余的点做的处理
    for (i = 0; i < remainder; i++, n++) {
        id = IAssignPointToVoxel(
                cptr_remainder,
                dim_x_[0],
                dim_x_[1],
                dim_y_[0],
                dim_y_[1],
                dim_z_[0],
                dim_z_[1],
                voxel_width_x_rec,
                voxel_width_y_rec,
                static_cast<int>(nr_voxel_x_),
                static_cast<int>(nr_voxel_y_));

        if (id >= 0) {
            voxels_[id].indices_.push_back(n);
        }

        cptr_remainder += nr_point_element;
    }

    // for (i = 0; i < nr_voxel; ++i) {
    //     voxels_[i].indices_.clear();
    // }
    // for (i = 0; i < nr_points; i++, n++) {
    //     float x = data_[nr_point_element_ * i];
    //     float y = data_[nr_point_element_ * i + 1];
    //     float z = data_[nr_point_element_ * i + 2];
    //     id = AssignPointToVoxel(x, y, z, dim_x_[0], dim_x_[1], dim_y_[0], dim_y_[1], dim_z_[0], dim_z_[1],
    //         voxel_width_x_rec, voxel_width_y_rec, static_cast<int>(nr_voxel_x_), static_cast<int>(nr_voxel_y_));

    //     if (id >= 0) {
    //         voxels_[id].indices_.push_back(i);
    //     }
    // }

    initialized_ = true;
    return (initialized_);
}

如何使用ransac算法,实现地平面拟合

以下代码是实现地面检测的核心实现,其中,

  • vg_fine_->SetS(point_cloud, nr_points, nr_point_elements),实现将点云分配的细分体素方格中;
  • Filter();以细分体素方格为单位实现了候选点的过滤,把过滤后的候选点存储在隶属于同一粗分方格的方格内;
  • FitInOrder();以粗分体素方格为单位,使用ransac算法,实现平面拟合;
  • SmoothInOrder();实现了粗分平面间的平滑处理,其中权重的计算与参与拟合的候选点相关;
  • ComputeSignedGroundHeight(point_cloud, height_above_ground, nr_points, nr_point_elements);以粗分体素方格为单位,计算体素方格内激光点到拟合平面的高度。
bool PlaneFitGroundDetector::Detect(
        const float *point_cloud,
        float *height_above_ground,
        unsigned int nr_points,
        unsigned int nr_point_elements) {
    assert(point_cloud != nullptr);
    assert(height_above_ground != nullptr);
    assert(nr_points <= param_.nr_points_max);
    assert(nr_point_elements >= 3);
    // setup the fine voxel grid
    // 配置细分的体素网格,其中,
    // point_cloud:激光点云数据;nr_points:有效点云个数;nr_point_elements:3
    if (!vg_fine_->SetS(point_cloud, nr_points, nr_point_elements)) {
        return false;
    }
    // setup the coarse voxel grid
    // 配置粗分的体素网格
    if (!vg_coarse_->SetS(point_cloud, nr_points, nr_point_elements)) {
        return false;
    }

    // when single-frame-detect, should empty ground_planes_
    // single_ground_detect: false
    if (param_.single_frame_detect) {
        for (size_t row = 0; row < param_.nr_grids_coarse; ++row) {
            for (size_t col = 0; col < param_.nr_grids_coarse; ++col) {
                ground_planes_[row][col].ForceInvalid();
                ground_planes_sphe_[row][col].ForceInvalid();
            }
        }
    }
    // int nr_candis = 0;
    // int nr_valid_grid = 0;
    unsigned int r = 0;
    unsigned int c = 0;
    // Filter to generate plane fitting candidates
    // int nr_candis =
    // 以细分方格为单位,进行地面候选点的过滤,并将符合条件的候选点以粗分方格为单位存放
    Filter();
    // std::cout << "# of plane candidates: " << nr_candis << std::endl;
    //  Fit local plane using ransac
    // nr_valid_grid = Fit();
    // int nr_valid_grid =
    // 通过ransac算法,以粗分方格为单位,拟合地平面
    FitInOrder();
    // std::cout << "# of valid plane geometry (fitting): " << nr_valid_grid <<
    // std::endl;
    // Smooth plane using neighborhood information:
    // use_math_optimize: false
    // 相邻方格之间做平滑处理
    if (param_.use_math_optimize) {
        SmoothInMath();
    } else {
        // nr_smooth_iter: 5
        for (int iter = 0; iter < param_.nr_smooth_iter; ++iter) {
            // Smooth();
            SmoothInOrder();
        }
    }
    // 如果粗分方格中没有拟合的候选点,则强制置为无效
    for (r = 0; r < param_.nr_grids_coarse; ++r) {
        for (c = 0; c < param_.nr_grids_coarse; ++c) {
            if ((*vg_coarse_)(r, c).Empty()) {
                ground_planes_[r][c].ForceInvalid();
            }
        }
    }

    // std::cout << "# of valid plane geometry (Smooth): " << nr_valid_grid <<
    // std::endl;
    // compute point to ground distance
    // 计算点云距离地面的高度,该函数会求取落在此方格内所有点云的高度值,
    // 如果方格没有被拟合成平面,则点云高度的高度会置一个非常大的数
    ComputeSignedGroundHeight(point_cloud, height_above_ground, nr_points, nr_point_elements);
    return true;
}

下面的函数是FitInOrder()中的主体实现,其中应用的就是ransac算法。

  • hypothesis[kNr_iter]存储了候选平面。其有2个获得途径,第一个是通过随机采样3个点,算出一个平面,配置文件中设置为48次;第二个是加入周围8个体素方格的平面;会形成总共56后候选平面;
  • *groundplane = hypothesis[best];选出了最优的平面作为最终的结果。最优结果的选取有2个条件,第一个是符合高度阈值的内点最多;第二个是如果内点数量相同,则选择与z轴夹角最小的;
  • IPlaneFitTotalLeastSquare(pf_threeds_, groundplane->params, nr_inliers);对最优平面的内点,使用最小二乘法,重新求出一个平面作为拟合结果。
int PlaneFitGroundDetector::FitGridWithNeighbors(
        int r,
        int c,
        const float *point_cloud,
        GroundPlaneLiDAR *groundplane,
        unsigned int nr_points,
        unsigned int nr_point_element,
        float dist_thre) {
    // initialize the best plane
    groundplane->ForceInvalid();
    // not enough samples, failed and return

    PlaneFitPointCandIndices &candi = local_candis_[r][c];
    std::vector<std::pair<int, int>> neighbors;
    // 获取本体素周围8个体素方格的索引
    GetNeighbors(r, c, param_.nr_grids_coarse, param_.nr_grids_coarse, &neighbors);
    // 如果体素中的点大于周围z的均值的±1.0,则滤除该点
    FilterCandidates(r, c, point_cloud, &candi, &neighbors, nr_point_element);
    // inliers_min_threshold: 6
    if (candi.Size() < param_.nr_inliers_min_threshold) {
        return 0;
    }

    GroundPlaneLiDAR plane;
    // nr_ransac_iter_threshold = 48;
    // 48+8
    int kNr_iter = param_.nr_ransac_iter_threshold + static_cast<int>(neighbors.size());
    //  check hypothesis initialized correct or not
    if (kNr_iter < 1) {
        return 0;
    }
    // 存储可能的候选平面
    GroundPlaneLiDAR hypothesis[kNr_iter];

    float ptp_dist = 0.0f;
    int best = 0;
    int nr_inliers = 0;
    int nr_inliers_best = -1;
    float angle_best = std::numeric_limits<float>::max();

    int rseed = I_DEFAULT_SEED;
    int indices_trial[] = {0, 0, 0};
    // nr_samples_min_threshold = 256;nr_samples_max_threshold = 1024;
    // 对候选点做修剪,随机减去一些候选点
    int nr_samples = candi.Prune(param_.nr_samples_min_threshold, param_.nr_samples_max_threshold);
    // termi_inlier_percen_threshold = 0.99f;
    int nr_inliers_termi = IRound(static_cast<float>(nr_samples) * param_.termi_inlier_percen_threshold);
    // 3x3 matrix stores: x, y, z; x, y, z; x, y, z;
    float samples[9];
    // copy 3D points
    float *psrc = nullptr;
    float *pdst = pf_threeds_;
    int r_n = 0;
    int c_n = 0;
    float angle = -1.f;
    // 将点云重新赋值给pf_threeds_
    for (int i = 0; i < nr_samples; ++i) {
        assert(candi[i] < static_cast<int>(nr_points));
        ICopy3(point_cloud + (nr_point_element * candi[i]), pdst);
        pdst += dim_point_;
    }
    // generate plane hypothesis and vote
    // 拟合平面48次
    for (int i = 0; i < param_.nr_ransac_iter_threshold; ++i) {
        // 在[0,nr_samples)范围随机采样3个索引点,存储在indices_trial中
        IRandomSample(indices_trial, 3, nr_samples, &rseed);
        IScale3(indices_trial, dim_point_);
        // 把采样到的点(x,y,z)赋值给samples
        ICopy3(pf_threeds_ + indices_trial[0], samples);
        ICopy3(pf_threeds_ + indices_trial[1], samples + 3);
        ICopy3(pf_threeds_ + indices_trial[2], samples + 6);
        // 三个点决定一个平面,一个平面由4个参数组成,从而求出候选平面
        IPlaneFitDestroyed(samples, hypothesis[i].params);
        // check if the plane hypothesis has valid geometry
        // 求解候选平面与z轴的夹角,如果夹角大于planefit_orien_threshold: 5.0度,则忽略
        if (hypothesis[i].GetDegreeNormalToZ() > param_.planefit_orien_threshold) {
            continue;
        }
        // iterate samples and check if the point to plane distance is below
        // threshold
        psrc = pf_threeds_;
        nr_inliers = 0;
        for (int j = 0; j < nr_samples; ++j) {
            // 求候选点到该平面的距离
            ptp_dist = IPlaneToPointDistanceWUnitNorm(hypothesis[i].params, psrc);
            // 该距离小于该粗分体素方格的设定阈值(位于0.1~0.2)之间
            if (ptp_dist < dist_thre) {
                nr_inliers++;
            }
            psrc += dim_point_;
        }
        // Assign number of supports
        // 有多少候选点满足阈值条件
        hypothesis[i].SetNrSupport(nr_inliers);
        // 设置标志位
        hypothesis[i].SetOrigin(0);

        if (nr_inliers > nr_inliers_termi) {
            break;
        }
    }

    for (size_t i = 0; i < neighbors.size(); ++i) {
        r_n = neighbors[i].first;
        c_n = neighbors[i].second;
        if (ground_planes_[r_n][c_n].IsValid()) {
            // 用相邻粗分方格拟合平面作为此粗分方格的一个候选平面
            hypothesis[i + param_.nr_ransac_iter_threshold] = ground_planes_[r_n][c_n];
            psrc = pf_threeds_;
            nr_inliers = 0;
            for (int j = 0; j < nr_samples; ++j) {
                ptp_dist = IPlaneToPointDistanceWUnitNorm(hypothesis[i + param_.nr_ransac_iter_threshold].params, psrc);
                if (ptp_dist < dist_thre) {
                    nr_inliers++;
                }
                psrc += dim_point_;
            }
            if (nr_inliers < static_cast<int>(param_.nr_inliers_min_threshold)) {
                hypothesis[i + param_.nr_ransac_iter_threshold].ForceInvalid();
                continue;
            }
            hypothesis[i + param_.nr_ransac_iter_threshold].SetNrSupport(nr_inliers);
            hypothesis[i + param_.nr_ransac_iter_threshold].SetOrigin(1);
        }
    }

    nr_inliers_best = -1;
    // 从候选平面中选择一个候选点最多的平面作为拟合结果,如果候选点一样,则选择与相邻平面夹角最小的作为拟合结果
    for (int i = 0; i < kNr_iter; ++i) {
        if (!(hypothesis[i].IsValid())) {
            continue;
        }
        nr_inliers = hypothesis[i].GetNrSupport();
        if (nr_inliers >= nr_inliers_best) {
            if (nr_inliers == nr_inliers_best) {
                angle = CalculateAngleDist(hypothesis[i], neighbors);
                if (angle < angle_best && angle > 0) {
                    angle_best = angle;
                    best = i;
                }
            } else {
                nr_inliers_best = nr_inliers;
                angle_best = CalculateAngleDist(hypothesis[i], neighbors);
                best = i;
            }
        }
    }

    *groundplane = hypothesis[best];

    // check if meet the inlier number requirement
    if (!groundplane->IsValid()) {
        groundplane->SetInvalidStatus(0);
        return 0;
    }
    // inliers_min_threshold: 6
    if (groundplane->GetNrSupport() < static_cast<int>(param_.nr_inliers_min_threshold)) {
        groundplane->ForceInvalid();
        groundplane->SetInvalidStatus(1);
        return 0;
    }
    // iterate samples and check if the point to plane distance is within
    // threshold
    nr_inliers = 0;
    psrc = pf_threeds_;
    pdst = pf_threeds_;
    for (int i = 0; i < nr_samples; ++i) {
        ptp_dist = IPlaneToPointDistanceWUnitNorm(groundplane->params, psrc);
        if (ptp_dist < dist_thre) {
            ICopy3(psrc, pdst);
            pdst += 3;
            nr_inliers++;
        }
        psrc += dim_point_;
    }
    groundplane->SetNrSupport(nr_inliers);

    // note that pf_threeds_ will be destroyed after calling this routine
    // 用最小二乘法,将pf_threeds_重新拟合一套平面参数,存储在groundplane->params
    IPlaneFitTotalLeastSquare(pf_threeds_, groundplane->params, nr_inliers);
    if (angle_best <= CalculateAngleDist(*groundplane, neighbors)) {
        *groundplane = hypothesis[best];
        groundplane->SetStatus(true);
    }
    // inliers_min_threshold: 6
    if (groundplane->GetDegreeNormalToZ() > param_.planefit_orien_threshold) {
        groundplane->ForceInvalid();
        groundplane->SetInvalidStatus(2);
        return 0;
    }

    const auto &voxel_cur = (*vg_coarse_)(r, c);
    // 体素方格的半径
    float radius = voxel_cur.dim_x_ * 0.5f;
    // 计算体素方格的中心位置
    float cx = voxel_cur.v_[0] + radius;
    float cy = voxel_cur.v_[1] + radius;
    // 计算体素方格中心处的高度值
    float cz = -(groundplane->params[0] * cx + groundplane->params[1] * cy + groundplane->params[3])
            / groundplane->params[2];
    // 高度值赋值
    ground_z_[r][c].first = cz;
    ground_z_[r][c].second = true;
    // 参与拟合的候选点的数量
    return nr_inliers;
}

效果展示

过滤结果展示

下图为不同角度的三维展示效果,其中红色部分为地面点,蓝色部分为非地面点,从中可以看出,符合实际情况。
请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海边捡贝壳的人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值