本系列博客旨在记录自己在学习百度无人驾驶开源框架Apollo的心得和体会,欢迎大家阅读和点赞,并提出宝贵意见,大家相互学习,如需转载,请注明出处!
这篇文章主要介绍Apollo 5.0的感知模块,首先是对感知模块进行一个整体的概述,介绍一些大的框架,从而能够帮助大家从宏观上把握整个感知模块的构成。
Apollo 5.0感知模块的基本架构和算法处理流程和3.5版本的没有什么区别:
主要用到的传感器类型包括相机、激光雷达和毫米波雷达,相机和激光雷达的目标检测部分都是利用深度学习网络完成,然后都进行了目标跟踪,最后设计了一个融合模块,用来融合三种传感器跟踪后的目标序列,获得更加稳定可靠的感知结果。
Apollo 5.0感知模块新特征: 在线传感器标定服务、手动相机标定、CIPO目标检测、灭点检测。
大体上包括:
3D障碍物感知: 主要有是三个主要部分提供3D障碍物感知:lidar、radar和fusion模块。
Radar感知: 毫米波雷达检测与跟踪
Obstacle Results Fusion:
用来融合lidar和radar的检测结果。
融合中有publish-sensor
的概念,apollo中publish-sensor
是lidar,即对radar结果进行缓存,lidar结果用来触发融合动作,因此融合输出频率等于publish-sensor的频率。Apollo保留所有传感器结果,每个融合目标丢失存活时间根据传感器不同,一个目标必须至少有一个传感器结果存活。Apollo在近距离里提供lidar和radar的融合结果,在远距离只有radar结果。
传感器结果到融合序列关联: 传感器目标与融合目标进行匹配时,首先匹配相同传感器相同的跟踪ID,然后建立二分图,对没有匹配上的那些结果进行匈牙利匹配,距离损失矩阵是通过计算anchor点的欧氏距离完成。
Motion Fusion: 使用基于自适应卡尔曼滤波的CA模型做运动估计,运动状态量包括anchor point位置、速度和加速度。在激光跟踪和radar检测中提供位置和速度的不确定性,将所有的这些状态和不确定量输入给自适应卡尔曼滤波,来得到融合结果。滤波过程使用击穿阈值来抵消更新增益的过估计。
base
——整个感知模块公用的一些基础类型定义,比如表示感知目标的Object
等类型;
camera
——相机处理子模块;
common
——整个感知模块公用的一些基础操作定义,如点云预处理、图像预处理等;
data
——感知模块用到的数据,目前只有相机的标定参数yaml文件;
fusion
——融合处理子模块;
interface
——整个感知模块的一些公用接口定义,目前里面好像都是视觉感知与显卡的一些的接口;
lib
——整个感知模块公用的一些算法定义,如config_manager参数配置操作;
lidar
——激光雷达处理子模块;
map
——高精度地图的一些操作,主要在感知模块中用于提取感兴趣区域;
onboard
——上车运行程序,其中component
文件夹可以认为是感知模块的入口;
production
——感知模块所有配置参数定义;
proto
——感知模块protobuf
文件定义;
radar
——毫米波雷达处理子模块
Apollo感知模块中因为考虑到接口的统一性和实现的方便,使用了大量的工厂模式方法来对基类进行定义,方便根据配置参数来选择实例化不同的类,从而完成不同的功能类组合。因此,为了能够消除大家在阅读具体算法源码时的困惑,特别是每个算法类最后定义的PERCEPTION_REGISTER_***(***)
具体含义,首先详细介绍下具体的实现细节。
lib/registerer
路径下定义了工厂模式:
ObjectFactory
类定义为没有拷贝和赋值的类,包含一个NewInstance
的公有函数:
class ObjectFactory {
public:
ObjectFactory() {}
virtual ~ObjectFactory() {}
virtual Any NewInstance() { return Any(); }
ObjectFactory(const ObjectFactory &) = delete;
ObjectFactory &operator=(const ObjectFactory &) = delete;
private:
};
例如radar/lib/interface/base_tracker.h
中基类BaseTracker类最后定义:
PERCEPTION_REGISTER_REGISTERER(BaseTracker);
#define PERCEPTION_REGISTER_TRACKER(name) \
PERCEPTION_REGISTER_CLASS(BaseTracker, name)
则会利用PERCEPTION_REGISTER_REGISTERER
宏定义一个BaseTrackerRegisterer
类,该类定义了很多静态公有函数,主要经常用到的是GetInstanceByName
函数,可以用来查询已经注册的哪些跟踪算法类,从而构造一个实例;
而PERCEPTION_REGISTER_TRACKER
宏能够在匿名命名空间下定义一个ObjectFactory##name
的类,该类继承自ObjectFactory
类,并且重载了函数NewInstance
,使得函数返回Any(new name())
,即构造了这个name
类的一个实例。
例如定义在radar/lib/tracker/conti_ars_tracker
下的ContiArsTracker
类,继承自BaseTracker
类,它的cpp文件最后调用了PERCEPTION_REGISTER_TRACKER(ContiArsTracker)
,从而会定义一个ObjectFactoryContiArsTracker
类。
对于一个大系统项目来说,程序运行的参数配置一直是一个比较重要的功能,为了尽量减少在程序中通过直接设置数值的方式来配置参数,一般都需要定义一些参数配置文件,比较常见的当然就是yaml
、xml
以及ROS中比较常见的通过launch
文件的方式配置程序运行参数了。
Apollo因为大量使用Google的protobuf
文件来定义消息传递的数据结构,因此也借用了protobuf
文件来配置算法参数。因此本小节主要就是目的就是分析一下干感知模块中的算法参数配置类ConfigManager
的实现细节。
ConfigManager
类定义在modules/perception/lib/config_manager/config_manager.h
,为单例模式类。其关键成员变量是一个map映射:
std::map<std::string, ModelConfig *> model_config_map_;
其实是<模块名,具体配置参数>的一个键值对,因此最终配置的参数肯定都存储在ModelConfig
类中。实际在使用ConfigManager
来获得算法所需配置参数都是通过成员函数GetModelConfig
实现的:
bool ConfigManager::GetModelConfig(const std::string &model_name,
const ModelConfig **model_config) {
if (!inited_ && !Init()) {
return false;
}
// 从map中查找对应model_name所需的参数
auto citer = model_config_map_.find(model_name);
if (citer == model_config_map_.end()) {
return false;
}
*model_config = citer->second;
return true;
}
那具体是如何实现参数的载入的呢?实际上所有需要的参数定义格式的protobuf
文件是modules/perception/proto/perception_config_schema.proto
:
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映射。
modules/perception/proto/perception_config_schema.proto
定义好的这些protobuf
消息类型就相当于定义好了一个参数配置的框架,实际使用的具体配置参数都定义在modules/perception/production
文件夹下。
我们拿radar毫米波雷达模块的配置参数为例,首先由modules/perception/production/conf/perception/radar/config_manager.config
文件实例化上述protobuf
文件中定义的message ModelConfigFileListProto
:
model_config_path: "conf/perception/radar/modules/conti_ars_preprocessor.config"
model_config_path: "conf/perception/radar/modules/conti_ars_tracker.config"
model_config_path: "conf/perception/radar/modules/hm_matcher.config"
model_config_path: "conf/perception/radar/modules/front_radar_pipeline.config"
model_config_path: "conf/perception/radar/modules/rear_radar_pipeline.config"
可以看出其实就是对ModelConfigFileListProto
消息中的model_config_path
成员变量进行了实例化,指明了参数定义文件的路径。例如conti_ars_preprocessor.config
,我们打开modules/conti_ars_preprocessor.config
看下具体内容:
model_configs {
name: "ContiArsPreprocessor"
version: "1.0.0"
float_params {
name: "delay_time"
value: 0.07
}
}
可以看到,这个文件其实就是对MultiModelConfigProto
消息中的model_configs
成员变量进行了初始化,参数配置的model_name
就是"ContiArsPreprocessor",这个也就是ConfigManager
中用来查询具体参数的一个key,可以看到里面定义了一个float类型的"delay_time"参数,数值为0.07。
到此也就理清了ConfigManager
类保存的这些参数到底是怎么来的了,具体的参数解析过程可以参见ConfigManager::InitInternal()
函数实现细节,此处就不详细展开了,其实就是对protobuf
文件的解析过程。
其他算法类的配置参数也都是类似的理解过程,记住所有的参数都是定义在perception/production文件夹下。
我们以上述对毫米波雷达ContiArsPreprocessor参数载入为例,其实一般model_name
就是对应的算法类的类名。我们进到modules/perception/radar/lib/preprocessor/conti_ars_preprocessor/conti_ars_preprocessor.cc
文件,找到ContiArsPreprocessor::Init
函数:
bool ContiArsPreprocessor::Init() {
std::string model_name = "ContiArsPreprocessor";
const lib::ModelConfig* model_config = nullptr;
CHECK(lib::ConfigManager::Instance()->GetModelConfig(model_name,
&model_config));
CHECK(model_config->get_value("delay_time", &delay_time_));
return true;
}
可以看到,函数中通过"ContiArsPreprocessor"作为key,调用ConfigManager
类的GetModelConfig
就查询到了ContiArsPreprocessor
类的配置参数,存储在ModelConfig
类中,然后利用ModelConfig
类的get_value
函数,就可以对应查询到具体的配置参数数值了,还是非常方便的。