开发者说| Apollo 源码分析系列感知篇(四):将红绿灯检测和识别代码细致走读一遍
下面是由社区开发者—Frank909提供的文章,之前讲到了 Apollo 中红绿灯算法逻辑和各部分代码位置及粗略流程,本篇文章更进一步去分析具体代码实现。
Apollo 是一个开源的框架,这个框架非常的庞大,研究代码的时候很容易陷入进去,为此,我根据不同的任务梳理了脉络,在红绿灯检测模块,根据自己感兴趣的线索得到了下图:
Apollo 是讲究 Module 这个概念的,Module 里面有不同的 Component 实现。所以,如果要追踪整个红绿灯识别的代码流程,那么就需要从 TrafficLightPerceptionComponent.cc 开始。
1、基础流程
modules/perception/onboard/component/trafficlights_perception_component.cc上面是 trafficlights_perception_component.cc 的路径。阅读代码可以得到基础流程。
Component 进行了一系列的初始化,所以需要看看它到底进行了哪些内容的初始化。
2、InitConfig
这个是用来初始 Config 的配置文件,流程如下:
关于配置文件以 proto 文件保存的答案,在文章最开始的思维导图有提到。
modules/perception/production/conf/perception/camera/trafficlights_perception_component.config
其实就是将一些变量抽出来,做成了配置文件形式,方便后续的灵活配置。
3、InitAlgorithmPlugin
按照名字的意思是初始化算法插件,既然是插件,那肯定支持动态加载。而实际上,这一段非常重要,因为核心相关代码都在这里进行初始化。
前处理
算法处理
后处理
所以,在InitAlgorithmPlugin的时候,可以看到TLPreprocessor的身影,它就是用来进行前处理的。
另外,还初始化了:
SensorManager
TransformWrapper
TrafficLightCameraPerception
内容比较多,我们简化处理,重点关注这 2 个:
TLPreprocessor
TrafficLightCameraPerception
4、Init CameraFrame
Apollo 把 Camera 中相关的数据定义为 CameraFrame。CameraFrame 是一个 Struct,也不单单只为 TrafficLight 服务,它是通用的,可以应对车道线检测、目标检测、红绿灯检测任务。
对于 TrafficLight 检测,本文只需要关注 CameraFrame 中的这几个:
timestamp
frameid
data_provider
std::vector<base::TrafficLightPtr> 根据名字猜测,data_provider 应该是用来存储原始的图像数据,TrafficLightPtr 应该是检测到的红绿灯对象指针。
5、initCameraListeners
代码在初始化CameraFrame之后,进行了关键的一步,就是初始化 CameraListeners。因为 Camera 可能有几个,所以,Listener 也对应有几个。
int TrafficLightsPerceptionComponent::InitCameraListeners() {
// init camera listeners
for (size_t i = 0; i < camera_names_.size(); ++i) {
const auto& camera_name = camera_names_[i];
const auto& camera_channel_name = input_camera_channel_names_[i];
const std::string camera_listener_name = "tl_" + camera_name + "_listener";
typedef const std::shared_ptr<apollo::drivers::Image> ImageMsgType;
std::function<void(const ImageMsgType&)> sub_camera_callback =
std::bind(&TrafficLightsPerceptionComponent::OnReceiveImage, this,
std::placeholders::_1, camera_name);
auto sub_camera_reader =
node_->CreateReader(camera_channel_name, sub_camera_callback);
last_sub_camera_image_ts_[camera_name] = 0.0;
}
return cyber::SUCC;
}
值得注意的地方是,运用了 C++ 11 中的std::function和std::bind模板,主要作用就是用来设置回调函数。回调函数是onReceiveImage,当channel中有数据来临时,camera reader触发onReceiveImage函数,而红绿灯检测就自此开始。
6、OnReceiveImage
当图像来临时,代码如何处理?
updateCameraSelection简单概括一下,通过时间戳查询车辆的位置。通过给HDmap 输入位置和时间戳,来查询地图中的红绿灯。需要注意的是HDmap中查询得到的红绿灯用Signal表示,算法将Signal加工得到TrafficLight,TrafficLight会保存ROI信息,ROI是利用相机模型计算公式,将红绿灯距离车辆的距离通过内外参投影到对应的相机成像图片上。Camera 选择好后,需要进行信息同步SyncInformation。
if (image_timestamp < last_pub_img_ts_) {
AWARN << "TLPreprocessor reject the image pub ts:" << image_timestamp
<< " which is earlier than last output ts:" << last_pub_img_ts_
<< ", image_camera_name: " << cam_name;
return false;
}
在 Perception 目录下有一个 Inference 目录,有大家平时常用的各类推理引擎。
通过工厂方法模型可以针对模型名字选择合适的引擎。
自动驾驶最重要的不一定是算法,而是工程代码。工程代码决定是否能够落地,现在很多算法其实鲁棒性都不够,实际优秀的表现都是工程调参出来的。自动驾驶也是一个系统性问题,需要平衡的东西太多了,算法、代码、硬件、通信协议等等。如何把这么多东西糅合在一起并且能正常运行实在是太重要了。
*Frank909 微信公众号
https://mp.weixin.qq.com/s/Khx9b2svet7sVpMn_qQL9A
以上是" Apollo 源码分析系列感知篇(四):将红绿灯检测和识别代码细致走读一遍"的全部内容,更多话题讨论、技术交流可以扫描下方二维码添加『Apollo小哥哥』为好友,进开发者交流群。