🚙

自動運転AIチャレンジ ピットストップエリアの停止にチャレンジ

2024/08/13に公開

背景

自動運転AIチャレンジ2024にチームで参加しており、大会への貢献のために開発した内容をまとめております。今回はピットストップエリアでの停止機能を開発したのでその内容をまとめます。

ルール

今大会は前回大会から以下のルールが追加されております。

  • コンディション:車両の仮想的な値。一定以上になると速度制限がかかる。コースを一定間隔走行すると増加する。1周目では240まで上昇する。おそらく8つの障害物ポイントを通過するだけで 30 は増加する仕様。
  • 仮想障害物:コース上の8つのポイントに配置される。周ごとにランダムで上下左右に座標が変化する。衝突するとコンディションが大きく増加する。
  • ピットストップ:スタート地点に配置されるエリア。停止することでコンディションを0にし、速度制限をリセットできる。

そのため、速度制限がかかったタイミングでピットストップエリアに停止して、速度制限をなくすことが重要になります。

goal_pose_setterの変更

デフォルトの自動運転AIチャレンジ2024のリポジトリではgoal_pose_setter
という目的地を設定するパッケージが存在します。今回はこちらのパッケージを変更します。

設計

デフォルトのgoal_pose_setterでは以下のように、ゴールの座標、中間ゴールの座標を交互に目的地として送信しています。
before
地図

今回は以下のようにピットストップエリアの座標を目的地として送信する状態を追加します。
after.png

実装

コンディションの監視

まず、コンディションの値を監視するノードを作成します。以下のようにコンディションを受信するsubscriberを定義します。
以下ではコールバック関数を onVehicleCondition という名前にしております。

goal_pose_setter_node.cpp
    vehicle_condition_subscriber_ = this->create_subscription<std_msgs::msg::Int32>(
        "/aichallenge/pitstop/condition", 1,
        std::bind(&GoalPosePublisher::onVehicleCondition, this, std::placeholders::_1));

次に以下のようにコールバック関数を定義します。以下は受信したコンディションが一定の値(今回は1000)以上になると、pit_stop_flagをTrueにします。

goal_pose_setter_node.cpp
void GoalPosePublisher::onVehicleCondition(const std_msgs::msg::Int32::SharedPtr msg){
    vehicle_condition_ = msg->data;
    if (vehicle_condition_ >= 1000){
        pit_stop_flag = true;
    }
}

ピットストップエリアの設定

デフォルトではゴール座標、中間ゴール座標がdefault_goal_pose.param.yamlに設定されています。今回はピットストップエリアの座標をyamlに追加し、ソースコード内でロードします。
まず、以下のようにピットストップエリアの座標をyamlに追記します。

default_goal_pose.param.yaml
    pit_stop_area.position.x: 89626.3671875
    pit_stop_area.position.y: 43134.921875
    pit_stop_area.position.z: 42.10000228881836
    pit_stop_area .orientation.x: 0.0
    pit_stop_area.orientation.y: -0.0
    pit_stop_area.orientation.z: -0.878817200660705
    pit_stop_area.orientation.w: -0.47715866565704346
    pit_stop_area.size.x: 4.0
    pit_stop_area.size.y: 2.0

次に以下をソースコードに追記し、ノードでパラメータを定義します。注意点として、パラメータをas_double()のフォーマットにしているため、yamlファイルの値を変更することがあれば小数点以下まで記載してください。

goal_pose_setter_node.cpp
    this->declare_parameter("pit_stop_area.position.x", 89626.367);
    this->declare_parameter("pit_stop_area.position.y", 43134.921);
    this->declare_parameter("pit_stop_area.position.z", 42.100);
    this->declare_parameter("pit_stop_area.orientation.x", 0.0);
    this->declare_parameter("pit_stop_area.orientation.y", -0.0);
    this->declare_parameter("pit_stop_area.orientation.z", -0.878);
    this->declare_parameter("pit_stop_area.orientation.w", -0.477);

    pit_stop_goal_position_.position.x = this->get_parameter("pit_stop_area.position.x").as_double();
    pit_stop_goal_position_.position.y = this->get_parameter("pit_stop_area.position.y").as_double();
    pit_stop_goal_position_.position.z = this->get_parameter("pit_stop_area.position.z").as_double();
    pit_stop_goal_position_.orientation.x = this->get_parameter("pit_stop_area.orientation.x").as_double();
    pit_stop_goal_position_.orientation.y = this->get_parameter("pit_stop_area.orientation.y").as_double();
    pit_stop_goal_position_.orientation.z = this->get_parameter("pit_stop_area.orientation.z").as_double();
    pit_stop_goal_position_.orientation.w = this->get_parameter("pit_stop_area.orientation.w").as_double();

目的地の設定

各状態ごとで目的を変更するように odometry_callback 関数を以下のように変更します。
まず、"中間ゴールへの走行"状態に遷移する条件に"pit_stop_flag == false" を追加します

goal_pose_setter_node.cpp
    // Publish half goal pose for loop
    if( pit_stop_flag == false &&
        half_goal_pose_published_ == false &&
        tier4_autoware_utils::calcDistance2d(msg->pose.pose, goal_position_) < goal_range_) 

次に"ピットストップエリアへ走行"状態に遷移する部分を記載します。

goal_pose_setter_node.cpp
    // Publish pit_sotp_pose
    if (pit_stop_flag ==  true &&
        pit_stop_published == false && 
        half_goal_pose_published_ == false &&
        tier4_autoware_utils::calcDistance2d(msg->pose.pose, goal_position_) < goal_range_)
    {
        auto goal_pose = std::make_shared<geometry_msgs::msg::PoseStamped>();
        goal_pose->header.stamp = this->get_clock()->now();
        goal_pose->header.frame_id = "map";
        goal_pose->pose = pit_stop_goal_position_;

        goal_publisher_->publish(*goal_pose);
        RCLCPP_INFO(this->get_logger(), "Publishing goal pose for loop");
        pit_st

最後に"ピットストップエリアへ走行"状態から"中間ゴールへの走行"状態に遷移する部分を記載します。今回はコンディションの値が0になれば、遷移することにしました。

goal_pose_setter_node.cpp
    // Check vehicle condition and publish half goal pose
    if (vehicle_condition_ == 0 && pit_stop_published == true)
    {
        auto goal_pose = std::make_shared<geometry_msgs::msg::PoseStamped>();
        goal_pose->header.stamp = this->get_clock()->now();
        goal_pose->header.frame_id = "map";
        goal_pose->pose = half_goal_position_;

        goal_publisher_->publish(*goal_pose);
        RCLCPP_INFO(this->get_logger(), "Publishing half goal pose for loop");
        half_goal_pose_published_ = true;
        pit_stop_published = false;
        pit_stop_flag = false;
    }

ソースコード

変更したgoal_pose_setterのファイルはまとめると以下になります。

simple_pure_pursuiteの変更

デフォルトのsimple_pure_pursuiteでは目的値にピッタリ停止することができず、通り過ぎてしまうため、微調整を行いました。目的地に停止する方法はいろいろあると思いますが、できるだけデフォルトのソースコードから変更しない方法を紹介します。

設計

まず、デフォルトのsimple_pure_pursuiteのソースコードはこちらになります。simple_pure_pursuiteについてはこちらの記事で分かりやすく解説いただいております。
 simple_pure_pursuiteの縦方向の制御では、trajectory_point ごとに設定される速度をそのまま目標の速度として使用します。また、残りのtrajectory_points数がある一定個数(デフォルトでは5個)以下になると停止のために目標速度を0にし、アクセルを負の値にします。今回は残りのtrajectory_points数の閾値を調整し、ピットストップエリアを通りすぎないようにします。
trajectory_points

実装

パラメータの設定

残りのtrajectory_points数の閾値を minimum_trj_point_size_ とし、パラメータとして設定できるよういします。まず、以下のようなyamlファイルをパッケージ内に作成します。

default_simple_pure_pursuit.yaml
/**:
  ros__parameters:
    minimum_trj_point_size: 16

次にソースコード内でパラメータをロードするように以下を追記します。

simple_pure_pursuit.cpp
  this->declare_parameter("minimum_trj_point_size", 10);
  minimum_trj_point_size_ = this->get_parameter("minimum_trj_point_size").as_int();

最後に作成したdefault_simple_pure_pursuit.yamlを読み込むようにlaunch ファイルのsimple_pure_pursuitを起動する部分を変更します。

reference.launch.xml
  <node pkg="simple_pure_pursuit" exec="simple_pure_pursuit" name="simple_pure_pursuit_node" output="screen">
    <param from="$(find-pkg-share simple_pure_pursuit)/config/default_simple_pure_pursuit.yaml"/>
    <param name="use_external_target_vel" value="true"/>
    <param name="external_target_vel" value="8.0"/>
    <param name="lookahead_gain" value="0.3"/>
    <param name="lookahead_min_distance" value="4.0"/>
    <param name="speed_proportional_gain" value="1.0"/>
    
    <remap from="input/kinematics" to="/localization/kinematic_state"/>
    <remap from="input/trajectory" to="/planning/scenario_planning/trajectory"/>
    <remap from="output/control_cmd" to="/control/command/control_cmd"/>
  </node>

アルゴリズムの変更

ピットストップエリアに停止するためにsimple_pure_pursuit.cppのonTimer関数を変更していきます。
まず、以下で残りのtrajectory_points数を判定します。ここでパラメータ化したminimum_trj_point_size_ を使用します。さらに座標点の残りがminimum_trj_point_size_以下であれば停止のために速度を 0.0 、アクセルを -30.0 にします。

simple_pure_pursuit.cpp
  if (
    (closet_traj_point_idx == trajectory_->points.size() - 1) ||
    (trajectory_->points.size() <= minimum_trj_point_size_)) {
    cmd.longitudinal.speed = 0.0;
    cmd.longitudinal.acceleration = -30.0;
    RCLCPP_INFO_THROTTLE(get_logger(), *get_clock(), 1000 /*ms*/, "reached to the goal");
  } 

座標点の残りが少ない場合でも横方向の制御は計算するように、steering_tire_angleの計算部分をelse の外に出します。

simple_pure_pursuit.cpp
else {
    // get closest trajectory point from current position
    TrajectoryPoint closet_traj_point = trajectory_->points.at(closet_traj_point_idx);

    // calc longitudinal speed and acceleration
    target_longitudinal_vel =
      use_external_target_vel_ ? external_target_vel_ : closet_traj_point.longitudinal_velocity_mps;
    double current_longitudinal_vel = odometry_->twist.twist.linear.x;

    cmd.longitudinal.speed = target_longitudinal_vel;
    cmd.longitudinal.acceleration =
      speed_proportional_gain_ * (target_longitudinal_vel - current_longitudinal_vel);
  }
  // calc lateral control
  //// calc lookahead distance
  double lookahead_distance = lookahead_gain_ * target_longitudinal_vel + lookahead_min_distance_;
  
  //// calc center coordinate of rear wheel
  double rear_x = odometry_->pose.pose.position.x -
                  wheel_base_ / 2.0 * std::cos(odometry_->pose.pose.orientation.z);
  double rear_y = odometry_->pose.pose.position.y -
                  wheel_base_ / 2.0 * std::sin(odometry_->pose.pose.orientation.z);
  //// search lookahead point
  auto lookahead_point_itr = std::find_if(
    trajectory_->points.begin() + closet_traj_point_idx, trajectory_->points.end(),
    [&](const TrajectoryPoint & point) {
      return std::hypot(point.pose.position.x - rear_x, point.pose.position.y - rear_y) >=
              lookahead_distance;
    });
  if (lookahead_point_itr == trajectory_->points.end()) {
    lookahead_point_itr = trajectory_->points.end() - 1;
  }
  double lookahead_point_x = lookahead_point_itr->pose.position.x;
  double lookahead_point_y = lookahead_point_itr->pose.position.y;

  // calc steering angle for lateral control
  double alpha = std::atan2(lookahead_point_y - rear_y, lookahead_point_x - rear_x) -
                  tf2::getYaw(odometry_->pose.pose.orientation);
  cmd.lateral.steering_tire_angle =
    std::atan2(2.0 * wheel_base_ * std::sin(alpha), lookahead_distance);

  pub_cmd_->publish(cmd);

ソースコード

作成・変更したファイルはまとめると以下になります。

結果

simple_pure_pursuite(デフォルト)

以下はsimple_pure_pursuiteを変更せずに、目的地のみを変更した結果になります。動画のようにピットストップエリアを通り過ぎてしまいます。

https://www.youtube.com/watch?v=ShR_2FRHvVk&feature=youtu.be

simple_pure_pursuite (変更版)

以下が今回作成した内容で走行した結果になります。
https://www.youtube.com/watch?v=t1HEeSXiLno

リポジトリ

今回作成したソースコード全体はこちらに公開しております。

まとめ

今回はピットストップエリアに停止するための変更方法を紹介しました。今回はsimple_pure_pursuiteを変更することで停止するように車両制御しましたが、実車の環境では停止位置がズレる可能性が高いため、柔軟な対応が必要になります。そのため、今後は車両制御方法の変更にも挑戦していきたいです。

参考文献

[1] aichallenge2023-racingのsimple_pure_pursuitについてのメモ

Discussion