查看原文
其他

开发者说| Apollo 源码分析系列感知篇(四):将红绿灯检测和识别代码细致走读一遍

Frank909 Apollo开发者社区 2022-07-29


下面是由社区开发者—Frank909提供的文章,之前讲到了 Apollo 中红绿灯算法逻辑和各部分代码位置及粗略流程,本篇文章更进一步去分析具体代码实现。


    ENJOY THE FOLLOWING  


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

按照名字的意思是初始化算法插件,既然是插件,那肯定支持动态加载。而实际上,这一段非常重要,因为核心相关代码都在这里进行初始化。



红绿灯检测分为 3 部分:
  • 前处理

  • 算法处理

  • 后处理


所以,在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;  }

根据代码猜测应该是为了保证红绿灯的检测有序进行,过时的信息将不会重复处理,这可能是异步带来的问题。


FillImageData这个不需要解释太多,将接收到的Image信息存储到CameraFrame中。之后调用traffic_light_pipeline.Perception


这个traffic_light_pipeline 是什么呢?就是一个TrafficLightCameraPerception对象。Apollo 是一个大型的开源库,很多算法都是比较经典的论文复现。


TrafficLightCamera里面 detector_、recognizer_、tracker_ 是 3 个核心算法对象,分别对应红绿灯的检测、识别、跟踪,它们都是基于神经网络,感知相关的模型都存在一个 Models 目录中,路径如下:



它们经Lib 中的检测器调用,最终通过 Inferrence 选择合适的推理引擎,然后输出结果。


在 Camera/Lib 目录下存放着各类检测器的代码实现。


在 Perception 目录下有一个 Inference 目录,有大家平时常用的各类推理引擎。



通过工厂方法模型可以针对模型名字选择合适的引擎。



某个神经网络模型是何种模型,通过在Models 目录下的 pt 文件当中定义。RTNet 应该对应的是TensorRT的引擎文件,所以到这里Perception中的逻辑流程也通了。检测好的红绿灯信息,最终通过Writer发送到对应的Channel中去。大家可以看到,即使忽略很多代码,红绿灯检测的流程还是非常的长。


自动驾驶最重要的不一定是算法,而是工程代码。工程代码决定是否能够落地,现在很多算法其实鲁棒性都不够,实际优秀的表现都是工程调参出来的。自动驾驶也是一个系统性问题,需要平衡的东西太多了,算法、代码、硬件、通信协议等等。如何把这么多东西糅合在一起并且能正常运行实在是太重要了。


*Frank909 微信公众号

https://mp.weixin.qq.com/s/Khx9b2svet7sVpMn_qQL9A


以上是" Apollo 源码分析系列感知篇(四):将红绿灯检测和识别代码细致走读一遍"的全部内容,更多话题讨论、技术交流可以扫描下方二维码添加『Apollo小哥哥』为好友,进开发者交流群。

 


©️著作权归作者所有,如需转载,请注明出处,否则将追究法律责任。



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存