lidarを用いた自己位置推定(EMCL) ROS2 humble

# はじめに
前回の記事では,Cartographerでを使って地図を作成しました.今回は,その地図で自己位置推定を実装する方法を紹介します.
# 動作環境
|項目  | バージョン |
|---|---|
|ubuntu  |22.04  |
|ROS  |ROS2 humble  |
|Lidar|RPLidar A1 M8|

↓RPLidar A1 M8
<img width="400" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480989/eaa6db12-2033-50fa-5b85-7871d89eb37f.png">

# 自己位置推定の手法
自己位置推定の手法として,EMCL（Extended Monte Carlo Localization）を用いました.
EMCLは,AMCL（Adaptive Monte Carlo Localization）を基盤に上田隆一先生が改良したパーティクルフィルタによる自己位置推定手法です.AMCLの課題として,ロボットが停止すると推定も停止する点や,C++とCで書かれたコードの複雑さが挙げられます.EMCLでは,ロボットの動作に関係なく一定周期で,tfからオドメトリデータを取得し,パーティクルの位置を更新.センサデータは非同期に取り込まれ,オドメトリ処理後に重みを更新してリサンプリングを行います.尤度場を用いて重みを更新し,重みの総和がしきい値を下回ると「膨張リセット」を実施します.このアプローチにより,ロボットが止まってもパーティクルが動作を続け,初期位置の微調整が行われます.ただし,実機での運用ではセンサーノイズやタイムスタンプ,tfの位置関係の管理に注意が必要です.

# 実装方法
### マップの作成
マップの作成については,[前回の記事](https://qiita.com/tomtomApp/items/8d29a2dd00e60dade9e8)を御覧ください.
### emcl2のインストール
以下の手順に従って,ソースコードをクローンしてインストールしてください.
```
cd ~/ros2_ws/src
git clone https://github.com/Arcanain/emcl2.git
```
### rvizファイルの作成
可視化用のrvizファイルを作成します.mapやロボット,ridarの点群が可視化できます.
```c
Panels:
  - Class: rviz_common/Displays
    Help Height: 0
    Name: Displays
    Property Tree Widget:
      Expanded:
        - /Global Options1
        - /TF1
        - /TF1/Frames1
        - /TF1/Tree1
        - /LaserScan1
        - /PointCloud21
        - /Path1
        - /Path1/Offset1
        - /Marker1
        - /Marker2
        - /Marker2/Status1
        - /Marker2/Topic1
        - /Marker2/Namespaces1
      Splitter Ratio: 0.5833333134651184
    Tree Height: 550
  - Class: rviz_common/Selection
    Name: Selection
  - Class: rviz_common/Tool Properties
    Expanded:
      - /Publish Point1
    Name: Tool Properties
    Splitter Ratio: 0.5886790156364441
  - Class: rviz_common/Views
    Expanded:
      - /Current View1
    Name: Views
    Splitter Ratio: 0.5
  - Class: nav2_rviz_plugins/Navigation 2
    Name: Navigation 2
Visualization Manager:
  Class: ""
  Displays:
    - Alpha: 0.5
      Cell Size: 1
      Class: rviz_default_plugins/Grid
      Color: 160; 160; 164
      Enabled: true
      Line Style:
        Line Width: 0.029999999329447746
        Value: Lines
      Name: Grid
      Normal Cell Count: 0
      Offset:
        X: 0
        Y: 0
        Z: 0
      Plane: XY
      Plane Cell Count: 10
      Reference Frame: <Fixed Frame>
      Value: true
    - Alpha: 1
      Class: rviz_default_plugins/RobotModel
      Collision Enabled: false
      Description File: ""
      Description Source: Topic
      Description Topic:
        Depth: 5
        Durability Policy: Volatile
        History Policy: Keep Last
        Reliability Policy: Reliable
        Value: /robot_description
      Enabled: true
      Links:
        All Links Enabled: true
        Expand Joint Details: false
        Expand Link Details: false
        Expand Tree: false
        Link Tree Style: Links in Alphabetic Order
        base_footprint:
          Alpha: 1
          Show Axes: false
          Show Trail: false
        base_link:
          Alpha: 1
          Show Axes: false
          Show Trail: false
          Value: true
        drivewhl_l_link:
          Alpha: 1
          Show Axes: false
          Show Trail: false
          Value: true
        drivewhl_r_link:
          Alpha: 1
          Show Axes: false
          Show Trail: false
          Value: true
        dummy_link:
          Alpha: 1
          Show Axes: false
          Show Trail: false
        front_caster:
          Alpha: 1
          Show Axes: false
          Show Trail: false
          Value: true
      Mass Properties:
        Inertia: false
        Mass: false
      Name: RobotModel
      TF Prefix: ""
      Update Interval: 0
      Value: true
      Visual Enabled: true
    - Class: rviz_default_plugins/TF
      Enabled: true
      Frame Timeout: 15
      Frames:
        All Enabled: false
        base_footprint:
          Value: false
        base_link:
          Value: true
        drivewhl_l_link:
          Value: false
        drivewhl_r_link:
          Value: false
        dummy_link:
          Value: false
        front_caster:
          Value: false
        laser_frame:
          Value: false
        map:
          Value: true
        odom:
          Value: true
      Marker Scale: 1
      Name: TF
      Show Arrows: true
      Show Axes: true
      Show Names: true
      Tree:
        map:
          odom:
            base_link:
              base_footprint:
                {}
              drivewhl_l_link:
                {}
              drivewhl_r_link:
                {}
              front_caster:
                {}
              laser_frame:
                {}
      Update Interval: 0
      Value: true
    - Alpha: 1
      Autocompute Intensity Bounds: true
      Autocompute Value Bounds:
        Max Value: 10
        Min Value: -10
        Value: true
      Axis: Z
      Channel Name: intensity
      Class: rviz_default_plugins/LaserScan
      Color: 255; 255; 255
      Color Transformer: Intensity
      Decay Time: 0
      Enabled: true
      Invert Rainbow: false
      Max Color: 255; 255; 255
      Max Intensity: 47
      Min Color: 0; 0; 0
      Min Intensity: 47
      Name: LaserScan
      Position Transformer: XYZ
      Selectable: true
      Size (Pixels): 3
      Size (m): 0.05000000074505806
      Style: Flat Squares
      Topic:
        Depth: 5
        Durability Policy: Volatile
        Filter size: 10
        History Policy: Keep Last
        Reliability Policy: Best Effort
        Value: /scan
      Use Fixed Frame: true
      Use rainbow: true
      Value: true
    - Alpha: 1
      Autocompute Intensity Bounds: true
      Autocompute Value Bounds:
        Max Value: 10
        Min Value: -10
        Value: true
      Axis: Z
      Channel Name: intensity
      Class: rviz_default_plugins/PointCloud2
      Color: 255; 255; 255
      Color Transformer: ""
      Decay Time: 0
      Enabled: true
      Invert Rainbow: false
      Max Color: 255; 255; 255
      Max Intensity: 4096
      Min Color: 0; 0; 0
      Min Intensity: 0
      Name: Bumper Hit
      Position Transformer: ""
      Selectable: true
      Size (Pixels): 3
      Size (m): 0.07999999821186066
      Style: Spheres
      Topic:
        Depth: 5
        Durability Policy: Volatile
        Filter size: 10
        History Policy: Keep Last
        Reliability Policy: Best Effort
        Value: /mobile_base/sensors/bumper_pointcloud
      Use Fixed Frame: true
      Use rainbow: true
      Value: true
    - Alpha: 1
      Class: rviz_default_plugins/Map
      Color Scheme: map
      Draw Behind: true
      Enabled: true
      Name: Map
      Topic:
        Depth: 1
        Durability Policy: Transient Local
        Filter size: 10
        History Policy: Keep Last
        Reliability Policy: Reliable
        Value: /map
      Update Topic:
        Depth: 5
        Durability Policy: Volatile
        History Policy: Keep Last
        Reliability Policy: Reliable
        Value: /map_updates
      Use Timestamp: false
      Value: true
    - Alpha: 1
      Arrow Length: 0.019999999552965164
      Axes Length: 0.30000001192092896
      Axes Radius: 0.009999999776482582
      Class: rviz_default_plugins/PoseArray
      Color: 0; 180; 0
      Enabled: true
      Head Length: 0.07000000029802322
      Head Radius: 0.029999999329447746
      Name: Amcl Particle Swarm
      Shaft Length: 0.23000000417232513
      Shaft Radius: 0.009999999776482582
      Shape: Arrow (Flat)
      Topic:
        Depth: 5
        Durability Policy: Volatile
        Filter size: 10
        History Policy: Keep Last
        Reliability Policy: Best Effort
        Value: /particlecloud
      Value: true
    - Class: rviz_common/Group
      Displays:
        - Alpha: 0.30000001192092896
          Class: rviz_default_plugins/Map
          Color Scheme: costmap
          Draw Behind: false
          Enabled: true
          Name: Global Costmap
          Topic:
            Depth: 1
            Durability Policy: Transient Local
            Filter size: 10
            History Policy: Keep Last
            Reliability Policy: Reliable
            Value: /global_costmap/costmap
          Update Topic:
            Depth: 5
            Durability Policy: Volatile
            History Policy: Keep Last
            Reliability Policy: Reliable
            Value: /global_costmap/costmap_updates
          Use Timestamp: false
          Value: true
        - Alpha: 0.30000001192092896
          Class: rviz_default_plugins/Map
          Color Scheme: costmap
          Draw Behind: false
          Enabled: true
          Name: Downsampled Costmap
          Topic:
            Depth: 1
            Durability Policy: Transient Local
            Filter size: 10
            History Policy: Keep Last
            Reliability Policy: Reliable
            Value: /downsampled_costmap
          Update Topic:
            Depth: 5
            Durability Policy: Volatile
            History Policy: Keep Last
            Reliability Policy: Reliable
            Value: /downsampled_costmap_updates
          Use Timestamp: false
          Value: true
        - Alpha: 1
          Buffer Length: 1
          Class: rviz_default_plugins/Path
          Color: 255; 0; 0
          Enabled: true
          Head Diameter: 0.019999999552965164
          Head Length: 0.019999999552965164
          Length: 0.30000001192092896
          Line Style: Lines
          Line Width: 0.029999999329447746
          Name: Path
          Offset:
            X: 0
            Y: 0
            Z: 0
          Pose Color: 255; 85; 255
          Pose Style: Arrows
          Radius: 0.029999999329447746
          Shaft Diameter: 0.004999999888241291
          Shaft Length: 0.019999999552965164
          Topic:
            Depth: 5
            Durability Policy: Volatile
            Filter size: 10
            History Policy: Keep Last
            Reliability Policy: Reliable
            Value: /plan
          Value: true
        - Alpha: 1
          Autocompute Intensity Bounds: true
          Autocompute Value Bounds:
            Max Value: 10
            Min Value: -10
            Value: true
          Axis: Z
          Channel Name: intensity
          Class: rviz_default_plugins/PointCloud
          Color: 125; 125; 125
          Color Transformer: FlatColor
          Decay Time: 0
          Enabled: true
          Invert Rainbow: false
          Max Color: 255; 255; 255
          Max Intensity: 4096
          Min Color: 0; 0; 0
          Min Intensity: 0
          Name: VoxelGrid
          Position Transformer: XYZ
          Selectable: true
          Size (Pixels): 3
          Size (m): 0.05000000074505806
          Style: Boxes
          Topic:
            Depth: 5
            Durability Policy: Volatile
            Filter size: 10
            History Policy: Keep Last
            Reliability Policy: Reliable
            Value: /global_costmap/voxel_marked_cloud
          Use Fixed Frame: true
          Use rainbow: true
          Value: true
        - Alpha: 1
          Class: rviz_default_plugins/Polygon
          Color: 25; 255; 0
          Enabled: false
          Name: Polygon
          Topic:
            Depth: 5
            Durability Policy: Volatile
            Filter size: 10
            History Policy: Keep Last
            Reliability Policy: Reliable
            Value: /global_costmap/published_footprint
          Value: false
      Enabled: true
      Name: Global Planner
    - Class: rviz_common/Group
      Displays:
        - Alpha: 0.699999988079071
          Class: rviz_default_plugins/Map
          Color Scheme: costmap
          Draw Behind: false
          Enabled: true
          Name: Local Costmap
          Topic:
            Depth: 1
            Durability Policy: Transient Local
            Filter size: 10
            History Policy: Keep Last
            Reliability Policy: Reliable
            Value: /local_costmap/costmap
          Update Topic:
            Depth: 5
            Durability Policy: Volatile
            History Policy: Keep Last
            Reliability Policy: Reliable
            Value: /local_costmap/costmap_updates
          Use Timestamp: false
          Value: true
        - Alpha: 1
          Buffer Length: 1
          Class: rviz_default_plugins/Path
          Color: 0; 12; 255
          Enabled: true
          Head Diameter: 0.30000001192092896
          Head Length: 0.20000000298023224
          Length: 0.30000001192092896
          Line Style: Lines
          Line Width: 0.029999999329447746
          Name: Local Plan
          Offset:
            X: 0
            Y: 0
            Z: 0
          Pose Color: 255; 85; 255
          Pose Style: None
          Radius: 0.029999999329447746
          Shaft Diameter: 0.10000000149011612
          Shaft Length: 0.10000000149011612
          Topic:
            Depth: 5
            Durability Policy: Volatile
            Filter size: 10
            History Policy: Keep Last
            Reliability Policy: Reliable
            Value: /local_plan
          Value: true
        - Class: rviz_default_plugins/MarkerArray
          Enabled: false
          Name: Trajectories
          Namespaces:
            {}
          Topic:
            Depth: 5
            Durability Policy: Volatile
            History Policy: Keep Last
            Reliability Policy: Reliable
            Value: /marker
          Value: false
        - Alpha: 1
          Class: rviz_default_plugins/Polygon
          Color: 25; 255; 0
          Enabled: true
          Name: Polygon
          Topic:
            Depth: 5
            Durability Policy: Volatile
            Filter size: 10
            History Policy: Keep Last
            Reliability Policy: Reliable
            Value: /local_costmap/published_footprint
          Value: true
        - Alpha: 1
          Autocompute Intensity Bounds: true
          Autocompute Value Bounds:
            Max Value: 10
            Min Value: -10
            Value: true
          Axis: Z
          Channel Name: intensity
          Class: rviz_default_plugins/PointCloud
          Color: 255; 255; 255
          Color Transformer: RGB8
          Decay Time: 0
          Enabled: true
          Invert Rainbow: false
          Max Color: 255; 255; 255
          Max Intensity: 4096
          Min Color: 0; 0; 0
          Min Intensity: 0
          Name: VoxelGrid
          Position Transformer: XYZ
          Selectable: true
          Size (Pixels): 3
          Size (m): 0.009999999776482582
          Style: Flat Squares
          Topic:
            Depth: 5
            Durability Policy: Volatile
            Filter size: 10
            History Policy: Keep Last
            Reliability Policy: Reliable
            Value: /local_costmap/voxel_marked_cloud
          Use Fixed Frame: true
          Use rainbow: true
          Value: true
      Enabled: true
      Name: Controller
    - Class: rviz_common/Group
      Displays:
        - Class: rviz_default_plugins/Image
          Enabled: true
          Max Value: 1
          Median window: 5
          Min Value: 0
          Name: RealsenseCamera
          Normalize Range: true
          Topic:
            Depth: 5
            Durability Policy: Volatile
            History Policy: Keep Last
            Reliability Policy: Reliable
            Value: /intel_realsense_r200_depth/image_raw
          Value: true
        - Alpha: 1
          Autocompute Intensity Bounds: true
          Autocompute Value Bounds:
            Max Value: 10
            Min Value: -10
            Value: true
          Axis: Z
          Channel Name: intensity
          Class: rviz_default_plugins/PointCloud2
          Color: 255; 255; 255
          Color Transformer: RGB8
          Decay Time: 0
          Enabled: true
          Invert Rainbow: false
          Max Color: 255; 255; 255
          Max Intensity: 4096
          Min Color: 0; 0; 0
          Min Intensity: 0
          Name: RealsenseDepthImage
          Position Transformer: XYZ
          Selectable: true
          Size (Pixels): 3
          Size (m): 0.009999999776482582
          Style: Flat Squares
          Topic:
            Depth: 5
            Durability Policy: Volatile
            Filter size: 10
            History Policy: Keep Last
            Reliability Policy: Reliable
            Value: /intel_realsense_r200_depth/points
          Use Fixed Frame: true
          Use rainbow: true
          Value: true
      Enabled: false
      Name: Realsense
    - Class: rviz_default_plugins/MarkerArray
      Enabled: true
      Name: MarkerArray
      Namespaces:
        {}
      Topic:
        Depth: 5
        Durability Policy: Volatile
        History Policy: Keep Last
        Reliability Policy: Reliable
        Value: /waypoints
      Value: true
    - Alpha: 1
      Axes Length: 1
      Axes Radius: 0.10000000149011612
      Class: rviz_default_plugins/PoseWithCovariance
      Color: 255; 25; 0
      Covariance:
        Orientation:
          Alpha: 0.5
          Color: 255; 255; 127
          Color Style: Unique
          Frame: Local
          Offset: 1
          Scale: 1
          Value: true
        Position:
          Alpha: 0.30000001192092896
          Color: 204; 51; 204
          Scale: 1
          Value: true
        Value: true
      Enabled: true
      Head Length: 0.30000001192092896
      Head Radius: 0.10000000149011612
      Name: PoseWithCovariance
      Shaft Length: 1
      Shaft Radius: 0.05000000074505806
      Shape: Arrow
      Topic:
        Depth: 5
        Durability Policy: Volatile
        Filter size: 10
        History Policy: Keep Last
        Reliability Policy: Reliable
        Value: /amcl_pose
      Value: true
    - Alpha: 1
      Autocompute Intensity Bounds: true
      Autocompute Value Bounds:
        Max Value: 10
        Min Value: -10
        Value: true
      Axis: Z
      Channel Name: intensity
      Class: rviz_default_plugins/PointCloud2
      Color: 255; 255; 255
      Color Transformer: Intensity
      Decay Time: 1
      Enabled: true
      Invert Rainbow: false
      Max Color: 255; 255; 255
      Max Intensity: 4096
      Min Color: 0; 0; 0
      Min Intensity: 0
      Name: PointCloud2
      Position Transformer: XYZ
      Selectable: true
      Size (Pixels): 3
      Size (m): 0.009999999776482582
      Style: Flat Squares
      Topic:
        Depth: 5
        Durability Policy: Volatile
        Filter size: 10
        History Policy: Keep Last
        Reliability Policy: Reliable
        Value: /particle_cloud
      Use Fixed Frame: true
      Use rainbow: true
      Value: true
    - Alpha: 3
      Buffer Length: 5
      Class: rviz_default_plugins/Path
      Color: 0; 255; 0
      Enabled: true
      Head Diameter: 0.30000001192092896
      Head Length: 0.20000000298023224
      Length: 0.30000001192092896
      Line Style: Lines
      Line Width: 0.029999999329447746
      Name: Path
      Offset:
        X: 0
        Y: 0
        Z: 0
      Pose Color: 255; 85; 255
      Pose Style: None
      Radius: 0.029999999329447746
      Shaft Diameter: 0.10000000149011612
      Shaft Length: 0.10000000149011612
      Topic:
        Depth: 5
        Durability Policy: Volatile
        Filter size: 10
        History Policy: Keep Last
        Reliability Policy: Reliable
        Value: /visualize_tgt_path
      Value: true
    - Class: rviz_default_plugins/Marker
      Enabled: true
      Name: Marker
      Namespaces:
        target_circle: true
      Topic:
        Depth: 5
        Durability Policy: Volatile
        Filter size: 10
        History Policy: Keep Last
        Reliability Policy: Reliable
        Value: /look_ahead_range_marker
      Value: true
    - Class: rviz_default_plugins/Marker
      Enabled: true
      Name: Marker
      Namespaces:
        target_point: true
      Topic:
        Depth: 5
        Durability Policy: Volatile
        Filter size: 10
        History Policy: Keep Last
        Reliability Policy: Reliable
        Value: /target_point_marker
      Value: true
  Enabled: true
  Global Options:
    Background Color: 48; 48; 48
    Fixed Frame: map
    Frame Rate: 30
  Name: root
  Tools:
    - Class: rviz_default_plugins/MoveCamera
    - Class: rviz_default_plugins/Select
    - Class: rviz_default_plugins/FocusCamera
    - Class: rviz_default_plugins/Measure
      Line color: 128; 128; 0
    - Class: rviz_default_plugins/SetInitialPose
      Covariance x: 0.25
      Covariance y: 0.25
      Covariance yaw: 0.06853891909122467
      Topic:
        Depth: 5
        Durability Policy: Volatile
        History Policy: Keep Last
        Reliability Policy: Reliable
        Value: /initialpose
    - Class: rviz_default_plugins/PublishPoint
      Single click: true
      Topic:
        Depth: 5
        Durability Policy: Volatile
        History Policy: Keep Last
        Reliability Policy: Reliable
        Value: /clicked_point
    - Class: nav2_rviz_plugins/GoalTool
  Transformation:
    Current:
      Class: rviz_default_plugins/TF
  Value: true
  Views:
    Current:
      Angle: -1.5799998044967651
      Class: rviz_default_plugins/TopDownOrtho
      Enable Stereo Rendering:
        Stereo Eye Separation: 0.05999999865889549
        Stereo Focal Distance: 1
        Swap Stereo Eyes: false
        Value: false
      Invert Z Axis: false
      Name: Current View
      Near Clip Distance: 0.009999999776482582
      Scale: 27.15713882446289
      Target Frame: base_link
      Value: TopDownOrtho (rviz_default_plugins)
      X: 0
      Y: 0
    Saved: ~
Window Geometry:
  Displays:
    collapsed: true
  Height: 704
  Hide Left Dock: true
  Hide Right Dock: true
  Navigation 2:
    collapsed: false
  QMainWindow State: 000000ff00000000fd00000004000000000000016a00000264fc020000000afb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073000000003e00000264000000ca00fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb00000018004e0061007600690067006100740069006f006e00200032000000015e000001440000014400fffffffb0000001e005200650061006c00730065006e0073006500430061006d00650072006100000002c6000000c10000002800ffffff000000010000010f00000264fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003e00000264000000a600fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d00650100000000000004500000000000000000000002880000026400000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000
  RealsenseCamera:
    collapsed: false
  Selection:
    collapsed: false
  Tool Properties:
    collapsed: false
  Views:
    collapsed: true
  Width: 648
  X: 718
  Y: 27
```

### launchファイルの作成
```python
import os

from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, GroupAction
from launch.substitutions import LaunchConfiguration, TextSubstitution
from launch_ros.actions import Node, SetParameter

from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource


def generate_launch_description():
    package_dir      = get_package_share_directory("emcl2")
    emcl_params_file = os.path.join(package_dir, "config", 'emcl2_params.yaml')
    map_file         = os.path.join(package_dir, "map", 'map.yaml')
    print('\nMAP',map_file)

    rviz = os.path.join(package_dir, "rviz" , "nav2_default_view.rviz")
    print('\nRVIZ2 PATH',rviz)


    lifecycle_nodes = ['map_server']
    

    return LaunchDescription([

        # map server node
        Node(
            package    = 'nav2_map_server',
            executable = 'map_server',
            name       = 'map_server',
            output     = 'screen',
            parameters = [{'yaml_filename': map_file, 
                           'use_sim_time' : False
                         }],
        ),

        # emcl node
        Node(
            package    = 'emcl2',
            name       = 'emcl2',
            executable = 'emcl2_node',
            output     = 'screen',
            parameters = [emcl_params_file],
        ),

        # static TF conversion (base_link -> laser_frame)
        Node(
            package    = 'tf2_ros',
            executable = 'static_transform_publisher',
            output     = 'screen',
            arguments  = ['0.0', '0.0', '0.2', '0.0', '0.0', '0.0', 'base_link', 'laser_frame'],
        ),

        # rviz node
        Node(
            package    = 'rviz2',
            executable = 'rviz2',
            name       = 'rviz2',
            arguments  = ['-d', rviz],
            parameters = [{'use_sim_time': False}],
            output     = 'screen'
        ),

        # life cycle node: manages the life cycle states of nodes
        Node(
            package    = 'nav2_lifecycle_manager',
            executable = 'lifecycle_manager',
            name       = 'lifecycle_manager_localization',
            output     = 'screen',
            parameters = [{'autostart': True},
                          {'node_names': lifecycle_nodes}]
        ),
    ])
```
このlaunchファイルは,次のノードを立ち上げています.
・map_server: 地図データを提供するノード.
・emcl2: 位置推定を行うEMCLノード.
・tf2_ros: base_linkとlaser_frameを位置関係を固定し,座標変換を提供するノード.
・rviz2: RVizによる可視化ツール.
・nav2_lifecycle_manager: ノードのライフサイクルを管理するノード.
```python
# static TF conversion (base_link -> laser_frame)
Node(
    package    = 'tf2_ros',
    executable = 'static_transform_publisher',
    output     = 'screen',
    arguments  = ['0.0', '0.0', '0.2', '0.0', '0.0', '0.0', 'base_link', 'laser_frame'],
),
```
「laser_frame」の名前は,使うLiDARのフレームIDに適宜変更してください.

### オドメトリの計算
emcl2は,tfからオドメトリの移動量を考慮して自己位置を推定しているので,オドメトリを計算する必要があります.高い精度で移動量を推定するには,モーターのエンコーダーからホイールオドメトリを算出しまする必要がありますが,以下のコードでは,簡易的に実装するため速度指令値(cmd_vel)を積算させてオドメトリの値を計算しています.


```C
#include "rclcpp/rclcpp.hpp"
#include "geometry_msgs/msg/transform_stamped.hpp"
#include "geometry_msgs/msg/twist.hpp"
#include "nav_msgs/msg/odometry.hpp"
#include "nav_msgs/msg/path.hpp"  // Pathを使用するために追加
#include "nav_msgs/msg/occupancy_grid.hpp"
#include "tf2/LinearMath/Quaternion.h"  // tf2::Quaternionを使用するために追加
#include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"
#include "tf2_ros/static_transform_broadcaster.h"
#include "tf2_ros/transform_broadcaster.h"
#include "visualization_msgs/msg/marker.hpp"

using namespace std::chrono_literals;

class OdometryPublisher : public rclcpp::Node
{
public:
  OdometryPublisher()
  : Node("odometry_pub")
  {
    odom_pub = this->create_publisher<nav_msgs::msg::Odometry>("odom", 50);
    path_pub = this->create_publisher<nav_msgs::msg::Path>("odom_path", 50);
    localmap_pub = this->create_publisher<nav_msgs::msg::OccupancyGrid>("local_map", 10);
    laser_range_pub = this->create_publisher<visualization_msgs::msg::Marker>(
      "laser_range_marker",
      10);
    odom_broadcaster = std::make_shared<tf2_ros::TransformBroadcaster>(*this);

    // cmd_velサブスクライバを追加
    cmd_vel_subscriber = this->create_subscription<geometry_msgs::msg::Twist>(
      "cmd_vel", 10, std::bind(&OdometryPublisher::cmd_vel_callback, this, std::placeholders::_1));

    x = 0.0;
    y = 0.0;
    th = 0.0;

    current_time = this->get_clock()->now();
    last_time = this->get_clock()->now();

    path.header.frame_id = "odom";  // パスのフレームIDを設定

    timer_ = this->create_wall_timer(50ms, std::bind(&OdometryPublisher::timer_callback, this));

private:
  void cmd_vel_callback(const geometry_msgs::msg::Twist::SharedPtr msg)
  {
    // cmd_velメッセージから速度を更新
    vx = msg->linear.x;
    vth = msg->angular.z;
  }

  void timer_callback()
  {
    /**
    *******************************************************************************************
    * Odometry Calculation
    *******************************************************************************************
    */
    current_time = this->get_clock()->now();

    double dt = (current_time - last_time).seconds();
    double delta_x = vx * cos(th) * dt;
    double delta_y = vx * sin(th) * dt;
    double delta_th = vth * dt;

    x += delta_x;
    y += delta_y;
    th += delta_th;

    tf2::Quaternion odom_quat;
    odom_quat.setRPY(0, 0, th);  // ロール,ピッチ,ヨーをセット
    geometry_msgs::msg::Quaternion odom_quat_msg =
      tf2::toMsg(odom_quat);  // tf2::Quaternionからgeometry_msgs::msg::Quaternionに変換

    geometry_msgs::msg::TransformStamped odom_trans;
    odom_trans.header.stamp = current_time;
    odom_trans.header.frame_id = "odom";
    odom_trans.child_frame_id = "base_link";

    odom_trans.transform.translation.x = x;
    odom_trans.transform.translation.y = y;
    odom_trans.transform.translation.z = 0.0;
    odom_trans.transform.rotation = odom_quat_msg;

    odom_broadcaster->sendTransform(odom_trans);

    nav_msgs::msg::Odometry odom;
    odom.header.stamp = current_time;
    odom.header.frame_id = "odom";
    odom.child_frame_id = "base_link";

    odom.pose.pose.position.x = x;
    odom.pose.pose.position.y = y;
    odom.pose.pose.position.z = 0.0;
    odom.pose.pose.orientation = odom_quat_msg;

    odom.twist.twist.linear.x = vx;
    odom.twist.twist.linear.y = 0.0;
    odom.twist.twist.angular.z = vth;

    odom_pub->publish(odom);

    /**
    *******************************************************************************************
    * Odometry Path Publish
    *******************************************************************************************
    */
    // パスに現在の位置を追加
    geometry_msgs::msg::PoseStamped this_pose_stamped;
    this_pose_stamped.pose.position.x = x;
    this_pose_stamped.pose.position.y = y;
    this_pose_stamped.pose.orientation = odom_quat_msg;
    this_pose_stamped.header.stamp = current_time;
    this_pose_stamped.header.frame_id = "odom";
    path.poses.push_back(this_pose_stamped);

    // パスを公開
    path_pub->publish(path);

    /**
    *******************************************************************************************
    * local costmap Publish
    *******************************************************************************************
    */
    // localmapの設定とpublish
    auto map = nav_msgs::msg::OccupancyGrid();
    map.header.stamp = current_time;
    map.header.frame_id = "base_link";
    map.info.resolution = 0.1;  // メートル/ピクセル
    map.info.width = 40;       // 4m x 4mの地図
    map.info.height = 40;
    map.info.origin.position.x = -(map.info.height * map.info.resolution) / 2.0;
    map.info.origin.position.y = -(map.info.width * map.info.resolution) / 2.0;
    map.info.origin.position.z = 0.0;
    map.info.origin.orientation.w = 1.0;

    // 地図データの初期化（すべてを未知(-1)に設定）
    map.data = std::vector<int8_t>(map.info.width * map.info.height, -1);

    // ローカルマップをパブリッシュ
    localmap_pub->publish(map);

    /**
    *******************************************************************************************
    * laser range visualize publish
    *******************************************************************************************
    */
    // 円の線を描くマーカーを作成
    visualization_msgs::msg::Marker line_strip_marker;
    line_strip_marker.header.frame_id = "base_link";
    line_strip_marker.header.stamp = current_time;
    line_strip_marker.ns = "circle_line";
    line_strip_marker.id = 1;
    line_strip_marker.type = visualization_msgs::msg::Marker::LINE_STRIP;
    line_strip_marker.action = visualization_msgs::msg::Marker::ADD;
    line_strip_marker.pose.orientation.w = 1.0;
    line_strip_marker.scale.x = 0.05;  // 線の太さ
    line_strip_marker.color.r = 1.0;
    line_strip_marker.color.g = 0.0;
    line_strip_marker.color.b = 0.0;
    line_strip_marker.color.a = 1.0;  // 不透明
    int num_points = 100;
    // 円周上の点を追加
    double radius = 1.0;
    for (int i = 0; i <= num_points; ++i) {
      double angle = i * 2.0 * M_PI / num_points;
      geometry_msgs::msg::Point p;
      p.x = radius * cos(angle);
      p.y = radius * sin(angle);
      p.z = 0.0;
      line_strip_marker.points.push_back(p);
    }

    // マーカーをパブリッシュth
    // 時刻の保存
    last_time = current_time;
  }

  rclcpp::Publisher<nav_msgs::msg::Odometry>::SharedPtr odom_pub;
  rclcpp::Publisher<nav_msgs::msg::Path>::SharedPtr path_pub;
  rclcpp::Publisher<nav_msgs::msg::OccupancyGrid>::SharedPtr localmap_pub;
  rclcpp::Publisher<visualization_msgs::msg::Marker>::SharedPtr laser_range_pub;
  rclcpp::Subscription<geometry_msgs::msg::Twist>::SharedPtr cmd_vel_subscriber;
  std::shared_ptr<tf2_ros::TransformBroadcaster> odom_broadcaster;
  rclcpp::TimerBase::SharedPtr timer_;
  nav_msgs::msg::Path path;  // Pathメッセージのメンバ変数を追加
  double x, y, th, vx, vth;
  rclcpp::Time current_time, last_time;
};

int main(int argc, char ** argv)
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<OdometryPublisher>());
  rclcpp::shutdown();
  return 0;
}
```
githubで公開しているのでインストールするか,ros2ファイルを新しく作成してください.
```
cd ~/ros2_ws/src
git clone https://github.com/Arcanain/arcanain_simulator.git
```

### lidarを起動
```
source ~/ros2_ws/install/setup.bash
ros2 launch sllidar_ros2 sllidar_a1_launch.py
```

### オドメトリの起動
```
source ~/ros2_ws/install/setup.bash
ros2 launch arcanain_simulator odometory_pub.py
```

### emclを起動
```
source ~/ros2_ws/install/setup.bash
ros2 launch emcl2 emcl2_navigation_launch.py
```
### emclを用いた経路追従制御
<iframe width="560" height="315" src="https://www.youtube.com/embed/QkoWc1MazG8?si=MA5GmntL2r_jCh1a" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

経路追従制御(pur pursuit法)の自己位置推定にemclを用いて実験しました.基本的には,精度良くマッチングしてくれましたが,初期座標,オドメトリの移動量,回転とセンサーによる位置推定にずれが生じると,自己位置が破綻しそうになる箇所がありました.EMCLは,適切なパラメータ設定と情報の組み合わせによって高精度な位置推定が可能です.しかし,自己位置推定の精度は,初期位置の設定,オドメトリの信頼性,センサーの精度,およびパーティクルの重み付けによって大きく左右されます.実験結果を基に,これらのパラメータを適切に調整し,オドメトリやセンサーの誤差が自己位置推定に与える影響を軽減することで,精度をさらに向上させることができそうです.

### パラメータの設定
パラメータはLuaファイルで設定されていましたが,なぜかその設定が反映されていませんでした.そこで,直接コード内(src/emcl2_node.cpp)のパラメータを変更しました.ロボットの挙動に応じて,適宜調整してください.

```C++
// グローバル座標系の名前を設定します.通常は「map」として設定され,地図上の座標系.
this->declare_parameter("global_frame_id", std::string("map"));

// ロボットのフットプリント座標系の名前を設定します.通常は「base_footprint」で,ロボットの基準となる座標系.
this->declare_parameter("footprint_frame_id", std::string("base_footprint"));

// オドメトリの座標系の名前を設定します.通常は「odom」で,ロボットの移動履歴を追跡する座標系.
this->declare_parameter("odom_frame_id", std::string("odom"));

// ロボット本体の座標系の名前を設定します.通常は「base_link」で,ロボットの基準座標.
this->declare_parameter("base_frame_id", std::string("base_link"));

// オドメトリ情報の更新頻度　.
this->declare_parameter("odom_freq", 20);

// トランスフォームの許容誤差.これにより,座標系間の変換の信頼性が決まります.
this->declare_parameter("transform_tolerance", 0.2);

// レーザーセンサーの最小測定距離を設定します.0.0は測定距離の制限なしを意味します.
this->declare_parameter("laser_min_range", 0.0);

// レーザーセンサーの最大測定距離を設定します.
this->declare_parameter("laser_max_range", 100000000.0);

// スキャンの増分を設定します.センサーのスキャン解像度に関連します.
this->declare_parameter("scan_increment", 1);

// 初期位置のx座標を設定します.ロボットのスタート位置のx座標.
this->declare_parameter("initial_pose_x", 0.0);

// 初期位置のy座標を設定します.ロボットのスタート位置のy座標.
this->declare_parameter("initial_pose_y", 0.0);

// 初期位置の角度（ラジアン単位）を設定します.ロボットのスタート位置の向き.
this->declare_parameter("initial_pose_a", 0.0);

// EMCLで使用するパーティクルの数.
this->declare_parameter("num_particles", 500);

// パーティクルの重みの閾値.重みがこの閾値以下の場合,再サンプリングが行われます.
this->declare_parameter("alpha_threshold", 0.5);

// 位置の拡張半径.パーティクル群の位置更新の際に使用されます.
this->declare_parameter("expansion_radius_position", 0.1);

// 向きの拡張半径.パーティクル群の向き更新の際に使用されます.
this->declare_parameter("expansion_radius_orientation", 0.2);

// パーティクルの抽出率.再サンプリングの頻度を調整します.
this->declare_parameter("extraction_rate", 0.1);

// センサーの情報を元に位置推定の精度がどの程度低いとみなされるかを決定する範囲の閾値を設定します.
this->declare_parameter("range_threshold", 0.1);

// センサーのリセットを有効にするかどうか.デフォルトは無効です.
this->declare_parameter("sensor_reset", false);

// オドメトリの前進移動時の誤差.ロボットが前進した距離に応じた誤差です.
this->declare_parameter("odom_fw_dev_per_fw", 0.19);

// オドメトリの回転時の誤差.ロボットが回転した角度に応じた誤差です.
this->declare_parameter("odom_fw_dev_per_rot", 0.0001);

// オドメトリの回転時の誤差を前進移動量に基づいて設定します.
this->declare_parameter("odom_rot_dev_per_fw", 0.13);

// オドメトリの回転時の誤差を回転角度に基づいて設定します.
this->declare_parameter("odom_rot_dev_per_rot", 0.2);

// レーザーセンサーの信頼性を最大距離.この距離内でセンサーの情報が信頼されます.
this->declare_parameter("laser_likelihood_max_dist", 0.2);
```
## 参考文献
・上田 隆一(2019), 詳解 確率ロボティクス Pythonによる基礎アルゴリズムの実装

 https://github.com/CIT-Autonomous-Robot-Lab/emcl2_ros2
 
 https://github.com/ryuichiueda/emc
 


## 謝辞
この取り組みは, GxP(グロースエクスパートナーズ)株式会社様のサポートを受けて実施しています. 貴重なアドバイスや, ロボットに必要な機材の支援をいただきました. 心より感謝申し上げます.

https://www.gxp.co.jp/

https://qiita.com/organizations/gxp