文章目录

  • 组件启动
    • 实现组件类
      • 实现组件头文件
      • 实现组件源文件
    • 设置配置文件
    • 启动组件
  • 激光感知
    • 目录结构
    • 源码剖析
      • detection——init
        • InitAlgorithmPlugin
      • detection——Proc
        • 点云预处理
        • 高精地图定位信息获取
        • 障碍物检测
        • 障碍物边框构建
        • Bounding_box过滤

本文先以感知模块perception_lidar为例,先讲解组件component如何启动,后对lidar detection 模块代码进行剖析

apollo注释代码链接:https://github.com/Xiao-Hu-Z/apollo_xiaohu,后续会针对某些细节点进一步梳理

组件启动

Perception 是核心的组件之一,但像所有的 C++ 程序一样,每个应用都有一个 Main 函数入口,那么引出本章要探索的 2 个问题:

  • Perception 入口在哪里
  • Perception 如何启动

Perception 模块前,很有必要先去了解下cyber

Apollo Cyber 运行时框架(Apollo Cyber RT Framework) 是基于组件概念来构建的. 每个组件都是Cyber框架的一个构建块, 它包括一个特定的算法模块, 此算法模块处理一组输入数椐并产生一组输出数椐。

ROS 应用于自动驾驶领域的不足:

  • 调度的不确定性:各节点以独立进程运行,节点运行顺序无法确定,因而业务逻辑的调度顺序无法保证;
  • 运行效率:ROS 为分布式系统,存在通信开销

Apollo在3.5中引入了Cyber RT,替换了以前基于ROS的变体, CyberRT 删除了master 机制,用自动发现机制代替,此外,Cyber RT的核心设计将调度、任务从内核空间搬到了用户空间。

组件管理

要创建并启动一个算法组件, 需要通过以下4个步骤:

  • 初如化组件的文件结构
  • 实现组件类
  • 设置配置文件
  • 启动组件

一个 component 需要创建以下文件:

  • Header file: common_component_example.h
  • Source file: common_component_example.cc
  • Build file: BUILD
  • DAG dependency file: common.dag
  • Launch file: common.launch

Apollo 是多数据融合的,它融合 Camera、Lidar、Radar 目标,而这 3 个都是一个 component。

感知模块的入口在production目录,通过lanuch加载对应的dag,启动感知模块,感知模块包括多个子模块,在onboard目录中定义。

perception组件路径:

apollo/modules/perception/onboard/component

这个目录下,定义和实现了很多感知相关的组件,本文只关注于 Detection。

实现组件类

实现组件头文件

detection_component.h

  • 继承 Component 类
  • 定义自己的 Init 和 Proc 函数. Proc 需要指定输入数椐类型。
  • 使用CYBER_REGISTER_COMPONENT宏定义把组件类注册成全局可用。
namespace apollo
{namespace perception{namespace onboard{class DetectionComponent : public cyber::Component<drivers::PointCloud>{public:DetectionComponent() = default;virtual ~DetectionComponent() = default;bool Init() override;bool Proc(const std::shared_ptr<drivers::PointCloud> &message) override;private:bool InitAlgorithmPlugin();bool InternalProc(const std::shared_ptr<const drivers::PointCloud> &in_message,const std::shared_ptr<LidarFrameMessage> &out_message);private:static std::atomic<uint32_t> seq_num_;std::string sensor_name_;std::string detector_name_;bool enable_hdmap_ = true;float lidar_query_tf_offset_ = 20.0f;std::string lidar2novatel_tf2_child_frame_id_;std::string output_channel_name_;base::SensorInfo sensor_info_;// TransformWrapper类:用于查询不同坐标系之间的变换关系TransformWrapper lidar2world_trans_;std::unique_ptr<lidar::BaseLidarObstacleDetection> detector_;std::shared_ptr<apollo::cyber::Writer<LidarFrameMessage>> writer_;};// 使用CYBER_REGISTER_COMPONENT宏定义把组件类注册成全局可用CYBER_REGISTER_COMPONENT(DetectionComponent);} // namespace onboard}   // namespace perception
} // namespace apollo

实现组件源文件

对于源文件InitProc 这两个函数需要实现,代码就不贴了,下面会对该部分代码解析

BUILD 文件地址是:

apollo/modules/perception/onboard/component/BUILD

BUILD 文件定义了 perception 中所有的 component 如 camera,radar,lidar 等的信息,本文只关注 Detection。

cc_library(name = "detection_component",srcs = ["detection_component.cc"],hdrs = ["detection_component.h"],deps = [":lidar_inner_component_messages","//cyber/time:clock","//modules/common/util:string_util","//modules/perception/common/sensor_manager","//modules/perception/lib/registerer","//modules/perception/lidar/common","//modules/perception/lidar/app:lidar_obstacle_detection","//modules/perception/lidar/lib/ground_detector/spatio_temporal_ground_detector","//modules/perception/lidar/lib/interface","//modules/perception/lidar/lib/object_builder","//modules/perception/lidar/lib/object_filter_bank/roi_boundary_filter","//modules/perception/lidar/lib/roi_filter/hdmap_roi_filter","//modules/perception/lidar/lib/scene_manager/ground_service","//modules/perception/lidar/lib/scene_manager/roi_service","//modules/perception/lidar/lib/detector/point_pillars_detection:point_pillars_detection","//modules/perception/lidar/lib/detector/cnn_segmentation:cnn_segmentation","//modules/perception/lidar/lib/detector/ncut_segmentation:ncut_segmentation","//modules/perception/onboard/common_flags","//modules/perception/onboard/proto:lidar_component_config_cc_proto","//modules/perception/onboard/transform_wrapper","@eigen",],
)

设置配置文件

一个 Component 的配置文件有 2 种:

  • DAG
  • Launch

DAG 定义了模块的依赖关系,Launch 文件定义了模块的启动。

下面看激光雷达感知的Launch 文件:

launch文件:Apollo/modules/perception/production/launch/perception_lidar.launch

<!--this file list the modules which will be loaded dynamicly andtheir process name to be running in -->
<cyber><desc>cyber modules list config</desc><version>1.0.0</version><!-- sample module config, and the files should have relative path like./bin/cyber_launch./bin/mainboard./conf/dag_streaming_0.conf --><module><name>perception_lidar</name><dag_conf>/apollo/modules/perception/production/dag/dag_streaming_perception_lidar.dag</dag_conf><!-- if not set, use default process --><process_name>perception_lidar</process_name><version>1.0.0</version></module>
</cyber>

对应的dag文件:Apollo/modules/perception/production/dag/dag_streaming_perception_lidar.dag其中包括DetectionComponent, RecognitionComponent, FusionComponent, V2XFusionComponent四个组件类,即检测,识别跟踪、融合、车联网融合。单对于lidar模块,主要就是检测和识别跟踪两个组件类的具体实现,融合和车联网融合是lidar模块输出结果的后续处理。

dag_streaming_perception_lidar.dag内容:

module_config {module_library : "/apollo/bazel-bin/modules/perception/onboard/component/libperception_component_lidar.so"components {class_name : "DetectionComponent"config {name: "Velodyne128Detection"config_file_path: "/apollo/modules/perception/production/conf/perception/lidar/velodyne128_detection_conf.pb.txt"flag_file_path: "/apollo/modules/perception/production/conf/perception/perception_common.flag"readers {channel: "/apollo/sensor/lidar128/compensator/PointCloud2"}}}components {class_name : "RecognitionComponent"config {name: "RecognitionComponent"config_file_path: "/apollo/modules/perception/production/conf/perception/lidar/recognition_conf.pb.txt"readers {channel: "/perception/inner/DetectionObjects"}}}components {class_name: "FusionComponent"config {name: "SensorFusion"config_file_path: "/apollo/modules/perception/production/conf/perception/fusion/fusion_component_conf.pb.txt"readers {channel: "/perception/inner/PrefusedObjects"}}}
}module_config {module_library : "/apollo/bazel-bin/modules/v2x/fusion/apps/libv2x_fusion_component.so"components {class_name : "V2XFusionComponent"config {name : "v2x_fusion"flag_file_path : "/apollo/modules/v2x/conf/v2x_fusion_tracker.conf"readers: [{channel: "/perception/vehicle/obstacles"}]}}
}

该类配置参数proto定义:modules/perception/onboard/proto/lidar_component_config.proto
具体实现文件在:modules/perception/production/conf/perception/lidar/velodyne128_detection_conf.pb.txt,每个激光雷达一个实现文件。

通过modules/perception/lidar/app/lidar_obstacle_detection.cc中的LidarObstacleDetection类完成实际激光雷达点云障碍物检测工作;

Apollo7.0的lidar模块的处理过程就是:DetectionComponent(检测) >> RecognitionComponent(识别跟踪)。
component位于:

Apollo/modules/perception/onboard/component/detection_component.cc
Apollo/modules/perception/onboard/component/recognition_component.cc

启动组件

定义了一个 component 相关的文档后,就可以启动组件:

  • 使用launch文件来启动

    cyber_launch start apollo/modules/perception/production/launch/perception_lidar.launch
    

激光感知

先看perception模块的目录结构

.
|-- BUILD      // 基础类
|-- base       // 基础类
|-- camera     // 相机相关
|-- common     // 公共目录
|-- data       // 相机的内参和外参
|-- fusion     // 传感器融合
|-- inference  // 深度学习推理模块
|-- lib        // 一些基础的库,包括线程、文件配置等
|-- lidar      // 激光雷达相关
|-- map        // 地图
|-- onboard    // 各个子模块的入口
|-- production // 感知模块入口 --- 通过cyber启动子模块
|-- proto      // 数据格式,protobuf
|-- radar      // 毫米波雷达
|-- testdata   // 几个模块的测试数据
`-- tool       // 离线测试工具

目录结构

文件夹结构:以下文件夹都是在perception/lidar/下

  • app——lidar应用类,主处理类,即最终应用程序应该是实例化该文件夹下的类来完成

  • common——定义lidar感知模块需要用的通用数据结构,例如LidarFrame,通用处理方法等;

  • lib——激光雷达感知中算法实现库

  • interface——各种算法类的基类定义,作为算法通用类的接口

  • lib/roi_filter——包含hdmap_roi_filter和roi_service_filter两个文件夹,前者用来利用高精度地图的信息来对LidarFrame中给出的高精度地图查询信息对点云进行ROI限制。

  • perception/map/hdmap——用在感知模块用来查询与高精度地图相关的信息。

launch文件:Apollo/modules/perception/production/launch/perception_lidar.launch
对应的dag文件:Apollo/modules/perception/production/dag/dag_streaming_perception_lidar.dag

launch文件用来启动,dag文件描述了整个系统的拓扑关系,也定义了每个Component需要订阅的话题

launch文件和dag文件上面有介绍,就不多赘述了,下面直接看modules/perception/onboard/component/detection_component.cc代码

源码剖析

先看检测部分代码,对应文件:detection_component.cc

detection——init

      bool DetectionComponent::Init(){// 读取配置文件,配置文件定义:modules/perception/onboard/proto/lidar_component_config.protoLidarDetectionComponentConfig comp_config;if (!GetProtoConfig(&comp_config)){return false;}ADEBUG << "Lidar Component Configs: " << comp_config.DebugString(); // DebugString:打印输出comp_config对象output_channel_name_ = comp_config.output_channel_name();sensor_name_ = comp_config.sensor_name();detector_name_ = comp_config.detector_name();lidar2novatel_tf2_child_frame_id_ = comp_config.lidar2novatel_tf2_child_frame_id();lidar_query_tf_offset_ = static_cast<float>(comp_config.lidar_query_tf_offset());enable_hdmap_ = comp_config.enable_hdmap();// 发布消息writer_ = node_->CreateWriter<LidarFrameMessage>(output_channel_name_);/**配置文件参数* Apollo/modules/perception/production/conf/perception/lidar/velodyne128_detection_conf.pb.txt:* sensor_name: "velodyne128"* enable_hdmap: true* lidar_query_tf_offset: 0* lidar2novatel_tf2_child_frame_id: "velodyne128"* output_channel_name: "/perception/inner/DetectionObjects"*/// 初始化成员算法类if (!InitAlgorithmPlugin()){AERROR << "Failed to init detection component algorithm plugin.";return false;}return true;}

LidarDetectionComponentConfig 路径:{apollo/modules/perception/onboard/proto/lidar_component_config.proto

InitAlgorithmPlugin

在调用各模块类的处理逻辑process,先对个模块功能类进行参数初始化,先调用LidarObstacleDetection::Init,后分别分别调用各模块功能类(预处理,根据高精度hdmap获取离定位点一定范围的道路、交叉路口信息,pointpillar目标检测)的init方法

      bool DetectionComponent::InitAlgorithmPlugin(){// 读取传感器元数据,元数据的读取是通过SensorManager来完成的,SensorManager 类经宏定义 DECLARE_SINGLETON(SensorManager) 修饰成为单例类,单例对象调用GetSensorInfo函数获取传感器名sensor_name_对应的传感器信息SensorInfo// 其在初始化时会读取modules/perception/production/data/perception/common/sensor_meta.pt的包含所有传感器元数据的列表ACHECK(common::SensorManager::Instance()->GetSensorInfo(sensor_name_,&sensor_info_));// apollo/modules/perception/lib/registerer/registerer.h// 父类指针detector指向子类LidarObstacleDetection的对象lidar::BaseLidarObstacleDetection *detector = lidar::BaseLidarObstacleDetectionRegisterer::GetInstanceByName(detector_name_);     // 调用宏定义类的静态方法CHECK_NOTNULL(detector);detector_.reset(detector);// lidar型号,hdmap是否使用lidar::LidarObstacleDetectionInitOptions init_options;init_options.sensor_name = sensor_name_;// 调用DEFINE_bool宏获取hdmap选择FLAGS_obs_enable_hdmap_input// DEFINE_bool宏位于:modules/perception/onboard/common_flags/common_flags.cppinit_options.enable_hdmap_input = FLAGS_obs_enable_hdmap_input && enable_hdmap_;// 多态性:子类LidarObstacleDetection重写父类BaseLidarObstacleDetection的虚函数init// 调用子类LidarObstacleDetection的init函数ACHECK(detector_->Init(init_options)) << "lidar obstacle detection init error";lidar2world_trans_.Init(lidar2novatel_tf2_child_frame_id_);return true;}

1.获取传感器信息sensor_info_,对应代码:

        lidar::BaseLidarObstacleDetection *detector = lidar::BaseLidarObstacleDetectionRegisterer::GetInstanceByName(detector_name_);     // 调用宏定义类的静态方法CHECK_NOTNULL(detector);detector_.reset(detector);

SensorManager类路径:apollo/modules/perception/common/sensor_manager/sensor_manager.cc

common::SensorManager::Instance()会返回SensorManager的唯一实例,同时调用构造函数,而构造函数又调用Init()方法,对传感器元数据初始化,会读取modules/perception/production/data/perception/common/sensor_meta.pt的包含所有传感器元数据,并将传感器名字和传感器信息SensorInfo存储在sensor_info_map_字典

      // glog 提供了CHECK()宏帮助我们检查程序的错误,当CHECK()的条件不满足时,它会记录FATAL日志并终止程序SensorManager::SensorManager() { CHECK_EQ(this->Init(), true); }bool SensorManager::Init(){std::lock_guard<std::mutex> lock(mutex_);if (inited_){return true;}sensor_info_map_.clear();distort_model_map_.clear();undistort_model_map_.clear();// 传感器元数据(名字,传感器型号,摆放位置)文件路径: apollo/modules/perception/production/data/perception/common/sensor_meta.pt// 调用gflags库DEFINE_type宏获取传感器元数据文件路径FLAGS_obs_sensor_meta_pathconst std::string file_path = cyber::common::GetAbsolutePath(lib::ConfigManager::Instance()->work_root(), FLAGS_obs_sensor_meta_path);MultiSensorMeta sensor_list_proto;// 从文件中读取信息存储到sensor_list_proto中if (!GetProtoFromASCIIFile(file_path, &sensor_list_proto)){AERROR << "Invalid MultiSensorMeta file: " << FLAGS_obs_sensor_meta_path;return false;}auto AddSensorInfo = [this](const SensorMeta &sensor_meta_proto){SensorInfo sensor_info;sensor_info.name = sensor_meta_proto.name();sensor_info.type = static_cast<SensorType>(sensor_meta_proto.type());sensor_info.orientation =static_cast<SensorOrientation>(sensor_meta_proto.orientation());sensor_info.frame_id = sensor_meta_proto.name();// sensor_info_map_字典存储传感器名字和传感器信息SensorInfo// SensorInfo 结构体类型 位于apollo/modules/perception/base/sensor_meta.hauto pair = sensor_info_map_.insert(make_pair(sensor_meta_proto.name(), sensor_info));if (!pair.second){AERROR << "Duplicate sensor name error.";return false;}for (const SensorMeta &sensor_meta_proto : sensor_list_proto.sensor_meta()){if (!AddSensorInfo(sensor_meta_proto)){AERROR << "Failed to add sensor_info: " << sensor_meta_proto.name();return false;}}inited_ = true;AINFO << "Init sensor_manager success.";return true;}// 根据传感器名获取传感器信息SensorInfobool SensorManager::GetSensorInfo(const std::string &name,SensorInfo *sensor_info) const{if (sensor_info == nullptr){AERROR << "Nullptr error.";return false;}const auto &itr = sensor_info_map_.find(name);if (itr == sensor_info_map_.end()){return false;}*sensor_info = itr->second;return true;}

apollo/modules/perception/proto/sensor_meta_schema.proto

传感器信息proto字段

message SensorMeta {enum SensorType {UNKNOWN_SENSOR_TYPE = -1;VELODYNE_64 = 0;VELODYNE_32 = 1;VELODYNE_16 = 2;LDLIDAR_4 = 3;LDLIDAR_1 = 4;SHORT_RANGE_RADAR = 5;LONG_RANGE_RADAR = 6;MONOCULAR_CAMERA = 7;STEREO_CAMERA = 8;ULTRASONIC = 9;VELODYNE_128 = 10;}enum SensorOrientation {FRONT = 0;LEFT_FORWARD = 1;LEFT = 2;LEFT_BACKWARD = 3;REAR = 4;RIGHT_BACKWARD = 5;RIGHT = 6;RIGHT_FORWARD = 7;PANORAMIC = 8;}optional string name = 1;optional SensorType type = 2;optional SensorOrientation orientation = 3;
}message MultiSensorMeta {repeated SensorMeta sensor_meta = 1;
}

SensorInfo类型,位于位于apollo/modules/perception/base/sensor_meta.h

struct SensorInfo {std::string name = "UNKNONW_SENSOR";SensorType type = SensorType::UNKNOWN_SENSOR_TYPE;SensorOrientation orientation = SensorOrientation::FRONT;std::string frame_id = "UNKNOWN_FRAME_ID";void Reset() {name = "UNKNONW_SENSOR";type = SensorType::UNKNOWN_SENSOR_TYPE;orientation = SensorOrientation::FRONT;frame_id = "UNKNOWN_FRAME_ID";}
};

2.lidar障碍物检测基类对象指针指向子类的对象,对应代码:

        lidar::BaseLidarObstacleDetection *detector = lidar::BaseLidarObstacleDetectionRegisterer::GetInstanceByName(detector_name_);     // 调用宏定义类的静态方法

BaseLidarObstacleDetectionRegistere作为雷达障碍物检测的基类,通过多态形式调用子类的init函数,路径:

apollo/modules/perception/lidar/lib/interface/base_lidar_obstacle_detection.h

lidar::BaseLidarObstacleDetectionRegisterer调用一个宏定义类,类BaseLidarObstacleDetectionRegisterer路径:apollo/modules/perception/lib/registerer/registerer.h

#define PERCEPTION_REGISTER_REGISTERER(base_class)                    \class base_class##Registerer {                                      \typedef ::apollo::perception::lib::Any Any;                       \typedef ::apollo::perception::lib::FactoryMap FactoryMap;         \\public:                                                            \static base_class *GetInstanceByName(const ::std::string &name) { \FactoryMap &map =                                               \::apollo::perception::lib::GlobalFactoryMap()[#base_class]; \FactoryMap::iterator iter = map.find(name);                     \if (iter == map.end()) {                                        \for (auto c : map) {                                          \AERROR << "Instance:" << c.first;                           \}                                                             \AERROR << "Get instance " << name << " failed.";              \return nullptr;                                               \}                                                               \Any object = iter->second->NewInstance();                       \return *(object.AnyCast<base_class *>());                       \}                                                                 \static std::vector<base_class *> GetAllInstances() {              \std::vector<base_class *> instances;                            \FactoryMap &map =                                               \::apollo::perception::lib::GlobalFactoryMap()[#base_class]; \instances.reserve(map.size());                                  \for (auto item : map) {                                         \Any object = item.second->NewInstance();                      \instances.push_back(*(object.AnyCast<base_class *>()));       \}                                                               \return instances;                                               \}                                                                 \static const ::std::string GetUniqInstanceName() {                \FactoryMap &map =                                               \::apollo::perception::lib::GlobalFactoryMap()[#base_class]; \CHECK_EQ(map.size(), 1U) << map.size();                         \return map.begin()->first;                                      \}                                                                 \static base_class *GetUniqInstance() {                            \FactoryMap &map =                                               \::apollo::perception::lib::GlobalFactoryMap()[#base_class]; \CHECK_EQ(map.size(), 1U) << map.size();                         \Any object = map.begin()->second->NewInstance();                \return *(object.AnyCast<base_class *>());                       \}                                                                 \static bool IsValid(const ::std::string &name) {                  \FactoryMap &map =                                               \::apollo::perception::lib::GlobalFactoryMap()[#base_class]; \return map.find(name) != map.end();                             \}                                                                 \};

3.初始化雷达型号,是否使用hdmap,作为类LidarObstacleDetection的init函数传入参数

        lidar::LidarObstacleDetectionInitOptions init_options;init_options.sensor_name = sensor_name_;// 调用DEFINE_bool宏返回FLAGS_obs_enable_hdmap_input,bool类型,表达是否有hdmap输入// DEFINE_bool宏位于:modules/perception/onboard/common_flags/common_flags.cppinit_options.enable_hdmap_input = FLAGS_obs_enable_hdmap_input && enable_hdmap_;

类LidarObstacleDetectionInitOptions位于

apollo/modules/perception/lidar/lib/interface/base_lidar_obstacle_detection.h

struct LidarObstacleDetectionInitOptions {std::string sensor_name = "velodyne64";bool enable_hdmap_input = true;
};

4.调用子类LidarObstacleDetection的init函数初始化参数

        // 多态性:子类LidarObstacleDetection重写父类BaseLidarObstacleDetection的虚函数init// 调用子类LidarObstacleDetection的init函数ACHECK(detector_->Init(init_options)) << "lidar obstacle detection init error";

Init函数位于:apollo/modules/perception/lidar/app/lidar_obstacle_detection.cc

下面看雷达障碍物检测类的初始化函数init,初始化各模块的参数

      bool LidarObstacleDetection::Init(const LidarObstacleDetectionInitOptions &options){auto &sensor_name = options.sensor_name; // 传感器名,如"velodyne128"// ConfigManager类经宏定义 DECLARE_SINGLETON(ConfigManager) 修饰成为单例类auto config_manager = lib::ConfigManager::Instance();const lib::ModelConfig *model_config = nullptr;// 获取 LidarObstacleDetection 配置参数model_config,第一次调用GetModelConfig将各模块功能类和其配置参数存储在字典,从字典查找LidarObstacleDetection对应的参数信息// LidarObstacleDetection在文件中是存储在 apollo/modules/perception/production/conf/perception/lidar/modules/lidar_obstacle_pipeline.configACHECK(config_manager->GetModelConfig(Name(), &model_config));const std::string work_root = config_manager->work_root();std::string config_file;std::string root_path;// root_path:./data/perception/lidar/models/lidar_obstacle_pipelineACHECK(model_config->get_value("root_path", &root_path));// apollo/modules/perception/production/lidar/models/lidar_obstacle_pipelineconfig_file = cyber::common::GetAbsolutePath(work_root, root_path);// apollo/modules/perception/production/data/perception/lidar/models/lidar_obstacle_pipeline/velodyne128config_file = cyber::common::GetAbsolutePath(config_file, sensor_name);// apollo/modules/perception/production/data/perception/lidar/models/lidar_obstacle_pipeline/velodyne128/lidar_obstacle_detection.confconfig_file = cyber::common::GetAbsolutePath(config_file, "lidar_obstacle_detection.conf");/*message LidarObstacleDetectionConfig {optional string preprocessor = 1 [default = "PointCloudPreprocessor"];optional string detector = 2 [default = "PointPillarsDetection"];optional bool use_map_manager = 3 [default = true];optional bool use_object_filter_bank = 4 [default = true];}*/LidarObstacleDetectionConfig config;// 把lidar_obstacle_detection.conf写入proto LidarObstacleDetectionConfig信息中ACHECK(cyber::common::GetProtoFromFile(config_file, &config));use_map_manager_ = config.use_map_manager();               // trueuse_object_filter_bank_ = config.use_object_filter_bank(); // true// PointPillarsDetectionuse_object_builder_ = ("PointPillarsDetection" != config.detector() ||"MaskPillarsDetection" != config.detector());use_map_manager_ = use_map_manager_ && options.enable_hdmap_input; // trueSceneManagerInitOptions scene_manager_init_options;ACHECK(SceneManager::Instance().Init(scene_manager_init_options));// 是否使用高精度地图if (use_map_manager_){MapManagerInitOptions map_manager_init_options;// hdmap初始化if (!map_manager_.Init(map_manager_init_options)){AINFO << "Failed to init map manager.";use_map_manager_ = false;}}// 激光点云预处理:初始化基类对象,让其指针指向子类BasePointCloudPreprocessor *preprocessor = BasePointCloudPreprocessorRegisterer::GetInstanceByName(config.preprocessor());CHECK_NOTNULL(preprocessor);cloud_preprocessor_.reset(preprocessor);// 激光雷达的型号PointCloudPreprocessorInitOptions preprocessor_init_options;preprocessor_init_options.sensor_name = sensor_name;// 点云预处理初始化ACHECK(cloud_preprocessor_->Init(preprocessor_init_options)) << "lidar preprocessor init error";// 激光障碍物检测BaseLidarDetector *detector = BaseLidarDetectorRegisterer::GetInstanceByName(config.detector());BaseLidarDetectorRegisterer::GetInstanceByName(config.detector());detector_.reset(detector);LidarDetectorInitOptions detection_init_options;detection_init_options.sensor_name = sensor_name;// 激光雷达障碍物检测初始化ACHECK(detector_->Init(detection_init_options)) << "lidar detector init error";if (use_object_builder_){// ObjectBuilder:构建障碍物目标包围框类信息ObjectBuilderInitOptions builder_init_options;ACHECK(builder_.Init(builder_init_options));}if (use_object_filter_bank_){ObjectFilterInitOptions filter_bank_init_options;filter_bank_init_options.sensor_name = sensor_name;// ObjectFilterBank: 调用ObjectFilterBank:对目标进行ROIBoundaryFilterACHECK(filter_bank_.Init(filter_bank_init_options));}return true;}

1.先定义单例类ConfigManager对象,调用GetModelConfig获取障碍物检测类的配置参数

根据 LidarObstacleDetection类名获取其 配置参数,第一次调用GetModelConfig,它将进一步调用init函数将各模块功能类和其配置参数存储在字典中,从字典查找LidarObstacleDetection对应的参数信息,存储在存储在ModelConfig类对象中,然后利用ModelConfig类的get_value函数,就可以对应查询到具体的配置参数数值了

LidarObstacleDetection在文件中是存储在 apollo/modules/perception/production/conf/perception/lidar/modules/lidar_obstacle_pipeline.config

        auto config_manager = lib::ConfigManager::Instance();const lib::ModelConfig *model_config = nullptr;ACHECK(config_manager->GetModelConfig(Name(), &model_config));ACHECK(model_config->get_value("root_path", &root_path));

ConfigManager位于:apollo/modules/perception/lib/config_manager/config_manager.cc

下面具体解析下GetModelConfig函数,直接看代码:

// 根据类名获取模型配置参数
bool ConfigManager::GetModelConfig(const std::string &model_name,const ModelConfig **model_config) {if (!inited_ && !Init()) {return false;}auto citer = model_config_map_.find(model_name);if (citer == model_config_map_.end()) {return false;}*model_config = citer->second;return true;
}bool ConfigManager::Init() {MutexLock lock(&mutex_);return InitInternal();
}bool ConfigManager::InitInternal() {if (inited_) {return true;}// 释放内存for (auto iter = model_config_map_.begin(); iter != model_config_map_.end();++iter) {delete iter->second;}model_config_map_.clear();// 调用gflags库DEFINE_type宏获取传感器的参数文件路径FLAGS_config_manager_path// FLAGS_config_manager_path = "./conf"// apollo/modules/perception/production/conf/std::string config_module_path = GetAbsolutePath(work_root_, FLAGS_config_manager_path);AINFO << "WORK_ROOT: " << work_root_ << " config_root_path: " << config_module_path;std::vector<std::string> model_config_files;// 递归遍历conf文件夹下所有文件名,返回后缀包含config_manager的所有文件路径model_config_filesif (!common::GetFileList(config_module_path, "config_manager.config",&model_config_files)) {AERROR << "config_root_path : " << config_module_path << " get file list error.";return false;}for (const auto &model_config_file : model_config_files) {// 用定义的proto message ModelConfigFileListProto 读取文件 config_manager.config 的model_config_path参数ModelConfigFileListProto file_list_proto;if (!GetProtoFromASCIIFile(model_config_file, &file_list_proto)) {AERROR << "Invalid ModelConfigFileListProto file: " << model_config_file;return false;}// model_config_path : 每一个后缀名为"config_manager.config"的文件for (const std::string &model_config_path : file_list_proto.model_config_path()) {// 获取绝对路径const std::string abs_path = GetAbsolutePath(work_root_, model_config_path);// 用定义的proto message MultiModelConfigProto 读取文件 参数信息,存储moudle下所有文件名MultiModelConfigProto multi_model_config_proto;if (!GetProtoFromASCIIFile(abs_path, &multi_model_config_proto)) {AERROR << "Invalid MultiModelConfigProto file: " << abs_path;return false;}// model_config_proto 每一个模块下各功能的配置参数for (const ModelConfigProto &model_config_proto : multi_model_config_proto.model_configs()) {// // 用定义的proto message ModelConfig 读取moudle下所有文件的参数信息 ModelConfig *model_config = new ModelConfig();if (!model_config->Reset(model_config_proto)) {return false;}AINFO << "load ModelConfig succ. name: " << model_config->name();// 将各模块功能类名和配置参数存储在字典 model_config_map_ 中auto result = model_config_map_.emplace(model_config->name(), model_config);if (!result.second) {AWARN << "duplicate ModelConfig, name: " << model_config->name();return false;}}}}

用Proto读取文件参数,最终会将各模块功能类名和配置参数存储在字典 model_config_map_ ,定义proto message信息位于:apollo/modules/perception/proto/perception_config_schema.proto文件中,如下:

syntax = "proto2";
package apollo.perception;message KeyValueInt {optional string name = 1;optional int32 value = 2;
}message KeyValueString {optional string name = 1;optional bytes value = 2;
}message KeyValueDouble {optional string name = 1;optional double value = 2;
}message KeyValueFloat {optional string name = 1;optional float value = 2;
}message KeyValueBool {optional string name = 1;optional bool value = 2;
}message KeyValueArrayInt {optional string name = 1;repeated int32 values = 2;
}message KeyValueArrayString {optional string name = 1;repeated bytes values = 2;
}message KeyValueArrayDouble {optional string name = 1;repeated double values = 2;
}message KeyValueArrayFloat {optional string name = 1;repeated float values = 2;
}message KeyValueArrayBool {optional string name = 1;repeated bool values = 2;
}
message ModelConfigProto {optional string name = 1;optional string version = 2;repeated KeyValueInt integer_params = 3;repeated KeyValueString string_params = 4;repeated KeyValueDouble double_params = 5;repeated KeyValueFloat float_params = 6;repeated KeyValueBool bool_params = 7;repeated KeyValueArrayInt array_integer_params = 8;repeated KeyValueArrayString array_string_params = 9;repeated KeyValueArrayDouble array_double_params = 10;repeated KeyValueArrayFloat array_float_params = 11;repeated KeyValueArrayBool array_bool_params = 12;
}message MultiModelConfigProto {repeated ModelConfigProto model_configs = 1;
}message ModelConfigFileListProto {repeated string model_config_path = 1;
}

每一个message就相当于定义了一个struct,其中包含许多成员变量。其中ModelConfigFileListProto定义了一个向量,用来指定每个具体参数配置文件的位置,而MultiModelConfigProto则定义了一个ModelConfigProto类型的向量,即定义的具体配置参数,从ModelConfigProto类型的message文件不难看出,其实所谓的配置参数,就是string类型到不同数据类型的一个map映射。

拿lidar模块的配置参数为例,首先由apollo/modules/perception/production/conf/perception/lidar/config_manager.config文件实例化上述protobuf文件中定义的message ModelConfigFileListProto:

model_config_path: "./conf/perception/lidar/modules/map_manager.config"
model_config_path: "./conf/perception/lidar/modules/scene_manager.config"
model_config_path: "./conf/perception/lidar/modules/object_filter_bank.config"
model_config_path: "./conf/perception/lidar/modules/pointcloud_preprocessor.config"
model_config_path: "./conf/perception/lidar/modules/roi_boundary_filter.config"
model_config_path: "./conf/perception/lidar/modules/hdmap_roi_filter.config"
model_config_path: "./conf/perception/lidar/modules/cnnseg.config"
model_config_path: "./conf/perception/lidar/modules/ncut.config"
model_config_path: "./conf/perception/lidar/modules/spatio_temporal_ground_detector.config"
model_config_path: "./conf/perception/lidar/modules/lidar_obstacle_pipeline.config"
model_config_path: "./conf/perception/lidar/modules/fused_classifier.config"
model_config_path: "./conf/perception/lidar/modules/multi_lidar_fusion.config"
model_config_path: "./conf/perception/lidar/modules/roi_service.config"
model_config_path: "./conf/perception/lidar/modules/ground_service.config"
model_config_path: "./conf/perception/lidar/modules/ground_service_detector.config"

可以看出其实就是对ModelConfigFileListProto消息中的model_config_path成员变量进行了实例化,指明了参数定义文件的路径。例如lidar_obstacle_pipeline.config,我们打开modules/conti_ars_preprocessor.config看下具体内容:

model_configs {name: "LidarObstacleDetection"version: "1.0.0"string_params {name: "root_path"value: "./data/perception/lidar/models/lidar_obstacle_pipeline"}
}model_configs {name: "LidarObstacleTracking"version: "1.0.0"string_params {name: "root_path"value: "./data/perception/lidar/models/lidar_obstacle_pipeline"}
}

可以看到,该配置文件包含激光雷达检测和跟踪两个功能,这个文件其实就是对MultiModelConfigProto消息中的model_configs成员变量进行了初始化,name是功能类名

detection——Proc

初始化函数剖析完了,接下来看组件detection_component的Proc函数,主要调用Process() 算法处理逻辑

      bool DetectionComponent::Proc(const std::shared_ptr<drivers::PointCloud> &message){AINFO << std::setprecision(16)<< "Enter detection component, message timestamp: "<< message->measurement_time()<< " current timestamp: " << Clock::NowInSeconds();auto out_message = std::make_shared<LidarFrameMessage>();bool status = InternalProc(message, out_message);if (status){writer_->Write(out_message);AINFO << "Send lidar detect output message.";}return status;}

Proc调用InternalProc方法,InternalProc继续调用apollo/modules/perception/lidar/app/lidar_obstacle_detection.h中的Process() 算法处理逻辑

InternalProc方法,备注源码如下:

      bool DetectionComponent::InternalProc(const std::shared_ptr<const drivers::PointCloud> &in_message,const std::shared_ptr<LidarFrameMessage> &out_message)/*输入:drivers::PointCloud 原始点云输出:LidarFrameMessaglidar 处理结果*/{ // 序列号uint32_t seq_num = seq_num_.fetch_add(1);// 时间戳const double timestamp = in_message->measurement_time();// 当前时间const double cur_time = Clock::NowInSeconds();const double start_latency = (cur_time - timestamp) * 1e3;AINFO << std::setprecision(16) << "FRAME_STATISTICS:Lidar:Start:msg_time["<< timestamp << "]:sensor[" << sensor_name_ << "]:cur_time[" << cur_time<< "]:cur_latency[" << start_latency << "]";out_message->timestamp_ = timestamp;out_message->lidar_timestamp_ = in_message->header().lidar_timestamp();out_message->seq_num_ = seq_num;// 处理状态:检测out_message->process_stage_ = ProcessStage::LIDAR_DETECTION;// 错误码out_message->error_code_ = apollo::common::ErrorCode::OK;auto &frame = out_message->lidar_frame_;// 并发对象池,结合单例模式,获取目标的智能指针// 思想:一个对象只能有一个池子,用对象从池子里面取,每个池子有一个管理者来管理所对应的池子,取对象从管理者这里申请frame = lidar::LidarFramePool::Instance().Get();frame->cloud = base::PointFCloudPool::Instance().Get();frame->timestamp = timestamp;frame->sensor_info = sensor_info_;Eigen::Affine3d pose = Eigen::Affine3d::Identity();Eigen::Affine3d pose_novatel = Eigen::Affine3d::Identity();const double lidar_query_tf_timestamp = timestamp - lidar_query_tf_offset_ * 0.001;// 获取当前帧 坐标系到世界坐标系的位置变换,GPS到世界坐标系的位置变换// transform_wrapper原理和ROS tf变换一致,首先各传感器的位置关系需要通过广播形式发送出去if (!lidar2world_trans_.GetSensor2worldTrans(lidar_query_tf_timestamp, &pose, &pose_novatel)){out_message->error_code_ = apollo::common::ErrorCode::PERCEPTION_ERROR_TF;AERROR << "Failed to get pose at time: " << lidar_query_tf_timestamp;return false;}frame->lidar2world_pose = pose;frame->novatel2world_pose = pose_novatel;// 传感器名和lidar传感器转GPS的外参转换矩阵lidar::LidarObstacleDetectionOptions detect_opts;detect_opts.sensor_name = sensor_name_;// lidar到世界坐标系的变换 *lidar2world_trans_  =  *detect_opts.sensor2novatel_extrinsicslidar2world_trans_.GetExtrinsics(&detect_opts.sensor2novatel_extrinsics);// 掉用LidarObstacleDetection类的Process方法// frame.get() 获取存储的指针lidar::LidarProcessResult ret = detector_->Process(detect_opts, in_message, frame.get());if (ret.error_code != lidar::LidarErrorCode::Succeed){out_message->error_code_ =apollo::common::ErrorCode::PERCEPTION_ERROR_PROCESS;AERROR << "Lidar detection process error, " << ret.log;return false;}return true;}

看下LidarFrameMessageLidarFrame,字段需要了解,不然都不知道啥意思

  • LidarFrameMessage
class LidarFrameMessage {public:LidarFrameMessage() : lidar_frame_(nullptr) {type_name_ = "LidarFrameMessage";}~LidarFrameMessage() = default;std::string GetTypeName() const { return type_name_; }LidarFrameMessage* New() const { return new LidarFrameMessage; }public:double timestamp_ = 0.0;uint64_t lidar_timestamp_ = 0;uint32_t seq_num_ = 0;std::string type_name_;ProcessStage process_stage_ = ProcessStage::UNKNOWN_STAGE;apollo::common::ErrorCode error_code_ = apollo::common::ErrorCode::OK;std::shared_ptr<lidar::LidarFrame> lidar_frame_;
};
  • 结构体:LidarFrame
struct LidarFrame {EIGEN_MAKE_ALIGNED_OPERATOR_NEW// point cloudstd::shared_ptr<base::AttributePointCloud<base::PointF>> cloud;// world point cloudstd::shared_ptr<base::AttributePointCloud<base::PointD>> world_cloud;// timestampdouble timestamp = 0.0;// lidar to world poseEigen::Affine3d lidar2world_pose = Eigen::Affine3d::Identity();// lidar to world poseEigen::Affine3d novatel2world_pose = Eigen::Affine3d::Identity();// hdmap structstd::shared_ptr<base::HdmapStruct> hdmap_struct = nullptr;// segmented objectsstd::vector<std::shared_ptr<base::Object>> segmented_objects;// tracked objectsstd::vector<std::shared_ptr<base::Object>> tracked_objects;// point cloud roi indicesbase::PointIndices roi_indices;// point cloud non ground indicesbase::PointIndices non_ground_indices;// secondary segmentor indicesbase::PointIndices secondary_indices;// sensor infobase::SensorInfo sensor_info;// reserve stringstd::string reserve;void Reset() {if (cloud) {cloud->clear();}if (world_cloud) {world_cloud->clear();}timestamp = 0.0;lidar2world_pose = Eigen::Affine3d::Identity();novatel2world_pose = Eigen::Affine3d::Identity();if (hdmap_struct) {hdmap_struct->road_boundary.clear();hdmap_struct->road_polygons.clear();hdmap_struct->junction_polygons.clear();hdmap_struct->hole_polygons.clear();}segmented_objects.clear();tracked_objects.clear();roi_indices.indices.clear();non_ground_indices.indices.clear();secondary_indices.indices.clear();}void FilterPointCloud(base::PointCloud<base::PointF> *filtered_cloud,const std::vector<uint32_t> &indices) {if (cloud && filtered_cloud) {filtered_cloud->CopyPointCloudExclude(*cloud, indices);}}
};  // struct LidarFrame
#define UNUSED(param) (void)param

假如一个有返回值的函数 ,如调用时是没有使用它的返回值,编译器会给出一个警告 ,如果用void强制转换一下,则明确告诉编译器不使用返回值,也就是为了消除警告

接下来看detector_->Process() 算法逻辑

      LidarProcessResult LidarObstacleDetection::Process(const LidarObstacleDetectionOptions &options,const std::shared_ptr<apollo::drivers::PointCloud const> &message, LidarFrame *frame){const auto &sensor_name = options.sensor_name;// 用来屏蔽无效參数的,消除警告PERF_FUNCTION_WITH_INDICATOR(options.sensor_name);PERF_BLOCK_START();PointCloudPreprocessorOptions preprocessor_options;        // 传感器转GPS的外参 preprocessor_options.sensor2novatel_extrinsics = options.sensor2novatel_extrinsics;PERF_BLOCK_END_WITH_INDICATOR(sensor_name, "preprocess");// 点云预处理if (cloud_preprocessor_->Preprocess(preprocessor_options, message, frame)) {return ProcessCommon(options, frame);          // 模型推理}return LidarProcessResult(LidarErrorCode::PointCloudPreprocessorError,"Failed to preprocess point cloud.");}

阅读代码,可以得到这样的流程图

点云预处理

比较简单,就是去除点云中的NaN点,对xyz范围过滤,剔除车身周围点,然后将点云(位置xyz,时间戳,高度height,beam_id,标签label)形式存储到LidarFrame结构体中,后将点云转换到世界坐标系下

bool PointCloudPreprocessor::Preprocess(const PointCloudPreprocessorOptions& options,const std::shared_ptr<apollo::drivers::PointCloud const>& message,LidarFrame* frame) const {if (frame == nullptr) {return false;}if (frame->cloud == nullptr) {// 点云对象池,结合单例设计模式frame->cloud = base::PointFCloudPool::Instance().Get();}if (frame->world_cloud == nullptr) {frame->world_cloud = base::PointDCloudPool::Instance().Get();}frame->cloud->set_timestamp(message->measurement_time());if (message->point_size() > 0) {// 设置该帧点云数大小frame->cloud->reserve(message->point_size());base::PointF point;for (int i = 0; i < message->point_size(); ++i) {const apollo::drivers::PointXYZIT& pt = message->point(i);// 过滤无效点if (filter_naninf_points_) {if (std::isnan(pt.x()) || std::isnan(pt.y()) || std::isnan(pt.z())) {continue;}// 过滤超范围的点if (fabs(pt.x()) > kPointInfThreshold ||fabs(pt.y()) > kPointInfThreshold ||fabs(pt.z()) > kPointInfThreshold) {continue;}}Eigen::Vector3d vec3d_lidar(pt.x(), pt.y(), pt.z());// 点在GPS坐标系的xyz位置Eigen::Vector3d vec3d_novatel = options.sensor2novatel_extrinsics * vec3d_lidar;// 过滤车身范围内的点云,消除车身反射的影响if (filter_nearby_box_points_ && vec3d_novatel[0] < box_forward_x_ &&vec3d_novatel[0] > box_backward_x_ &&vec3d_novatel[1] < box_forward_y_ &&vec3d_novatel[1] > box_backward_y_) {continue;}// Z方向点云过滤if (filter_high_z_points_ && pt.z() > z_threshold_) {continue;}point.x = pt.x();point.y = pt.y();point.z = pt.z();point.intensity = static_cast<float>(pt.intensity());// 点的点云位置xyz,时间戳,高度height(float的最大值),beam_id(点在原始点云中的索引),标签labelframe->cloud->push_back(point, static_cast<double>(pt.timestamp()) * 1e-9,std::numeric_limits<float>::max(), i, 0);}// 将预处理后点云针转换到世界坐标系下TransformCloud(frame->cloud, frame->lidar2world_pose, frame->world_cloud);}return true;
}

高精地图定位信息获取

简单介绍下高精度地图:

  • 传统地图的技术尚未达到自动驾驶的需求,高精地图更类似于自动驾驶的专题组,但国内可能为了称谓方便还是称它为高精地图。高精地图并不是特指精度,它在描述上更加的全面、准确和清晰,对实时性的要求更高。
  • 自动驾驶车辆搭载的传感器类型有很多,但64线激光雷达、Camera和Radar等传感器都有一定局限性,基于这些传感器本身的局限性,高精地图能够提供非常大的帮助。开发者可以把高精地图看作是离线的传感器,高精地图里已标注了道路元素的位置。出现物体的遮挡时,感知模块就可以提前做针对性的检测(感兴趣区域ROI),不仅可以减少感知模块的工作量,而且可以解决Deep Learning 的部分缺陷。识别可能会有些误差,但先验之后可提高识别率。

高精地图的主要特征:描述车道、车道的边界线、道路上各种交通设施和人行横道,即把所有东西、所有人能看到的影响交通驾驶行为的特性全部表述出来。

根据高精度地图hdmap(高度自动驾驶地图),查询当前帧点云的位置(常用采用ndt点云定位,它是一种scan-to-map的点云配准算法),获取高精地图中定位位置后,查找离定位位置距离为roi_search_distance_的所有道路、交叉路口的信息的road_polygons、road_boundary、hole_polygons、junction_polygons存到frame里

代码位置:apollo/modules/perception/lidar/lib/map_manager/map_manager.cc

bool MapManager::Update(const MapManagerOptions& options, LidarFrame* frame) {if (!frame) {// 判断点云是否为空AINFO << "Frame is nullptr.";return false;}if (!(frame->hdmap_struct)) {frame->hdmap_struct.reset(new base::HdmapStruct); // 重置地图}if (!hdmap_input_){ // 看看输入的地图是否为空初始化的时候给它赋的值AINFO << "Hdmap input is nullptr";return false;}if (update_pose_) // 是否需要更新位置{                 // 获取自身定位if (!QueryPose(&(frame->lidar2world_pose))) {AINFO << "Failed to query updated pose.";}}base::PointD point; // 设置一个point接收定位信息point.x = frame->lidar2world_pose.translation()(0);point.y = frame->lidar2world_pose.translation()(1);point.z = frame->lidar2world_pose.translation()(2);/*获取高精地图中,离我们所在定位位置距离为roi_search_distance_的所有道路信息的road_polygons、road_boundary、hole_polygons、junction_polygons存到frame里如果没有则hdmap_input_->GetRoiHDMapStruct()返回false,所有的道路信息置空*/if (!hdmap_input_->GetRoiHDMapStruct(point, roi_search_distance_,frame->hdmap_struct)) {// 路面的polygons多边形信息frame->hdmap_struct->road_polygons.clear();// 道路边界线frame->hdmap_struct->road_boundary.clear();// hole_polygonsm 没太看懂,没怎么用到这个信息frame->hdmap_struct->hole_polygons.clear();// 交叉路口多边形信息frame->hdmap_struct->junction_polygons.clear();AINFO << "Failed to get roi from hdmap.";}return true;
}

具体的GetRoiHDMapStruct()我也没太细看,有空再花点时间理理,代码位置:apollo/modules/perception/map/hdmap/hdmap_input.c

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 junctionstd::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);// 首先判断判断指针是否非空;接着调用hdmap_对象的GetRoadBoundaries()方法获取路面边界和路口信息if (hdmap_->GetRoadBoundaries(point, distance, &road_boundary_vec,&junctions_vec) != 0) {AERROR << "Failed to get road boundary, 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 junctionEigenVector<base::RoadBoundary> road_boundaries;// 将存储路面和路口信息的变量整合到hdmap_struct_ptr指向的变量中MergeBoundaryJunction(road_boundary_vec, junctions_vec, &road_boundaries,&(hdmap_struct_ptr->road_polygons),&(hdmap_struct_ptr->junction_polygons));// Filter road boundary by junction// 利用路口信息过滤掉多余的路面信息GetRoadBoundaryFilteredByJunctions(road_boundaries,hdmap_struct_ptr->junction_polygons,&(hdmap_struct_ptr->road_boundary));return true;
}

apollo5.0获取地位信息后,还需要经过高精地图ROI过滤器(HDMap ROI Filter),从高精地图检索到包含路面、路口的可驾驶区域后送入分割,apollo6.0,7.0取消了分割,直接送入3D点云检测模块

障碍物检测

Apollo 7.0 中引入 MaskPointPillar 激光雷达检测算法,本文先看pointPillar 算法,重点阐述下pointpillar的整体流程,具体cuda版Inference细节,之后另开一篇文章阐述,不熟悉pointpillar可以先去看看相关的论文。

代码路径::apollo/modules/perception/lidar/lib/detector/point_pillars_detection/point_pillars_detection.cc

调用PointPillarsDetectiondetect方法

参数都是调用apollo/modules/perception/common/perception_gflags.h封好的宏,这些宏通过调用googlegflags库,解析命令行参数,perception所有的命令行参数在/apollo/modules/perception/production/conf/perception/perception_common.flag配置

PointPillarsDetection的流程图:

bool PointPillarsDetection::Detect(const LidarDetectorOptions& options,LidarFrame* frame) {// check inputif (frame == nullptr) {AERROR << "Input null frame ptr.";return false;}if (frame->cloud == nullptr) {AERROR << "Input null frame cloud.";return false;}if (frame->cloud->size() == 0) {AERROR << "Input none points.";return false;}// record input cloud and lidar frameoriginal_cloud_ = frame->cloud;original_world_cloud_ = frame->world_cloud;lidar_frame_ref_ = frame;// check outputframe->segmented_objects.clear();// FLAGS_gpu_id 默认为0,使用0号显卡if (cudaSetDevice(FLAGS_gpu_id) != cudaSuccess) {AERROR << "Failed to set device to gpu " << FLAGS_gpu_id;return false;}// 调用Tine构造函数,成员变量_start = std::chrono::system_clock::now(); // 获取系统的时间戳,单位微秒Timer timer;int num_points;cur_cloud_ptr_ = std::shared_ptr<base::PointFCloud>(new base::PointFCloud(*original_cloud_));// down sample the point cloud through filtering beams// FLAGS_enable_downsample_beams 默认 falseif (FLAGS_enable_downsample_beams) {base::PointFCloudPtr downsample_beams_cloud_ptr(new base::PointFCloud());// beam_id下采样,下采样因子FLAGS_downsample_beams_factor默认为4,减少点云数据量if (DownSamplePointCloudBeams(original_cloud_, downsample_beams_cloud_ptr,FLAGS_downsample_beams_factor)) {cur_cloud_ptr_ = downsample_beams_cloud_ptr;} else {AWARN << "Down-sample beams factor must be >= 1. Cancel down-sampling."" Current factor: "<< FLAGS_downsample_beams_factor;}}// down sample the point cloud through filtering voxel grid// 体素滤波下采样,FLAGS_enable_downsample_pointcloud默认为falseif (FLAGS_enable_downsample_pointcloud) {pcl::PointCloud<pcl::PointXYZI>::Ptr pcl_cloud_ptr(new pcl::PointCloud<pcl::PointXYZI>());pcl::PointCloud<pcl::PointXYZI>::Ptr filtered_cloud_ptr(new pcl::PointCloud<pcl::PointXYZI>());TransformToPCLXYZI(*cur_cloud_ptr_, pcl_cloud_ptr);// 下采样尺寸xyz分为默认为0.01DownSampleCloudByVoxelGrid(pcl_cloud_ptr, filtered_cloud_ptr, FLAGS_downsample_voxel_size_x,FLAGS_downsample_voxel_size_y, FLAGS_downsample_voxel_size_z);// transform pcl point cloud to apollo point cloudbase::PointFCloudPtr downsample_voxel_cloud_ptr(new base::PointFCloud());TransformFromPCLXYZI(filtered_cloud_ptr, downsample_voxel_cloud_ptr);cur_cloud_ptr_ = downsample_voxel_cloud_ptr;}// 计算下采样时间downsample_time_ = timer.toc(true);num_points = cur_cloud_ptr_->size();AINFO << "num points before fusing: " << num_points;// fuse clouds of preceding frames with current cloud// fuse 的是当前的点云和的点云,作用:当点云比较稀疏时,为了提升检测效果,一般会把当前帧的点云和前几帧点云融合,弥补稀疏效果// 点云成员变量points_timestamp_初始化cur_cloud_ptr_->mutable_points_timestamp()->assign(cur_cloud_ptr_->size(),0.0);if (FLAGS_enable_fuse_frames && FLAGS_num_fuse_frames > 1) {// before fusing// 将融合时间间隔大于0.5的点过滤while (!prev_world_clouds_.empty() && frame->timestamp - prev_world_clouds_.front()->get_timestamp() > FLAGS_fuse_time_interval) {prev_world_clouds_.pop_front();}// transform current cloud to world coordinate and save to a new ptr// 将当前帧的点云转到世界坐标系下,包含xyzi信息base::PointDCloudPtr cur_world_cloud_ptr = std::make_shared<base::PointDCloud>();for (size_t i = 0; i < cur_cloud_ptr_->size(); ++i) {auto& pt = cur_cloud_ptr_->at(i);Eigen::Vector3d trans_point(pt.x, pt.y, pt.z);trans_point = lidar_frame_ref_->lidar2world_pose * trans_point;PointD world_point;world_point.x = trans_point(0);world_point.y = trans_point(1);world_point.z = trans_point(2);world_point.intensity = pt.intensity;cur_world_cloud_ptr->push_back(world_point);}cur_world_cloud_ptr->set_timestamp(frame->timestamp);// fusing cloudsfor (auto& prev_world_cloud_ptr : prev_world_clouds_) {num_points += prev_world_cloud_ptr->size();}// 将过滤后的之前点云加入到当前帧点云中FuseCloud(cur_cloud_ptr_, prev_world_clouds_);// after fusing// FLAGS_num_fuse_frames 默认为5while (static_cast<int>(prev_world_clouds_.size()) >= FLAGS_num_fuse_frames - 1) {prev_world_clouds_.pop_front();}prev_world_clouds_.emplace_back(cur_world_cloud_ptr);}AINFO << "num points after fusing: " << num_points;// 计算fuse时间fuse_time_ = timer.toc(true);// shuffle points and cut off// enable_shuffle_points 默认falseif (FLAGS_enable_shuffle_points) {// FLAGS_max_num_points 默认为int的最大值 2^31-1num_points = std::min(num_points, FLAGS_max_num_points);// 对[0-num_points)之间索引,打乱std::vector<int> point_indices = GenerateIndices(0, num_points, true);// 获取打乱后的点云base::PointFCloudPtr shuffle_cloud_ptr(new base::PointFCloud(*cur_cloud_ptr_, point_indices));cur_cloud_ptr_ = shuffle_cloud_ptr;}// 计算数据shuffle时间shuffle_time_ = timer.toc(true);// point cloud to arrayfloat* points_array = new float[num_points * FLAGS_num_point_feature]();// FLAGS_normalizing_factor :255// 过滤范围外的点云,将点云数据转为一维数组CloudToArray(cur_cloud_ptr_, points_array, FLAGS_normalizing_factor);cloud_to_array_time_ = timer.toc(true);// inferencestd::vector<float> out_detections;std::vector<int> out_labels;point_pillars_ptr_->DoInference(points_array, num_points, &out_detections,&out_labels);inference_time_ = timer.toc(true);// transfer output bounding boxes to objectsGetObjects(&frame->segmented_objects, frame->lidar2world_pose,&out_detections, &out_labels);collect_time_ = timer.toc(true);delete[] points_array;return true;
}

inference以后再细讲,继续看GetObjects方法:

作用:将推理的输出结构detectionslabels转换到ObjectObject包含信息有:包围框的方向direction,朝向角theta,每个类别概率type_probs,小类型概率(分的更细)sub_typelidar_supplement结构体(包含包围框8个顶点坐标cloud,8个顶点其在世界坐标系的坐标cloud_world,检测方法的类名raw_classification_methods等。

void PointPillarsDetection::GetObjects(std::vector<std::shared_ptr<Object>>* objects, const Eigen::Affine3d& pose,std::vector<float>* detections, std::vector<int>* labels) {// 目标个数int num_objects = detections->size() / FLAGS_num_output_box_feature; // FLAGS_num_output_box_feature 为 7objects->clear();// 结合单例设计模式,调用并发对象池,创建num_objects个Object对象base::ObjectPool::Instance().BatchGet(num_objects, objects);for (int i = 0; i < num_objects; ++i) {auto& object = objects->at(i);object->id = i;// read params of bounding boxfloat x = detections->at(i * FLAGS_num_output_box_feature + 0);float y = detections->at(i * FLAGS_num_output_box_feature + 1);float z = detections->at(i * FLAGS_num_output_box_feature + 2);float dx = detections->at(i * FLAGS_num_output_box_feature + 4);float dy = detections->at(i * FLAGS_num_output_box_feature + 3);float dz = detections->at(i * FLAGS_num_output_box_feature + 5);float yaw = detections->at(i * FLAGS_num_output_box_feature + 6);// 获取预测的偏航角,范围在[-pi/2,pi/2]yaw += M_PI / 2;yaw = std::atan2(sinf(yaw), cosf(yaw));yaw = -yaw;// directionsobject->theta = yaw;object->direction[0] = cosf(yaw);object->direction[1] = sinf(yaw);object->direction[2] = 0;object->lidar_supplement.is_orientation_ready = true;// compute vertexes of bounding box and transform to world coordinateobject->lidar_supplement.num_points_in_roi = 8;object->lidar_supplement.on_use = true;object->lidar_supplement.is_background = false;float roll = 0, pitch = 0;// 欧拉角转旋转向量Eigen::Quaternionf quater =Eigen::AngleAxisf(roll, Eigen::Vector3f::UnitX()) *Eigen::AngleAxisf(pitch, Eigen::Vector3f::UnitY()) *Eigen::AngleAxisf(yaw, Eigen::Vector3f::UnitZ());Eigen::Translation3f translation(x, y, z);// 计算放射变换矩阵,平移向量*旋转向量Eigen::Affine3f affine3f = translation * quater.toRotationMatrix();// 包围框的8个顶点坐标,8个顶点在世界坐标系下的位置for (float vx : std::vector<float>{dx / 2, -dx / 2}) {for (float vy : std::vector<float>{dy / 2, -dy / 2}) {for (float vz : std::vector<float>{0, dz}) {Eigen::Vector3f v3f(vx, vy, vz);v3f = affine3f * v3f;PointF point;point.x = v3f.x();point.y = v3f.y();point.z = v3f.z();object->lidar_supplement.cloud.push_back(point);Eigen::Vector3d trans_point(point.x, point.y, point.z);trans_point = pose * trans_point;PointD world_point;world_point.x = trans_point(0);world_point.y = trans_point(1);world_point.z = trans_point(2);object->lidar_supplement.cloud_world.push_back(world_point);}}}// classification// 枚举 MAX_OBJECT_TYPE = 6 表示大目标// raw_probs二维数组,表示每个分类方法的概率object->lidar_supplement.raw_probs.push_back(std::vector<float>(static_cast<int>(base::ObjectType::MAX_OBJECT_TYPE), 0.f));// Name()返回"PointPillarsDetection" ,raw_classification_methods 存储检测方法的类名object->lidar_supplement.raw_classification_methods.push_back(Name());// 获取object的子类型,比较细的划分:CAR,PEDESTRIAN,CYCLIST,UNKNOWNobject->sub_type = GetObjectSubType(labels->at(i));// 大类别,分的不是很细:VEHICLE,BICYCLE,UNKNOWN_MOVABLEobject->type = base::kSubType2TypeMap.at(object->sub_type);object->lidar_supplement.raw_probs.back()[static_cast<int>(object->type)] = 1.0f;// 每个类别的概率存储在type_probs字段object->type_probs.assign(object->lidar_supplement.raw_probs.back().begin(),object->lidar_supplement.raw_probs.back().end());}
}

数据结构Object

Object:用于表征一帧检测中每个检测物体/检测框的一些属性,文件位置:Apollo/modules/perception/base/object.h

只看boundingbox相关的字段信息,Object结构体如下:

struct alignas(16) Object {EIGEN_MAKE_ALIGNED_OPERATOR_NEWObject();std::string ToString() const;void Reset();int id = -1;// 每帧object的id,必要// @brief convex hull of the object, requiredPointCloud<PointD> polygon;// object拟合凸多边形的顶点坐标,必要// 方向包围框的信息Eigen::Vector3f direction = Eigen::Vector3f(1, 0, 0); // 方向向量,必要float theta = 0.0f;  // 包围框朝向角,即偏航角yawfloat theta_variance = 0.0f;  // 角度方差,必要Eigen::Vector3d center = Eigen::Vector3d(0, 0, 0);// 包围框中心坐标(cx, cy, cz),必要Eigen::Matrix3f center_uncertainty; // center不确定性的协方差矩阵,必要Eigen::Vector3f size = Eigen::Vector3f(0, 0, 0);// 包围框的[length, width, height],length必要的Eigen::Vector3f size_variance = Eigen::Vector3f(0, 0, 0);//size 的方差,必要的// @brief anchor point, requiredEigen::Vector3d anchor_point = Eigen::Vector3d(0, 0, 0);ObjectType type = ObjectType::UNKNOWN; // 类别,必要的std::vector<float> type_probs; // 每个类别的概率,必要的ObjectSubType sub_type = ObjectSubType::UNKNOWN; // 子类型,可选std::vector<float> sub_type_probs; // 子类型的概率,可选float confidence = 1.0f; // 存在置信度,必要的// @brief sensor-specific object supplements, optionalLidarObjectSupplement lidar_supplement;
};

LidarObjectSupplement 结构体提供该Object的一些原始数据,以及不在Object类别定义中的各种传感器特有的东西,Object定义为所有module共用的部分

GetObjects方法分类部分,根据字典映射,将子类别sub_type转为大类别typekSubType2TypeMap字典如下:

/*** ObjectSubType mapping*/
const std::map<ObjectSubType, ObjectType> kSubType2TypeMap = {{ObjectSubType::UNKNOWN, ObjectType::UNKNOWN},{ObjectSubType::UNKNOWN_MOVABLE, ObjectType::UNKNOWN_MOVABLE},{ObjectSubType::UNKNOWN_UNMOVABLE, ObjectType::UNKNOWN_UNMOVABLE},{ObjectSubType::CAR, ObjectType::VEHICLE},{ObjectSubType::VAN, ObjectType::VEHICLE},{ObjectSubType::TRUCK, ObjectType::VEHICLE},{ObjectSubType::BUS, ObjectType::VEHICLE},{ObjectSubType::CYCLIST, ObjectType::BICYCLE},{ObjectSubType::MOTORCYCLIST, ObjectType::BICYCLE},{ObjectSubType::TRICYCLIST, ObjectType::BICYCLE},{ObjectSubType::PEDESTRIAN, ObjectType::PEDESTRIAN},{ObjectSubType::TRAFFICCONE, ObjectType::UNKNOWN_UNMOVABLE},{ObjectSubType::MAX_OBJECT_TYPE, ObjectType::MAX_OBJECT_TYPE},
};

障碍物边框构建

apollo/modules/perception/lidar/app/lidar_obstacle_detection.cc中的ProcessCommo方法:

        if (use_object_builder_){ObjectBuilderOptions builder_options;if (!builder_.Build(builder_options, frame)){return LidarProcessResult(LidarErrorCode::ObjectBuilderError,"Failed to build objects.");}}

上面代码会调用类ObjectBuilderBuild方法,代码位于:apollo/modules/perception/lidar/lib/object_builder/object_builder.cc

该模块为检测到的障碍物构建边界框。由于遮挡或距离对激光雷达的影响,点云形成障碍物可能存在稀疏或只覆盖了障碍物表面的一部分。因此,边界框构建器的工作就是恢复完整的边界框并给出边界框点。边界框的主要目的是估计障碍物的名称,即便点云数据是稀疏的。同样地,边界框也被用于可视化障碍物。

apollo边框构建主要计算Object结构体的字段信息,如:

  • 拟合凸多边形的顶点坐标polygon ,调用Apollo/modules/perception/common/geometry/convex_hull_2d.hGetConvexHull
  • bounding-box的点云的尺寸size和中心center,调用Apollo/modules/perception/common/geometry/common.hCalculateBBoxSizeCenter2DXY
  • object其它的信息,包括:object->anchor_pointobject->latest_tracked_time
static const float kEpsilonForSize = 1e-2f;
static const float kEpsilonForLine = 1e-3f;
using apollo::perception::base::PointD;
using apollo::perception::base::PointF;
using ObjectPtr = std::shared_ptr<apollo::perception::base::Object>;
using PointFCloud = apollo::perception::base::PointCloud<PointF>;
using PolygonDType = apollo::perception::base::PointCloud<PointD>;bool ObjectBuilder::Init(const ObjectBuilderInitOptions& options) {return true;
}bool ObjectBuilder::Build(const ObjectBuilderOptions& options,LidarFrame* frame) {if (frame == nullptr) {return false;}std::vector<ObjectPtr>* objects = &(frame->segmented_objects);for (size_t i = 0; i < objects->size(); ++i) {if (objects->at(i)) {objects->at(i)->id = static_cast<int>(i);// // 计算点云拟合的凸多边形ComputePolygon2D(objects->at(i));// 计算3d bounding-box的点云的尺寸和中心ComputePolygonSizeCenter(objects->at(i));// 计算object其它的信息,包括:anchor_point,latest_tracked_timeComputeOtherObjectInformation(objects->at(i));}}return true;
}void ObjectBuilder::ComputePolygon2D(ObjectPtr object) {Eigen::Vector3f min_pt;Eigen::Vector3f max_pt;PointFCloud& cloud = object->lidar_supplement.cloud;// 获取object点云的xyz最大和最小值GetMinMax3D(cloud, &min_pt, &max_pt);// PointPillarsDetection::GetObjects计算cloud.size()为8,代表8个顶点,走不到这部if (cloud.size() < 4u) {SetDefaultValue(min_pt, max_pt, object);return;}// 判断是否为线程扰动,不是是线性扰动,需要第一个点x加上kEpsilonForLine,第二个点y加上kEpsilonForLineLinePerturbation(&cloud);common::ConvexHull2D<PointFCloud, PolygonDType> hull;// 计算点云拟合的凸多边形顶点坐标,存储在polygon字段中hull.GetConvexHull(cloud, &(object->polygon));
}void ObjectBuilder::ComputeOtherObjectInformation(ObjectPtr object) {object->anchor_point = object->center;double timestamp = 0.0;size_t num_point = object->lidar_supplement.cloud.size();for (size_t i = 0; i < num_point; ++i) {timestamp += object->lidar_supplement.cloud.points_timestamp(i);}if (num_point > 0) {timestamp /= static_cast<double>(num_point);}// 最近测量的时间戳,latest_tracked_time是必要的object->latest_tracked_time = timestamp;
}void ObjectBuilder::ComputePolygonSizeCenter(ObjectPtr object) {if (object->lidar_supplement.cloud.size() < 4u) {return;}Eigen::Vector3f dir = object->direction;// 计算3d bounding-box的点云的尺寸和中心common::CalculateBBoxSizeCenter2DXY(object->lidar_supplement.cloud, dir,&(object->size), &(object->center));// PointPillarsDetection::GetObject函数中is_background设为falseif (object->lidar_supplement.is_background) {float length = object->size(0);float width = object->size(1);Eigen::Matrix<float, 3, 1> ortho_dir(-object->direction(1),object->direction(0), 0.0);// 保证object的size中第一个值是最大的if (length < width) {object->direction = ortho_dir;object->size(0) = width;object->size(1) = length;}}// 对于小于kEpsilonForSize的size,将其设为大小kEpsilonForSizefor (size_t i = 0; i < 3; ++i) {if (object->size(i) < kEpsilonForSize) { // kEpsilonForSize = 1e-2fobject->size(i) = kEpsilonForSize;}}object->theta = static_cast<float>(atan2(object->direction(1), object->direction(0)));
}

Bounding_box过滤

代码位置:/root/apollo_ws/apollo/modules/perception/lidar/lib/object_filter_bank/object_filter_bank.cc

先看下初始化函数init,直接看代码,注释很详细,参数读取过程都是相同的

bool ObjectFilterBank::Init(const ObjectFilterInitOptions& options) {auto config_manager = lib::ConfigManager::Instance();const lib::ModelConfig* model_config = nullptr;// 第一次调用GetModelConfig已经初始化,直接从字典model_config_map_查找"ObjectFilterBank"文件的信息,以proto存储的// apollo/modules/perception/production/conf/perception/lidar/modules/object_filter_bank.configACHECK(config_manager->GetModelConfig(Name(), &model_config));const std::string work_root = config_manager->work_root();std::string config_file;std::string root_path;// ./data/perception/lidar/models/object_filter_bankACHECK(model_config->get_value("root_path", &root_path));// apollo/modules/perception/production//data/perception/lidar/models/object_filter_bankconfig_file = GetAbsolutePath(work_root, root_path);// apollo/modules/perception/production/data/perception/lidar/models/object_filter_bank/velodyne128config_file = GetAbsolutePath(config_file, options.sensor_name);// apollo/modules/perception/production/data/perception/lidar/models/object_filter_bank/velodyne128/filter_bank.confconfig_file = GetAbsolutePath(config_file, "filter_bank.conf");// proto定义message文件路径:pollo/modules/perception/lidar/lib/object_filter_bank/proto/filter_bank_config.protoFilterBankConfig config;// 用定义的meaage读取filter_bank.conf文件信息  ACHECK(apollo::cyber::common::GetProtoFromFile(config_file, &config));filter_bank_.clear();for (int i = 0; i < config.filter_name_size(); ++i) {const auto& name = config.filter_name(i); // ROIBoundaryFilter// 通过工厂方法模式获取 ROIBoundaryFilter 类的实例指针,也是多态运用BaseObjectFilter* filter = BaseObjectFilterRegisterer::GetInstanceByName(name);if (!filter) {AINFO << "Failed to find object filter: " << name << ", skipped";continue;}// 调用ROIBoundaryFilter::Init函数if (!filter->Init()) {AINFO << "Failed to init object filter: " << name << ", skipped";continue;}filter_bank_.push_back(filter);AINFO << "Filter bank add filter: " << name;}return true;
}

ROIBoundaryFilter的初始化init方法,初始化读取参数过程都是类似的,代码如下:

代码路径:apollo/modules/perception/lidar/lib/object_filter_bank/roi_boundary_filter/roi_boundary_filter.cc

bool ROIBoundaryFilter::Init(const ObjectFilterInitOptions& options) {auto config_manager = lib::ConfigManager::Instance();const lib::ModelConfig* model_config = nullptr;// 第一次调用GetModelConfig已经初始化,直接从字典model_config_map_查找"ROIBoundaryFilter"文件的信息,以proto存储的// apollo/modules/perception/production/conf/perception/lidar/modules/roi_boundary_filter.configACHECK(config_manager->GetModelConfig(Name(), &model_config));const std::string work_root = config_manager->work_root();std::string config_file;zhiusstd::string root_path;// data/perception/lidar/models/object_filter_bankACHECK(model_config->get_value("root_path", &root_path));// apollo/modules/perception/production/data/perception/lidar/models/object_filter_bankconfig_file = GetAbsolutePath(work_root, root_path);// // apollo/modules/perception/production/data/perception/lidar/models/object_filter_bank/roi_boundary_filter.confconfig_file = GetAbsolutePath(config_file, "roi_boundary_filter.conf");// proto定义message文件路径:apollo/modules/perception/proto/roi_boundary_filter_config.protoROIBoundaryFilterConfig config;// 用定义的meaage读取roi_boundary_filter.conf文件信息ACHECK(cyber::common::GetProtoFromFile(config_file, &config));distance_to_boundary_threshold_ = config.distance_to_boundary_threshold(); // -1.0confidence_threshold_ = config.confidence_threshold();                     // 0.5cross_roi_threshold_ = config.cross_roi_threshold();                       // 0.6inside_threshold_ = config.inside_threshold();                             // 1.0return true;
}

下面看 ObjectFilterBank的处理逻辑,就是对检测build出的objects目标进行ROIBoundaryFilter,即调用之前找到的激光雷达周围高精度地图感兴趣区域,来限定部分激光雷达目标;

bool ObjectFilterBank::Filter(const ObjectFilterOptions& options,LidarFrame* frame) {size_t object_number = frame->segmented_objects.size(); // 包围框bounding_box的数量for (auto& filter : filter_bank_) {// 调用ROIBoundaryFilter::Filter方法if (!filter->Filter(options, frame)) {AINFO << "Failed to filter objects in: " << filter->Name(); // ROIBoundaryFilter}}AINFO << "Object filter bank, filtered objects size: from " << object_number<< " to " << frame->segmented_objects.size();return true;
}

根据高精度地图的道路和交叉路口信息对边框构建器ObjectBuilder得到的objects进行过滤,ROIBoundaryFilter::Filter方法具体就不作展开

欢迎大家关注笔者,你的关注是我持续更博的最大动力

Apollo 7.0——percception:lidar源码剖析(万字长文)相关推荐

  1. Apollo 2.0 框架及源码分析(一) | 软硬件框架

    原文地址:https://zhuanlan.zhihu.com/p/33059132 前言 如引言中介绍的,这篇软硬件框架多为现有消息的整合加一些个人的想法.关于 Apollo 介绍的文章已经有许多, ...

  2. Apollo源码剖析学习笔记2

    Apollo 源码剖析学习笔记2 Talker-ListenerNode 目录中包含了 Node 对象.Reader 对象和 Writer 对象.Node 对象主要对应 Ros 中的 Node 节点, ...

  3. 【知其然,知其所以然】配置中心 Apollo源码剖析

    第2章 Apollo源码剖析 能力目标 能够基于Git导入Apollo源码 能够基于IDEA实现DEBUG分析APP创建 掌握Namespace创建过程 掌握Item创建过程 掌握灰度发布创建过程 1 ...

  4. data access components 2.0未响应_Vue2.x 源码剖析之响应式原理

    # Study Notes 本博主会持续更新各种前端的技术,如果各位道友喜欢,可以关注.收藏.点赞下本博主的文章. Vue.js 源码剖析-响应式原理 响应式处理的入口 src/core/insta ...

  5. python解释器源码 pdf_《python解释器源码剖析》第0章--python的架构与编译python

    本系列是以陈儒先生的<python源码剖析>为学习素材,所总结的笔记.不同的是陈儒先生的<python源码剖析>所剖析的是python2.5,本系列对应的是python3.7. ...

  6. JS魔法堂:mmDeferred源码剖析

    一.前言 avalon.js的影响力愈发强劲,而作为子模块之一的mmDeferred必然成为异步调用模式学习之旅的又一站呢!本文将记录我对mmDeferred的认识,若有纰漏请各位指正,谢谢.项目请见 ...

  7. Kafka源码剖析 —— 网络I/O篇 —— 浅析KafkaSelector

    为什么80%的码农都做不了架构师?>>>    ##NioSelector和KafkaSelector有什么区别? 先说结论,KafkaSelector(org.apache.kaf ...

  8. 老李推荐:第3章3节《MonkeyRunner源码剖析》脚本编写示例: MonkeyImage API使用示例 1...

    老李推荐:第3章3节<MonkeyRunner源码剖析>脚本编写示例: MonkeyImage API使用示例 在上一节的第一个"增加日记"的示例中,我们并没有看到日记 ...

  9. Mongoose源码剖析:外篇之web服务器

    引言 在深入Mongoose源码剖析之前,我们应该清楚web服务器是什么?它提供什么服务?怎样提供服务?使用什么协议?客户端如何唯一标识web服务器的资源?下面我们抛开Mongoose,来介绍一个we ...

最新文章

  1. CentOS7 64位下MySQL5.7安装与配置
  2. C++ 关于方法传值
  3. 步进电机红外遥控C语言程序,单片机红外遥控+步进电机+1602液晶显示c语言源程序...
  4. 一篇文章快速掌握Linux基本命令
  5. Druid Spring JDBC Servlet 实现登录功能
  6. 简单的小工具wordlight——让VS变量高亮起来
  7. runtime—新手必学
  8. linux: kill -9
  9. 通过使用阿里云+vuepress快速搭建静态个人博客网页页面
  10. C++函数重载的概念
  11. EasyUI中Datagrid列定位方法
  12. 一分钟在云端快速创建MySQL数据库实例
  13. windows server 2003 版本的识别 及 小技巧
  14. UE4 - 默认鼠标指针的样式修改自定义
  15. 华为华三学习工具模拟器安装教 程(ENSP与HCL)
  16. matplotlib is required for plotting.
  17. 未能联接game center服务器,苹果game center无法连接服务器怎么办呢?
  18. 二手房房源信息数据分析项目完整流程
  19. html恶搞之无限弹窗
  20. XAF将ListView和DetailView一起显示

热门文章

  1. 萨塞克斯大学计算机专业,萨塞克斯大学计算机与数字媒体硕士专业.pdf
  2. 计算机组成原理之概述篇
  3. Varjo:XR体验的终极目标是“串流全世界”
  4. 无界XR落地工体元宇宙:不仅是球赛,还有XR社交新玩法
  5. 第八届cccc团体程序设计天梯赛——个人参赛总结——无代码纯粹的参赛总结
  6. 攻防世界 web篇(一)
  7. 法国国立计算机科学研究所,董未名 - 中国科学院自动化研究所 -
  8. c和python哪个工资高? python开发学习
  9. 程序人生(二)汉语拼音之父周有光去世——一个播音专业安卓程序员有感
  10. Linux开发起步学习笔记(11)----shell基本机制