Apollo源码分析,感知篇(三)_红绿灯检测与识别简述

Apollo源码分析,感知篇(三):红绿灯检测与识别简述

image

附赠自动驾驶学习资料和量产经验:链接

人在驾驶过程中会注意红绿灯的信息,而自动驾驶更离不开红绿灯信息,有了红绿灯信息,自动驾驶车辆才能更好地与车路进行交互。本篇分析 Apollo 6.0 中红绿灯检测和识别中的相关算法逻辑及部分代码实现。

先看感知架构图。

image

Apollo 中的红绿灯

Apollo 默认有 2 个前视摄像头:

  • 25mm 焦距看远处,视距长,但 FOV 小。

  • 6mm 焦距看近处,视距短,但 FOV 大。

两个摄像头都可以检测到红绿灯,它们相互冗余,但是同一时刻只能以一个为主。

image

image

上面的图片是来自于长焦相机,能看得很远,但视野窄,下面的正好相反。

Apollo 红绿灯模块定义了红绿灯 5 种状态:

  • 绿

  • 未知

算法流程

在 Apollo 中红绿灯模块有一套固定的处理流程:

  1. 预处理阶段

  2. 处理阶段

预处理阶段-信号灯投影

预处理阶段第一个任务就是要根据车辆定位信息从高精度地图中查询红绿灯的物理信息,得到信号灯的物理坐标,然后再通过相机模型和标定好的相机内参,将信号灯从 3D 世界中投影到 2D 图像当中变成一个 Bounding Box。

信号灯的物理位置信息长这样:

signal info:
id {
  id: "xxx"
}
boundary {
  point { x: ...  y: ...  z: ...  }
  point { x: ...  y: ...  z: ...  }
  point { x: ...  y: ...  z: ...  }
  point { x: ...  y: ...  z: ...  }
}

预处理阶段-相机选择

前面讲到有 2 个相机,那么选用哪个呢?

优先选择长焦的相机,因为长焦能将远处的信号灯显示的比较大且清晰,容易做颜色识别。

什么时候选择短焦呢?

当长焦没有办法检测到所有红绿灯的时候。

同一个算法处理周期,只有一个摄像头的图片才能够进行处理。

预处理阶段-图片信息及缓存同步

在自动驾驶中,因为考虑到车辆行驶速度很快,因此障碍物的识别一般要求实时,也就是 30FPS 以上。

但相对于障碍物,红绿灯的位置信息没有那么重要,重要的是它的语义信息,也就是红绿颜色变化,但这种频率是非常低的,所以对于红绿灯检测而言,我们不需要那么高的频率,也因此不需要针对每一帧图片都做红绿灯处理。

因此,我们可以隔一个固定的时间周期去查询高精度地图中的红绿灯信息,然后选择最近的图片缓存一起送入到红绿灯处理模型当中,其它的图片就可以丢掉了。

处理阶段-修整(Rectifier)

预处理会产生一张图片和对应的红绿灯信息(2D bbox),但因为误差问题,2D bbox 和实际上观察到的红绿灯是有偏差的,甚至很大,所以要基于 bbox 设置一个 ROI 区域。

将带有 ROI 信息的图片传输给一个 CNN 做检测,最终会输出一系列的信号灯结果。

处理阶段-识别(Recognizer)

检测是为了估算位置,而识别是要分辨信号灯的颜色。

将 ROI 和信号灯结果连同图像输入给一个 CNN,这个 CNN 就会给出信号灯的颜色信息。

预处理阶段-修正(Revisor)

因为交通灯都会闪烁,所以前一个流程的结果可能不准确,因此需要结合历史信息进行推断。

比如,收到的信号是绿或者红,那么直接输出。

如果是黑色,或者是未知状态,就要根据历史缓存进行推断,如果前面的状态不是黑色或者未知就输出历史状态,否则输出黑色或者未知。

并且,黄灯只能在绿色和红色之间,如果顺序不对就会被丢弃。

上面的内容来自于 Apollo Gitee 仓库的说明,很明显红绿灯识别是个多阶段任务。

我一直在想能不能用端到端的模型一步到位呢?

比如用 LSTM 直接输出最终的结果?

这个需要尝试一下。

理论知识讲解完后,就要开始在代码层面验证了。

红绿灯模块启动

之前的文章有写到过,cyberRT 用 launch 文件定义 Module 启动相关。

<!--this file list the modules which will be loaded dynamicly and
    their process name to be running in -->

<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.conf -->

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

</cyber>

所以我们可以很快找到它的依赖关系。

module_config {
  module_library : "/apollo/bazel-bin/modules/perception/onboard/component/libperception_component_camera.so"

  components {
    class_name : "TrafficLightsPerceptionComponent"
    config {
      name: "TrafficLightsComponent"
      config_file_path: "/apollo/modules/perception/production/conf/perception/camera/trafficlights_perception_component.config"
      flag_file_path: "/apollo/modules/perception/production/conf/perception/perception_common.flag"
    }
  }
}

顺藤摸瓜,把配置文件也瞅瞅。

tl_tf2_frame_id : "world"
#tl_tf2_child_frame_id : "perception_localization_100hz"
tl_tf2_child_frame_id : "novatel"
tf2_timeout_second : 0.01
camera_names : "front_6mm,front_12mm"
camera_channel_names : "/apollo/sensor/camera/front_6mm/image,/apollo/sensor/camera/front_12mm/image"
tl_image_timestamp_offset : 0.0
max_process_image_fps : 8
query_tf_interval_seconds : 0.3
valid_hdmap_interval : 1.5
image_sys_ts_diff_threshold : 0.5
sync_interval_seconds : 0.5
camera_traffic_light_perception_conf_dir : "../modules/perception/production/conf/perception/camera"
camera_traffic_light_perception_conf_file : "trafficlight.pt"
default_image_border_size : 100
#traffic_light_output_channel_name : "/perception/traffic_light_status"
traffic_light_output_channel_name : "/apollo/perception/traffic_light"
simulation_channel_name : "/perception/traffic_light_simulation"
v2x_trafficlights_input_channel_name : "/apollo/v2x/traffic_light"
v2x_sync_interval_seconds : 0.1
max_v2x_msg_buff_size : 50

因为前面已经讲过红绿灯模块的处理逻辑,所以秘密其实就全部藏在配置文件中。

定位的频率,摄像头的名字,摄像头图像的 channel,查询定位的时间间隔等等。

现在,我们可以跳转到 module 目录下看代码了。

image

namespace apollo {
namespace perception {
namespace camera {

class TrafficLightCameraPerception : public BaseCameraPerception {
 public:
  TrafficLightCameraPerception()
      : detector_(nullptr), recognizer_(nullptr), tracker_(nullptr) {}
  ~TrafficLightCameraPerception() = default;
  bool Init(const CameraPerceptionInitOptions &options) override;
  bool Perception(const CameraPerceptionOptions &options,
                  CameraFrame *frame) override;
  std::string Name() const override { return "TrafficLightCameraPerception"; }

 private:
  std::shared_ptr<BaseTrafficLightDetector> detector_;
  std::shared_ptr<BaseTrafficLightDetector> recognizer_;
  std::shared_ptr<BaseTrafficLightTracker> tracker_;
  app::TrafficLightParam tl_param_;
};

}  // namespace camera
}  // namespace perception
} 

头文件定义了 3 个属性,detector_,recognizer_,tracker_ 显然对应前面修整、识别、修正 3 个阶段,下面来看看实现。 modules/perception/camera/app/http://traffic_light_camera_perceptio.cc

#include "modules/perception/camera/app/traffic_light_camera_perception.h"

#include "cyber/common/file.h"
#include "cyber/common/log.h"
#include "modules/common/util/perf_util.h"
#include "modules/perception/camera/common/util.h"
#include "modules/perception/camera/lib/traffic_light/detector/detection/detection.h"
#include "modules/perception/camera/lib/traffic_light/detector/recognition/recognition.h"
#include "modules/perception/camera/lib/traffic_light/tracker/semantic_decision.h"

namespace apollo {
namespace perception {
namespace camera {

using cyber::common::GetAbsolutePath;

bool TrafficLightCameraPerception::Init(
    const CameraPerceptionInitOptions &options) {
  std::string work_root = "";
  if (options.use_cyber_work_root) {
    work_root = GetCyberWorkRoot();
  }
  std::string proto_path = GetAbsolutePath(options.root_dir, options.conf_file);
  proto_path = GetAbsolutePath(work_root, proto_path);
  AINFO << "proto_path " << proto_path;
  if (!cyber::common::GetProtoFromFile(proto_path, &tl_param_)) {
    AINFO << "load proto param failed, root dir: " << options.root_dir;
    return false;
  }

  TrafficLightDetectorInitOptions init_options;
  auto plugin_param = tl_param_.detector_param(0).plugin_param();

  init_options.root_dir = GetAbsolutePath(work_root, plugin_param.root_dir());
  init_options.conf_file = plugin_param.config_file();
  init_options.gpu_id = tl_param_.gpu_id();
  detector_.reset(BaseTrafficLightDetectorRegisterer::GetInstanceByName(
      plugin_param.name()));
  ACHECK(detector_ != nullptr);
  if (!detector_->Init(init_options)) {
    AERROR << "tl detector init failed";
    return false;
  }

  plugin_param = tl_param_.detector_param(1).plugin_param();
  init_options.root_dir = GetAbsolutePath(work_root, plugin_param.root_dir());
  init_options.conf_file = plugin_param.config_file();
  init_options.gpu_id = tl_param_.gpu_id();
  recognizer_.reset(BaseTrafficLightDetectorRegisterer::GetInstanceByName(
      plugin_param.name()));
  ACHECK(recognizer_ != nullptr);
  if (!recognizer_->Init(init_options)) {
    AERROR << "tl recognizer init failed";
    return false;
  }

  TrafficLightTrackerInitOptions tracker_init_options;
  auto tracker_plugin_param = tl_param_.tracker_param().plugin_param();
  tracker_init_options.root_dir =
      GetAbsolutePath(work_root, tracker_plugin_param.root_dir());
  tracker_init_options.conf_file = tracker_plugin_param.config_file();
  tracker_.reset(BaseTrafficLightTrackerRegisterer::GetInstanceByName(
      tracker_plugin_param.name()));
  ACHECK(tracker_ != nullptr);
  AINFO << tracker_init_options.root_dir << " "
        << tracker_init_options.conf_file;
  if (!tracker_->Init(tracker_init_options)) {
    AERROR << "tl tracker init failed";
    return false;
  }

  AINFO << "tl pipeline init done";
  return true;
}

bool TrafficLightCameraPerception::Perception(
    const CameraPerceptionOptions &options, CameraFrame *frame) {
  PERF_FUNCTION();
  PERF_BLOCK_START();
  TrafficLightDetectorOptions detector_options;
  if (!detector_->Detect(detector_options, frame)) {
    AERROR << "tl failed to detect.";
    return false;
  }

  TrafficLightDetectorOptions recognizer_options;
  if (!recognizer_->Detect(recognizer_options, frame)) {
    AERROR << "tl failed to recognize.";
    return false;
  }

  TrafficLightTrackerOptions tracker_options;
  if (!tracker_->Track(tracker_options, frame)) {
    AERROR << "tl failed to track.";
    return false;
  }
  return true;
}

}  // namespace camera
}  // namespace perception
} 

代码非常简单,在 Perception 方法中就做简单的调库,并且代码中也有体现被调的SDK路径。

image

前面文章有介绍到红绿灯模块工作分 2 个阶段,预处理和处理,上面是红绿灯模块的 Lib 目录,基本上是算法的实现。

我们对某个过程感兴趣就可以从里面找答案。

比如,我想了解红绿灯的检测用的是哪个模型,我就可以去 detector 目录找答案。

/modules/perception/camera/lib/traffic_light/detector/detection/detection.h

#include "modules/perception/base/blob.h"
#include "modules/perception/base/image_8u.h"
#include "modules/perception/camera/lib/interface/base_traffic_light_detector.h"
#include "modules/perception/camera/lib/traffic_light/detector/detection/cropbox.h"
#include "modules/perception/camera/lib/traffic_light/detector/detection/select.h"
#include "modules/perception/camera/lib/traffic_light/proto/detection.pb.h"
#include "modules/perception/inference/inference.h"

我们知道 Apollo 最早支持的 AI 算法框架是 Caffe2,那么自然就可以找到 detector 大概在 Proto 文件夹中定义。

/modules/perception/camera/lib/traffic_light/proto/detection.proto

syntax = "proto2";

package apollo.perception.camera.traffic_light.detection;

message DetectionParam {
  optional int32 min_crop_size = 1 [default = 270];
  optional int32 crop_method = 2 [default = 0];
  optional float mean_b = 3 [default = 95];
  optional float mean_g = 4 [default = 99];
  optional float mean_r = 5 [default = 96];
  optional bool is_bgr = 6 [default = true];
  optional float crop_scale = 7 [default = 2.5];
  optional string input_blob_name = 8;
  optional string im_param_blob_name = 9;
  optional string output_blob_name = 10;
  optional string model_name = 11 [default = "RTNet"];
  optional string model_type = 12 [default = "RTNet"];
  optional string proto_file = 13 [default = "caffe.pt"];
  optional string weight_file = 14 [default = "caffe.model"];
  optional int32 max_batch_size = 15 [default = 1];
}

这个文件确实定义清楚了用于红绿灯检测的模型名称默认是 RTNet,protofile 是 caffe.pt,模型文件是 caffe.model。

这些文件在哪里呢?

之前的 trafficlight.pt 给出了信息。

gpu_id: 0
detector_param{
    plugin_param{
        name: "TrafficLightDetection"
        root_dir:"/apollo/modules/perception/production/data/perception/camera/models/traffic_light_detection"
        config_file:"detection.pt"
        }
}
detector_param{
    plugin_param{
        name: "TrafficLightRecognition"
        root_dir:"/apollo/modules/perception/production/data/perception/camera/models/traffic_light_recognition"
        config_file:"recognition.pt"
        }
}
tracker_param{
    plugin_param{
        name: "SemanticReviser"
        root_dir:"/apollo/modules/perception/production/data/perception/camera/models/traffic_light_tracker"
        config_file:"semantic.pt"
        }
}

感知相关的模型都定义在这个目录。

image

我们点击traffic_light_detection,可以看到:

image

一清二楚,只不过权重文件没有出现估计是没有训练,其它模块目录是有权重文件的。

总结

Apollo 的红绿灯检测流程比较繁琐,因为篇幅所限,没有办法再去深入每一行代码,但好在知道了它的整体思路,知道了它的边边角角,某天状态好的时候可以一行一行代码分析它,后面看具体情况吧。

总之,红绿灯检测模块在 Apollo 中考虑还是很细致的,但我觉得光有算法还是不够的,能够将算法兑现成可行的代码还是非常具有挑战性的,尤其是对于自动驾驶小团队,算法工程师一般要设计模型也要负责代码实现。

希望我们的代码能力都能逐步提升。

最后,本文图片都来自于 Apollo 开源仓库,本文只做单纯的技术分享用,如果侵权请告知,我将删掉。

感谢 Apollo 团队。

参考

  1. https://gitee.com/ApolloAuto/ap
  • 28
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用\[2\]中提供的信息,ApolloScape数据集中包含了信号灯的数据,但是只能在线使用。该数据集是目前行业内环境最复杂、标注最精准、数据量最大的自动驾驶公开数据集。它包括了14.7万帧的像素级语义标注图像,其中包括了感知分类和路网数据等数十万帧逐像素语义分割标注的高分辨率图像数据。此外,ApolloScape还计划产出至少20万张图片用于举行不同的挑战赛,其中将会覆盖来自个城市的5个站点的20KM的道路。所以,如果你对ApolloScape数据集中的红绿灯数据感兴趣,你可以在线使用该数据集来进行相关的研究和开发。 #### 引用[.reference_title] - *1* *3* [ApolloScape自动驾驶数据集](https://blog.csdn.net/sinat_37196107/article/details/82151570)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [【网上开源的数据集】交通 数据集:【数据集-交通标志 红绿灯红绿灯 交通标志](https://blog.csdn.net/weixin_42419002/article/details/100605115)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值