apollo自动驾驶-感知-地图ROI过滤:pointcloud_map_based_roi

功能介绍

该组件是对接收的激光雷达点云进行过滤,目标是只保留地图内的点云,滤除地图外的点云。主要分为2步:1)提取高精地图结构;2)标记哪些点云在地图内。

提取高精地图结构

目标是得到车辆周围指定距离范围内的road(道路)和junction(交叉口)构成的所有多边形。

为此,分为以下几步实现:

  • 先读取高精地图文件信息,并做细分处理(创建lane_table_、junction_table_等),同时,创建KD二叉树,方便查找车辆周围的road和junction。
  • 再通过二叉树搜索获取车辆一定范围内的road,再获取road的左右边界点;通过二叉树搜索读取了地图中junction的多边形。
  • 最后经过对road的左右边界点进行了下采样后,获取每条road边界点构成的多边形;同时,也直接读取地图中junction的多边形。

标记哪些点云在地图内

这里使用了位图的技术,节省了存储空间。其实现思路如下:

  • 先将车辆指定范围内的空间分隔为大小为0.25*0.25的小方格,每个小方格在位图中都有对应的位置;
  • 地图由road和junction的多边形构成,把多边形信息编码到位图中,被多边形占据的方格置1,否则置0;
  • 对于激光点云,若落在方格为1的点则认为在地图内,否则认为在地图外。

这里很关键的一步是如何将多边形信息编码到位图中,现介绍如下:

如下图所示,以其中的一条分隔线为例。
求取分隔线与多边形边的交点,此处共有4个交点,y值从低到高排序为1、2、3、4,可以组成2组分隔对,为(1, 2),(3, 4)。
从图中可以看出这2组分隔对之间的部分属于多边形内部,每组分隔对之间的部分所占据的方格会对应位图中的连续空间。
实现多边形信息编码到位图的重要任务,就是求取多边形的有效边与每条分隔线的交点,从而得到所有这样的分隔对。因为多边形是封闭空间,所以与分割线的交点一定是偶数。
请添加图片描述

核心代码解读

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

如何提取高精地图结构

1)读取高精地图文件信息,并做细分处理(创建lane_table_、junction_table_等),同时,创建KD二叉树,方便查找车辆周围的road和junction。

高精地图初始化入口函数,会转到map_manager.cc执行初始化函数。

        if (!map_manager_.Init(map_manager_init_options)) {
            AINFO << "Failed to init map manager.";
            use_map_manager_ = false;
        }

该初始化会读取高精地图信息,注意apollo的高精地图是基于OpenDrive规范,推荐大家主动了解一下。会转到hdmap_input.cc读取地图数据。

    if (!hdmap_input_->Init()) {
        AINFO << "Failed to init hdmap input.";
        return false;
    }

加载地图文件,会转到hdmap_impl.cc读取地图数据和进行数据处理。

    if (hdmap_->LoadMapFromFile(hdmap_file_) != 0) {
        AERROR << "Failed to load hadmap file: " << hdmap_file_;
        return false;
    }

创建LaneInfo和JunctionInfo等数据结构,创建KD二叉树,以方便处理。

int HDMapImpl::LoadMapFromProto(const Map& map_proto) {
    if (&map_proto != &map_) {  // avoid an unnecessary copy
        Clear();
        map_ = map_proto;
    }
    // 根据地图中lane信息,创建LaneInfo类,并把LaneInfo类加入到lane_table_表中
    // LaneInfo中有对lane的精细处理,是必须理解的
    for (const auto& lane : map_.lane()) {
        lane_table_[lane.id().id()].reset(new LaneInfo(lane));
    }
    // 根据地图中junction(交叉口)信息,创建JunctionInfo类,并加入到junction_table_表中
    // JunctionInfo在本模块是需要使用的,是必须理解的
    for (const auto& junction : map_.junction()) {
        junction_table_[junction.id().id()].reset(new JunctionInfo(junction));
    }
    。。。
    // 创建lane中segment的KD二叉树,以LaneSegment为例进行详细说明
    BuildLaneSegmentKDTree();
    。。。

以BuildLaneSegmentKDTree二叉树创建过程为例做出说明。会转到aaboxkdtree2d.h文件创建二叉树。

void HDMapImpl::BuildLaneSegmentKDTree() {
    AABoxKDTreeParams params;
    // 设定叶子最大距离跨度
    params.max_leaf_dimension = 5.0;  // meters.
    // 设定叶子所能包含的最大对象数量
    params.max_leaf_size = 16;
    // 转到aaboxkdtree2d.h文件创建二叉树
    BuildSegmentKDTree(lane_table_, params, &lane_segment_boxes_, &lane_segment_kdtree_);
}

void HDMapImpl::BuildSegmentKDTree(
        const Table& table,
        const AABoxKDTreeParams& params,
        BoxTable* const box_table,
        std::unique_ptr<KDTree>* const kdtree) {
    box_table->clear();
    // 对每条lane创建LaneSegmentBox类型的线段轴对齐包围盒
    for (const auto& info_with_id : table) {
        const auto* info = info_with_id.second.get();
        for (size_t id = 0; id < info->segments().size(); ++id) {
            const auto& segment = info->segments()[id];
            box_table->emplace_back(apollo::common::math::AABox2d(segment.start(), segment.end()), info, &segment, id);
        }
    }
    // 使用线段轴对齐包围盒容器创建二叉树
    kdtree->reset(new KDTree(*box_table, params));
}

开始创建二叉树。从AABoxKDTree2dNode的构建过程可以看出,实际上不断采用递归调用的方式实现的。其中,
SplitToSubNodes(objects, params)函数决定什么情况下可以分子节点。
InitObjects(objects)函数决定子节点内部对象如何排序。

创建KD二叉树的过程结束。

    AABoxKDTree2d(const std::vector<ObjectType> &objects, const AABoxKDTreeParams &params) {
        if (!objects.empty()) {
            std::vector<ObjectPtr> object_ptrs;
            for (const auto &object : objects) {
                object_ptrs.push_back(&object);
            }
            // 创建根节点,设置深度是0
            root_.reset(new AABoxKDTree2dNode<ObjectType>(object_ptrs, params, 0));
        }
    }

    AABoxKDTree2dNode(const std::vector<ObjectPtr> &objects, const AABoxKDTreeParams &params, int depth) :
            depth_(depth) {
        ACHECK(!objects.empty());
        // 计算本节点所有对象的轴对齐后的最大边界
        ComputeBoundary(objects);
        // 确定分区方案,以范围更大的轴方向作为参考
        ComputePartition();
        // 确定是否分子节点
        if (SplitToSubNodes(objects, params)) {
            std::vector<ObjectPtr> left_subnode_objects;
            std::vector<ObjectPtr> right_subnode_objects;
            // 将该节点分为左右子节点
            PartitionObjects(objects, &left_subnode_objects, &right_subnode_objects);

            // Split to sub-nodes.
            // 若已经分出类子节点,则采用递归调用的方法,反复产生子节点,直到不可再分,由此产生类二叉树
            if (!left_subnode_objects.empty()) {
                left_subnode_.reset(new AABoxKDTree2dNode<ObjectType>(left_subnode_objects, params, depth + 1));
            }
            if (!right_subnode_objects.empty()) {
                right_subnode_.reset(new AABoxKDTree2dNode<ObjectType>(right_subnode_objects, params, depth + 1));
            }
        } else {
            InitObjects(objects);
        }
    }

2)再通过二叉树搜索获取车辆一定范围内的road,再获取road的左右边界点;通过二叉树搜索读取了地图中junction的多边形。最后经过对road的左右边界点进行了下采样后,获取每条road边界点构成的多边形;同时,也直接读取地图中junction的多边形。

提取地图拓扑结构主体函数,主要是获得road和junction多边形。会转到map_manager.cc中实现。

        // 提取地图拓扑结构,主要包括road和junction
        if (!map_manager_.Update(map_manager_options, message->lidar_frame_.get())) {
            AINFO << "Failed to update map structure.";
            return false;
        }

提取高精地图拓扑结构的主体函数,会转到hdmap_input.cc函数中执行。

    // 提取高精地图拓扑结构的主体函数
    if (!hdmap_input_->GetRoiHDMapStruct(point, roi_search_distance_, frame->hdmap_struct)) {
        frame->hdmap_struct->road_polygons.clear();
        frame->hdmap_struct->road_boundary.clear();
        frame->hdmap_struct->hole_polygons.clear();
        frame->hdmap_struct->junction_polygons.clear();
        AINFO << "Failed to get roi from hdmap.";
    }

这个函数就是提取地图拓扑结构的实现部分,其中,
GetRoadBoundaries(point, distance, &road_boundary_vec, &junctions_vec)是通过二叉树搜索获取最终获得每条road的道路左右边界。
GetJunctions(point, distance, &junctions_vec)同样是二叉树搜索获取交叉口多边形。
MergeBoundaryJunction(
road_boundary_vec,
junctions_vec,
&road_boundaries,
&(hdmap_struct_ptr->road_polygons),
&(hdmap_struct_ptr->junction_polygons))函数则得到的是最终所需要的road和junction多边形。

bool HDMapInput::GetRoiHDMapStruct(
        const base::PointD& pointd,
        const double distance,
        std::shared_ptr<base::HdmapStruct> hdmap_struct_ptr) {
    lib::MutexLock lock(&mutex_);
    if (hdmap_.get() == nullptr) {
        AERROR << "hdmap is not available";
        return false;
    }
    // Get original road boundary and junction
    // 用于存储道路边界,主要包括左边界和右边界,结构体的定义
    std::vector<RoadRoiPtr> road_boundary_vec;
    std::vector<JunctionInfoConstPtr> junctions_vec;
    apollo::common::PointENU point;
    point.set_x(pointd.x);
    point.set_y(pointd.y);
    point.set_z(pointd.z);
    // 获取road边界
    if (hdmap_->GetRoadBoundaries(point, distance, &road_boundary_vec, &junctions_vec) != 0) {
        AERROR << "Failed to get road boundary, point: " << point.DebugString();
        return false;
    }
    junctions_vec.clear();
    // 获取指定范围内的JunctionInfo
    if (hdmap_->GetJunctions(point, distance, &junctions_vec) != 0) {
        AERROR << "Failed to get junctions, point: " << point.DebugString();
        return false;
    }
    if (hdmap_struct_ptr == nullptr) {
        return false;
    }
    hdmap_struct_ptr->hole_polygons.clear();
    hdmap_struct_ptr->junction_polygons.clear();
    hdmap_struct_ptr->road_boundary.clear();
    hdmap_struct_ptr->road_polygons.clear();

    // Merge boundary and junction
    // 将上述的道路边界和交叉口联合考虑,得出道路多边形和交叉口多边形
    // road_boundary_vec:道路边界
    // junctions_vec:指定范围内的JunctionInfo
    // road_boundaries:存储每个road执行下采样后的左右边界的点
    // road_polygons:存储每个road的左右边界的点
    // junction_polygons:存储每个交叉口多边形的点
    EigenVector<base::RoadBoundary> road_boundaries;
    MergeBoundaryJunction(
            road_boundary_vec,
            junctions_vec,
            &road_boundaries,
            &(hdmap_struct_ptr->road_polygons),
            &(hdmap_struct_ptr->junction_polygons));
    // Filter road boundary by junction
    // 滤除road边界点在交叉口多边形内的点,存储在hdmap_struct_ptr->road_boundary中
    GetRoadBoundaryFilteredByJunctions(
            road_boundaries, hdmap_struct_ptr->junction_polygons, &(hdmap_struct_ptr->road_boundary));
    return true;
}

int HDMapImpl::GetRoadBoundaries(
        const PointENU& point,
        double radius,
        std::vector<RoadRoiPtr>* road_boundaries,
        std::vector<JunctionInfoConstPtr>* junctions) const {
    if (road_boundaries == nullptr || junctions == nullptr) {
        AERROR << "the pointer in parameter is null";
        return -1;
    }
    road_boundaries->clear();
    junctions->clear();
    std::set<std::string> junction_id_set;
    std::vector<RoadInfoConstPtr> roads;
    // 获得车辆在radius范围内的所有road
    if (GetRoads(point, radius, &roads) != 0) {
        AERROR << "can not get roads in the range.";
        return -1;
    }
    for (const auto& road_ptr : roads) {
        if (road_ptr->has_junction_id()) {
            // 对于含有交叉口的road,获取road_id中junction_id对应的交叉口信息JunctionInfo
            JunctionInfoConstPtr junction_ptr = GetJunctionById(road_ptr->junction_id());
            if (junction_id_set.find(junction_ptr->id().id()) == junction_id_set.end()) {
                // 把搜索到的JunctionInfo的id号push到junction_id_set中
                junctions->push_back(junction_ptr);
                junction_id_set.insert(junction_ptr->id().id());
            }
        } else {
            // 对于普通的road
            RoadRoiPtr road_boundary_ptr(new RoadRoi());
            // 获取road的边界信息,主要包括左右边界
            const std::vector<apollo::hdmap::RoadBoundary>& temp_road_boundaries = road_ptr->GetBoundaries();
            road_boundary_ptr->id = road_ptr->id();
            // 针对road每个section的边界进行处理
            for (const auto& temp_road_boundary : temp_road_boundaries) {
                // 获取section边界的外围多边形,这个读取的是地图proto格式的信息
                apollo::hdmap::BoundaryPolygon boundary_polygon = temp_road_boundary.outer_polygon();
                for (const auto& edge : boundary_polygon.edge()) {
                    if (edge.type() == apollo::hdmap::BoundaryEdge::LEFT_BOUNDARY) {
                        // 边类型为左边界
                        for (const auto& s : edge.curve().segment()) {
                            for (const auto& p : s.line_segment().point()) {
                                // 把左边界的所有点push到RoadRoi对象的left_boundary中
                                road_boundary_ptr->left_boundary.line_points.push_back(p);
                            }
                        }
                    }
                    if (edge.type() == apollo::hdmap::BoundaryEdge::RIGHT_BOUNDARY) {
                        for (const auto& s : edge.curve().segment()) {
                            for (const auto& p : s.line_segment().point()) {
                                road_boundary_ptr->right_boundary.line_points.push_back(p);
                            }
                        }
                    }
                }
                // 对于道路上有洞洞的情况,增加了一个hole_boundary类型
                if (temp_road_boundary.hole_size() != 0) {
                    for (const auto& hole : temp_road_boundary.hole()) {
                        PolygonBoundary hole_boundary;
                        for (const auto& edge : hole.edge()) {
                            if (edge.type() == apollo::hdmap::BoundaryEdge::NORMAL) {
                                for (const auto& s : edge.curve().segment()) {
                                    for (const auto& p : s.line_segment().point()) {
                                        hole_boundary.polygon_points.push_back(p);
                                    }
                                }
                            }
                        }
                        road_boundary_ptr->holes_boundary.push_back(hole_boundary);
                    }
                }
            }
            // road_boundary_ptr为获取的每个road的边界信息
            // road_boundaries存储类所有指定范围内的road的边界信息
            road_boundaries->push_back(road_boundary_ptr);
        }
    }
    return 0;
}

void HDMapInput::MergeBoundaryJunction(
        const std::vector<apollo::hdmap::RoadRoiPtr>& boundary,
        const std::vector<apollo::hdmap::JunctionInfoConstPtr>& junctions,
        EigenVector<base::RoadBoundary>* road_boundaries_ptr,
        EigenVector<base::PointCloud<base::PointD>>* road_polygons_ptr,
        EigenVector<base::PointCloud<base::PointD>>* junction_polygons_ptr) {
    const int boundary_size = static_cast<int>(boundary.size());
    const int junctions_size = static_cast<int>(junctions.size());
    const int polygon_size = boundary_size;
    road_boundaries_ptr->clear();
    road_polygons_ptr->clear();
    junction_polygons_ptr->clear();
    road_polygons_ptr->resize(polygon_size);
    junction_polygons_ptr->resize(junctions_size);
    road_boundaries_ptr->resize(polygon_size);
    int polygons_index = 0;
    // Merge boundary
    PointDCloudPtr temp_cloud = base::PointDCloudPool::Instance().Get();
    for (int step = 0, i = 0; i < polygon_size; ++i) {
        temp_cloud->clear();
        const LineBoundary& left_boundary = boundary[i]->left_boundary;
        const std::vector<apollo::common::PointENU>& left_line_points = left_boundary.line_points;
        ADEBUG << "Input left road_boundary size = " << left_line_points.size();
        // 用于稀疏采样
        step = (left_line_points.size() > 2) ? hdmap_sample_step_ : 1;
        for (unsigned int idx = 0; idx < left_line_points.size(); idx += step) {
            PointD pointd;
            pointd.x = left_line_points.at(idx).x();
            pointd.y = left_line_points.at(idx).y();
            pointd.z = left_line_points.at(idx).z();
            // 道路左边界的点push到temp_cloud,里面存储该road里面所有的左边界的点
            temp_cloud->push_back(pointd);
        }
        // 执行road左边界下采样,放到RoadBoundary的left_boundary边界下
        DownsamplePoints(temp_cloud, &(road_boundaries_ptr->at(polygons_index).left_boundary));
        for (unsigned int index = 0; index < road_boundaries_ptr->at(polygons_index).left_boundary.size(); ++index) {
            // 把road左边界的点push到对应road的多边形中
            road_polygons_ptr->at(polygons_index)
                    .push_back(road_boundaries_ptr->at(polygons_index).left_boundary[index]);
        }
        ADEBUG << "Left road_boundary downsample size = "
               << road_boundaries_ptr->at(polygons_index).left_boundary.size();
        temp_cloud->clear();
        const LineBoundary& right_boundary = boundary[i]->right_boundary;
        const std::vector<apollo::common::PointENU>& right_line_points = right_boundary.line_points;
        ADEBUG << "Input right road_boundary size = " << right_line_points.size();
        step = (right_line_points.size() > 2) ? hdmap_sample_step_ : 1;
        for (unsigned int idx = 0; idx < right_line_points.size(); idx += step) {
            PointD pointd;
            pointd.x = right_line_points.at(idx).x();
            pointd.y = right_line_points.at(idx).y();
            pointd.z = right_line_points.at(idx).z();
            temp_cloud->push_back(pointd);
        }
        DownsamplePoints(temp_cloud, &(road_boundaries_ptr->at(polygons_index).right_boundary));
        for (unsigned int index = 0; index < road_boundaries_ptr->at(polygons_index).right_boundary.size(); ++index) {
            road_polygons_ptr->at(polygons_index)
                    .push_back(road_boundaries_ptr->at(polygons_index)
                                       .right_boundary
                                               [road_boundaries_ptr->at(polygons_index).right_boundary.size() - 1
                                                - index]);
        }
        ADEBUG << "Right road_boundary downsample size = "
               << road_boundaries_ptr->at(polygons_index).right_boundary.size();
        ++polygons_index;
    }

    // Merge junctions
    for (int idx = 0; idx < junctions_size; ++idx) {
        const Polygon2d& polygon = junctions[idx]->polygon();
        const std::vector<Vec2d>& points = polygon.points();
        for (size_t idj = 0; idj < points.size(); ++idj) {
            PointD pointd;
            pointd.x = points[idj].x();
            pointd.y = points[idj].y();
            pointd.z = 0.0;
            junction_polygons_ptr->at(idx).push_back(pointd);
        }
    }
}

如何实现将多边形信息编码到位图

实现过程位于hdmap_roi_filter.cc中的Filter函数中。将road和junction多边形统一成polygons_world_进行处理。

    // 把道路多边形和交叉口多边形统一当成多边形push到polygons_world_中
    for (auto& polygon : road_polygons) {
        polygons_world_[i++] = &polygon;
    }
    for (auto& polygon : junction_polygons) {
        polygons_world_[i++] = &polygon;
    }

    // transform to local
    base::PointFCloudPtr cloud_local = base::PointFCloudPool::Instance().Get();
    TransformFrame(frame->cloud, frame->lidar2world_pose, polygons_world_, &polygons_local_, &cloud_local);
    // 点云过滤的主体实现
    bool ret = FilterWithPolygonMask(cloud_local, polygons_local_, &(frame->roi_indices));

FilterWithPolygonMask函数中的DrawPolygonsMask函数是将多边形信息编码到位图的实现部分。最终会转到polygon_mask.h中实现。

bool HdmapROIFilter::FilterWithPolygonMask(
        const base::PointFCloudPtr& cloud,
        const EigenVector<PolygonDType>& map_polygons,
        base::PointIndices* roi_indices) {
	。。。
    return DrawPolygonsMask<double>(raw_polygons, &bitmap_, extend_dist_, no_edge_table_)
            && Bitmap2dFilter(cloud, bitmap_, roi_indices);
}

DrawPolygonsMask函数实现了整个编码过程,其中,
poly_scan_cvter.ScansCvt(
valid_range, static_cast(major_dir), cell_size[major_dir], &(scans_intervals)); 得到的是之前介绍的分隔对,存储在scans_intervals中。
bitmap->Set(x, valid_y_range.first, valid_y_range.second); 实现的是编码过程,将每个分隔对之间占据的空间编码到位图中。

bool DrawPolygonMask(
        const typename PolygonScanCvter<T>::Polygon& polygon,
        Bitmap2D* bitmap,
        const double extend_dist,
        const bool no_edge_table) {
    typedef typename PolygonScanCvter<T>::IntervalIn IntervalIn;
    typedef typename PolygonScanCvter<T>::IntervalOut IntervalOut;
    typedef typename PolygonScanCvter<T>::DirectionMajor PolyDirMajor;
    if (bitmap->Empty()) {
        AERROR << "bitmap is empty";
        return false;
    }
    Eigen::Vector2d poly_min_p, poly_max_p;
    // 将2个元素初始化为double类型能表示的最大值
    poly_min_p.setConstant(std::numeric_limits<double>::max());
    poly_max_p = -poly_min_p;
    // 求取多边形轴对齐方向xy最大最小范围
    for (const auto& pt : polygon) {
        poly_min_p.x() = std::min(pt.x(), poly_min_p.x());
        poly_min_p.y() = std::min(pt.y(), poly_min_p.y());

        poly_max_p.x() = std::max(pt.x(), poly_max_p.x());
        poly_max_p.y() = std::max(pt.y(), poly_max_p.y());
    }
    if (poly_max_p.x() <= poly_min_p.x()) {
        AERROR << "Invalid polygon";
        return false;
    }
    if (poly_max_p.y() <= poly_min_p.y()) {
        AERROR << "Invalid polygon";
        return false;
    }
    //(-120,-120)   (120,120)   (0.25,0.25)
    const Eigen::Vector2d& bitmap_min_range = bitmap->min_range();
    const Eigen::Vector2d& bitmap_max_range = bitmap->max_range();
    const Eigen::Vector2d& cell_size = bitmap->cell_size();
    const int major_dir = bitmap->dir_major();
    const int op_major_dir = bitmap->op_dir_major();

    // check major x range
    IntervalIn valid_range;
    // 假设为x轴为主方向,first:多边形的最小x值,second:多边形的最大x值
    valid_range.first = std::max(poly_min_p[major_dir], bitmap_min_range[major_dir]);
    valid_range.second = std::min(poly_max_p[major_dir], bitmap_max_range[major_dir]);

    // for numerical stability
    // 保证数值稳定性,以四舍五入方式,将first转换为可以被cell_size[major_dir]整除的值
    valid_range.first
            = (static_cast<int>((valid_range.first - bitmap_min_range[major_dir]) / cell_size[major_dir]) + 0.5)
                    * cell_size[major_dir]
            + bitmap_min_range[major_dir];

    if (valid_range.second < valid_range.first + cell_size[major_dir]) {
        AWARN << "Invalid range: " << valid_range.first << " " << valid_range.second
              << ". polygon major directory range: " << poly_min_p[major_dir] << " " << poly_max_p[major_dir]
              << ". cell size: " << cell_size[major_dir];
        return true;
    }

    // start calculating intervals of scans
    // 创建一个多边形扫描器
    PolygonScanCvter<T> poly_scan_cvter;
    poly_scan_cvter.Init(polygon);
    std::vector<std::vector<IntervalOut>> scans_intervals;
    if (no_edge_table) {
        size_t scans_size = static_cast<size_t>((valid_range.second - valid_range.first) / cell_size[major_dir]);
        scans_intervals.resize(scans_size);
        for (size_t i = 0; i < scans_size; ++i) {
            double scan_loc = valid_range.first + i * cell_size[major_dir];
            poly_scan_cvter.ScanCvt(scan_loc, static_cast<PolyDirMajor>(major_dir), &(scans_intervals[i]));
        }
    } else {
        // 执行该分支
        poly_scan_cvter.ScansCvt(
                valid_range, static_cast<PolyDirMajor>(major_dir), cell_size[major_dir], &(scans_intervals));
    }
    // start to draw
    // x指的是分割线上主方向的点
    double x = valid_range.first;
    for (size_t i = 0; i < scans_intervals.size(); x += cell_size[major_dir], ++i) {
        // 对每条分割线进行处理
        for (auto scan_interval : scans_intervals[i]) {
            if (scan_interval.first > scan_interval.second) {
                AERROR << "The input polygon is illegal(complex polygon)";
                return false;
            }

            // extend
            scan_interval.first -= extend_dist;
            scan_interval.second += extend_dist;

            IntervalOut valid_y_range;
            valid_y_range.first = std::max(bitmap_min_range[op_major_dir], scan_interval.first);
            valid_y_range.second = std::min(bitmap_max_range[op_major_dir], scan_interval.second);
            if (valid_y_range.first > valid_y_range.second) {
                continue;
            }
            // valid_y_range代表一个分割包低值和高值,分别是两条线段与分割线的交点
            // 落在该范围内的点,认为是位于多边形内部
            bitmap->Set(x, valid_y_range.first, valid_y_range.second);
        }
    }
    return true;
}

ScansCvt函数的实现部分,其中,
BuildEt();创建多边形边的table,会剔除掉不予任何分隔线相交的边。
UpdateAet(&((*scans_intervals)[i]));这个实现也很关键,会最终得出所需要的分隔对,其中需要重点理解的是在求取多边形的边与下一个分隔线交点时,如果保留有效边。

void PolygonScanCvter<T>::ScansCvt(
        const IntervalIn& scans_interval,
        const DirectionMajor dir_major,
        const T& step,
        std::vector<std::vector<IntervalOut>>* scans_intervals) {
    dir_major_ = dir_major;
    op_dir_major_ = OppositeDirection(dir_major_);
    CHECK_GT(step, 0.0);
    step_ = step;
    CHECK_GT(scans_interval.second, scans_interval.first + step);

    // first:多边形的最小x值,second:多边形的最大x值
    bottom_x_ = scans_interval.first;
    double top_x = scans_interval.second;
    // 扫描步数,0.25分辨率
    scans_size_ = static_cast<size_t>((top_x - bottom_x_) / step_);

    top_segments_.clear();
    top_segments_.reserve(2);
    // 多边形解析,求取线段斜率和标记点的位置关系
    ParsePolygon(dir_major, true);

    // allocate output data
    scans_intervals->clear();
    scans_intervals->resize(scans_size_);
    // 创建多边形边的table
    BuildEt();
    // initialization aet
    (*scans_intervals)[0].reserve(polygon_.size());
    // add et to aet
    // 提取第一条分割线对应的边
    for (const auto& edge : et_[0]) {
        // 斜率有限的,说明与分割线有交点
        if (std::isfinite(edge.k)) {
            aet_.second.push_back(edge);
        } else {  // 斜率是无穷大的
            // 需要把边的最小的y和最大的y打包成一个分割包
            (*scans_intervals)[0].push_back(IntervalOut(edge.y, edge.max_y));
        }
    }
    // sort
    // 将线段与分割线的交点的y值按照从小到大左排序
    std::sort(aet_.second.begin(), aet_.second.end());
    CHECK_EQ(aet_.second.size() & 1, static_cast<size_t>(0));

    // add aet to result
    // 因为多边形是封闭的,与分割线的角度一定是偶数
    for (size_t i = 0; i < aet_.second.size(); i += 2) {
        double min_y = aet_.second[i].y;
        double max_y = aet_.second[i + 1].y;
        // 每2个交点打包成一个分割包
        (*scans_intervals)[0].push_back(IntervalOut(min_y, max_y));
    }
    for (size_t i = 1; i < scans_size_; ++i) {
        // 针对剩余的分割线进行分析
        UpdateAet(&((*scans_intervals)[i]));
    }

    if (top_segments_.size()) {
        scans_intervals->resize(scans_size_ + 1);
        for (auto& seg : top_segments_) {
            double y1 = seg.first;
            double y2 = seg.second;
            double min_y = std::min(y1, y2);
            double max_y = std::max(y1, y2);
            (*scans_intervals)[scans_size_].push_back(IntervalOut(min_y, max_y));
        }
    }
}

void PolygonScanCvter<T>::BuildEt() {
    const auto& segments = segments_[dir_major_];
    // allocate memory
    et_.clear();
    et_.resize(scans_size_);
    for (auto& edge_list : et_) {
        // 有多少点,创建多少边
        edge_list.reserve(polygon_.size());
    }

    // convert segments to edges
    std::vector<std::pair<int, Edge>> edges;
    edges.reserve(segments.size());
    for (size_t i = 0; i < segments.size(); ++i) {
        std::pair<int, Edge> out_edge;
        // 将线段信息转换为边信息,若线段与分割线没有交点,可不考虑此线段
        if (ConvertSegment(i, &(out_edge))) {
            edges.push_back(out_edge);
        }
    }

    // initial active edge table
    aet_.first = 0;
    aet_.second.clear();
    aet_.second.reserve(segments.size());
    for (size_t i = 0; i < edges.size(); ++i) {
        // 分割线索引
        int x_id = edges[i].first;
        const Edge& edge = edges[i].second;
        if (x_id >= static_cast<int>(scans_size_)) {
            continue;
        }
        // add x_id == 0 to act
        // 用分割线索引和对应的边建立一个边的table
        // 注意:分割线索引为大于低点x值最近的分割线
        if (x_id >= 0) {
            et_[x_id].push_back(edge);
        } else {
            // check if intersect at x_id = 0
            Edge active_edge = edge;
            if (active_edge.MoveUp(0.0 - active_edge.x)) {
                aet_.second.push_back(active_edge);
            }
        }
    }
}

void PolygonScanCvter<T>::UpdateAet(std::vector<IntervalOut>* scan_intervals) {
    size_t x_id = aet_.first + 1;
    CHECK_LT(x_id, et_.size());
    aet_.first += 1;
    scan_intervals->clear();
    scan_intervals->reserve(polygon_.size());

    // check
    // 检查之前的边移动一个步数,查询与现在的分割线是否有交点
    size_t valid_edges_num = aet_.second.size();
    size_t invalid_edges_num = 0;
    for (auto& edge : aet_.second) {
        // 如果没有交点,则后面分析时可排除之前没有交点的边,只保留有交点的边
        if (!edge.MoveUp(step_)) {
            --valid_edges_num;
            ++invalid_edges_num;
            edge.y = s_inf_;
        }
    }

    // add et to aet
    size_t new_edges_num = 0;
    for (const auto& edge : et_[x_id]) {
        // 再次push与本分割线对应的新出来的边
        if (std::isfinite(edge.k)) {
            ++valid_edges_num;
            ++new_edges_num;
            aet_.second.push_back(edge);
        } else {
            scan_intervals->push_back(IntervalOut(edge.y, edge.max_y));
        }
    }
    CHECK_EQ(valid_edges_num & 1, static_cast<size_t>(0))
            << boost::format(
                       "valid edges num: %d x: %lf bottom_x: %lf \n vertices num: %d "
                       "\n")
                    % valid_edges_num % (x_id * step_ + bottom_x_) % bottom_x_ % polygon_.size()
            << aet_.second[0] << "\n"
            << aet_.second[1] << "\n"
            << aet_.second[2] << "\n"
            << aet_.second[3];

    // sort
    // 提取有效的边,即所有与此分割线有交点的边
    if (invalid_edges_num != 0 || new_edges_num != 0) {
        // 因为无效的边的y值都被赋值到s_inf_,sort后,无效边都在后面
        std::sort(aet_.second.begin(), aet_.second.end());
        // remove invalid edges
        aet_.second.resize(valid_edges_num);
    }
    // 将所有有效边与此分割线的交点打包成一个一个的分割包
    AINFO << "autodrv-PolygonScanCvter<T>::UpdateAet-aet_.second.size():" << aet_.second.size();
    // 因为多边形是封闭的,与分割线的角度一定是偶数
    for (size_t i = 0; i < aet_.second.size(); i += 2) {
        // corner case, same point in the pre scan and complex polygon
        double min_y = aet_.second[i].y;
        double max_y = aet_.second[i + 1].y;
        scan_intervals->push_back(IntervalOut(min_y, max_y));
    }
}

bitmap->Set(x, valid_y_range.first, valid_y_range.second);函数的实现要转到 bitmap2d.cc文件中。这个实现的就是地图的的编码过程,其中用到的min_y和max_y就是分隔对的值。

void Bitmap2D::Set(const double x, const double min_y, const double max_y) {
    Eigen::Vector2d real_left, real_right;
    real_left[op_dir_major()] = min_y;
    real_right[op_dir_major()] = max_y;
    real_left[dir_major()] = real_right[dir_major()] = x;
    // real_left:分割线交点的低点,real_right:分割线交点的高点
    // 将实际位置信息转换成位图的索引
    const Vec3ui left_bit_p = RealToBitmap(real_left);
    const Vec3ui right_bit_p = RealToBitmap(real_right);
    // 对于高点和低点位于位图的同一个字时
    if (left_bit_p.y() == right_bit_p.y()) {
        // 求取需要索引的字
        const int idx = Index(left_bit_p);
        // 设置该索引的字从哪一位到哪一位需要全部置1,以标识该区域属于多边形内部
        SetRangeBits(right_bit_p.z(), left_bit_p.z(), &bitmap_[idx]);
        return;
    }
    // 下面是针对高点和低点不位于位图的同一个字时
    const size_t left_idx = Index(left_bit_p);
    const size_t right_idx = Index(right_bit_p);
    // 设置一段连续置1的位图空间,以标识该区域属于多边形内部
    SetHeadBits(left_bit_p.z(), &bitmap_[left_idx]);
    SetTailBits(right_bit_p.z(), &bitmap_[right_idx]);
    for (size_t i = left_idx + 1; i < right_idx; ++i) {
        bitmap_[i] = static_cast<uint64_t>(-1);
    }
}

效果展示

提取高精地图结构

上图为dreamview加载的地图,下图则为代码中提取到的多边形的点,其中红色点为road多边形的点,蓝色为junction多边形的点。可以看出上下图基本是相似的。
请添加图片描述

点云过滤效果展示

蓝色点为点云,红色点为地图多边形的点。上图为未经过点云过滤的图,可见有很多点云在地图外。下图为过滤后的点云,地图外的点云被过滤。
请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

海边捡贝壳的人

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

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

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

打赏作者

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

抵扣说明:

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

余额充值