ZED 2i ROS 2 Wrapper: Velocity (Linear/Angular) Stuck at Zero on /odom Topic

Hello everyone,
I’m working on an autonomous drone project using a ZED 2i camera for vision-based navigation. I’m trying to publish the drone’s position and velocity(obtained from camera) to a Pixhawk flight controller via MAVROS using the ZED ROS 2 wrapper.
I can successfully publish the position data, but the linear and angular velocity values from the /zed_node/odom topic are always zero, even when the drone is in motion.

My Current Setup:
Camera: ZED 2i
ROS: ROS 2 (Humble)
Hardware: Jetson with a Pixhawk flight controller
Objective: Publish vision_pose and vision_speed to the Pixhawk via MAVROS.
The Problem:

  1. I am running a custom Python ROS 2 node that subscribes to /zed_node/odom and /zed_node/pose.
  2. The position data from /zed_node/pose is correctly received and published to MAVROS’s vision_pose/pose.
  3. The velocity data (both linear and angular) from /zed_node/odom consistently reports zero values despite movement.
    I have verified this by directly echoing the /zed_node/odom topic—the velocities are always zero, although the position data within the same message is updating correctly.

I’m using the ZED ROS 2 wrapper with the following launch arguments:

launch_arguments={
“camera_model”: “zed2i”,
“camera_name”: “zed2i”,
“general.pub_frame_rate”: “30.0”,
“pos_tracking.enable_pose_tracking”: “true”,
“pos_tracking.publish_tf”: “true”,
“pos_tracking.publish_pose”: “true”,
“pos_tracking.use_zed_localization”: “false”,
“pos_tracking.map_frame”: “map”,
“pos_tracking.odom_frame”: “odom”,
“pos_tracking.base_frame”: “base_link”,
“pos_tracking.set_gravity_as_origin”: “false”,
“pos_tracking.enable_imu_fusion”: “true”,
“pos_tracking.pos_tracking_mode”: “GEN_2”,
“pos_tracking.path_pub_rate”: “2.0”, # Lower rate for /path topic (optional)
“pos_tracking.smoothing”: “true”,
“pos_tracking.area_memory”: “false”,
“pos_tracking.publish_imu”: “true”, # Publishes raw IMU data for debugging
“pos_tracking.vel_with_covariance”: “true”, # Publishes velocity with covariance
“sensors.publish_imu_data”: “true”, # Raw IMU topic
“sensors.publish_imu_tf”: “false”, # IMU TF not needed for this setup
}.items(),
)

Questions for the Community:

  1. Why would the linear and angular velocity in /zed_node/odom always be zero? Are there specific launch parameters or camera settings that need to be enabled for velocity data?

  2. Could this be an issue with the static transform I’m using( but i am getting the correct static transform done…confirmed it on rviz2)? My camera is mounted facing downward, with base_link as the parent and zed_camera as the child frame.

  3. Is it possible the ZED SDK needs to be configured differently to calculate velocity? When I tested the ZED SDK directly (outside of ROS), the terminal showed correct velocity data.
    Is it better to use a custom node (with the ROS wrapper) or to try publishing data directly from the ZED SDK to the Pixhawk? What are the pros and cons in this specific scenario?

Any insights or suggestions would be greatly appreciated. Thank you!

Hi @kumuda,

The ZED ROS2 Wrapper uses the nav_msgs/msg/Odometry message, which does not contain velocity data.

If the ZED ROS2 Wrapper doesn’t publish velocity, is there any alternative way to send velocity data to the flight controller with it?

Or should I avoid relying on the wrapper for this and instead use another approach? If so, which approach would you recommend(please specify it).

Also, just to be clear, which timestamp should I use so the position and velocity data stay in sync — the msg.header.stamp, self.get_clock().now(), or does it not really matter?

The ZED SDK does not compute velocity data, you can compute it yourself from the odom topic itself.

We also provide IMU data on the topic ~/imu/data , which contains linear acceleration and angular velocity data. This is raw data not processed by the SDK’s VIO however.

The msg.header.stamp is timestamped with the image capture timestamp for all data from the wrapper, except for the other sensor data (IMU, temperature, barometer, etc).

For pose data, this means that the timestamp is not when the pose is computed, but when the image is captured, so you should probably use the get_clock().now()

If I stick to using only the ZED ROS2 Wrapper, can I take the pose data from /zed_node/pose, differentiate it with respect to time to compute velocity, and then send that to /mavros/vision_speed/speed_twist? Would this approach work reliably for integration with a flight controller? Also, does this method still count as using the ZED’s VIO, or is it essentially bypassing the VIO part?

Yes, I think differentiating the position is a good approach. I would however recommend using the /zed_node/odom topic to differentiate, as the /zed_node/pose can contain discrete jumps in the map frame during loop closures, which could mess up your calculations.

This does still count as using the VIO.

Thank you so much for the insight!