introlab / rtabmap_ros

RTAB-Map's ROS package.
http://wiki.ros.org/rtabmap_ros
BSD 3-Clause "New" or "Revised" License
916 stars 549 forks source link

Odom topic in python #1172

Open r0n1tr opened 3 weeks ago

r0n1tr commented 3 weeks ago

Hi sir,

We are using a python script to overlay odometry feed from a cloud generated environment (.ply) that is converted into a 2d occupancy grid for autonomous navigation, it seems to be that there's a significant difference in latency between the odom feed in rtab_map and then using it in python. Additionally, when we do "reset odometry" in rtabmap, the odometry shown in rtabmap corrects itself once localised, but the odometry topic in python is not corrected, and becomes very incorrect. I was wondering if there was a specific reason or a severe oversight on our part. Thank you for your time.

Here is our code for extra context:

import os
import threading
import rospy
import open3d as o3d
import numpy as np
import matplotlib.pyplot as plt
from sensor_msgs.msg import PointCloud2
from nav_msgs.msg import Odometry
import sensor_msgs.point_cloud2 as pc2

class OccupancyMapNode:
    def _init_(self, resolution=0.01):
        self.resolution = resolution
        self.occupancy_grid = None
        self.odometry_x = 0.0
        self.odometry_y = 0.0
        self.odometry_yaw = 0.0
        self.robot_height = None
        self.lock = threading.Lock()
        self.height_threshold_lower = 0.1  # Exclude points below this height

        # Set the environment variables
        local_ip = "172.17.0.2"
        remote_pi_ip = "192.168.137.26"
        os.environ['ROS_IP'] = local_ip
        os.environ['ROS_MASTER_URI'] = f"http://{remote_pi_ip}:11311"

        rospy.init_node('occupancy_map_node', anonymous=True)

        # Subscribe to the point cloud and odometry topics
        point_cloud_topic = "/rtabmap/cloud_obstacles"
        odometry_topic = "/rtabmap/odom"
        self.point_cloud_subscriber = rospy.Subscriber(point_cloud_topic, PointCloud2, self.point_cloud_callback)
        self.odometry_subscriber = rospy.Subscriber(odometry_topic, Odometry, self.odometry_callback)

        # Start ROS thread
        self.ros_thread = threading.Thread(target=self.ros_spin)
        self.ros_thread.start()

        plt.ion()
        self.fig, self.ax = plt.subplots()

    def ros_spin(self):
        rospy.spin()

    def point_cloud_callback(self, msg):
        points = []
        for p in pc2.read_points(msg, field_names=("x", "y", "z"), skip_nans=True):
            points.append([p[0], p[1], p[2]])
        points = np.array(points)

        if len(points) == 0:
            print("No points found in the point cloud :(")
            return

        with self.lock:
            self.occupancy_grid = points

    def odometry_callback(self, msg):
        position = msg.pose.pose.position
        orientation = msg.pose.pose.orientation
        with self.lock:
            self.odometry_x = position.x
            self.odometry_y = position.y
            quaternion = (orientation.x, orientation.y, orientation.z, orientation.w)
            euler = self.quaternion_to_euler(quaternion)
            self.odometry_yaw = euler[2]  # Yaw is the third element

    def quaternion_to_euler(self, quaternion):
        # Convert quaternion to euler angles (roll, pitch, yaw)
        x, y, z, w = quaternion
        t0 = +2.0 * (w * x + y * z)
        t1 = +1.0 - 2.0 * (x * x + y * y)
        roll = np.arctan2(t0, t1)

        t2 = +2.0 * (w * y - z * x)
        t2 = +1.0 if t2 > +1.0 else t2
        t2 = -1.0 if t2 < -1.0 else t2
        pitch = np.arcsin(t2)

        t3 = +2.0 * (w * z + x * y)
        t4 = +1.0 - 2.0 * (y * y + z * z)
        yaw = np.arctan2(t3, t4)

        return roll, pitch, yaw

    def display_occupancy_map(self):
        with self.lock:
            if self.occupancy_grid is None:
                return

            # Dynamically determine the ceiling height threshold based on the 90th percentile
            height_threshold_upper = np.percentile(self.occupancy_grid[:, 2], 50)

            # Filter points based on height thresholds
            filtered_points = self.occupancy_grid[(self.occupancy_grid[:, 2] > self.height_threshold_lower) &
                                                  (self.occupancy_grid[:, 2] < height_threshold_upper)]

            self.ax.clear()
            self.ax.scatter(filtered_points[:, 0], filtered_points[:, 1], s=1)  # Display occupancy grid

            # Plot odometry position as a red dot
            self.ax.scatter(self.odometry_x, self.odometry_y, color='red')  

            self.ax.set_xlabel('X')
            self.ax.set_ylabel('Y')
            self.ax.set_title('Occupancy Map with Odometry Path')

            plt.draw()
            plt.gcf().canvas.flush_events()

node = OccupancyMapNode(resolution=0.01)

try:
    while not rospy.is_shutdown():
        node.display_occupancy_map()
        plt.pause(0.1)
except (KeyboardInterrupt, SystemExit):
    print("Shutting down")
    rospy.signal_shutdown('Exiting...')
matlabbe commented 2 weeks ago

For the odometry latency, do you mean there is more latency on python code or rtabmap's odometry?

For the odometry correctly, either you use TF to get map->odom transform to correct the received odometry, or subscribe to /rtabmap/mapGraph and read mapToOdom transform to correct the odometry afterwards.