🤖 ROS 2 & NVIDIA Isaac ROS on Jetson Orin Nano¶
Author: Dr. Kaikai Liu, Ph.D. · San Jose State University
Prerequisite: 05b — Object Detection on Jetson. This chapter turns the
jetson_object_detection_toolkit.pydetector into a ROS 2 node so it can be part of a robotics pipeline.
🧠 What is ROS 2?¶
ROS 2 (Robot Operating System 2) is the de‑facto middleware for robots: a set of libraries + tools for building nodes (small programs) that talk over topics (named, typed pub/sub streams), services, and actions. It uses DDS for discovery and transport, so nodes find each other automatically on a network. The current LTS is Humble Hawksbill (Ubuntu 22.04 / 24.04), which is what Jetson + Isaac ROS use.
Key concepts you'll use here:
- Node — a process (e.g. a camera driver, a detector, a planner).
- Topic — a typed channel, e.g. /image_raw carrying sensor_msgs/Image.
- Message — a typed payload, e.g. vision_msgs/Detection2DArray for detections.
- colcon — the ROS 2 build tool; workspace — a src/ folder of packages built with colcon build.
⚡ What is NVIDIA Isaac ROS?¶
NVIDIA Isaac ROS is a collection of GPU‑accelerated ROS 2 packages ("GEMs") for perception and navigation on Jetson — e.g. stereo depth, visual SLAM (isaac_ros_visual_slam), AprilTags, DNN inference (isaac_ros_dnn_inference, isaac_ros_yolov8), and image processing. Its secret sauce is NITROS (NVIDIA Isaac Transport for ROS): type‑adapted, zero‑copy transport that keeps data in GPU memory between nodes, avoiding the CPU copies that normally bottleneck ROS image pipelines. Isaac ROS ships as containers built on CUDA/TensorRT bases.
| Plain ROS 2 | Isaac ROS | |
|---|---|---|
| Transport | DDS, CPU copies | DDS + NITROS (GPU zero‑copy) |
| Perception | you bring your own | prebuilt accelerated GEMs |
| Best for | learning, custom nodes (this chapter) | production GPU perception |
🧭 Hardware support & the lightweight choice¶
Isaac ROS support is hardware‑ and JetPack‑specific — check before you invest:
| Isaac ROS line | Target Jetson | JetPack / L4T | Ubuntu / ROS 2 |
|---|---|---|---|
| Current | Jetson Thor (T5000/T4000) | JetPack 7.1 (R38.4) | 24.04 / Jazzy |
| Previous (3.x) | Orin family (AGX Orin, Orin NX, Orin Nano) | JetPack 6.x | 22.04 / Humble |
⚠️ Isaac ROS is too heavy for the Jetson Orin Nano (8 GB). Even where it's "supported" (Orin Nano on JetPack 6), the GEMs are built for the bigger Orin/Thor modules: the Docker images are multi‑GB, and the GPU perception graphs (stereo depth, DNN inference, multiple NITROS nodes) routinely exhaust the Orin Nano's 8 GB of shared CPU/GPU memory. On JetPack 7 / L4T r39 Orin Nano (like our lab boxes) it's worse — that config isn't a supported Isaac ROS target at all (the new line is Thor‑only).
So on an Orin Nano, default to plain ROS 2 (the container below) with our ros2_jetson_detect node — our toolkit already gives accelerated detection (YOLO+TensorRT from 05b) without NITROS, and it fits comfortably in 8 GB. Reach for Isaac ROS only on a Thor (or a bigger Orin) when you specifically need an accelerated GEM (VSLAM, stereo depth, AprilTags…).
Lightweight options, heaviest → lightest:
1. Full Isaac ROS Docker dev container (isaac_ros_common) — multi‑GB; bigger Orin / Thor only.
2. Isaac ROS Debian/apt packages — sudo apt install ros-humble-isaac-ros-<gem> (JetPack‑6 Orin) — installs just one GEM, no container build.
3. Isaac ROS CLI venv/baremetal modes (Thor) — lighter than Docker.
4. Plain ROS 2 + our node — the right fit for Orin Nano (used in the rest of this chapter).
🐳 Why containers (and which one)¶
On Jetson, ROS 2 and Isaac ROS are run in Docker containers — this avoids polluting the L4T host and gives you the exact CUDA/TensorRT/ROS versions that match your JetPack. Two well‑trodden paths:
jetson-containers(Dusty‑NV) — the easiest way to get ROS 2 plus the ML stack (PyTorch, Ultralytics, OpenCV) in one image. Recipes live underpackages/physicalAI/ros. We use this for our detector node (it needs PyTorch + Ultralytics).- Isaac ROS dev container (
isaac_ros_common) — the official environment for the Isaac ROS GEMs.
✅ Verified on a JetPack 7 (L4T r39) Orin Nano (
sjsujetson-61): ROS 2 Humble runs in a container, rclpy pub/sub works, and our node'svision_msgsmessages build correctly. Notes below mark what was verified vs. reference steps.
Quick sanity check (no GPU, ~1 GB) — confirm ROS 2 runs at all¶
docker pull ros:humble-ros-base
docker run --rm -it ros:humble-ros-base bash -lc \
'source /opt/ros/humble/setup.bash && ros2 run demo_nodes_cpp talker' # Ctrl-C to stop
📦 Install the ROS 2 container with jetson-containers¶
# 1. Install the tooling (one time; needs sudo for apt + adds 'jetson-containers'/'autotag')
git clone https://github.com/dusty-nv/jetson-containers
bash jetson-containers/install.sh
# 2. See which prebuilt ROS image matches your JetPack/L4T
autotag ros # e.g. -> dustynv/ros:humble-desktop-r36.x (or builds if none)
# 3. Run it, mounting the course repo so the toolkit is available inside
jetson-containers run -v /Developer:/Developer $(autotag ros)
jetson-containers run adds --runtime nvidia, --network host, device mounts, and X11 forwarding automatically. Inside the container you now have ROS 2 Humble on the GPU.
If
autotaghas no prebuilt image for a brand‑new L4T (e.g. r39/JetPack 7), build a combined image once:Thenjetson-containers build --name=ros_ml ros:humble-desktop pytorch opencvjetson-containers run -v /Developer:/Developer ros_ml.
NVIDIA Isaac ROS path — Jetson Thor (JetPack 7.1, ROS 2 Jazzy)¶
Use this only on a Jetson Thor (T5000/T4000), not the Orin Nano. Confirm the platform first:
cat /etc/nv_tegra_release # expect: R38 (release), REVISION: 4.0 (JetPack 7.1)
The current Isaac ROS uses a single CLI (isaac-ros-cli) with three isolation modes — docker (recommended), venv (apt + isolated Python, lighter), or baremetal (advanced). Per the official Isaac ROS getting‑started docs:
# 1. add the NVIDIA Isaac ROS apt repo + install the CLI (one time)
sudo apt-get update && sudo apt-get install -y isaac-ros-cli
# 2. initialize an environment (pick ONE mode)
sudo isaac-ros init docker # full container (recommended)
# sudo isaac-ros init venv # lighter: system apt + isolated Python venv
# sudo isaac-ros init baremetal # advanced: system-level install
# 3. enter the environment and pull/build the GEM(s) you need, e.g. accelerated YOLOv8
# (each GEM has a Quickstart under github.com/NVIDIA-ISAAC-ROS)
isaac_ros_common + ./scripts/run_dev.sh flow is the JetPack‑6/Orin (Humble) equivalent.
▶️ Play existing examples inside the container¶
Open two shells into the same running container (docker exec -it <name> bash, then source /opt/ros/humble/setup.bash in each).
ROS 2 demos (talker / listener):
# shell 1
ros2 run demo_nodes_cpp talker
# shell 2
ros2 run demo_nodes_py listener # prints "I heard: Hello World: N"
ros2 topic list # /chatter
ros2 topic echo /chatter --once
turtlesim (needs an X display — jetson-containers run forwards it):
ros2 run turtlesim turtlesim_node &
ros2 run turtlesim turtle_teleop_key # drive the turtle with arrow keys
Isaac ROS example (reference): each GEM has a Quickstart, e.g. isaac_ros_yolov8 — run an accelerated YOLOv8 on an image and view vision_msgs/Detection2DArray, or isaac_ros_visual_slam for VSLAM from a stereo/RGB‑D camera. See the per‑package READMEs under NVIDIA-ISAAC-ROS.
🛠️ Sample package: wrap our detector as a ROS 2 node¶
We ship a ready package, jetson/ros2_jetson_detect, that wraps jetson_object_detection_toolkit.py. It subscribes to an image topic, runs any toolkit detector on the GPU, and publishes results:
| Topic | Type |
|---|---|
~/detections |
vision_msgs/Detection2DArray (boxes + class + score) |
~/detections_image |
sensor_msgs/Image (annotated, for RViz/rqt) |
The whole wrapper is ~120 lines — the relevant core:
results = self.toolkit.detect(frame, **self._detect_kwargs()) # reuse the 05b toolkit
for box, score, name in zip(results['boxes'], results['scores'], results['class_names']):
x1, y1, x2, y2 = (float(v) for v in box)
det = Detection2D()
det.bbox.center.position.x = (x1 + x2) / 2.0 # vision_msgs (Humble) layout
det.bbox.center.position.y = (y1 + y2) / 2.0
det.bbox.size_x, det.bbox.size_y = x2 - x1, y2 - y1
hyp = ObjectHypothesisWithPose()
hyp.hypothesis.class_id, hyp.hypothesis.score = str(name), float(score)
det.results.append(hyp); arr.detections.append(det)
vision_msgs layout was verified against real Humble messages on jetson‑61.)
Build it — on a GPU‑PyTorch base (the smart choice)¶
The node needs both the toolkit's deps (PyTorch, Ultralytics, OpenCV) and ROS deps (vision_msgs, cv_bridge). Rather than install CUDA PyTorch from scratch, start from the jetson-llm container sjsujetsontool already uses — it ships CUDA PyTorch — and just add ROS 2 on top. Since that image is Ubuntu 24.04, the matching ROS 2 distro is Jazzy (our node's vision_msgs layout is the same on Humble and Jazzy):
# 1. a GPU container from the jetson-llm image, with the repo mounted
docker run -d --name ros2gpu --runtime nvidia --network host --ipc=host \
-e NVIDIA_VISIBLE_DEVICES=all -v /Developer:/Developer jetson-llm:v1 sleep infinity
# 2. add ROS 2 Jazzy + the message/bridge packages, and YOLO
docker exec ros2gpu bash -c '
apt-get update && apt-get install -y curl gnupg
curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key \
-o /usr/share/keyrings/ros-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] \
http://packages.ros.org/ros2/ubuntu noble main" > /etc/apt/sources.list.d/ros2.list
apt-get update && apt-get install -y ros-jazzy-ros-base ros-jazzy-vision-msgs ros-jazzy-cv-bridge
pip install ultralytics "numpy<2" # see gotcha below
'
⚠️ numpy gotcha:
pip install ultralyticspulls numpy 2.x, which breaks the image's prebuilt OpenCV (ImportError: numpy.core.multiarray failed to import). Pinnumpy<2(as above) to match the prebuilt cv2/torch.
Then build the workspace (or run the node directly):
docker exec -it ros2gpu bash
source /opt/ros/jazzy/setup.bash
mkdir -p ~/ros2_ws/src && ln -s /Developer/edgeAI/jetson/ros2_jetson_detect ~/ros2_ws/src/
cd ~/ros2_ws && colcon build --packages-select ros2_jetson_detect && source install/setup.bash
Run it¶
Our package includes a still_image_publisher (ROS's image_publisher mishandles a single .jpg — it treats it as an image sequence), so the launch file works on one image:
ros2 launch ros2_jetson_detect detect.launch.py model:=yolo image:=/Developer/models/bus.jpg
# inspect in another shell:
ros2 topic echo /jetson_detector/detections --once
ros2 run rqt_image_view rqt_image_view # pick /jetson_detector/detections_image
ros2 run usb_cam usb_cam_node_exe & # or isaac_ros_argus for CSI cameras
ros2 launch ros2_jetson_detect detect.launch.py model:=yolo image_topic:=/image_raw
model:= (faster-rcnn, maskrcnn, detr, owl-vit + prompts:="person,car") — exactly as in 05b.
✅ Verified GPU result (jetson‑61, Orin, ROS 2 Jazzy, YOLOv8n on CUDA)¶
[jetson_detector]: Loading detector 'yolo' on cuda ...
[jetson_detector]: Detector ready.
[jetson_detector]: Subscribed to '/image_raw', publishing '~/detections'.
[jetson_detector]: 6 detections (1232 ms)
RECEIVED Detection2DArray with 6 detections:
bus 0.87 center=(414,494) size=(782x526)
person 0.87 center=(147,651) size=(197x504)
person 0.85 center=(740,635) size=(140x485)
person 0.83 center=(283,632) size=(123x452)
person 0.26 center=(32,712) size=(63x323)
stop sign 0.25 center=(16,290) size=(32x70)
vision_msgs/Detection2DArray — ready to feed any downstream planner/visualizer. (For a one‑shot smoke test without colcon, run test_gpu_detect.py.)
🧯 Troubleshooting¶
| Symptom | Fix |
|---|---|
colcon: command not found |
you're not in the ROS container, or didn't source /opt/ros/humble/setup.bash |
ModuleNotFoundError: jetson_object_detection_toolkit |
set -p toolkit_path:=/Developer/edgeAI/jetson or mount /Developer |
No module named torch/ultralytics |
use a jetson-containers image that includes PyTorch, or pip install inside it |
cv_bridge/vision_msgs import error |
apt-get install ros-humble-cv-bridge ros-humble-vision-msgs |
| rqt/turtlesim: no display | launch with jetson-containers run (sets up X11), or xhost +local: on the host |
| node loads but 0 detections | lower confidence:=0.1; for owl-vit/grounding-dino you must pass prompts:= |
📌 Summary¶
- ROS 2 = nodes + topics + messages; Isaac ROS adds GPU‑accelerated perception GEMs with NITROS zero‑copy transport.
- Run ROS 2 / Isaac ROS in containers via
jetson-containers(easiest for ROS + ML) orisaac_ros_common. - We wrapped the 05b detector as a ROS 2 node (
ros2_jetson_detect) that publishesvision_msgs/Detection2DArray— drop it into any robot pipeline.