vlambda博客
学习文章列表

自动驾驶 Apollo 源码分析系列,感知篇(二):Perception 如何启动?

从 Apollo 的官方文档,我们很容易得知 Perception 是核心的组件之一,但像所有的 C++ 程序一样,每个应用都有一个 Main 函数入口,那么引出本文要探索的 2 个问题:

  1. Perception 的入口在哪里?
  2. Perception 如何启动?

CyberRT

在讲 Perception 组件具体内容前,非常有必要讲 CyberRT。

Apollo Cyber RT framework is built based on the concept of component. As a basic building block of Apollo Cyber RT framework, each component contains a specific algorithm module which process a set of data inputs and generate a set of outputs. -- Apollo github.com

CyberRT 就是 Apollo 中的一套基础框架,是面向组件(component)的。组件呈现高度的模块化。

每个组件包含特定的算法,用于处理数据输入,并输出算法处理的结果。

玩过 ROS 的同学应该对此类东西不陌生,CyberRT 和 ROS 类似,实际上 Apollo 最初也是用的 ROS1,后来因为时延问题得不到满足,所以自行开发了一个类似的,但性能更好。

回到组件问题,Perception 也是组件,之前的文章有介绍,它接收传感器的数据,然后输出障碍物的 3D 信息。

在 CyberRT 中,有如何定义、实现、启动组件的机制说明。

组件管理

通常 4 个步骤进行组件开发:

  • 设置组件文件结构
  • 实现组件类
  • 设置配置文件
  • 启动组件

Perception 组件相关文件

按照 Apollo 官方文档提示,一个 component 相关的文档有这几个:

Header file: common_component_example.h
Source file: common_component_example.cc
Build file: BUILD
DAG dependency file: common.dag
Launch file: common.launch

按照提示,我们就可以去源码中搜索相关的文件。

Apollo 6.0 官方文档有这么一句:

The Perception module is now capable of detecting and classifying obstacles within only one component named Detection component.

这是说 Perception 模块现在可以只有一个组件:Dectection。

为什么这么说呢?

Apollo 是多数据融合的,它融合 Camera、Lidar、Radar 目标,而这 3 个都是一个 component。




图片来自Apollo github 主页


我们可以先从总入口出发,再一个个分析。

所以,我们得先寻找 Detection 相关的组件。

很容易找到,路径:

apollo/modules/perception/onboard/component

这个目录下,定义和实现了很多感知相关的组件,本文只关注于 Detection。

它的两个相关文件是:

  • detection_component.h
  • detection_component.cc

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:
  static std::atomic<uint32_t> seq_num_;
  std::string sensor_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 lidar2world_trans_;
  std::unique_ptr<lidar::LidarObstacleDetection> detector_;
  std::shared_ptr<apollo::cyber::Writer<LidarFrameMessage>> writer_;
};

CYBER_REGISTER_COMPONENT(DetectionComponent);

}  // namespace onboard
}  // namespace perception

可以看到是以点云数据输入为主。

实现组件

detection_component.cc

bool DetectionComponent::Init() {
  LidarDetectionComponentConfig comp_config;
  if (!GetProtoConfig(&comp_config)) {
    return false;
  }
  ADEBUG << "Lidar Component Configs: " << comp_config.DebugString();
  output_channel_name_ = comp_config.output_channel_name();
  sensor_name_ = comp_config.sensor_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_);

  if (!InitAlgorithmPlugin()) {
    AERROR << "Failed to init detection component algorithm plugin.";
    return false;
  }
  return true;
}

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;
}

Init 和 Proc 是组件两个核心方法。

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/app:lidar_obstacle_detection",
        "//modules/perception/lidar/common",
        "//modules/perception/onboard/common_flags",
        "//modules/perception/onboard/proto:lidar_component_config_cc_proto",
        "//modules/perception/onboard/transform_wrapper",
        "@eigen",
    ],
)

设置配置文件

一个 Component 的配置文件有 2 种:

  • DAG
  • Launch

DAG 定义了模块的依赖关系。

Launch 文件定义了模块的启动。

先看 Launch 文件。

apollo/modules/perception/production/launch/perception_all.launch

<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</name>
        <dag_conf>/apollo/modules/perception/production/dag/dag_streaming_perception.dag</dag_conf>
        <!-- if not set, use default process -->
        <process_name>perception</process_name>
        <version>1.0.0</version>
    </module>
    <module>
        <name>perception_camera</name>
        <dag_conf>/apollo/modules/perception/production/dag/dag_streaming_perception_camera.dag</dag_conf>
        <!-- if not set, use default process -->
        <process_name>perception</process_name>
        <version>1.0.0</version>
    </module>

    <module>
        <name>perception_traffic_light</name>
        <dag_conf>/apollo/modules/perception/production/dag/dag_streaming_perception_trafficlights.dag</dag_conf>
        <!-- if not set, use default process -->
        <process_name>perception_trafficlights</process_name>
        <version>1.0.0</version>
    </module>
    <module>
        <name>motion_service</name>
        <dag_conf>/apollo/modules/perception/production/dag/dag_motion_service.dag</dag_conf>
        <!-- if not set, use default process -->
        <process_name>motion_service</process_name>
        <version>1.0.0</version>
    </module>
</cyber>

我们可以发现 Perception 模块中的 dag 路径:

 <dag_conf>/apollo/modules/perception/production/dag/dag_streaming_perception.dag</dag_conf>

它里面的内容长什么样子呢?

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: "RadarDetectionComponent"
    config {
      name: "FrontRadarDetection"
      config_file_path: "/apollo/modules/perception/production/conf/perception/radar/front_radar_component_conf.pb.txt"
      readers {
          channel: "/apollo/sensor/radar/front"
        }
    }
  }

  components {
    class_name: "RadarDetectionComponent"
    config {
      name: "RearRadarDetection"
      config_file_path: "/apollo/modules/perception/production/conf/perception/radar/rear_radar_component_conf.pb.txt"
      readers {
          channel: "/apollo/sensor/radar/rear"
        }
    }
  }

  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"
        }
      ]
    }
  }
}

我们发现了 DetectionComponent 的身影,看它的配置参数是 Velodyne 128 线激光雷达的配置文件,确实可以看到 Apollo 是以 Lidar 为主。

启动组件

定义了一个 component 相关的文档后,就可以启动了。

命令:

cyber_launch start apollo/modules/perception/production/launch/perception_all.launch

自此,我们就发现了在 Apollo 中启动 perception 一个组件的整个代码过程。

总结

因为 Perception 这个模块非常庞大,涉及到 Camera、Radar、Lidar 3 种类型的传感器,针对各个传感器的数据处理和模型处理会产生 10 多个任务,每个任务都被设计成了组件。

直接去阅读相关的代码会非常容易让自己迷失在代码森林中,所以,本文先讲解一下 CyberRT 这个基础框架 Component 的配置机制,熟悉基础套路后,以后再去学习相应组件时,查询代码就变得非常容易。

后续文章将从 Camera 开始,逐个分析与数据融合相关的 component 基础算法及代码实现,任务有点艰巨,但相信会有很多收获。