見出し画像

Self Driving and ROS 2 - Learn by Doing! Map & Localization: 占有グリッドマッピング (セクション8-1/11)

  • 占有グリッドマッピング:オドメトリーデータとLiDARセンサーデータを用いて、環境の占有グリッドマップを作成し、ロボットの位置と周囲の情報を更新します。

  • 確率的アプローチ:各セルを確率変数として扱い、環境の最も可能性の高いマップを構築することで、センサーデータとオドメトリーデータの誤差を考慮します。

  • 実践的な実装:ROS 2でPythonノードを作成し、占有グリッドマップをパブリッシュする方法を学び、マップの精度と品質を向上させます。

はじめに

マッピングは自律航法の基礎の一つであり、ロボットの自己位置推定、経路計画、および障害物回避を可能にします。占有グリッドマップは、環境をグリッドに分割し、各セルが占有されているか、空いているか、不明かを示す基本的な表現です。この投稿では、Udemyの「Self Driving and ROS 2 - Learn by Doing! Map & Localization」コースのレッスン102から104で説明された概念と実装について詳しく見ていきます。

レッスン102: 既知の位置を用いたマッピング

このレッスンでは、既知の位置を用いたマッピングの概念を紹介しました。特定の時点でロボットの位置が分かることが、センサーデータに基づいてマップを正確に更新するために重要です。主な要素は次の通りです:

  • マップの表現:環境の離散化された表現である占有グリッドの理解。

  • 位置の更新:オドメトリーデータを使用してロボットの位置を追跡。

  • センサーの統合:センサー(例:LiDAR)からのデータを統合して、ロボットの周囲に基づいて占有グリッドを更新。

このレッスンでは、マップが自律航法においてどれほど重要であり、ロボットの自己位置推定や経路計画にどのように役立つかを強調しました。

レッスン103: 占有グリッドマッピング

このレッスンでは、LiDARデータを使用して占有グリッドマップを作成および更新する具体的な方法について詳しく説明しました:

  • 初期位置:開始時にはグリッド内のすべてのセルが不明とされます。ロボットが移動し、周囲をスキャンすることでマップが更新されます。

  • 確率的アプローチ:グリッド内の各セルは確率変数として扱われ、占有、空き、または不明の可能性を示します。このアプローチは、センサーデータやオドメトリーデータの誤差と不確実性を考慮しています。

確率的なフレームワークは、環境のより堅牢で現実的な表現を可能にし、センサーデータの誤差や不整合を考慮します。

レッスン104: 占有グリッドパブリッシャー

このレッスンでは、占有グリッドを公開するPythonノードの実装に取り組みました:

  • ノードの初期化:ノードは占有グリッドマップを幅、高さ、および解像度のパラメータで初期化します。LiDARスキャンメッセージをサブスクライブし、オドメトリーデータを使用してロボットの位置を追跡します。

  • 変換:`tf2_ros`ライブラリを使用して、ロボットの参照フレームからマップの参照フレームに座標を変換します。

  • マップの更新:ロボットの位置とセンサーデータに基づいて占有グリッドを更新し、セルを占有または空きとしてマークします。

以下に実装の主要部分を示します:

class MappingWithKnownPoses(Node):
    def __init__(self, name):
        super().__init__(name)
        self.declare_parameter("width", 50.0)
        self.declare_parameter("height", 50.0)
        self.declare_parameter("resolution", 0.1)
        # Initialize map
        self.map_ = OccupancyGrid()
        self.map_.info.resolution = resolution
        self.map_.info.width = int(width / resolution)
        self.map_.info.height = int(height / resolution)
        self.map_.info.origin.position.x = float(-round(width / 2.0))
        self.map_.info.origin.position.y = float(-round(height / 2.0))
        self.map_.header.frame_id = "odom"
        self.map_.data = [-1] * (self.map_.info.width * self.map_.info.height)
        # Publishers and subscribers
        self.map_pub = self.create_publisher(OccupancyGrid, "map", 1)
        self.scan_sub = self.create_subscription(LaserScan, "scan", self.scan_callback, 10)
        self.timer = self.create_timer(1.0, self.timer_callback)
        self.tf_buffer = Buffer()
        self.tf_listener = TransformListener(self.tf_buffer, self)

    def scan_callback(self, scan: LaserScan):
        try:
            t = self.tf_buffer.lookup_transform(self.map_.header.frame_id, scan.header.frame_id, rclpy.time.Time())
        except LookupExeption as e:
            self.get_logger().error("Unable to lookup transform: %s" % str(e))
            return
        robot_p = coordinatesToPose(t.transform.translation.x, t.transform.translation.y, self.map_.info)
        if not poseOnMap(robot_p, self.map_.info):
            self.get_logger().error("Robot is out of map")
            return
        robot_cell = poseToCell(robot_p, self.map_.info)
        self.map_.data[robot_cell] = 100

    def timer_callback(self):
        self.map_.header.stamp = self.get_clock().now().to_msg()
        self.map_pub.publish(self.map_)

def main(args=None):
    rclpy.init(args=args)
    node = MappingWithKnownPoses("mapping_with_known_poses")
    rclpy.spin(node)
    rclpy.shutdown()

if __name__ == "__main__":
    main()

重要なポイント

  • マップの表現の理解:占有グリッドの概念を理解し、それが自律航法をどのように支援するかを学びます。

  • 不確実性の取り扱い:センサーデータとオドメトリーデータの誤差を考慮する確率的アプローチの採用。

  • 実践的な実装:Pythonを使用してROS 2ノードを構築し、オドメトリーデータとLiDARデータを統合して占有グリッドマップを作成および公開。

これらのレッスンは、ロボティクスにおけるマッピングの複雑さと課題を理解するための強固な基盤を提供し、学生が自律航法のための堅牢なマップを作成するための知識とツールを身につけることができます。

次回のセクション8の続きでは、より高度な確率モデルを統合し、マップの精度を向上させる方法について詳しく説明します。マッピングの旅を楽しんでください!

「超本当にドラゴン」へ

この記事が気に入ったらサポートをしてみませんか?