解析百度Apollo自動駕駛平臺

paulquei發表於2018-06-25

最近對百度的自動駕駛平臺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 (ROS),這裡是相關程式碼。目前釋出的原始碼基於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通過瀏覽器訪問,其介面看起來是這個樣子:

dreamview_2_5.png

關於Dreamview的更多說明,請參見這裡:Dreamview Usage Table

開發

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

  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的作用是類似的。

ros101-1.png

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

ros101-3.png

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

  1. 通訊效能優化
  2. 去中心化網路拓撲
  3. 資料相容性擴充套件

通訊效能優化

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

去中心化網路拓撲

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

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

關於RTPS詳見這裡:Real-Time Publish-Subscribe

rtps_step1.png

rtps_step2.png

rtps_step3.png

rtps_step4.png

資料相容性擴充套件

Apollo專案對於ROS最後一個較大的改進就是對於資料格式的調整。

在ROS系統中,使用msg描述檔案定義模組間的訊息介面。但不幸的是,介面升級之後不同的版本的模組難以相容。

data_not_compatible.png

因此,Apollo選擇了Google的Protocol Buffers格式資料來解決這個問題。

Protocol Buffers,是Google公司開發的一種資料描述語言,類似於XML能夠將結構化資料序列化,可用於資料儲存、通訊協議等方面。它不依賴於語言和平臺並且可擴充套件性極強。現階段官方支援C++、JAVA、Python三種程式語言,但可以找到大量的幾乎涵蓋所有語言的第三方擴充包。

注:如果你檢視了Apollo專案的原始碼,可以看到很多名稱為“proto”的資料夾,這些資料夾中包含的就是Protocol Buffers(簡稱protobuf)格式的資料結構。

apollo_protobuf.png

硬體架構

Apollo 2.5上必須的硬體如下表所示:

perception_required_hardware.png

外設包括下面這些:

perception_peripherals.png

硬體架構如下圖所示:

Hardware_overview.png

軟體架構

Apollo平臺的軟體架構如下圖所示:

Apollo_2_5.png

在Apollo上,執行的核心軟體模組包括:

  • Perception:感知模組識別自動車輛周圍的世界。在Perception模組中有兩個重要的子模組:障礙物檢測和交通燈檢測。
  • Prediction:Prediction模組預測未來的感知障礙物的運動軌跡。
  • Routing:Routing模組告訴自動車輛如何通過一系列車道或道路到達目的地。
  • Planning:Planning模組計劃自主車輛的時空軌跡。
  • Control:Control模組通過生成諸如節流閥,制動器和轉向的控制命令來執行計劃的時空軌跡。
  • CanBus:CanBus將控制命令傳遞給車輛硬體的介面。它還將機架資訊傳遞給軟體系統。
  • HD-Map:提供有關道路特定結構化的資訊。
  • Localization:該模組利用各種資訊來源,例如GPS,LiDAR和IMU來估計自動車輛所在的位置。

這些模組的互動結構如下圖所示:

Apollo_2_0_Software_Arch.png

每個模組都作為獨立的基於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,相關結構如下圖所示:

modules.png

ApolloApp類的結構如下圖所示:

ApolloApp.png

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

函式名 說明
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_flow_chart_apollo_2.5.png

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

Evaluator.png

Prediction模組中第三類重要的子模組就是Predictor。它用來預測障礙物的軌跡。

不同的障礙物運動的軌跡會不一樣,因此實現中包含了很多個型別的Predictor,它們的結構如下圖所示。

Predictor.png

類似的,會有一個PredictorManager來管理Predictor。

Routing(路由)

模組介紹

Routing模組根據請求生成導航資訊。

模組輸入:

  • 地圖資料
  • 請求,包括:開始和結束位置

模組輸出:

  • 路由導航資訊

模組解析

Routing模組的內部結構如下圖所示:

Routing_module.png

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*演算法查詢目標路徑的演算法過程。

Astar_progress_animation.gif

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模組內建了三個控制器,它們的結構和說明如下:

Controller.png

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

結束語

本文主要以Apollo專案2.5版本為基礎做了一些調查分析。

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

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

一款是小型巴士。

IMG_0619.jpg

還有一款是小型物流車。

IMG_0620.jpg

今後我也會繼續保持對該專案的關注,如果有更多的資訊會繼續分享給大家。

參考資料與推薦讀物


相關文章