# ros2-depth-anything-v3-trt: metric depth and point clouds in ROS 2

> The node from ika-rwth-aachen runs Depth Anything V3 through TensorRT inside ROS 2, subscribing to a camera image and camera info, then publishing a metric depth image and a PointCloud2 cloud. It fits robotics and driving research teams that need depth on the topic graph without writing their own inference node, and it comes with a documented model format contract and a prebuilt container.

**ika-rwth-aachen/ros2-depth-anything-v3-trt** — ROS2 TensorRT Node for Monocular Metric Depth estimation and Point Cloud generation from camera images with Depth Anything V3

- Repository: https://github.com/ika-rwth-aachen/ros2-depth-anything-v3-trt
- Stars: 657 · Forks: 84
- Language: C++
- License: Apache-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/ika-rwth-aachen-ros2-depth-anything-v3-trt

## One camera topic becomes a depth image and a point cloud

Monocular depth estimation is attractive for a robot because a single camera is enough, and awkward because the model, the TensorRT plumbing and the ROS plumbing are three separate jobs. This node exists to collapse them into one package.

The description says it plainly: a ROS 2 node for Depth Anything V3 depth estimation using TensorRT for real-time inference, which subscribes to camera image and camera info topics and publishes directly both a metric depth image and a PointCloud2 point cloud. The word that matters is metric. The node is built for pipelines where downstream consumers need distances in meters rather than a relative depth map for visualisation.

The code is C++, released under Apache-2.0, and maintained by the Institute for Automotive Engineering at RWTH Aachen University, whose work sits in the Vehicle Intelligence and Automated Driving domain. That origin explains the shape of the package: a research group solving its own perception problem, and publishing the node rather than a general purpose library.

The repository also carries onnx/ and generate_engines.sh at the root, alongside a run.sh and the package directory depth_anything_v3, which tells you the model conversion path is treated as part of the project rather than an afterthought.

## Two subscriptions in, three publications out

The topic interface is small and conventional. The node subscribes to ~/input/image as a sensor_msgs/msg/Image, which supports raw and compressed transport through image_transport, and to ~/input/camera_info as a sensor_msgs/msg/CameraInfo for calibration.

On the output side it publishes ~/output/depth_image as a 32FC1 Image, ~/output/point_cloud as a PointCloud2, and ~/output/depth_image_debug as an Image when debug visualisation is enabled. Using 32FC1 for the depth image is the detail to note: downstream nodes get a single float32 channel of meters per pixel rather than an encoded 8-bit range, which is what makes the result usable for geometry.

Everything is configurable. Parameters can be set through config/depth_anything_v3.param.yaml or passed at launch time, and the launch file accepts remapped topic arguments, so the node drops into an existing graph without renaming everything upstream.

Because the point cloud is generated from the depth image plus the camera info, the intrinsics matter. A node running with a default or empty camera_info topic will still publish, and the resulting cloud will be wrong in a way that is easy to miss on a desk and obvious on a map.

## Sky comes from a second model output, not from a threshold on depth

Depth Anything V3 exposes two outputs according to the model preparation notes: depth, a metric depth map, and sky, a sky classification logit map where lower values mean sky. The node uses that second output rather than guessing sky from the depth values themselves.

Two parameters control it. sky_threshold is the threshold for sky classification from the model's sky output, with the note that a lower value means more sky is detected, and it defaults to 0.3. sky_depth_cap is the maximum depth value in meters assigned to sky pixels, defaulting to 200.0.

The mechanism is straightforward: pixels whose sky logit falls below the threshold are classified as sky, and those pixels are then given a capped depth rather than the value the network predicts for them. This is what keeps a sky region, where a monocular network has no reliable distance, from filling the point cloud with extreme outliers.

The cap of 200 meters is a scene assumption rather than a physical limit. If your workspace extends further than that, or if the classification is too eager, the sky pixels saturate at the cap and you lose the real distance information. Tuning both values against your own scenes is the first thing to do after the node runs at all.

## Launching the node, remapping topics and turning on debug output

The basic launch is a single command:

```bash
ros2 launch depth_anything_v3 depth_anything_v3.launch.py
```

To attach the node to an existing camera pipeline, remap the four topic arguments:

```bash
ros2 launch depth_anything_v3 depth_anything_v3.launch.py \
    input_image_topic:=/your_camera/image \
    input_camera_info_topic:=/your_camera/camera_info \
    output_depth_topic:=/depth_anything_v3/depth \
    output_point_cloud_topic:=/depth_anything_v3/points
```

Debug visualisation is a separate parameter file rather than a set of flags on the command line:

```bash
ros2 launch depth_anything_v3 depth_anything_v3.launch.py \
    params_file:=src/depth_anything_v3/config/debug.param.yaml
```

That structure tells you something about how the author expects this to be used. The default path is quiet, and the interesting variants live in yaml files under the package config directory. If you want different behaviour per robot or per recording session, you are expected to add parameter files rather than to maintain a long argument list, which is the right instinct for a system that will be run by several people.

The debug output itself is configurable in detail: enable_debug, debug_colormap with a default of JET, debug_filepath defaulting to /tmp/depth_anything_v3_debug/, write_colormap to save images to disk, and min and max depth values of 0.0 and 50.0 meters for normalising the colour ramp. Sixteen colormaps are listed, including VIRIDIS, MAGMA, INFERNO, PARULA and CIVIDIS.

## The prebuilt container avoids the dependency matrix, at the cost of a moving tag

Building this node means satisfying TensorRT, CUDA, a TensorRT engine for the model and ROS 2 at the same time. The documentation sidesteps that with a prepared image:

```bash
docker pull tillbeemelmanns/ros2-depth-anything-v3:latest-dev
```

Two things stand out. The tag is latest-dev, so the image tracks development rather than a pinned release, which means a pull tomorrow can give you a different container than today. And the image name belongs to an individual rather than to the institute organisation, which is worth noting if you plan to rely on it inside a company registry.

For visualisation and bag playback, the documentation adds the ROS packages you need:

```bash
apt update && apt install -y \
ros-jazzy-rviz2 \
ros-jazzy-rosbag2 \
ros-jazzy-rosbag2-storage-mcap
```

The base images the project was tested with are nvcr.io/nvidia/tensorrt:25.08-py3 and nvcr.io/nvidia/tensorrt:25.03-py3, on Ubuntu 24.04 with ROS 2 Jazzy, one with CUDA 13 and TensorRT 10.9 and the other with CUDA 12.8.1 and TensorRT 10.9. The guidance is to pick the base image according to your driver and CUDA version, which is the main compatibility decision you have to make.

## The model format contract is narrow and stated up front

The model preparation section is the most useful part of the documentation, because it defines exactly what the node expects. You obtain the ONNX file either by downloading it from the Huggingface repository listed in the README or by generating it following the instructions in onnx/README.md. Then you place the ONNX or engine file in the models/ directory and set the path in config/depth_anything_v3.param.yaml. The onnx_path parameter defaults to models/DA3METRIC-LARGE.onnx and accepts either an ONNX file or a TensorRT engine file.

The expected input format is a tensor of shape 1, 3, 280, 504 with float32 values normalised with ImageNet mean and standard deviation into the range 0 to 1. The expected outputs are depth and sky, each of shape 1, 1, 280, 504, with the sky output being logits where lower values mean sky.

A fixed 280 by 504 input is a real constraint. Cameras rarely produce that aspect ratio natively, so an internal resize is implied, and the published depth image resolution relative to your source image is something the documentation does not spell out. If you need depth at native sensor resolution, verify this before building a pipeline on top of the node.

Precision is a single parameter, precision, taking fp16 or fp32 with fp16 as the default, which matches the way TensorRT engines are usually built for deployment.

## Point cloud size and colour are the two knobs you will actually tune

The point cloud parameters decide how much data goes on the wire, which is the practical bottleneck once inference works. point_cloud_downsample_factor publishes every Nth point, with 1 meaning full resolution and 10 every tenth point, and it defaults to 2. colorize_point_cloud is true by default and adds RGB colours taken from the input image, which makes the cloud directly viewable in rviz2 but costs bandwidth.

Both defaults are conservative choices for a research stack: half resolution and colour on. A robot streaming to a remote viewer may want colour off and a higher downsample factor, and a bag recording you intend to process offline can afford full resolution.

For that offline case, the documentation recommends the docker image in combination with the group's other tools, docker-ros for building minimal container images of ROS applications and docker-run as a CLI for simplified interaction with those images. The repository itself has no GitHub releases, and the last push was on 2026-08-21, so there is no versioned artefact to pin: you either track the latest-dev image or build from source against the branch.

## Conclusion

Adopt this node if your stack already speaks ROS 2 on NVIDIA hardware, needs metric depth and a point cloud as first-class topics, and can accept a model file placed in the models directory with the input shape of 1, 3, 280, 504. Do not reach for it if your rig has no TensorRT-capable GPU, if you need relative depth rather than metric depth, or if you need a released, versioned artefact, because the repository publishes no GitHub releases and the last push was on 2026-08-21. Before deploying, verify the onnx_path you set in config/depth_anything_v3.param.yaml matches the model you downloaded, confirm the camera_info topic carries valid intrinsics, and check that the base image you choose matches your driver and CUDA version.

## FAQ

### What topics does ros2-depth-anything-v3-trt subscribe to and publish?

It subscribes to ~/input/image as a sensor_msgs/msg/Image, with raw and compressed transport supported through image_transport, and to ~/input/camera_info as a sensor_msgs/msg/CameraInfo. It publishes ~/output/depth_image in 32FC1 format, ~/output/point_cloud as a PointCloud2, and ~/output/depth_image_debug when debug visualisation is enabled.

### How do I run the Depth Anything V3 TensorRT node in ROS 2?

The basic launch is ros2 launch depth_anything_v3 depth_anything_v3.launch.py, and topic arguments such as input_image_topic and output_point_cloud_topic can be passed on the command line to attach it to an existing camera pipeline. Debug output is selected with params_file:=src/depth_anything_v3/config/debug.param.yaml.

### What model file does the node need and where does it go?

You download or generate an ONNX file, place it in the models/ directory, and set the path in config/depth_anything_v3.param.yaml through the onnx_path parameter, which defaults to models/DA3METRIC-LARGE.onnx. The expected input is float32 with shape 1, 3, 280, 504, and the model must output both depth and sky maps.

### How does the node decide which pixels are sky?

It uses the sky output of the model, which is a logit map where lower values mean sky, and compares it against the sky_threshold parameter, 0.3 by default. Sky pixels are then assigned a depth capped at sky_depth_cap, 200 meters by default.

## Sources

- [ika-rwth-aachen/ros2-depth-anything-v3-trt on GitHub](https://github.com/ika-rwth-aachen/ros2-depth-anything-v3-trt)
- [Issues](https://github.com/ika-rwth-aachen/ros2-depth-anything-v3-trt/issues)
- [License: Apache-2.0](https://github.com/ika-rwth-aachen/ros2-depth-anything-v3-trt/blob/main/LICENSE)
- [README](https://github.com/ika-rwth-aachen/ros2-depth-anything-v3-trt/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ika-rwth-aachen-ros2-depth-anything-v3-trt
