因此,Apollo选择了Google的Protocol Buffers格式数据来解决这个问题。
Protocol Buffers,是Google公司开发的一种数据描述语言,类似于XML能够将结构化数据序列化,可用于数据存储、通信协议等方面。它不依赖于语言和平台并且可扩展性极强。现阶段官方支持C++、JAVA、Python三种编程语言,但可以找到大量的几乎涵盖所有语言的第三方拓展包。
注:如果你查看了Apollo项目的源码,可以看到很多名称为“proto”的文件夹,这些文件夹中包含的就是Protocol Buffers(简称protobuf)格式的数据结构。
硬件架构
Apollo 2.5上必须的硬件如下表所示:
外设包括下面这些:
硬件架构如下图所示:
软件架构
Apollo平台的软件架构如下图所示:
在Apollo上,运行的核心软件模块包括:
- Perception:感知模块识别自动车辆周围的世界。在Perception模块中有两个重要的子模块:障碍物检测和交通灯检测。
- Prediction:Prediction模块预测未来的感知障碍物的运动轨迹。
- Routing:Routing模块告诉自动车辆如何通过一系列车道或道路到达目的地。
- Planning:Planning模块计划自主车辆的时空轨迹。
- Control:Control模块通过生成诸如节流阀,制动器和转向的控制命令来执行计划的时空轨迹。
- CanBus:CanBus将控制命令传递给车辆硬件的接口。它还将机架信息传递给软件系统。
- HD-Map:提供有关道路特定结构化的信息。
- Localization:该模块利用各种信息来源,例如GPS,LiDAR和IMU来估计自动车辆所在的位置。
这些模块的交互结构如下图所示:
每个模块都作为独立的基于CarOS的ROS节点运行。每个模块节点都会发布和订阅某些主题。订阅的主题用作数据输入,而发布的主题用作数据输出。
关于Apollo平台的系统架构可以阅读这篇文档:HOW TO UNDERSTAND ARCHITECTURE AND WORKFLOW。
从这篇文档中我们看到:
- 自动驾驶车辆由规划引擎通过CAN总线(Controller Area Network bus)来进行控制。
- 为了计算效率,Location模块,Perception模块,Planning模块作为独立的输入源和输出源通过P2P一起工作。
- 通过阅读源码
${MODULE_NAME}/conf
目录下的配置文件,我们可以获得有关模块订阅和发布的主题的基本信息。 - 每个模块通过触发
Init
接口和注册回调开始。 - 所有模块都会在下面这个点上注册:AdapterManager::Init。该函数部分代码片段如下:
void AdapterManager::Init(const AdapterManagerConfig &configs) {
if (Initialized()) {
return;
}
instance()->initialized_ = true;
if (configs.is_ros()) {
instance()->node_handle_.reset(new ros::NodeHandle());
}
for (const auto &config : configs.config()) {
switch (config.type()) {
case AdapterConfig::POINT_CLOUD:
EnablePointCloud(FLAGS_pointcloud_topic, config);
break;
case AdapterConfig::GPS:
EnableGps(FLAGS_gps_topic, config);
break;
case AdapterConfig::IMU:
EnableImu(FLAGS_imu_topic, config);
break;
case AdapterConfig::RAW_IMU:
EnableRawImu(FLAGS_raw_imu_topic, config);
break;
case AdapterConfig::CHASSIS:
EnableChassis(FLAGS_chassis_topic, config);
break;
case AdapterConfig::LOCALIZATION:
EnableLocalization(FLAGS_localization_topic, config);
break;
case AdapterConfig::PERCEPTION_OBSTACLES:
EnablePerceptionObstacles(FLAGS_perception_obstacle_topic, config);
break;
case AdapterConfig::TRAFFIC_LIGHT_DETECTION:
EnableTrafficLightDetection(FLAGS_traffic_light_detection_topic,
config);
...
下面是对系统中主要的核心模块的一些解析。
核心模块
apollo/modules/中包含了系统中的各个模块的源码。
阅读这些源码会发现,这些核心模块的类都继承自一个公共基类ApolloApp,相关结构如下图所示:
ApolloApp类的结构如下图所示:
该类中的主要函数说明如下:
函数名 | 说明 |
---|
在apollo_app.h
这个头文件中,还包含了一个宏以方便每个模块声明main函数,相关代码如下:
#define APOLLO_MAIN(APP) \
int main(int argc, char **argv) { \
google::InitGoogleLogging(argv[0]); \
google::ParseCommandLineFlags(&argc, &argv, true); \
signal(SIGINT, apollo::common::apollo_app_sigint_handler); \
APP apollo_app_; \
ros::init(argc, argv, apollo_app_.Name()); \
apollo_app_.Spin(); \
return 0; \
}
每个模块的根目录都包含了一个README.md
文件,是对这个模块的说明。我们可以以此为入口来了解模块的实现。
Perception(感知)
模块介绍
自动驾驶车辆通过前置摄像头和雷达与最近的车辆(closest in-path vehicle,简称CIPV)保持距离。子模块还预测障碍物运动和位置信息(例如,航向和速度)。Apollo 2.5支持高速公路上的高速自动驾驶,无需任何地图。深度网络算法已经学会处理图像数据。随着收集更多数据,深度网络的性能将随着时间的推移而提高。
模块输入:
- 雷达数据
- 图像数据
- 雷达传感器校准的外部参数(来自YAML文件)
- 前置相机校准的外部和内部参数(来自YAML文件)
- 车辆的速度和角速度
模块输出:
- 3D障碍物跟踪航向,速度和分类信息
- 带有拟合曲线参数的车道标记信息,空间信息以及语义信息
模块解析
Perception模块需要根据输入信息快速的解析出两类信息,即:道路和物体。其中的深入网络基于YOLO算法[1][2]。
Apollo 2.5不支持高曲率,没有车道标志的道路,包括当地道路和交叉路口。感知模块基于使用具有有限数据的深度网络的视觉检测。因此,在发布更好的网络之前,驾驶员在驾驶时应小心谨慎,并始终准备好通过将车轮转向正确的方向来解除自主驾驶。
-
推荐道路
- 两侧清晰的白色车道线
-
不推荐道路
- 高曲率的道路
- 没有车道线标记的道路
- 路口
- 对接点或虚线车道线
- 公共道路
而对于物体来说,又分为静态物体和动态物体。静态物体包括道路和交通灯等。动态物体包括机动车,自行车,行人,动物等。
为了保持车辆在车道上,需要一系列模块的配合,相关流程图如下所示:
Perception模块在Init
函数中会注册一系列类以完成模块启动后的正常工作,相关代码如下:
void Perception::RegistAllOnboardClass() {
/// regist sharedata
RegisterFactoryLidarObjectData();
RegisterFactoryRadarObjectData();
RegisterFactoryCameraObjectData();
RegisterFactoryCameraSharedData();
RegisterFactoryCIPVObjectData();
RegisterFactoryLaneSharedData();
RegisterFactoryFusionSharedData();
traffic_light::RegisterFactoryTLPreprocessingData();
/// regist subnode
RegisterFactoryLidarProcessSubnode();
RegisterFactoryRadarProcessSubnode();
RegisterFactoryCameraProcessSubnode();
RegisterFactoryCIPVSubnode();
RegisterFactoryLanePostProcessingSubnode();
RegisterFactoryAsyncFusionSubnode();
RegisterFactoryFusionSubnode();
RegisterFactoryMotionService();
lowcostvisualizer::RegisterFactoryVisualizationSubnode();
traffic_light::RegisterFactoryTLPreprocessorSubnode();
traffic_light::RegisterFactoryTLProcSubnode();
}
我们可以以这里为入口了解各个子模块的逻辑。
以RegisterFactoryLidarProcessSubnode
为例。
代码中其实并不存在RegisterFactoryLidarProcessSubnode
这个函数,该函数的定义其实是由宏完成的。相关代码如下:
Lidar(也称之为LIDAR,LiDAR,或LADAR)的全称是Light Detection And Ranging,即激光探测与测量。
// /modules/perception/onboard/subnode.h
#define REGISTER_SUBNODE(name) REGISTER_CLASS(Subnode, name)
// /modules/perception/lib/base/registerer.h
#define REGISTER_CLASS(clazz, name) \
class ObjectFactory##name : public apollo::perception::ObjectFactory { \
public: \
virtual ~ObjectFactory##name() {} \
virtual perception::Any NewInstance() { \
return perception::Any(new name()); \
} \
}; \
inline void RegisterFactory##name() { \
perception::FactoryMap &map = perception::GlobalFactoryMap()[#clazz]; \
if (map.find(#name) == map.end()) map[#name] = new ObjectFactory##name(); \
}
而在lidar_process_subnode.h
中使用了上面这个宏。
REGISTER_SUBNODE(LidarProcessSubnode);
于是就会生成一个名称为ObjectFactoryLidarProcessSubnode
的类,该类继承自apollo::perception::ObjectFactory
,并且其中包含了名称为RegisterFactoryLidarProcessSubnode
的函数。
Prediction(预测)
模块介绍
Prediction模块从Perception模块接受障碍物信息。该模块需要的信息包括位置,航向,速度,加速度,并产生具有障碍概率的预测轨迹。
模块输入:
- 来自Prediction模块的障碍物信息
- 来自Localizaton模块的位置信息
模块输出:
- 障碍物的预测轨迹
模块解析
Prediction的Init
函数中添加了三个回调用来从其他模块获取信息的更新:
AdapterManager::AddLocalizationCallback(&Prediction::OnLocalization, this);
AdapterManager::AddPlanningCallback(&Prediction::OnPlanning, this);
AdapterManager::AddPerceptionObstaclesCallback(&Prediction::RunOnce, this);
这里最重要的就是Prediction::RunOnce
这个函数。这个函数中包含了Prediction模块的主要逻辑,它会在接收到一个新的障碍物消息时触发。
Prediction模块中有三类重要的子模块。
第一类是Container,用来存储从订阅频道获取的数据。包括:
- 感到到的障碍物信息
- 车辆位置信息
- 车辆计划信息
第二类是Evaluator,用来针对指定的障碍物预测路线和速度。目前有三类Evaluator,包括:
Evaluator通过EvaluatorManager
类管理,Evaluator类结构如下图所示:
Prediction模块中第三类重要的子模块就是Predictor。它用来预测障碍物的轨迹。
不同的障碍物运动的轨迹会不一样,因此实现中包含了很多个类型的Predictor,它们的结构如下图所示。
类似的,会有一个PredictorManager
来管理Predictor。
Routing(路由)
模块介绍
Routing模块根据请求生成导航信息。
模块输入:
- 地图数据
- 请求,包括:开始和结束位置
模块输出:
- 路由导航信息
模块解析
Routing模块的内部结构如下图所示:
Routing模块的输入是地图数据和导航请求,因此其Init
函数就是围绕这个逻辑的:
apollo::common::Status Routing::Init() {
const auto routing_map_file = apollo::hdmap::RoutingMapFile();
AINFO << "Use routing topology graph path: " << routing_map_file;
navigator_ptr_.reset(new Navigator(routing_map_file));
CHECK(common::util::GetProtoFromFile(FLAGS_routing_conf_file, &routing_conf_))
<< "Unable to load routing conf file: " + FLAGS_routing_conf_file;
AINFO << "Conf file: " << FLAGS_routing_conf_file << " is loaded.";
hdmap_ = apollo::hdmap::HDMapUtil::BaseMapPtr();
CHECK(hdmap_) << "Failed to load map file:" << apollo::hdmap::BaseMapFile();
AdapterManager::Init(FLAGS_routing_adapter_config_filename);
AdapterManager::AddRoutingRequestCallback(&Routing::OnRoutingRequest, this);
return apollo::common::Status::OK();
}
这段代码的重点是下面三个地方:
apollo::hdmap::RoutingMapFile()
包含了HD地图数据。Navigator
负责导航,我们很容易想到这个类应当是该模块的核心。Routing::OnRoutingRequest
是接收导航请求的回调函数。
在Routing::OnRoutingRequest
中,最主要的就是通过Navigator::SearchRoute
来搜索导航路径。
void Routing::OnRoutingRequest(const RoutingRequest& routing_request) {
AINFO << "Get new routing request:" << routing_request.DebugString();
RoutingResponse routing_response;
apollo::common::monitor::MonitorLogBuffer buffer(&monitor_logger_);
const auto& fixed_request = FillLaneInfoIfMissing(routing_request);
if (!navigator_ptr_->SearchRoute(fixed_request, &routing_response)) {
AERROR << "Failed to search route with navigator.";
buffer.WARN("Routing failed! " + routing_response.status().msg());
return;
}
buffer.INFO("Routing success!");
AdapterManager::PublishRoutingResponse(routing_response);
return;
}
目前,Apollo 2.5版本中的导航基于A*算法。这是一种在图形平面上,有多个节点的路径,求出最低通过成本的算法。该算法综合了Best-First Search和Dijkstra算法的优点:在进行启发式搜索提高算法效率的同时,可以保证找到一条最优路径(基于评估函数)。
在A*算法计算的过程中,会尝试多条路径。一旦遇到障碍物,便将该路径上的点标记为不需要继续探索(图中的实心点)。继续以剩下的空心点为基础探索。最终求得最优路径。
下图动态描述了A*算法查找目标路径的算法过程。
Planing(计划)
模块介绍
Planing模块根据定位信息,车辆状态(位置,速度,加速度,底盘),地图,路线,感知和预测,计算出安全和舒适的形式线路让控制器执行。
目前的系统实现中包含了四种计划器:
- RTKReplayPlanner(自Apollo 1.0以来):RTK重放计划器首先在初始化时加载记录的轨迹,并根据当前系统时间和车辆位置发送适当的轨迹段。
- EMPlanner(自Apollo1.5以来。EM是Expectation Maximization的缩写):EM计划器,会根据地图,路线和障碍物计算驾驶决策和线路。基于动态规划(Dynamic programming,简称DP)的方法首先用于确定原始路径和速度曲线,然后使用基于二次规划(Quadratic programming,简称QP)的方法来进一步优化路径和速度曲线以获得平滑的轨迹。
- LatticePlanner:网格计划器
- NaviPlanner:这是一个基于实时相对地图的计划器。它使用车辆的FLU(Front-Left-Up)坐标系来完成巡航,跟随,超车,接近,变道和停车任务。
模块输入:
-
RTK重放计划器:
- Localization
- 记录的RTK轨迹
-
EM计划器:
- Localization
- Perception
- Prediction
- HD Map
- Routing
模块输出:
- 无碰撞和舒适的轨迹让控制模块的执行
模块解析
Planing模块在初始化的Init
函数中,这里面会注册所有的计划器,然后根据配置文件中的配置确定当前所使用的计划器。目前,配置文件中配置的是EM计划器。
Planing模块在Start
函数中设定了一个Timer用来完成定时任务:
Status Planning::Start() {
timer_ = AdapterManager::CreateTimer(
ros::Duration(1.0 / FLAGS_planning_loop_rate), &Planning::OnTimer, this);
...
Planning::OnTimer
最主要的就是调用RunOnce()
,而后者包含了Planing模块的核心逻辑。目前,FLAGS_planning_loop_rate
值是10。也就是说,Planing模块运行的频度是每秒钟10次。
在Planning::Plan函数中,更通过配置的计划器进行路线的计算,然后将结果对外发布。关键代码如下:
Status Planning::Plan(const double current_time_stamp,
const std::vector<TrajectoryPoint>& stitching_trajectory,
ADCTrajectory* trajectory_pb) {
auto* ptr_debug = trajectory_pb->mutable_debug();
if (FLAGS_enable_record_debug) {
ptr_debug->mutable_planning_data()->mutable_init_point()->CopyFrom(
stitching_trajectory.back());
}
auto status = planner_->Plan(stitching_trajectory.back(), frame_.get());
ExportReferenceLineDebug(ptr_debug);
const auto* best_ref_info = frame_->FindDriveReferenceLineInfo();
if (!best_ref_info) {
std::string msg("planner failed to make a driving plan");
AERROR << msg;
if (last_publishable_trajectory_) {
last_publishable_trajectory_->Clear();
}
return Status(ErrorCode::PLANNING_ERROR, msg);
}
ptr_debug->MergeFrom(best_ref_info->debug());
trajectory_pb->mutable_latency_stats()->MergeFrom(
best_ref_info->latency_stats());
// set right of way status
trajectory_pb->set_right_of_way_status(best_ref_info->GetRightOfWayStatus());
for (const auto& id : best_ref_info->TargetLaneId()) {
trajectory_pb->add_lane_id()->CopyFrom(id);
}
best_ref_info->ExportDecision(trajectory_pb->mutable_decision());
...
last_publishable_trajectory_->PrependTrajectoryPoints(
stitching_trajectory.begin(), stitching_trajectory.end() - 1);
for (size_t i = 0; i < last_publishable_trajectory_->NumOfPoints(); ++i) {
if (last_publishable_trajectory_->TrajectoryPointAt(i).relative_time() >
FLAGS_trajectory_time_high_density_period) {
break;
}
ADEBUG << last_publishable_trajectory_->TrajectoryPointAt(i)
.ShortDebugString();
}
last_publishable_trajectory_->PopulateTrajectoryProtobuf(trajectory_pb);
best_ref_info->ExportEngageAdvice(trajectory_pb->mutable_engage_advice());
return status;
}
Control(控制)
模块介绍
控制模块根据计划和当前的汽车状态,使用不同的控制算法来生成舒适的驾驶体验。控制模块可以在正常模式和导航模式下工作。
模块输入:
- 计划的线路
- 车辆状态
- 定位信息
- Dreamview AUTO模式更改请求
模块输出:
- 控制命令(转向,油门,刹车)到底盘
模块解析
Control模块的主体逻辑也是通过Timer定时执行的。在定时触发的函数Control::OnTimer
中,会生成命令然后派发出去:
void Control::OnTimer(const ros::TimerEvent &) {
double start_timestamp = Clock::NowInSeconds();
if (FLAGS_is_control_test_mode && FLAGS_control_test_duration > 0 &&
(start_timestamp - init_time_) > FLAGS_control_test_duration) {
AERROR << "Control finished testing. exit";
ros::shutdown();
}
ControlCommand control_command;
Status status = ProduceControlCommand(&control_command);
AERROR_IF(!status.ok()) << "Failed to produce control command:"
<< status.error_message();
double end_timestamp = Clock::NowInSeconds();
**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**
**深知大多数Linux运维工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**
**因此收集整理了一份《2024年Linux运维全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
![img](https://img-blog.csdnimg.cn/img_convert/89b9cec56fd1c8ed13cdfb41d7e8898e.png)
![img](https://img-blog.csdnimg.cn/img_convert/52d92315eed864a4bb341a455d37ace3.png)
![img](https://img-blog.csdnimg.cn/img_convert/261798246cc82dd59e1fd11e673b6494.png)
![img](https://img-blog.csdnimg.cn/img_convert/932f9ca78dc64fd0e96bdf54cae19e5e.png)
![img](https://img-blog.csdnimg.cn/img_convert/9ec701a012ffbac995580d115c01b8a7.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Linux运维知识点,真正体系化!**
**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**
**如果你觉得这些内容对你有帮助,可以添加VX:vip1024b (备注Linux运维获取)**
![img](https://img-blog.csdnimg.cn/img_convert/f67273b9b0a2013dfdc9840cef30eca2.jpeg)
为了做好运维面试路上的助攻手,特整理了上百道 **【运维技术栈面试题集锦】** ,让你面试不慌心不跳,高薪offer怀里抱!
这次整理的面试题,**小到shell、MySQL,大到K8s等云原生技术栈,不仅适合运维新人入行面试需要,还适用于想提升进阶跳槽加薪的运维朋友。**
![](https://img-blog.csdnimg.cn/img_convert/00231552317299d3a677a5aa2031a76e.png)
本份面试集锦涵盖了
* **174 道运维工程师面试题**
* **128道k8s面试题**
* **108道shell脚本面试题**
* **200道Linux面试题**
* **51道docker面试题**
* **35道Jenkis面试题**
* **78道MongoDB面试题**
* **17道ansible面试题**
* **60道dubbo面试题**
* **53道kafka面试**
* **18道mysql面试题**
* **40道nginx面试题**
* **77道redis面试题**
* **28道zookeeper**
**总计 1000+ 道面试题, 内容 又全含金量又高**
* **174道运维工程师面试题**
> 1、什么是运维?
> 2、在工作中,运维人员经常需要跟运营人员打交道,请问运营人员是做什么工作的?
> 3、现在给你三百台服务器,你怎么对他们进行管理?
> 4、简述raid0 raid1raid5二种工作模式的工作原理及特点
> 5、LVS、Nginx、HAproxy有什么区别?工作中你怎么选择?
> 6、Squid、Varinsh和Nginx有什么区别,工作中你怎么选择?
> 7、Tomcat和Resin有什么区别,工作中你怎么选择?
> 8、什么是中间件?什么是jdk?
> 9、讲述一下Tomcat8005、8009、8080三个端口的含义?
> 10、什么叫CDN?
> 11、什么叫网站灰度发布?
> 12、简述DNS进行域名解析的过程?
> 13、RabbitMQ是什么东西?
> 14、讲一下Keepalived的工作原理?
> 15、讲述一下LVS三种模式的工作过程?
> 16、mysql的innodb如何定位锁问题,mysql如何减少主从复制延迟?
> 17、如何重置mysql root密码?
**一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
![img](https://img-blog.csdnimg.cn/img_convert/225ddf3a32c78d2c5f66e6bf64fa81cc.jpeg)
**40道nginx面试题**
* **77道redis面试题**
* **28道zookeeper**
**总计 1000+ 道面试题, 内容 又全含金量又高**
* **174道运维工程师面试题**
> 1、什么是运维?
> 2、在工作中,运维人员经常需要跟运营人员打交道,请问运营人员是做什么工作的?
> 3、现在给你三百台服务器,你怎么对他们进行管理?
> 4、简述raid0 raid1raid5二种工作模式的工作原理及特点
> 5、LVS、Nginx、HAproxy有什么区别?工作中你怎么选择?
> 6、Squid、Varinsh和Nginx有什么区别,工作中你怎么选择?
> 7、Tomcat和Resin有什么区别,工作中你怎么选择?
> 8、什么是中间件?什么是jdk?
> 9、讲述一下Tomcat8005、8009、8080三个端口的含义?
> 10、什么叫CDN?
> 11、什么叫网站灰度发布?
> 12、简述DNS进行域名解析的过程?
> 13、RabbitMQ是什么东西?
> 14、讲一下Keepalived的工作原理?
> 15、讲述一下LVS三种模式的工作过程?
> 16、mysql的innodb如何定位锁问题,mysql如何减少主从复制延迟?
> 17、如何重置mysql root密码?
**一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
[外链图片转存中...(img-Ad70KPAY-1712886826090)]