Robotics StackExchange | Archived questions

Fusing GPS in robot_localization

Hi, I am trying to fuse GPS with all other odom and IMUs My local odometry has barely any drift now. The only issue is that the direction of the GPS odometry seems to be rotated with respect to the other odometry..

image description

you can see that the robot is driving towards the left but the GPS is translating upwards. particular attention to imu0

ekf_filter_node_odom:
  ros__parameters:

    use_sim_time: true

    frequency: 30.0
    sensor_timeout: 0.1
    two_d_mode: false
    transform_time_offset: 0.0
    transform_timeout: 0.0
    print_diagnostics: false
    debug: false

    map_frame: map
    odom_frame: odom
    base_link_frame: base_link
    world_frame: odom

    odom0: /wheel/odom
    odom0_config: [false, false, false,
                  false, false, false,
                  true,  true,  true,
                  false, false, true,
                  false, false, false]
    odom0_queue_size: 10
    odom0_nodelay: true
    odom0_differential: false
    odom0_relative: true

    odom1: /visual_slam//odometry
    odom1_config: [true, true, true,
                  true, true, true,
                  false,  false,  false,
                  false, false, false,
                  false, false, false]
    odom1_queue_size: 10
    odom1_nodelay: true
    odom1_differential: true
    odom1_relative: false

    odom2: /lidar/odom
    odom2_config: [true, true, true,
                  false, false, false,
                  false,  false,  false,
                  false, false, false,
                  false, false, false]
    odom2_queue_size: 10
    odom2_nodelay: true
    odom2_differential: false
    odom2_relative: false

    imu0: /imu/with/absolute/orientation/returns 0 when facing East
    imu0_config: [false, false, false,
                true, true, true,
                false, false, false,
                true, true, true,
                false, false, false]
    imu0_nodelay: false
    imu0_differential: false
    imu0_relative: true # IS THIS CORRECT? IF IT IS FALSE, THE DIRECTION OF base_link FACES ANOTHER DIRECTION FROM THE OTHER ODOMETRY SOURCES TRANSLATION DIRECTION
    imu0_queue_size: 10
    imu0_remove_gravitational_acceleration: false

    imu1: /imu1
    imu1_config: [false, false, false,
                false, false, false,
                false, false, false,
                true, true, true,
                false, false, false]
    imu1_nodelay: false
    imu1_differential: true
    imu1_relative: false
    imu1_queue_size: 10
    imu1_remove_gravitational_acceleration: false

    imu2: /imu2
    imu2_config: [false, false, false,
                false, false, false,
                false, false, false,
                true, true, true,
                false, false, false]
    imu2_nodelay: false
    imu2_differential: true
    imu2_relative: false
    imu2_queue_size: 10
    imu2_remove_gravitational_acceleration: false

    imu3: /imu3
    imu3_config: [false, false, false,
                false, false, false,
                false, false, false,
                true, true, true,
                false, false, false]
    imu3_nodelay: false
    imu3_differential: true
    imu3_relative: false
    imu3_queue_size: 10
    imu3_remove_gravitational_acceleration: false
    use_control: false

    process_noise_covariance: ...

    initial_estimate_covariance: ...

ekf_filter_node_map:
  ros__parameters:

    use_sim_time: true

    frequency: 30.0
    sensor_timeout: 0.1
    two_d_mode: false
    transform_time_offset: 0.0
    transform_timeout: 0.0
    print_diagnostics: false
    debug: false

    map_frame: map
    odom_frame: odom
    base_link_frame: base_link
    world_frame: map
    odom0: /wheel/odom
    odom0_config: [false, false, false,
                  false, false, false,
                  true,  true,  true,
                  false, false, true,
                  false, false, false]
    odom0_queue_size: 10
    odom0_nodelay: true
    odom0_differential: false
    odom0_relative: true

    odom1: /visual_slam//odometry
    odom1_config: [true, true, true,
                  true, true, true,
                  false,  false,  false,
                  false, false, false,
                  false, false, false]
    odom1_queue_size: 10
    odom1_nodelay: true
    odom1_differential: true
    odom1_relative: false

    odom2: /lidar/odom
    odom2_config: [true, true, true,
                  false, false, false,
                  false,  false,  false,
                  false, false, false,
                  false, false, false]
    odom2_queue_size: 10
    odom2_nodelay: true
    odom2_differential: false
    odom2_relative: false

    odom3: /odometry/gps
    odom3_config: [true,  true,  false,
                  false, false, false,
                  false, false, false,
                  false, false, false,
                  false, false, false]
    odom3_queue_size: 10
    odom3_nodelay: true
    odom3_differential: false
    odom3_relative: false

    imu0: /imu/with/absolute/orientation/returns 0 when facing East
    imu0_config: [false, false, false,
                true, true, true,
                false, false, false,
                true, true, true,
                false, false, false]
    imu0_nodelay: false
    imu0_differential: false
    imu0_relative: true # IS THIS CORRECT? IF IT IS FALSE, THE DIRECTION OF base_link FACES ANOTHER DIRECTION FROM THE OTHER ODOMETRY SOURCES TRANSLATION DIRECTION
    imu0_queue_size: 10
    imu0_remove_gravitational_acceleration: false

    imu1: /imu1
    imu1_config: [false, false, false,
                false, false, false,
                false, false, false,
                true, true, true,
                false, false, false]
    imu1_nodelay: false
    imu1_differential: false
    imu1_relative: false
    imu1_queue_size: 10
    imu1_remove_gravitational_acceleration: false

    imu2: /imu2
    imu2_config: [false, false, false,
                false, false, false,
                false, false, false,
                true, true, true,
                false, false, false]
    imu2_nodelay: false
    imu2_differential: false
    imu2_relative: false
    imu2_queue_size: 10
    imu2_remove_gravitational_acceleration: false

    imu3: /imu3
    imu3_config: [false, false, false,
                false, false, false,
                false, false, false,
                true, true, true,
                false, false, false]
    imu3_nodelay: false
    imu3_differential: false
    imu3_relative: false
    imu3_queue_size: 10
    imu3_remove_gravitational_acceleration: false
    use_control: false    

    use_control: false

    process_noise_covariance: ...

    initial_estimate_covariance: ...

navsat_transform:
  ros__parameters:
    use_sim_time: true
    broadcast_cartesian_transform: true
    broadcast_cartesian_transform_as_parent_frame: true
    broadcast_utm_transform: false
    broadcast_utm_transform_as_parent_frame_: false
    delay: 0.0
    frequency: 10.0
    magnetic_declination_radians: -0.1188569
    publish_filtered_gps: true
    transform_timeout: 0.0
    use_local_cartesian: false
    use_odometry_yaw: false
    wait_for_datum: true
    datum: [1.2824466988410665, 103.86530962903208,0.0]
    yaw_offset: 0.0
    zero_altitude: false

gps odometry on the left, local odometry on the right image description

Asked by MrOCW on 2022-07-05 23:23:48 UTC

Comments

Answers

The only issue is that the direction of the GPS odometry seems to be rotated with respect to the other odometry

This says that you have your yaw_offset or magnetic_declination parameters set incorrectly. It could also be a timing thing, especially with the way you have your filter set up. More on that later.

First, you are fusing absolute pose data from four different sources:

  • /visual_slam//odometry: position and orientation
  • /lidar/odom: position
  • /imu/with/absolute/orientation: orientation
  • /odometry/gps: position

(Your visual slam topic has two slashes in it, by the way).

Anyway, I sincerely doubt these sources are going to agree with one another over time, and so over time, the pose is going to start jumping between them, even without the GPS data. You should choose one as your most trustworthy pose source, and fuse velocity data from the others, or put them in differential mode if they don't provide velocity.

Just so we're clear, navsat_transform_node requires your IMU to read 0 facing east. The EKF doesn't care. navsat_transform_node needs three things:

  1. An IMU with an earth-referenced heading
  2. A GPS fix
  3. Your robot's current pose in its world frame

It doesn't care what (3) is, so long as it's right-handed. It doesn't have to agree at all with the GPS or IMU that you use in n_t_n. You could just use raw wheel odometry alone for your EKF state estimate, and it would work.

But you need to be careful here. If this hapens:

  1. EKF kicks off, gets a single IMU measurement from, e.g., imu3. So its first output has a pose and position of 0, with whatever angular velocity was in that message.
  2. navsat_transform_node is ready, and gets all its input data, including that first pose from the EKF (i.e., input 3 above). That pose is 0.
  3. EKF fuses pose from some other source. Let's say it's from your SLAM system, that says you are at some non-zero position and with non-zero orientation. Now you've effectively changed your EKF's coordinate frame (i.e., where its origin is). But navsat_transform_node has the wrong EKF pose, so when it transforms the GPS data, it will be wrong.

What you want is:

  1. EKF fuses data from all pose sources (again, you preferably one have one source for absolute pose)
  2. navsat_transform_node` is ready, and gets all its input data, including that first pose from the EKF
  3. Now all posese from n_t_n should be consistent with your robot's world frame.

This is why n_t_n has the delay parameter. It gives you time to get a sensor measurement from all your sensors into the EKF before it starts.

Update in response to comments:

should I then set lidar odom to differential=true and gps as absolute x y source?

Up to you, really, but as you have it now, your pose will eventually diverge from the GPS path (lidar odometry will be integrating laser scan matches, I presume, so it will gain error over time).

except imu as i thought n_t_n requires absolute IMU orientation

It does. See the requirements for what n_t_n expects above. I think you may be conflating IMU inputs to your EKF instance and IMU inputs to n_t_n. They are totally unrelated, unless you tell n_t_n to use the state estimate's yaw (use_odometry_yaw), and you don't have that configured (and shouldn't).

is this under the assumption that I have a fix the moment the global map->base_link ekf begins?

Not really, no. When n_t_n starts, it receives input (3) above from the EKF. The first time the EKF publishes, it may not have received an IMU measurement, so the EKF's first output has a yaw of 0. So n_t_n computes the transform from the GPS (really, UTM) frame to the robot's world frame, but the transform is based on that first pose from the EKF, which has a 0 yaw. Then the EKF gets its first IMU measurement, and it has a yaw of, e.g., 2.7 radians. So from then on, the transformed pose coming from n_t_n will be inconsistent with the robot's direction of travel. Instead, if n_t_n had waited until the EKF had received its initial orientation, then it would have based that UTM-to-robot transform on the heading of 2.7 radians, and the transform would be different.

The easiest way around this is for your EKF to only fuse orientation velocity (or at least yaw velocity). Then the robot's heading will start at 0, and will not suddenly jump in the next time step.

Asked by Tom Moore on 2022-07-28 09:34:33 UTC

Comments

"Anyway, I sincerely doubt these sources are going to agree with one another over time, and so over time, the pose is going to start jumping between them, even without the GPS data. You should choose one as your most trustworthy pose source, and fuse velocity data from the others, or put them in differential mode if they don't provide velocity."

in my config, there is only 1 absolute source (most trustworthy lidar odom), i've set the others to differential=true or only fuse the velocities (except imu as i thought n_t_n requires absolute IMU orientation), except in the global odom with lidar and gps odom providing absolute positions. should I then set lidar odom to differential=true and gps as absolute x y source?

requires your IMU to read 0 facing east

Yes it is 0 when facing east, hence yaw_offset = 0

But you need to be careful here. If this hapens

is this under the assumption that I have a fix the moment the global map->base_link ekf begins?

Asked by MrOCW on 2022-07-28 09:47:49 UTC