自己位置推定を理解する

# はじめに

自動運転AIチャレンジ2024決勝まで、残りわずかとなりました。

https://www.jsae.or.jp/jaaic/2024ver/summary/

目下、**自己位置推定** の改善にも取り組んでいます。

GNSS + IMU + 車輪速センサ で自己位置を推定するのですが、
GNSS遅延やIMU精度不足…など、実車特有の課題があります。

どのような原理でAutowareの自己位置推定が動いているのか、個人的に整理したく、まとめました。完全なメモですが、なにかの参考になれば…と思い公開します。

# 原理

今回は、拡張カルマンフィルタを用いた自己位置推定を行っています。
原理に関しては、ティアフォー様の以下技術ブログがとてもわかり易いです。

https://tech.tier4.jp/entry/2021/08/04/160000

個人的にこちらもスキ。

https://www.youtube.com/watch?v=zVAq_3HClGs

# 2024大会での自己位置推定

デフォルト設定で、どのように自己位置が推定されているか整理します。

## システムアーキテクチャ

GNSS + IMU + 車輪速センサ で自己位置を推定しています(without LiDAR / Camera)。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/e1cf7ee5-2a68-7154-120c-eb9d14b1b9f7.png)

https://automotiveaichallenge.github.io/aichallenge-documentation-2024/development/main-module.html

処理フローとしては、**1. 個別センサ値の前処理 → 2. センサ値の統合 → 3. 拡張カルマンフィルタでの自己位置推定** の流れでしょうか。

それぞれ個別ノードの詳細を見てみます。

### 1. 個別センサ値の前処理

#### imu_corrector

IMUのオフセット補正や標準偏差情報付与を行うノード。
IMUが信頼できない場合はこの標準偏差(=不確実性)を高く設定することで、後段の処理でこの観測値は更新に使われなくなる。

https://github.com/AutomotiveAIChallenge/aichallenge-2024/tree/main/aichallenge/workspace/src/aichallenge_submit/imu_corrector

#### vehicle_velocity_converter

車速情報を特定トピックに変換するノード。

https://github.com/AutomotiveAIChallenge/autoware.universe/tree/awsim-stable/sensing/vehicle_velocity_converter

共分散情報も付与。
車輪速センサが信頼できない場合はこの共分散(=不確実性)を高く設定することで、後段の処理でこの観測値は更新に使われなくなる。

https://github.com/AutomotiveAIChallenge/autoware.universe/blob/awsim-stable/sensing/vehicle_velocity_converter/src/vehicle_velocity_converter.cpp#L46-L51

### 2. センサ値の統合

#### imu_gnss_poser

IMUとGNSSを使って、`pose`を推定するノード。

https://github.com/AutomotiveAIChallenge/aichallenge-2024/tree/main/aichallenge/workspace/src/aichallenge_submit/imu_gnss_poser

GNSSから受け取ったデータに基づいて、姿勢情報が無効な場合はIMUのデータを使って補完。
共分散(=不確実性)を調整。

https://github.com/AutomotiveAIChallenge/aichallenge-2024/blob/main/aichallenge/workspace/src/aichallenge_submit/imu_gnss_poser/src/imu_gnss_poser_node.cpp#L28-L56

#### gyro_odometer

IMUと車速を使って、`twist`を推定するノード。

https://github.com/AutomotiveAIChallenge/aichallenge-2024/tree/main/aichallenge/workspace/src/aichallenge_submit/gyro_odometer

データ量に応じて、共分散(=不確実性)を調整。

https://github.com/AutomotiveAIChallenge/aichallenge-2024/blob/main/aichallenge/workspace/src/aichallenge_submit/gyro_odometer/src/gyro_odometer_core.cpp#L51-L58

### 3. 拡張カルマンフィルタでの自己位置推定

#### ekf_localizer

拡張カルマンフィルタによる自己位置推定。

- 予測モデル：運動学モデル
- 観測値：imu_gnss_poser が推定した`pose`、gyro_odometer が推定した`twist`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/9d8f0234-15fd-7378-fc03-a4a60b59eaf3.png)

https://github.com/AutomotiveAIChallenge/autoware.universe/tree/awsim-stable/localization/ekf_localizer

`How to tune EKF parameters`に書いてあるEKF調整方法は以下と理解しました。

- 0.準備
    - `pose`と`twist`のヘッダータイムスタンプが正しく設定できていることを確認せよ
        - 遅延がある場合は、`twist_additional_delay` / `pose_additional_delay`を設定せよ
    - `pose`と`twist`の関係が適切か確認せよ
        - 具体的には`pose`の微分が`twist`と一致するか確認せよ
- 1.センサーパラメータ調整
    - 各センサに標準偏差を設定せよ
    - `pose_measure_uncertainty_time`, `pose_smoothing_steps`, `twist_smoothing_steps`を調整せよ
- 2.プロセスモデルパラメータ調整
    - `proc_stddev_vx_c`, `proc_stddev_wz_c`, `proc_stddev_yaw_c`, `proc_stddev_yaw_bias_c`を調整せよ


## センサ特性

今大会で採用されているセンサは以下になります。

||型番|
|-|-|
|GNSS|u-blox ZED-F9R|
|IMU|ICM-20948|

https://automotiveaichallenge.github.io/aichallenge-documentation-2024/specifications/hardware.html

GNSSは0.5秒ほど遅延あり、IMUは方位角がほぼ使えないことがわかっています。
つまり、**ある程度信頼できる0.5秒前のGNSS情報と、IMU / 車輪速情報を合わせて、現在の自己位置を推定する**ことが必要と理解しています。

### 実際のデータを見てみる

bagファイル(.db3)を、CSVにして可視化してみます。
今回は plotjuggler で bagファイルをすべて読み込んで CSV Exporter で出力してます。

plotjugglerを起動します。

```bash
ros2 run plotjuggler plotjuggler 
```

`File - Data` を押下、`metadata.yaml` を選択。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/ce8427d5-b077-f6f5-ddbc-d7145eab33ae.png)

CSV出力したいmessageを選択。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/bb625f79-e4d2-cc31-204c-d3dff147ffe8.png)

`CSV Exporter`のチェックボックスをクリック、`Start time`: `first`をクリック、`End time`: `last`をクリック、`Export Data Range`: `File`をクリック、出力CSV名を入力して`Save`します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/5335eeef-ab64-581c-ff77-d2f502f0c591.png)


<details><summary>(参考) bag2csvの他方法</summary>


https://qiita.com/nonanonno/items/8f7bce03953709fd5af9

https://zenn.dev/xen_nippon/articles/0ae35719f6cc44

https://gitlab.com/ternaris/rosbags

https://github.com/alesof/rosbag2_editor?tab=readme-ov-file

https://github.com/AtsushiSakai/rosbag_to_csv

https://github.com/fishros/ros2bag_convert

</details>

運営様から共有いただいたデモ走行データを、1周分だけ可視化してみます。

※ 以下で確認した限り当日の衛星数は問題なし、極端なGNSS精度劣化はないようです。

https://www.gnssplanning.com/#/charts

#### GNSS

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/a70028e0-4afb-df30-de3b-aeb0b79994c5.png)

`/sensing/gnss/pose_with_covariance/pose/pose/position/x,y,z`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/2ed432ba-9cf0-ad68-a53e-502eda2899c1.png)

`/sensing/gnss/pose_with_covariance/pose/pose/orientation/x,y,z`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/734ef0c5-cd56-ab3e-3960-b8f71a736f03.png)

Orientation Zの共分散値が大きめでした。
FIX解, FLOAT解問わず固定の共分散値かもしれません。


#### IMU

`/sensing/imu/imu_raw/linear_acceleration/x,y,z`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/466adabd-a1c9-d814-7045-a1529c18cda2.png)

`/sensing/imu/imu_raw/angular_velocity/x,y,z`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/3c641019-45da-a6de-5374-cf6d7a705f00.png)

`/sensing/imu/imu_raw/orientation/pitch,roll,yaw`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/9700a9db-4a81-b593-6af7-00b1c48290e3.png)

やはりYawは使用不可なようです。


使用したスクリプトはこちらにあります。

https://colab.research.google.com/drive/1JZwhQuXL_1Bsf5pIKYzPSmmQDXIBk0jh?usp=sharing

(余談) Colab、コード補完があってGoodです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/dfb4d1e5-d813-533b-00cc-4fd547ec5066.png)


## 実車センサデータを使った自己位置推定シミュレーション

センサデータは確認できたのですが、`/localization/imu_gnss_poser`や`/localization/twist_estimator`、`/localization/kinematic_state`などの
自己位置推定の結果がありませんでした。

そこで、実際のセンサデータを入力して自己位置推定のシミュレーションを行ってみます。
使用する`reference.launch.xml`とコマンドは以下です。

<details><summary>reference.launch.xml</summary>

```reference.launch.xml
<?xml version="1.0" encoding="UTF-8"?>
<launch>
  <!-- Essential parameters -->
  <arg name="vehicle_id" default="$(env VEHICLE_ID default)" description="vehicle specific ID"/>
  <arg name="simulation" description="used for sensor kit param"/>
  <arg name="use_sim_time"/>
  <arg name="map_path" default="$(find-pkg-share aichallenge_submit_launch)/map/demo"/>
  <arg name="vehicle_model" default="racing_kart"  description="vehicle model name"/>
  <arg name="sensor_model" default="racing_kart_sensor_kit" description="sensor model name"/>
  <!-- Optional parameters -->
  <!-- Map -->
  <arg name="lanelet2_map_file" default="lanelet2_map.osm" description="lanelet2 map file name"/>
  <arg name="pointcloud_map_file" default="pointcloud_map.pcd" description="pointcloud map file name"/>

  <!-- Vehicle -->
  <arg name="launch_vehicle_interface" default="false"/>
  <log message="echo launch param use_sim_time: $(var use_sim_time) launch_vehicle_interface: $(var launch_vehicle_interface) sensor_model: $(var sensor_model)"/>

  <!-- Global parameters -->
  <group scoped="false">
    <include file="$(find-pkg-share global_parameter_loader)/launch/global_params.launch.py">
      <arg name="use_sim_time" value="$(var use_sim_time)"/>
      <arg name="vehicle_model" value="$(var vehicle_model)"/>
    </include>
  </group>

  <group>
    <arg name="model_file" default="$(find-pkg-share tier4_vehicle_launch)/urdf/vehicle.xacro" description="path to the file of model settings (*.xacro)"/>
    <arg name="config_dir" default="$(find-pkg-share racing_kart_sensor_kit_description)/config"/>
    <node name="robot_state_publisher" pkg="robot_state_publisher" exec="robot_state_publisher">
      <param name="robot_description" value="$(command 'xacro $(var model_file) vehicle_model:=$(var vehicle_model) sensor_model:=$(var sensor_model) config_dir:=$(var config_dir) simulation:=$(var simulation)' 'warn')"/>
    </node>
  </group>

  <!-- Map -->
  <group>
    <push-ros-namespace namespace="map"/>

    <!-- map_container -->
    <node_container pkg="rclcpp_components" exec="component_container" name="map_container" namespace="">

      <!-- map_loader::Lanelet2MapLoaderNode -->
      <composable_node pkg="map_loader" plugin="Lanelet2MapLoaderNode" name="lanelet2_map_loader" namespace="">
        <remap from="output/lanelet2_map" to="vector_map" />
        <param name="lanelet2_map_path" value="$(var map_path)/$(var lanelet2_map_file)" />
        <param from="$(find-pkg-share aichallenge_submit_launch)/config/map/lanelet2_map_loader.param.yaml" />
        <extra_arg name="use_intra_process_comms" value="false" />
      </composable_node>

      <!-- map_loader::Lanelet2MapVisualizationNode -->
      <composable_node pkg="map_loader" plugin="Lanelet2MapVisualizationNode" name="lanelet2_map_visualization" namespace="">
        <remap from="input/lanelet2_map" to="vector_map" />
        <remap from="output/lanelet2_map_marker" to="vector_map_marker" />
        <param name="lanelet2_map_path" value="$(var map_path)/$(var lanelet2_map_file)" />
        <param from="$(find-pkg-share aichallenge_submit_launch)/config/map/lanelet2_map_loader.param.yaml" />
        <extra_arg name="use_intra_process_comms" value="false" />
      </composable_node>

      <!-- map_tf_generator::VectorMapTFGeneratorNode -->
      <composable_node pkg="map_tf_generator" plugin="VectorMapTFGeneratorNode" name="vector_map_tf_generator" namespace="">
        <param name="map_frame" value="map" />
        <param name="viewer_frame" value="viewer" />
        <extra_arg name="use_intra_process_comms" value="false" />
      </composable_node>

    </node_container>

  </group> <!-- map -->

  <!-- Sensing -->
  <group>
    <push-ros-namespace namespace="sensing"/>
    <!-- Vehicle Velocity Converter  -->
    <include file="$(find-pkg-share vehicle_velocity_converter)/launch/vehicle_velocity_converter.launch.xml">
      <arg name="input_vehicle_velocity_topic" value="/vehicle/status/velocity_status"/>
      <arg name="output_twist_with_covariance" value="/sensing/vehicle_velocity_converter/twist_with_covariance"/>
      <arg name="config_file" value="$(find-pkg-share aichallenge_submit_launch)/config/vehicle_velocity_converter.param.yaml"/>
    </include>
    <group>
      <push-ros-namespace namespace="imu"/>
      <arg name="imu_corrector_param_file" default="$(find-pkg-share imu_corrector)/config/imu_corrector.param.yaml"/>
      <include file="$(find-pkg-share imu_corrector)/launch/imu_corrector.launch.xml">
        <arg name="input_topic" value="imu_raw"/>
        <arg name="output_topic" value="imu_data"/>
        <arg name="param_file" value="$(var imu_corrector_param_file)"/>
      </include>
    </group>
  </group>

    <!-- Localization -->
  <let name="pose_additional_delay_var" value="0.5" />
  <group>
    <push-ros-namespace namespace="localization"/>
    <include file="$(find-pkg-share gyro_odometer)/launch/gyro_odometer.launch.xml">
      <arg name="input_vehicle_twist_with_covariance_topic" value="/sensing/vehicle_velocity_converter/twist_with_covariance"/>
      <arg name="input_imu_topic" value="/sensing/imu/imu_data"/>
      <arg name="output_twist_with_covariance_topic" value="/localization/twist_estimator/twist_with_covariance"/>
      <arg name="output_twist_with_covariance_raw_topic" value="/localization/twist_estimator/twist_with_covariance_raw"/>
    </include>

    <node pkg="imu_gnss_poser" exec="imu_gnss_poser_node" name="imu_gnss_poser" output="screen"/>

    <include file="$(find-pkg-share ekf_localizer)/launch/ekf_localizer.launch.xml">
      <arg name="enable_yaw_bias_estimation" value="false"/>
      <arg name="tf_rate" value="50.0"/>
      <arg name="twist_smoothing_steps" value="1"/>
      <arg name="pose_smoothing_steps" value="1"/>
      <arg name="input_initial_pose_name" value="/localization/initial_pose3d"/>
      <arg name="input_pose_with_cov_name" value="/localization/imu_gnss_poser/pose_with_covariance"/>
      <arg name="input_twist_with_cov_name" value="/localization/twist_estimator/twist_with_covariance"/>
      <arg name="output_odom_name" value="kinematic_state"/>
      <arg name="output_pose_name" value="pose"/>
      <arg name="output_pose_with_covariance_name" value="/localization/pose_with_covariance"/>
      <arg name="output_biased_pose_name" value="biased_pose"/>
      <arg name="output_biased_pose_with_covariance_name" value="biased_pose_with_covariance"/>
      <arg name="output_twist_name" value="twist"/>
      <arg name="output_twist_with_covariance_name" value="twist_with_covariance"/>
      <arg name="proc_stddev_vx_c" value="10.0"/>
      <arg name="proc_stddev_wz_c" value="5.0"/>
      <arg name="pose_additional_delay" value="$(var pose_additional_delay_var)"/>
      <arg name="extend_state_step" value="100"/>
    </include>

    <!-- twist2accel -->
    <group>
      <node pkg="twist2accel" exec="twist2accel" name="twist2accel" output="screen">
        <param name="accel_lowpass_gain" value="0.9"/>
        <param name="use_odom" value="true"/>
        <remap from="input/odom" to="/localization/kinematic_state"/>
        <remap from="input/twist" to="/localization/twist_estimator/twist_with_covariance"/>
        <remap from="output/accel" to="/localization/acceleration"/>
      </node>
    </group>

    <!-- localization_error_monitor -->
    <group>
        <push-ros-namespace namespace="error_monitor"/>
        <include file="$(find-pkg-share localization_error_monitor)/launch/localization_error_monitor.launch.xml">
          <arg name="input/odom" value="/localization/kinematic_state"/>
          <arg name="param_file" value="$(find-pkg-share autoware_launch)/config/localization/localization_error_monitor.param.yaml"/>
        </include>
      </group>

  </group>

  <!-- vehicle -->
  <group if="$(var launch_vehicle_interface)">
    <include file="$(find-pkg-share raw_vehicle_cmd_converter)/launch/raw_vehicle_converter.launch.xml">
      <arg name="converter_param_path" value="$(find-pkg-share aichallenge_submit_launch)/config/converter.param.yaml"/>
      <arg name="csv_path_accel_map" value="$(find-pkg-share aichallenge_submit_launch)/data/accel_map.csv"/>
      <arg name="csv_path_brake_map" value="$(find-pkg-share aichallenge_submit_launch)/data/brake_map.csv"/>

      <arg name="max_throttle" value="1.0"/>
      <arg name="max_brake" value="1.0"/>
      <arg name="convert_accel_cmd" value="true"/>
      <arg name="convert_brake_cmd" value="true"/>
      <arg name="convert_steer_cmd" value="false"/>

      <arg name="input_control_cmd" value="/control/command/control_cmd"/>
      <arg name="input_odometry" value="/localization/kinematic_state"/>
      <arg name="input_steering" value="/vehicle/status/steering_status"/>
      <arg name="output_actuation_cmd" value="/control/command/actuation_cmd"/>
    </include>
  </group>

  <!-- API -->
  <group>
    <!-- default_ad_api -->
    <include file="$(find-pkg-share default_ad_api)/launch/default_ad_api.launch.py" />

    <!-- ad_api_adaptors -->
    <include file="$(find-pkg-share ad_api_adaptors)/launch/rviz_adaptors.launch.xml" />
  </group>

</launch>

```

</details>

```bash:Terminal 1
source /aichallenge/workspace/install/setup.bash
sudo ip link set multicast on lo
sudo sysctl -w net.core.rmem_max=2147483647 >/dev/null
ros2 launch aichallenge_system_launch aichallenge_system.launch.xml simulation:=false use_sim_time:=true run_rviz:=true
```

```bash:Terminal 2
ros2 bag play /output/0920_demo_interface_only/0920_demo_interface_only_0.db3 --clock --start-offset 400
```

```bash:Terminal 3
ros2 bag record -a 
```

`ekf_localizer` の `README.md - EKFパラメータを調整する方法`を参考に、
調整代がありそうか確認してみます。

### 0.準備

>遅延がある場合は、`twist_additional_delay` / `pose_additional_delay` を設定せよ

`pose`(GNSS)には`0.5`の遅延を設定しています。

https://github.com/AutomotiveAIChallenge/aichallenge-2024/blob/main/aichallenge/workspace/src/aichallenge_submit/aichallenge_submit_launch/launch/reference.launch.xml#L56


>`pose`の微分が`twist`と一致するか確認せよ

`pose`(`/localization/imu_gnss_poser`)の微分 と `twist`(`/localization/twist_estimator`)を並べて、`pose` / `twist` の関係を確認してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/89e94bc8-babb-43ef-6938-e7b994d7f0c2.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/716ea654-6e76-83c5-53eb-48466bd462fc.png)

概ね良さそうです。

### 1.センサーパラメータ調整

>各センサに標準偏差を設定せよ

すべて設定済み。GNSSは、RTK測位状態に応じて変更するのもありかもしれません。

>`pose_measure_uncertainty_time`, `pose_smoothing_steps`, `twist_smoothing_steps` を調整せよ

以下3条件でシミュレーションをしてみます。

1) `pose_measure_uncertainty_time`: 0.01 → 0.001

2) `pose_smoothing_steps`: 1 → 5

3) `twist_smoothing_steps`: 1 → 5

`reference.launch.xml` を以下のように変えます。

```diff_xml:reference.launch.xml
...
    <include file="$(find-pkg-share ekf_localizer)/launch/ekf_localizer.launch.xml">
      <arg name="enable_yaw_bias_estimation" value="false"/>
      <arg name="tf_rate" value="50.0"/>
-     <arg name="twist_smoothing_steps" value="1"/>
-     <arg name="pose_smoothing_steps" value="1"/>
+     <arg name="twist_smoothing_steps" value="5"/>
+     <arg name="pose_smoothing_steps" value="5"/>
+     <arg name="pose_measure_uncertainty_time" value="0.001"/>
      <arg name="input_initial_pose_name" value="/localization/initial_pose3d"/>
      <arg name="input_pose_with_cov_name" value="/localization/imu_gnss_poser/pose_with_covariance"/>
      <arg name="input_twist_with_cov_name" value="/localization/twist_estimator/twist_with_covariance"/>
      <arg name="output_odom_name" value="kinematic_state"/>
      <arg name="output_pose_name" value="pose"/>
      <arg name="output_pose_with_covariance_name" value="/localization/pose_with_covariance"/>
      <arg name="output_biased_pose_name" value="biased_pose"/>
      <arg name="output_biased_pose_with_covariance_name" value="biased_pose_with_covariance"/>
      <arg name="output_twist_name" value="twist"/>
      <arg name="output_twist_with_covariance_name" value="twist_with_covariance"/>
      <arg name="proc_stddev_vx_c" value="10.0"/>
      <arg name="proc_stddev_wz_c" value="5.0"/>
      <arg name="pose_additional_delay" value="$(var pose_additional_delay_var)"/>
      <arg name="extend_state_step" value="100"/>
    </include>
...
```

シミュレーション結果比較が以下です。
`/localization/kinematic_state/pose/pose/position` のxy座標のプロットです。

今回の設定値の範囲では、自己位置推定結果はほとんど変わりませんでした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/a92eaee4-f9a1-d4e3-ae96-0bed983fa220.png)

(参考) `pose_smoothing_steps`: 100, `twist_smoothing_steps`: 100だと以下になります。
スムージングを強くするため、自己位置推定が滑らかになりますが実測値への追従性が低下します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/a4eba228-e823-243c-1f45-d8f44f3549ba.png)



### 2.プロセスパラメータ調整

>proc_stddev_vx_c, proc_stddev_wz_c, proc_stddev_yaw_c, proc_stddev_yaw_bias_cを調整せよ

READMEを参考に、`proc_stddev_vx_c` と `proc_stddev_wz_c` を計測された加速度/角加速度の最大値である`1.0`に変えて自己位置推定結果の`/localization/kinematic_state`を比較してみます。

>`proc_stddev_vx_c` : set to maximum linear acceleration
>`proc_stddev_wz_c` : set to maximum angular acceleration

`reference.launch.xml` を以下のように変えます。

```diff_xml:reference.launch.xml
...
    <include file="$(find-pkg-share ekf_localizer)/launch/ekf_localizer.launch.xml">
      <arg name="enable_yaw_bias_estimation" value="false"/>
      <arg name="tf_rate" value="50.0"/>
      <arg name="twist_smoothing_steps" value="1"/>
      <arg name="pose_smoothing_steps" value="1"/>
      <arg name="input_initial_pose_name" value="/localization/initial_pose3d"/>
      <arg name="input_pose_with_cov_name" value="/localization/imu_gnss_poser/pose_with_covariance"/>
      <arg name="input_twist_with_cov_name" value="/localization/twist_estimator/twist_with_covariance"/>
      <arg name="output_odom_name" value="kinematic_state"/>
      <arg name="output_pose_name" value="pose"/>
      <arg name="output_pose_with_covariance_name" value="/localization/pose_with_covariance"/>
      <arg name="output_biased_pose_name" value="biased_pose"/>
      <arg name="output_biased_pose_with_covariance_name" value="biased_pose_with_covariance"/>
      <arg name="output_twist_name" value="twist"/>
      <arg name="output_twist_with_covariance_name" value="twist_with_covariance"/>
-     <arg name="proc_stddev_vx_c" value="10.0"/>
-     <arg name="proc_stddev_wz_c" value="5.0"/>
+     <arg name="proc_stddev_vx_c" value="1.0"/>
+     <arg name="proc_stddev_wz_c" value="1.0"/>
      <arg name="pose_additional_delay" value="$(var pose_additional_delay_var)"/>
      <arg name="extend_state_step" value="100"/>
    </include>
...
```

シミュレーション結果比較が以下です。
`/localization/kinematic_state/pose/pose/position` のxy座標のプロットです。

結果がやや変わりました。
カーブの始まりで内側 / 終わりで外側　に推定される傾向が出ています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/3c367ea3-66b4-345b-ddb9-29a2a1a9dee3.png)


真値がないのでとりあえずGNSS(0.5秒の遅延補正済)を真値と仮置きして、
0.5秒前のGNSS + IMU + 車輪速でどれくらい自己位置が推定できているか見てみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/34350ae1-f86e-cf58-8270-fd94eda3f956.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/90ce9f5b-8c38-1c50-fc88-ca7b0aa8e934.png)

xy座標で書いてみると以下で、外に膨らんでしまう傾向があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628135/7ca176fd-b025-c83d-2695-e953451ef3b8.png)

これを抑えるには、パッと思いつくところでは以下がありそうですがまだ試せていません…

- EKFのパラメータを調整する（望み薄かも？）
- EKFの予測モデルを変更する（[動力学モデル](https://zenn.dev/atfujita/articles/47b63638bd20b2#lesson-1%3A-kinematic-modeling-in-2d)や機械学習モデルなど）
- UKFに変更する

※ EKFパラメータ調整はGNSS(遅延補正済み)と推定値の差分を目的関数に以下で自動調整もできそう…と思いましたが、完全に手が追い付いておりません…😭

https://qiita.com/soyaoki/items/cf1b6573d6130ccdf5ab

# おわりに

拡張カルマンフィルタをつかった自己位置推定について理解が深まりました。

カーブで自己位置推定が外側に膨らんでしまう問題は、私が試した範囲のパラメータ調整では改善が見込めませんでした。根本的な予測モデルの変更や自己位置推定アルゴリズム自体の変更が必要なのかもしれません。

この記事は、AI文章校正ツール「ちゅらいと」で校正されています。


(参考) GNSS + IMU + 車輪速で自己位置推定する例は他にもありそうでした。

https://www.denshi.e.kaiyodai.ac.jp/wp-content/uploads/pdf/content1/2011chin_ms.pdf

https://www.denshi.e.kaiyodai.ac.jp/wp-content/uploads/pdf/content1/2022komatsu.pdf

Eagleyeと併用も可能？なようです、使ってみたいですね。

https://github.com/AutomotiveAIChallenge/autoware-documentation/blob/main/docs/how-to-guides/integrating-autoware/launch-autoware/localization-methods/eagleye-guide.md