> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.stereolabs.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.stereolabs.com/_mcp/server.

# Using Depth Perception with ROS 2

## Configuring the depth computation

All the depth parameters live in the `depth` namespace of `common_stereo.yaml`. See the [Depth parameters](/docs/integrations/ros-2/zed-stereo-node/#depth-parameters) reference for the full list. The ones that drive the computation cost are:

* `depth.depth_mode`: the quality of the depth map, from `NEURAL_LIGHT` to `NEURAL` and `NEURAL_PLUS`. Set it to `NONE` to disable depth extraction entirely, which also disables Positional Tracking, Object Detection, Body Tracking and Mapping.
* `depth.depth_precision` \[ZED SDK >= 5.5]: the precision of the neural depth inference. `INT8` is faster and uses less GPU memory than the default `FP16`, for a small accuracy cost, and requires `depth.depth_mode: 'NEURAL'`. It is always safe to set: the ZED SDK falls back to `FP16` when the depth mode or the GPU does not support it.
* `depth.allow_depth_cuda_graph` \[ZED SDK >= 5.5]: lets the ZED SDK record the depth computation once and replay it at every grab, which lowers the CPU cost of launching it and matters most on NVIDIA® Jetson™. The depth output is unchanged. It applies to the `NEURAL` depth mode family only, and the SDK reverts to the regular computation if the recording fails.

To reduce the bandwidth taken by the point cloud without lowering the resolution of the depth map, set `depth.voxel_point_cloud` to `true` \[ZED SDK >= 5.3] and tune the decimation with `depth.voxel_size_mm`, `depth.voxel_resolution_mode` and `depth.voxel_resolution_scale`. All the voxel parameters are dynamically reconfigurable.

## Depth with RViz 2

In this tutorial, you will learn in detail how to configure your own RViz 2 session to see only the depth data that you require.

Depth information can be visualized in many different ways: 2D depth images, 3D point clouds, 3D registered depth clouds, and the confidence image to name a few.

### Depth image

To visualize a `depth image`, you can use the simple `Image plugin` since the depth data are published on topics of type `sensor_msgs/Image`. It differs from a "normal" image in that the data is encoded in 32-bit (floating point) rather than 8-bit.

![image](/_fern-img/0d68f10c19f75b52ce8080353ebf176d6b8215256c4c97a1a146a6fae1dfb55b.webp)

The parameters are the same as for `Image` (see the [Video tutorial](/docs/integrations/ros-2/video-capture#video-with-rviz-2)) with three additions:

* `Normalize range`: Since a floating point image is not directly rendered, it is converted to an 8-bit grayscale image. Enabling this field means the normalization range is automatically calculated
* `Min value`: If `Normalize range` is unchecked, you can manually set the minimum depth range in meters
* `Max value`: If `Normalize range` is unchecked, you can manually set the maximum depth range in meters

Manually setting the normalization range is useful if you know the maximum value measured for the depth and you want to keep the image scale static.

### Pointcloud

To directly visualize a topic of type `sensor_msgs/PointCloud2` you can use the `PointCloud2` plugin.

![image](/_fern-img/94784ab7e23bab4b6b774fded7761bab6a5c50ab56dcec08ccdb3ca462659d91.webp)

Key parameters:

* `Topic`: Selects the topic to visualize from the list of available point cloud messages
* `Depth`: The depth of the incoming message queue
* `History policy`: Set the QoS history policy. `Keep Last` is suggested for performance and compatibility
* `Reliability Policy`: Set the QoS reliability policy. `Best Effort` is suggested for performance, `Reliable` is used for compatibility
* `Durability Policy`: Set the QoS durability policy. `Volatile` is suggested for compatibility
* `Style`: The style used to render each point. Use `Points` and `Size=1` to maximize FPS
* `Color transformer`: Selects how points are colored. Use `RGB8` to match the color image pixels to the corresponding depth pixels. Use `Axis color` to use a color proportional to the value of the `Axis` value (e.g. `Z` to use a color scale proportional to the distance from the floor or `X` to use a color proportional to the distance from the camera)

> **Note**
>
> If mapping is enabled (see the `mapping.mapping_enabled` parameter), the `PointCloud2` plugin is also used to visualize the fused point cloud produced by the mapping module, by subscribing to the topic `/zed/zed_node/mapping/fused_cloud`.

### Confidence

To visualize the `Confidence Map` as an image, you can use the `Image` plugin subscribing to the topic `~/confidence/confidence_map`.

![image](/_fern-img/d5d46b3c682fcdd27c60b97b3a62e4a49cb684ff7f03345117532754b1084e45.webp)

The confidence map contains the raw ZED SDK confidence values, from `0` (high confidence) to `100` (low confidence). In a grayscale view, the lighter a pixel, the **less** reliable its corresponding depth value is.

## Depth subscribing in C++

In this tutorial, you will learn how to write a simple C++ node that subscribes to messages of type
`sensor_msgs/Image` in order to retrieve depth images published by the ZED node and to get the measured distance at the center of the image.

### Introduction

Open a new console and use this command to connect the camera to the ROS 2 network:

```bash
ros2 launch zed_display_rviz2 display_zed_cam.launch.py camera_model:=<camera model>
```

The ZED node will start to publish image data in the network only if there is another node that subscribes to the relative topic.

### Running the tutorial

If you properly followed the [ROS 2 Examples Installation Guide](https://github.com/stereolabs/zed-ros2-examples#build-the-package), the executable of this tutorial has been compiled and you can run the subscriber node using this command:

```bash
ros2 run zed_tutorial_depth zed_tutorial_depth 
```

The tutorial node subscribes to generic `depth` topic, so a remapping is required to connect to the correct topic published by the ZED node:

```bash
ros2 run zed_tutorial_depth zed_tutorial_depth --ros-args -r depth:=/zed/zed_node/depth/depth_registered
```

If the ZED node is running and a camera is connected or you have loaded an SVO file, you will receive
the following stream of messages confirming that you are correctly subscribing to the ZED image topics:

```bash
[INFO] [zed_depth_tutorial]: Center distance : 0.981005 m
[INFO] [zed_depth_tutorial]: Center distance : nan m
[INFO] [zed_depth_tutorial]: Center distance : nan m
[INFO] [zed_depth_tutorial]: Center distance : 1.20664 m
[INFO] [zed_depth_tutorial]: Center distance : 1.16232 m
[INFO] [zed_depth_tutorial]: Center distance : 1.15096 m
[INFO] [zed_depth_tutorial]: Center distance : 1.15708 m
[INFO] [zed_depth_tutorial]: Center distance : 1.14709 m
[INFO] [zed_depth_tutorial]: Center distance : 1.15806 m
[INFO] [zed_depth_tutorial]: Center distance : 1.16716 m
[INFO] [zed_depth_tutorial]: Center distance : 1.17654 m
[INFO] [zed_depth_tutorial]: Center distance : 1.16571 m
[INFO] [zed_depth_tutorial]: Center distance : 1.16145 m


[...]
```

> **Note**
>
> `nan` values for depths are reported if the central pixel of the image does not contain a valid depth measure (occlusion, flare, low textured surface, ...).

### The code

The source code of the subscriber node `zed_depth_sub_tutorial.cpp`:

**`C++`**

```cpp C++
#include "rclcpp/rclcpp.hpp"
#include "sensor_msgs/msg/image.hpp"

using namespace std::placeholders;

class MinimalDepthSubscriber : public rclcpp::Node {
  public:
    MinimalDepthSubscriber()
        : Node("zed_depth_tutorial") {

        /* Note: it is very important to use a QOS profile for the subscriber that is compatible
         * with the QOS profile of the publisher.
         * The ZED component node uses a default QoS profile with reliability set as "RELIABLE"
         * and durability set as "VOLATILE".
         * To be able to receive the subscribed topic the subscriber must use compatible
         * parameters.
         */

        // https://github.com/ros2/ros2/wiki/About-Quality-of-Service-Settings

        rclcpp::QoS depth_qos(10);
        depth_qos.keep_last(10);
        depth_qos.best_effort();
        depth_qos.durability_volatile();

        // Create depth map subscriber
        mDepthSub = create_subscription<sensor_msgs::msg::Image>(
                   "depth", depth_qos,
                   std::bind(&MinimalDepthSubscriber::depthCallback, this, _1) );
    }

  protected:
    void depthCallback(const sensor_msgs::msg::Image::SharedPtr msg) {
        // Get a pointer to the depth values casting the data
        // pointer to floating point
        float* depths = (float*)(&msg->data[0]);

        // Image coordinates of the center pixel
        int u = msg->width / 2;
        int v = msg->height / 2;

        // Linear index of the center pixel
        int centerIdx = u + msg->width * v;

        // Output the measure
        RCLCPP_INFO(get_logger(), "Center distance : %g m", depths[centerIdx]);
    }

  private:
    rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr mDepthSub;
};

// The main function
int main(int argc, char* argv[]) {
    rclcpp::init(argc, argv);

    auto depth_node = std::make_shared<MinimalDepthSubscriber>();

    rclcpp::spin(depth_node);
    rclcpp::shutdown();
    return 0;
}
```

### The code explained

The tutorial is written using the new concept of `Component` introduced in ROS 2 in order to take advantage of the [node composition](https://docs.ros.org/en/humble/Concepts/Intermediate/About-Composition.html) capabilities.

A component named `MinimalDepthSubscriber` is created subclassing the ROS 2 object `rclcpp::Node`.

In the constructor, we initialize the parent class `Node` with the name of our node, `zed_depth_tutorial`:

**`C++`**

```cpp C++
    MinimalDepthSubscriber()
        : Node("zed_depth_tutorial") {
        rclcpp::QoS depth_qos(10);
        depth_qos.keep_last(10);
        depth_qos.best_effort();
        depth_qos.durability_volatile();

        // Create depth map subscriber
        mDepthSub = create_subscription<sensor_msgs::msg::Image>(
                   "depth", depth_qos,
                   std::bind(&MinimalDepthSubscriber::depthCallback, this, _1) );
    }

```

The constructor mainly defines `mDepthSub`, a `std::shared_ptr` to an object that creates a subscriber in the node. The subscriber listens to the `depth` topic and calls the callback function `MinimalDepthSubscriber::depthCallback` every time it receives a message.

ROS 2 uses the new constructs available with C++11 and C++14, so to bind the callback (a class method in this case) to the `mDepthSub` object we use the form

`std::bind(&MinimalDepthSubscriber::depthCallback, this, _1)`.

It is important that the subscription uses a QoS profile compatible with the QoS profile of the publisher of the topics.

In this case, the QoS profile is configured to keep the last received 10 messages with "best effort" reliability and "volatile" durability. This configuration is highly compatible with many possible publisher configurations.

For more information about QoS compatibility, refer to the [ZED node guide](/docs/integrations/ros-2/zed-stereo-node/#qos-profiles).

Now we must define the callback to execute when one of the subscribed topics is received:

**`C++`**

```cpp C++
    void depthCallback(const sensor_msgs::msg::Image::SharedPtr msg) {
        // Get a pointer to the depth values casting the data
        // pointer to floating point
        float* depths = (float*)(&msg->data[0]);

        // Image coordinates of the center pixel
        int u = msg->width / 2;
        int v = msg->height / 2;

        // Linear index of the center pixel
        int centerIdx = u + msg->width * v;

        // Output the measure
        RCLCPP_INFO(get_logger(), "Center distance : %g m", depths[centerIdx]);
    }
```

This callback is executed when the subscriber node receives a message of type `sensor_msgs/Image` that matches the subscribed topic `depth`.
The parameter of the callback is a `std::shared_ptr` to the received message. This means that you don't have to worry about memory management for it.

The callback demonstrates how to access the message data:

* The pointer to the `data` field is cast to `float*`, because `data` is declared as an array of bytes (`uint8`) while the depth values are 32-bit floats
* The image coordinates \[u,v] of the center of the depth image are calculated
* Since the data are organized in a linear vector, the index of the central pixel is calculated from the image coordinates
* The measure in meters of the center point is printed on the screen

Finally, the `main` function is standard for a ROS 2 node that executes a single component:

**`C++`**

```cpp C++
int main(int argc, char* argv[]) {
    rclcpp::init(argc, argv);

    auto depth_node = std::make_shared<MinimalDepthSubscriber>();

    rclcpp::spin(depth_node);
    rclcpp::shutdown();
    return 0;
}
```

The ROS 2 environment is initialized using the `rclcpp::init` command. Then we create a `depth_node` Component as a `std::shared_ptr`
to the `MinimalDepthSubscriber` class that we defined above.

Then the code of the node is executed in the main thread using the `rclcpp::spin(depth_node);` command. The node will run until the user presses Ctrl+C to stop it, shut it down, and exit.

### Conclusions

The full source code of this tutorial is available on GitHub in the [zed\_depth\_tutorial](https://github.com/stereolabs/zed-ros2-examples/tree/master/tutorials/zed_depth_tutorial) sub-package.

Along with the node source code, you can find the `package.xml` and `CMakeLists.txt` files that complete the tutorial package.