# yolo_ros: running YOLO in ROS 2 without a Python runtime

> yolo_ros is a ROS 2 wrapper for Ultralytics YOLO models that runs entirely in C++ on ONNX Runtime, with no Python process and no ultralytics package at run time. Detection, tracking, 3D lifting and debug rendering are pluginlib plugins inside one yolo_node executable, exchanging data over an in-memory typed blackboard.

**mgonzs13/yolo_ros** — Ultralytics YOLOv8, YOLOv9, YOLOv10, YOLOv11, YOLOv12 for ROS 2

- Repository: https://github.com/mgonzs13/yolo_ros
- Website: https://mgonzs13.github.io/yolo_ros/
- Stars: 1,152 · Forks: 274
- Language: C++
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/mgonzs13-yolo-ros

## The one architectural decision

yolo_ros is a ROS 2 wrapper for YOLO models from Ultralytics, covering object detection and tracking, instance segmentation, human pose estimation, oriented bounding box, and image classification, with 3D versions of detection, instance segmentation and pose estimation based on depth images.

The architecture paragraph is the one to read twice, because it explains everything else. The pipeline is a pure C++ and ONNX Runtime implementation, there is no Python runtime and no ultralytics dependency at run time, it runs any Ultralytics-exported ONNX model, and every stage runs inside a single yolo_node executable as a pluginlib plugin, with detection, tracking, 3D lifting and debug rendering exchanging data over an in-memory typed blackboard.

That is a different proposition from the usual ROS machine learning wrapper. The common pattern is a Python node that loads a PyTorch model and publishes detections, which means a Python interpreter in your deployment, a bridge or a separate process, and the model weights shipped as PyTorch checkpoints. This package removes all three by running the exported graph directly through ONNX Runtime.

The consequence for the user is a model format constraint. You do not pass it a checkpoint, you pass it an ONNX file that Ultralytics exported. That is the trade: you lose the ability to use the Python API features that never make it into the ONNX graph, and you gain a deployment with no Python in it.

The project is MIT licensed, the primary language is C++, the home page is mgonzs13.github.io/yolo_ros, and the last push to the repository was on 2026-10-14. The recent releases are 4.7.1 from 2026-09-05, 4.7.0 from 2026-09-04, and 4.6.1 from 2026-05-23.

## The plugin stages and the typed blackboard

The stages inside yolo_node are named in the README, and the mechanism that joins them is worth understanding because it is not the ROS topic model.

The stages are detection, tracking, 3D lifting and debug rendering, and each is a pluginlib plugin loaded into the same executable. The data passes between them over what the README calls an in-memory typed blackboard. That is a shared memory structure inside the process rather than a set of ROS topics between nodes.

The design has clear consequences. Latency is lower because there is no serialisation to a DDS message and no network hop, and the stages share memory rather than copying images. The cost is that you cannot run the detection stage on a different machine from the tracking stage, and you cannot substitute a stage written in Python, because the blackboard is typed and in-process.

This is why the package is a set of plugins rather than a set of nodes. It also explains the topic list in the repository: yolo_msgs/ for the message definitions, yolo_bringup/ for the launch and configuration, yolo_ros/ for the main package, and two vendor packages, yolo_onnxruntime_vendor/ and yolo_hfhub_vendor/. The ROS interface still exists at the edges, where a node publishes results and subscribes to images, so the package composes with the rest of a ROS 2 system normally.

The list of supported model families is broad, which reflects the fact that the ONNX graph shape differs between them. The topic list names YOLOv8, YOLOv9, YOLOv10, YOLOv11, YOLOv12, plus YOLO-World, YOLOE, and YOLO26, and the feature list covers the five task types plus 3D lifting from depth.

## Building the workspace and vendoring the runtime

The node builds as a standard ROS 2 colcon workspace, and the onboarding is two steps that are common to every backend.

```shell
# Clone this repo
cd ~/ros2_ws/src
git clone https://github.com/mgonzs13/yolo_ros.git

# Install rosdep dependencies
cd ~/ros2_ws
rosdep install --from-paths src --ignore-src -r -y
```

The ONNX Runtime is not something you install by hand. ONNX Runtime 1.20.0 is downloaded automatically by yolo_onnxruntime_vendor at configure time, the CPU tarball by default and the GPU tarball with -DONNX_GPU=ON.

That vendor package is a good pattern to understand, because it moves a large binary dependency out of the package manager and into the build. CMake at configure time fetches the right ONNX Runtime build for the target, unpacks it into the workspace, and the rest of the build links against it. The user never downloads a tarball by hand, and the CPU and GPU variants differ by one CMake flag.

The sibling package yolo_hfhub_vendor/ suggests a second vendored dependency, presumably tooling that reaches the Hugging Face Hub. The model fetcher is therefore part of the build system rather than something you wire up separately.

The repository also carries a.clang-format and a.pre-commit-config.yaml, so the C++ formatting and the pre-commit hooks are configured in the tree rather than left to the contributor.

## The distro matrix, and what the table tells you

The README leads with a table of supported ROS 2 distributions, and the table is unusual in a way worth noting: every row points at the same branch, main.

The listed distros are Foxy, Galactic, Humble, Iron, Jazzy, Kilted, and Lyrical, and each one has a build badge tied to its own CI workflow, so the build matrix genuinely covers all of them from a single branch. That is a maintenance commitment: the CI runs a build per distro, and a change that breaks on an older distro fails visibly rather than silently.

There is a Docker image per distro as well, published under a Docker Hub organisation with a tag per distro, and the images are built from a Dockerfile whose first line is an ARG ROS_DISTRO=humble. That argument is the mechanism, since a single Dockerfile can produce the image for any distro in the matrix.

The Dockerfile is a two stage build. The deps stage starts from ros:${ROS_DISTRO}-ros-core, installs git, build-essential, python3-rosdep, python3-colcon-common-extensions and curl, copies the sources into /root/ros2_ws/src, and runs rosdep init, rosdep update with --include-eol-distros, and then a rosdep install that separates buildtool, build, and exec dependency types.

The --include-eol-distros flag is worth noting, since it is what lets the build resolve dependencies for Foxy and Galactic, both of which are end of life. Then the builder stage sources the ROS 2 setup file and runs colcon build, and the setup file is appended to the bashrc so the workspace is sourced in any shell that starts in the image. The final command is a bash shell rather than a node, so the image is a development environment as much as a deployment image.

## 3D lifting from depth, and the split between 2D and 3D

The package does more than 2D detection, and the 3D variants are the part that is less common in ROS wrappers.

Alongside object detection and tracking, instance segmentation, human pose estimation, oriented bounding box, and image classification, there are 3D versions of object detection, instance segmentation and human pose estimation, and these are based on depth images. The 3D lifting stage is one of the pluginlib plugins inside the same executable.

That is a meaningful capability for robotics, because a 2D bounding box in a camera image is only useful for navigation if you can turn it into a position. A depth image gives the range to each pixel, and a 2D detection gives the region, and combining them yields a 3D box or a 3D pose in the camera frame. Doing that in the same process as the detection avoids a second round trip through ROS topics and keeps the depth and colour images time-synchronised.

The oriented bounding box task is the other one that is specific rather than generic. An axis-aligned box is the wrong shape for an object at an angle, and an oriented box is the right one for anything that is not viewed straight on, which in practice means most objects in a cluttered scene.

The human pose estimation support covers both the 2D keypoints case and the 3D lifting from depth, and pose is the task where the 3D version matters most, since a 2D skeleton says nothing about whether a person is reaching towards the camera or away from it.

## What you give up, and the real alternative

The honest comparison is with the ordinary approach of running a Python node with the ultralytics package, which is what most teams do first because it is the path of least resistance.

The difference in approach is where the model lives. The Python path keeps the whole Ultralytics stack available, so any feature of the library, including the parts that are not expressible in an ONNX graph, is usable, and exporting is optional. This package needs the export to happen first and then works only with what survives the export. The export step is a real boundary: post-processing that lives in Python, some training-time augmentations, and anything that depends on the PyTorch model internals is not available at run time.

What the C++ path buys is deployment shape. There is no Python interpreter in the robot, no model file to keep in sync with a Python environment, and no separate process to supervise. For a robot that has to boot fast and run unattended, and for a system where the inference has to share a deadline with control loops, that matters.

The second alternative is to use a ROS 2 node built on a different inference runtime entirely, which is what you would do if you wanted a different model family or a TensorRT-specific optimisation rather than the general ONNX Runtime path. The trade there is losing the Ultralytics export compatibility, and the same plugin blackboard is not the point of the comparison.

The third alternative is to do nothing and use a hardware accelerated detector purpose built for the job. That is faster and cheaper, but it is a closed model, and this package's argument is that your Ultralytics model, which you may have fine-tuned, is the one you want to run.

## Conclusion

Adopt yolo_ros if you run ROS 2 and want YOLO detection without adding a Python runtime or a Python bridge to your stack, and if you are willing to export your model to ONNX. Do not adopt it if you need the newest Ultralytics features the moment they ship, since the pipeline consumes exported ONNX models rather than the Python package. Verify first that your ROS 2 distro is on the supported list and that the model you want to use has been exported, because the run time dependency is the ONNX file, not the training code.

## FAQ

### Does yolo_ros need Python or the ultralytics package at run time?

No. The pipeline is a pure C++ and ONNX Runtime implementation with no Python runtime and no ultralytics dependency at run time. It runs any Ultralytics-exported ONNX model, so the export step happens before deployment rather than on the target.

### Which ROS 2 distributions does yolo_ros support?

The table lists Foxy, Galactic, Humble, Iron, Jazzy, Kilted, and Lyrical, each with its own build badge from a per-distro CI workflow, all built from the main branch. A Docker image is published per distro, and the Dockerfile takes the distro as a build argument defaulting to humble.

### How do I build yolo_ros from source?

As a standard ROS 2 colcon workspace. Clone the repository into the src directory of your workspace, then run rosdep install --from-paths src --ignore-src -r -y from the workspace root. ONNX Runtime 1.20.0 is downloaded automatically by yolo_onnxruntime_vendor at configure time, the CPU tarball by default and the GPU tarball with -DONNX_GPU=ON.

### How does yolo_ros handle tracking, 3D lifting, and rendering?

Detection, tracking, 3D lifting and debug rendering each run as a pluginlib plugin inside a single yolo_node executable, exchanging data over an in-memory typed blackboard rather than over ROS topics. The 3D versions of detection, instance segmentation and pose estimation are based on depth images.

### Which YOLO model families can yolo_ros run?

The package description covers Ultralytics YOLOv8, YOLOv9, YOLOv10, YOLOv11 and YOLOv12, and the repository topics also name YOLO-World, YOLOE and YOLO26, across the tasks of object detection and tracking, instance segmentation, human pose estimation, oriented bounding box, and image classification.

## Sources

- [License: MIT](https://github.com/mgonzs13/yolo_ros/blob/main/LICENSE)
- [mgonzs13/yolo_ros on GitHub](https://github.com/mgonzs13/yolo_ros)
- [Project website](https://mgonzs13.github.io/yolo_ros/)
- [README](https://github.com/mgonzs13/yolo_ros/blob/main/README.md)
- [Releases](https://github.com/mgonzs13/yolo_ros/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mgonzs13-yolo-ros
