从 Apollo 的官方文档,我们很容易得知 Perception 是核心的组件之一,但像所有的 C 程序一样,每个应用都有一个 Main 函数入口,那么引出本文要探索的 2 个问题:
- Perception 的入口在哪里?
- 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 相关的文档有这几个:
代码语言:javascript复制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。
我们可以先从总入口出发,再一个个分析。
所以,我们得先寻找 Detection 相关的组件。
很容易找到,路径:
代码语言:javascript复制apollo/modules/perception/onboard/component
这个目录下,定义和实现了很多感知相关的组件,本文只关注于 Detection。
它的两个相关文件是:
- detection_component.h
- detection_component.cc
detection_component.h
代码语言:javascript复制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
代码语言:javascript复制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 是组件两个核心方法。
BUILD 文件地址是:
apollo/modules/perception/onboard/component/BUILD
BUILD 文件定义了 perception 中所有的 component 如 camera,radar,lidar 等的信息,本文只关注 Detection。
代码语言:javascript复制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
代码语言:javascript复制<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 路径:
代码语言:javascript复制 <dag_conf>/apollo/modules/perception/production/dag/dag_streaming_perception.dag</dag_conf>
它里面的内容长什么样子呢?
代码语言:javascript复制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 相关的文档后,就可以启动了。
命令:
代码语言:javascript复制cyber_launch start apollo/modules/perception/production/launch/perception_all.launch
自此,我们就发现了在 Apollo 中启动 perception 一个组件的整个代码过程。
总结
因为 Perception 这个模块非常庞大,涉及到 Camera、Radar、Lidar 3 种类型的传感器,针对各个传感器的数据处理和模型处理会产生 10 多个任务,每个任务都被设计成了组件。
直接去阅读相关的代码会非常容易让自己迷失在代码森林中,所以,本文先讲解一下 CyberRT 这个基础框架 Component 的配置机制,熟悉基础套路后,以后再去学习相应组件时,查询代码就变得非常容易。
后续文章将从 Camera 开始,逐个分析与数据融合相关的 component 基础算法及代码实现,任务有点艰巨,但相信会有很多收获。