本文先以感知模块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 需要创建以下文件:
Apollo 是多数据融合的,它融合 Camera、Lidar、Radar 目标,而这 3 个都是一个 component。
感知模块的入口在production目录,通过lanuch加载对应的dag,启动感知模块,感知模块包括多个子模块,在onboard目录中定义。
perception组件路径:
apollo/modules/perception/onboard/component
这个目录下,定义和实现了很多感知相关的组件,本文只关注于 Detection。
detection_component.h
:
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
对于源文件Init
和 Proc
这两个函数需要实现,代码就不贴了,下面会对该部分代码解析
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 文件定义了模块的启动。
下面看激光雷达感知的Launch 文件:
launch文件:Apollo/modules/perception/production/launch/perception_lidar.launch
<!--this file list the modules which will be loaded dynamicly and
their 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
bool DetectionComponent::Init()
{
// 读取配置文件,配置文件定义:modules/perception/onboard/proto/lidar_component_config.proto
LidarDetectionComponentConfig 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
在调用各模块类的处理逻辑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.cpp
init_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_path
const 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.h
auto 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;
}
// 根据传感器名获取传感器信息SensorInfo
bool 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()); \
} \
static std::vector GetAllInstances() { \
std::vector 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())); \
} \
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()); \
} \
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.cpp
init_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.config
ACHECK(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_pipeline
ACHECK(model_config->get_value("root_path", &root_path));
// apollo/modules/perception/production/lidar/models/lidar_obstacle_pipeline
config_file = cyber::common::GetAbsolutePath(work_root, root_path);
// apollo/modules/perception/production/data/perception/lidar/models/lidar_obstacle_pipeline/velodyne128
config_file = cyber::common::GetAbsolutePath(config_file, sensor_name);
// apollo/modules/perception/production/data/perception/lidar/models/lidar_obstacle_pipeline/velodyne128/lidar_obstacle_detection.conf
config_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(); // true
use_object_filter_bank_ = config.use_object_filter_bank(); // true
// PointPillarsDetection
use_object_builder_ = ("PointPillarsDetection" != config.detector() ||
"MaskPillarsDetection" != config.detector());
use_map_manager_ = use_map_manager_ && options.enable_hdmap_input; // true
SceneManagerInitOptions 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:对目标进行ROIBoundaryFilter
ACHECK(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_files
if (!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_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_extrinsics
lidar2world_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;
}
看下LidarFrameMessage
和LidarFrame
,字段需要了解,不然都不知道啥意思
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 cloud
std::shared_ptr<base::AttributePointCloud<base::PointF>> cloud;
// world point cloud
std::shared_ptr<base::AttributePointCloud<base::PointD>> world_cloud;
// timestamp
double timestamp = 0.0;
// lidar to world pose
Eigen::Affine3d lidar2world_pose = Eigen::Affine3d::Identity();
// lidar to world pose
Eigen::Affine3d novatel2world_pose = Eigen::Affine3d::Identity();
// hdmap struct
std::shared_ptr<base::HdmapStruct> hdmap_struct = nullptr;
// segmented objects
std::vector<std::shared_ptr<base::Object>> segmented_objects;
// tracked objects
std::vector<std::shared_ptr<base::Object>> tracked_objects;
// point cloud roi indices
base::PointIndices roi_indices;
// point cloud non ground indices
base::PointIndices non_ground_indices;
// secondary segmentor indices
base::PointIndices secondary_indices;
// sensor info
base::SensorInfo sensor_info;
// reserve string
std::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& 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(pt.intensity());
// 点的点云位置xyz,时间戳,高度height(float的最大值),beam_id(点在原始点云中的索引),标签label
frame->cloud->push_back(point, static_cast(pt.timestamp()) * 1e-9,std::numeric_limits::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 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);
// 首先判断判断指针是否非空;接着调用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 junction
EigenVector<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
调用PointPillarsDetection
的detect
方法
参数都是调用apollo/modules/perception/common/perception_gflags.h
封好的宏,这些宏通过调用google
的gflags
库,解析命令行参数,perception所有的命令行参数在/apollo/modules/perception/production/conf/perception/perception_common.flag
配置
bool PointPillarsDetection::Detect(const LidarDetectorOptions& options,
LidarFrame* frame) {
// check input
if (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 frame
original_cloud_ = frame->cloud;
original_world_cloud_ = frame->world_cloud;
lidar_frame_ref_ = frame;
// check output
frame->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 默认 false
if (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默认为false
if (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.01
DownSampleCloudByVoxelGrid(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 cloud
base::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 clouds
for (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 默认为5
while (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 默认false
if (FLAGS_enable_shuffle_points) {
// FLAGS_max_num_points 默认为int的最大值 2^31-1
num_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 array
float* 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);
// inference
std::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 objects
GetObjects(&frame->segmented_objects, frame->lidar2world_pose,&out_detections, &out_labels);
collect_time_ = timer.toc(true);
delete[] points_array;
return true;
}
inference
以后再细讲,继续看GetObjects
方法:
作用:将推理的输出结构detections
和labels
转换到Object
,Object
包含信息有:包围框的方向direction
,朝向角theta
,每个类别概率type_probs
,小类型概率(分的更细)sub_type
,lidar_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 为 7
objects->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 box
float 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;
// directions
object->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 coordinate
object->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,UNKNOWN
object->sub_type = GetObjectSubType(labels->at(i));
// 大类别,分的不是很细:VEHICLE,BICYCLE,UNKNOWN_MOVABLE
object->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_NEW
Object();
std::string ToString() const;
void Reset();
int id = -1;// 每帧object的id,必要
// @brief convex hull of the object, required
PointCloud<PointD> polygon;// object拟合凸多边形的顶点坐标,必要
// 方向包围框的信息
Eigen::Vector3f direction = Eigen::Vector3f(1, 0, 0); // 方向向量,必要
float theta = 0.0f; // 包围框朝向角,即偏航角yaw
float 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, required
Eigen::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, optional
LidarObjectSupplement lidar_supplement;
};
LidarObjectSupplement 结构体提供该Object的一些原始数据,以及不在Object类别定义中的各种传感器特有的东西,Object定义为所有module共用的部分
GetObjects
方法分类部分,根据字典映射,将子类别sub_type
转为大类别type
,kSubType2TypeMap
字典如下:
/**
* 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.");
}
}
上面代码会调用类ObjectBuilder
的Build
方法,代码位于:apollo/modules/perception/lidar/lib/object_builder/object_builder.cc
该模块为检测到的障碍物构建边界框。由于遮挡或距离对激光雷达的影响,点云形成障碍物可能存在稀疏或只覆盖了障碍物表面的一部分。因此,边界框构建器的工作就是恢复完整的边界框并给出边界框点。边界框的主要目的是估计障碍物的名称,即便点云数据是稀疏的。同样地,边界框也被用于可视化障碍物。
apollo边框构建主要计算Object结构体的字段信息,如:
polygon
,调用Apollo/modules/perception/common/geometry/convex_hull_2d.h
的GetConvexHull
size
和中心center
,调用Apollo/modules/perception/common/geometry/common.h
的CalculateBBoxSizeCenter2DXY
object->anchor_point
,object->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_time
ComputeOtherObjectInformation(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加上kEpsilonForLine
LinePerturbation(&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设为false
if (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,将其设为大小kEpsilonForSize
for (size_t i = 0; i < 3; ++i) {
if (object->size(i) < kEpsilonForSize) { // kEpsilonForSize = 1e-2f
object->size(i) = kEpsilonForSize;
}
}
object->theta = static_cast<float>(atan2(object->direction(1), object->direction(0)));
}
代码位置:/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.config
ACHECK(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_bank
ACHECK(model_config->get_value("root_path", &root_path));
// apollo/modules/perception/production//data/perception/lidar/models/object_filter_bank
config_file = GetAbsolutePath(work_root, root_path);
// apollo/modules/perception/production/data/perception/lidar/models/object_filter_bank/velodyne128
config_file = GetAbsolutePath(config_file, options.sensor_name);
// apollo/modules/perception/production/data/perception/lidar/models/object_filter_bank/velodyne128/filter_bank.conf
config_file = GetAbsolutePath(config_file, "filter_bank.conf");
// proto定义message文件路径:pollo/modules/perception/lidar/lib/object_filter_bank/proto/filter_bank_config.proto
FilterBankConfig 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.config
ACHECK(config_manager->GetModelConfig(Name(), &model_config));
const std::string work_root = config_manager->work_root();
std::string config_file;zhius
std::string root_path;
// data/perception/lidar/models/object_filter_bank
ACHECK(model_config->get_value("root_path", &root_path));
// apollo/modules/perception/production/data/perception/lidar/models/object_filter_bank
config_file = GetAbsolutePath(work_root, root_path);
// // apollo/modules/perception/production/data/perception/lidar/models/object_filter_bank/roi_boundary_filter.conf
config_file = GetAbsolutePath(config_file, "roi_boundary_filter.conf");
// proto定义message文件路径:apollo/modules/perception/proto/roi_boundary_filter_config.proto
ROIBoundaryFilterConfig config;
// 用定义的meaage读取roi_boundary_filter.conf文件信息
ACHECK(cyber::common::GetProtoFromFile(config_file, &config));
distance_to_boundary_threshold_ = config.distance_to_boundary_threshold(); // -1.0
confidence_threshold_ = config.confidence_threshold(); // 0.5
cross_roi_threshold_ = config.cross_roi_threshold(); // 0.6
inside_threshold_ = config.inside_threshold(); // 1.0
return 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
方法具体就不作展开