【Apollo】Apollo2.5自動駕駛總體簡介

最近對百度的自動駕駛平臺Apollo項目做了一些瞭解。下面將我所瞭解到的一些信息分享給大家。

Apollo項目介紹

阿波羅(Apollo)是百度發佈的面向汽車行業及自動駕駛領域的合作伙伴提供的軟件平臺。發佈時間是2017年4月19日,旨在向汽車行業及自動駕駛領域的合作伙伴提供一個開放、完整、安全的軟件平臺,幫助他們結合車輛和硬件系統,快速搭建一套屬於自己的完整的自動駕駛系統。而將這個計劃命名爲“Apollo”計劃,就是借用了阿波羅登月計劃的含義。

可以在這裏感受一下Apollo的實車駕車體驗:CES 2018 百度Apollo 2.0無人車美國桑尼維爾試乘

SAE Level

對於自動駕駛,SAE(Society of Automotive Engineers,美國汽車工程師學會) International於2014年發佈了從全手動系統到全自動系統六個不同級別的分類系統,這6個級別的描述如下:

SAE Level Name System capability Driver involvement
0 No Automation None The human at the wheel steers, brakes, accelerates, and negotiates traffic.
1 Drive Assistance Under certain conditions, the car controls either the steering or the vehicle speed, but not both simultaneously. The driver performs all other aspects of driving and has full responsibility for monitoring the road and taking over if the assistance system fails to act appropriately.
2 Partial Automation The car can steer, accelerate, and brake in certain circumstances. Tactical maneuvers such as responding to traffic signals or changing lanes largely fall to the driver, as does scanning for hazards. The driver may have to keep a hand on the wheel as a proxy for paying attention.
3 Conditional Automatio In the right conditions, the car can manage most aspects of driving, including monitoring the environment. The system prompts the driver to intervene when it encounters a scenario it can’t navigate. The driver must be available to take over at any time.
4 High Automation The car can operate without human input or oversight but only under select conditions defined by factors such as road type or geographic area. In a shared car restricted to a defined area, there may not be any. But in a privately owned Level 4 car, the driver might manage all driving duties on surface streets then become a passenger as the car enters a highway.
5 Full Automation The driverless car can operate on any road and in any conditions a human driver could negotiate. Entering a destination.

阿波羅項目的官網地址如下:http://apollo.auto

在阿波羅項目的官網,介紹了該項目有如下特點:

  • 開放能力:Apollo阿波羅阿波羅是一個開放的、完整的、安全的平臺,將幫助汽車行業及自動駕駛領域的合作伙伴結合車輛和硬件系統,快速搭建一套屬於自己的自動駕駛系統。
  • 共享資源、加速創新:Apollo開放平臺,爲你提供技術領先、覆蓋廣、高自動化的高精地圖服務;全球唯一開放,擁有海量數據的仿真引擎;全球開放數據量第一,基於深度學習自動駕駛算法End-to-End。
  • 持續共贏:Apollo開放平臺,你可以更快地研發、測試和部署自動駕駛車輛。參與者越多,積累的行駛數據就越多。與封閉的系統相比,Apollo能以更快的速度成熟,讓每個參與者得到更多的受益,同時Apollo平臺也將在你的參與之下變得更好!

目前,其官網上列出的合作伙伴已經接近100家。

阿波羅項目的藍圖如下:

  • 2017-07:封閉場地的自動駕駛能力
  • 2017-12:在城市簡單路況下的自動駕駛能力
  • 2020-12:高速公路和普通城市道路上的全自動駕駛

最新發布的Apollo 2.5版本主要目標是L2級自動駕駛。

詳細的Apollo版本演進信息如下圖所示:

源碼

可以在這裏獲取到阿波羅項目的源碼:https://github.com/ApolloAuto。這個路徑中包含了5個開源項目:

  • apollo:Apollo自動駕駛平臺的源碼。
  • apollo-platform:Apollo項目基於Robot Operating System ROSROS,這裏是相關代碼。目前發佈的源碼基於ROS Indigo。
  • apollo-DuerOS:Apollo-DuerOS是一套與Apollo相關的遠程信息處理產品,這其中包含了幾個開源產品。關於DuerOS,請看這裏:DuerOS
  • apollo-kernel:Apollo項目的Linux內核。
  • ApolloAuto.github.io:Apollo相關文檔,可以訪通過https://apolloauto.github.io訪問這些文檔。

編譯和運行

關於如何編譯和運行阿波羅項目請參見這裏:https://apolloauto.github.io

執行該任務需要Ubuntu和Docker環境。

編譯完成之後,可以在電腦上通過該項目提供的Dreamview功能來熟悉環境,Dreamview通過瀏覽器訪問,其界面看起來是這個樣子:

開發

阿布羅平臺的開發包含下面幾個步驟:

  1. 瞭解離線模擬引擎Dreamviewer和ApolloAuto核心軟件模塊
    • 瞭解算法如何在汽車上運作
    • 不需要使用真正的汽車或硬件,就立即開始開發
  2. 核心模塊集成
    • Location模塊
    • Perception模塊:(支持第三方解決方案,如基於Mobileye ES4芯片的攝像頭,用於L2開發)處理來自Lidar的點雲數據,並根據請求返回分段對象信息。
    • Planning模塊:計算微調路徑,爲路徑服務的路徑段提供汽車動態控制信息。
    • Routine模塊:通過Navigator接口查找路徑段的本地實現。
  3. 高清地圖。L4級別的自動駕駛需要高清地圖。由於自動駕駛汽車需要在系統中重建3D世界,因此參考對象座標在重新定位地圖和現實世界中的自動駕駛方面發揮着重要作用。
  4. 基於雲的在線仿真驅動場景引擎和數據中心。
    • 作爲百度的合作伙伴,將被授予Docker證書來提交新圖像並重播你在雲上開發的算法。
    • 創建和管理複雜的場景以模擬真實世界的駕駛體驗

Apollo與ROS

ROS全稱是Robot Operating System。它包含了一套開源的軟件庫和工具,專門用來構建機器人應用。其官網地址在這裏:http://www.ros.org

在一個ROS系統中,包含了一系列的獨立節點(nodes)。這些節點之間,通過發佈/訂閱的消息模型進行通信。例如,某個傳感器的驅動可以實現爲一個節點,然後以發佈消息的形式對外發送傳感器數據。這些數據可以被多個其他節點接收,例如:過濾器,日誌系統等等。

ROS系統中的節點可能位於不同的主機上,例如:在一個Arduino設備上發佈消息,一臺筆記本電腦訂閱這些消息,一個Android手機也監測這些消息。

ROS系統中包含了一個主(Master)節點。主節點使得其他節點可以查詢彼此以進行通訊。所有節點都需要在主節點上進行註冊,然後就可以與其他節點通訊了。如下圖所示:

熟悉Android系統的人可能很容易發現,這和Binder中的ServiceManager的作用是類似的。

節點之間通過發佈和訂閱主題(Topics)進行通訊。例如,在某個機器人系統中,位於機器人上有一個相機模塊可以獲取圖像數據。另外在機器人上有一個圖像處理模塊需要獲取圖像數據,與此同時還有另外一個位於個人PC上的模塊也需要這些圖像數據。那麼,相機模塊可以發佈/image_data這個主題供其他兩個模塊來訂閱。其結構如下圖所示:

Apollo項目基於ROS,但是對其進行了改造,主要包括下面三個方面:

  1. 通信性能優化
  2. 去中心化網絡拓撲
  3. 數據兼容性擴展

通信性能優化

自動駕駛車輛中包含了大量的傳感器,這些傳感器可能以非常高頻的速度產生數據,所以整個系統對於數據傳輸效率要求很高。在ROS系統中,從數據的發佈到訂閱節點之間需要進行數據的拷貝。在數據量很大的情況下,很顯然這會影響數據的傳輸效率。所以Apollo項目對於ROS第一個改造就是將通過共享內存來減少數據拷貝,以提升通信性能。如下圖所示:

去中心化網絡拓撲

前文我們提到,ROS系統中包含了一個通信的主節點,所有其他節點都要藉助於這個節點來進行通信。所以,很顯然的,假如這個節點發生了通信故障,就會影響整個系統的通信。並且,整個結構還缺乏異常恢復機制。

所以Apollo項目對於ROS的第二個改造就是去除這種中心化的網絡結構。Apollo使用RTPS(Real-Time Publish-Subscribe)服務發現協議實現完全的P2P網絡拓撲。整個通信過程包含下面四個步驟:

數據兼容性擴展

Apollo項目對於ROS最後一個較大的改進就是對於數據格式的調整。

在ROS系統中,使用msg描述文件定義模塊間的消息接口。但不幸的是,接口升級之後不同的版本的模塊難以兼容。

因此,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類的結構如下圖所示:

該類中的主要函數說明如下:

函數名 說明
std::string Name() const 返回模塊名稱
apollo::common::Status Init() 模塊的初始化函數,模塊啓動的第一個函數
apollo::common::Status Start() 模塊的啓動函數
int Spin() 模塊的入口點
void Stop() 模塊的停止函數

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,包括:

  • Cost evaluator:通過一組代價函數來計算可能性
  • MLP evaluator:通過MLP模型來計算可能性
  • RNN evaluator:通過RNN模型來計算可能性

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();

  if (pad_received_) {
    control_command.mutable_pad_msg()->CopyFrom(pad_msg_);
    pad_received_ = false;
  }

  const double time_diff_ms = (end_timestamp - start_timestamp) * 1000;
  control_command.mutable_latency_stats()->set_total_time_ms(time_diff_ms);
  control_command.mutable_latency_stats()->set_total_time_exceeded(
      time_diff_ms < control_conf_.control_period());
  ADEBUG << "control cycle time is: " << time_diff_ms << " ms.";
  status.Save(control_command.mutable_header()->mutable_status());

  SendCmd(&control_command);
}

Control模塊內置了三個控制器,它們的結構和說明如下:

  • LatController:基於LQR的橫向控制器,計算轉向目標。詳見:Vehicle Dynamics and Control
  • LonController:縱向控制器,計算制動/油門值。
  • MPCController:MPC控制器,組合了橫向和縱向控制器。

結束語

本文主要以Apollo項目2.5版本爲基礎做了一些調查分析。

上文中我們也提到,目前的2.5版本僅僅是針對L2級自動駕駛的,而百度計劃在2019年實現L3級自動駕駛,2021年實現L4級自動駕駛。可見,這個項目接下來的時間裏將會非常高速的發展。

另外,今年我剛好有機會參加了上海的CES展。在展會上也看到了百度展出的兩款自動駕駛車型:

一款是小型巴士。

參考資料與推薦讀物

轉載自:https://paul.pub/baidu-apollo/

還有一款是小型物流車。

今後我也會繼續保持對該項目的關注,如果有更多的信息會繼續分享給大家。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章