Run Ultralytics YOLO Oriented Bounding Box (OBB) detection β YOLOv8-OBB and YOLO11-OBB β on NVIDIA DeepStream 9.0, end to end. DeepStream 9.0 already ships the OBB parser, and nvdsosd draws the
rotated boxes for you. You bring an ONNX model + a config, and you get rotated detection boxes on video.
Clean multi-class detection on real DOTA imagery (tennis courts, swimming pool, vehicles shown). Each box is oriented (rotated to the object's heading), not axis-aligned. The demo runs on 5 real DOTA tiles spanning ships, planes, vehicles, courts, harbors, and pool.
βββββββββββββββββββββββββββββββββββββββββββ
β Input (file / RTSP) β
ββββββββββββββββββββ¬βββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββ
β nvstreammux β
β Batches decoded frames β
ββββββββββββββββββββ¬βββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββ
β nvinfer β
β YOLO-OBB ONNX β TensorRT (FP16) β
β β
β NvDsInferParseCustomYoloV11OBB: β
β β’ Decodes output tensor [1, 20, N] β
β β’ Applies confidence filter + NMS β
β β’ Sets bbox + rotation_angle (rad) β
ββββββββββββββββββββ¬βββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββ
β nvtiler β
β Tiles streams into one canvas β
β (single stream in this config) β
ββββββββββββββββββββ¬βββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββ
β nvdsosd β
β Draws oriented (rotated) boxes+labels β
β rotation_angle β NvOSD_RectParams β
ββββββββββββββββββββ¬βββββββββββββββββββββββ
β
ββββββββββ΄βββββββββ
βΌ βΌ
ββββββββββββββββββββ ββββββββββββββββββββ
β [sink0] File β β [sink1] Display β
β H.264 MP4/NVENC β β Live X11 window β
β out_obb.mp4 β β (optional) β
β (default) β β β
ββββββββββββββββββββ ββββββββββββββββββββ
DeepStream 9.0 ships two things that make this entirely config-driven:
-
Prebuilt OBB parser β
NvDsInferParseCustomYoloV11OBBis compiled intolibnvds_infercustomparser.soinside the container. Despite the name, this parser handles both YOLOv8-OBB and YOLO11-OBB output format. The pgie config points at it withparse-bbox-func-nameandcustom-lib-path; no source code or build step required. -
Automatic OBB rendering β
gstnvinfercopies the parserβs per-objectrotation_angle(radians) intoNvOSD_RectParams.rotation_angle(degrees), andnvdsosdrenders the rotated rectangle natively. No application code is needed.
Deepstream-OBB/
βββ README.md
βββ configs/
β βββ config_deepstream_app.txt # the deepstream-app pipeline
β βββ config_pgie_yolo_obb.txt # the nvinfer (primary GIE) config
β βββ labels_dota.txt # 15 DOTA class names
βββ assets/ # sample annotated frames (committed, for the README)
βββ data/ # input videos (tracked; update [source0] uri to point at your file)
βββ models/ # ONNX + TensorRT engine (gitignored; generated on first run)
βββ output/ # annotated mp4 results (gitignored)
DeepStream 9.0 requirements (source)
- Ubuntu 24.04
- NVIDIA driver 590.48.01
- CUDA 13.1
- TensorRT 10.14.1.48
- GStreamer 1.24.2
This project uses the Docker image (
nvcr.io/nvidia/deepstream:9.0-samples-multiarch) which bundles all of the above β only the host driver needs to meet the minimum version.
Host requirements
- NVIDIA GPU
- NVIDIA driver β₯ 590.48.01
- Docker + NVIDIA Container Toolkit (
--gpus allmust work):docker run --rm --gpus all nvcr.io/nvidia/deepstream:9.0-samples-multiarch nvidia-smi
- For model export on the host:
python3withpip install ultralytics.
What is NOT in this repo (gitignored β generate locally per the steps below):
models/β ONNX and TensorRT engine filesoutput/β pipeline results
From the repo root:
# 1) (once) pull the DeepStream image
docker pull nvcr.io/nvidia/deepstream:9.0-samples-multiarch
# 2) export a YOLO-OBB model to ONNX -> models/<model>.onnx
pip install ultralytics
mkdir -p models && cd models
# YOLOv8 (pick one):
python3 -c "from ultralytics import YOLO; YOLO('yolov8n-obb.pt').export(format='onnx', imgsz=1024, opset=20)"
# or YOLO11:
# python3 -c "from ultralytics import YOLO; YOLO('yolo11n-obb.pt').export(format='onnx', imgsz=1024, opset=20)"
cd ..
mkdir -p output
# Then update onnx-file and model-engine-file in configs/config_pgie_yolo_obb.txt
# 3) run the pipeline -> output/out_obb.mp4
# (first run builds the TensorRT engine: a few minutes; cached afterwards)
docker run --rm --gpus all \
-v "$PWD":/workspace -w /workspace/configs \
-e LD_LIBRARY_PATH=/opt/nvidia/deepstream/deepstream/lib \
nvcr.io/nvidia/deepstream:9.0-samples-multiarch \
deepstream-app -c config_deepstream_app.txtOpen output/out_obb.mp4 β a slideshow of 5 real DOTA tiles with clean oriented boxes per scene
(ships + harbors, planes, tennis & basketball courts, small/large vehicles, swimming pool).
To run on your own footage, drop a file in data/ and point configs/config_deepstream_app.txt
β [source0] uri at it.
docker pull nvcr.io/nvidia/deepstream:9.0-samples-multiarchVerify GPU access works:
docker run --rm --gpus all nvcr.io/nvidia/deepstream:9.0-samples-multiarch nvidia-smiExport a YOLO-OBB model to ONNX on the host. Both YOLOv8-OBB and YOLO11-OBB are supported:
pip install ultralytics
mkdir -p models && cd models
# Option A β YOLOv8-OBB (nano; replace with yolov8s/m/l/x-obb for larger variants)
python3 -c "from ultralytics import YOLO; YOLO('yolov8n-obb.pt').export(format='onnx', imgsz=1024, opset=20)"
# Option B β YOLO11-OBB (nano; replace with yolo11s/m/l/x-obb for larger variants)
# python3 -c "from ultralytics import YOLO; YOLO('yolo11n-obb.pt').export(format='onnx', imgsz=1024, opset=20)"
cd ..
mkdir -p outputThen update onnx-file and model-engine-file in configs/config_pgie_yolo_obb.txt to match the exported filename.
Place your input video in data/ and update [source0] uri in
configs/config_deepstream_app.txt to point at it (e.g. file:///workspace/data/your_video.mp4).
Also set [streammux] width/height to your video's resolution, then run from the repo root:
docker run --rm --gpus all \
-v "$PWD":/workspace -w /workspace/configs \
-e LD_LIBRARY_PATH=/opt/nvidia/deepstream/deepstream/lib \
nvcr.io/nvidia/deepstream:9.0-samples-multiarch \
deepstream-app -c config_deepstream_app.txtThe TensorRT engine is written into models/ (mounted), so it is reused on subsequent runs.
Output is saved to output/out_obb.mp4.
Key settings in configs/config_pgie_yolo_obb.txt:
| Property | Value | Why |
|---|---|---|
net-scale-factor |
0.0039215697 |
1/255 β YOLO expects 0β1 RGB input |
model-color-format |
0 |
RGB |
num-detected-classes |
15 |
DOTAv1 classes |
network-mode |
2 |
FP16 (use 0 for FP32) |
maintain-aspect-ratio |
0 |
frame is stretched to fill the network input |
cluster-mode |
4 |
No clustering β the parser does its own NMS and sets the angle; DeepStream clustering would drop it |
parse-bbox-func-name |
NvDsInferParseCustomYoloV11OBB |
the prebuilt OBB parser (works for both YOLOv8-OBB and YOLO11-OBB) |
custom-lib-path |
β¦/libnvds_infercustomparser.so |
where that parser lives in the image |
[class-attrs-all] pre-cluster-threshold |
0.25 |
global confidence threshold (see note below) |
Pipeline / I/O is in configs/config_deepstream_app.txt β source,
streammux resolution, OSD, and the file/EGL sink.
Edit configs/config_deepstream_app.txt:
[source0]
uri=file:///workspace/data/your_video.mp4 # put the file in ./data
# or an RTSP camera:
# type=4
# uri=rtsp://user:pass@host:554/stream
[streammux]
width=1920 # set to your video's resolution for best box alignment
height=1080Put the file in ./data/ (mounted to /workspace/data), then run the docker run command above.
The model is DOTA = aerial / top-down. It detects well on nadir scenes (marinas, ports, parking lots, airports from above) and finds little on street-level or strongly oblique footage. That is the domain, not a bug.
The same config and parser work for any Ultralytics YOLO-OBB model. To switch:
-
Export the model to ONNX:
cd models # YOLOv8 variants: python3 -c "from ultralytics import YOLO; YOLO('yolov8s-obb.pt').export(format='onnx', imgsz=1024, opset=20)" # YOLO11 variants: # python3 -c "from ultralytics import YOLO; YOLO('yolo11s-obb.pt').export(format='onnx', imgsz=1024, opset=20)" # Available sizes: n / s / m / l / x
-
Update
configs/config_pgie_yolo_obb.txt:onnx-file=/workspace/models/yolov8s-obb.onnx model-engine-file=/workspace/models/yolov8s-obb.onnx_b1_gpu0_fp16.engine num-detected-classes=15 # adjust if using a non-DOTA model
-
If using custom classes, replace
configs/labels_dota.txtwith your own labels (one per line) and setnum-detected-classesaccordingly. The parser is generic:num_classes = channels β 5.
To poke around inside the container (run gst-inspect-1.0, deepstream-app --help, etc.):
docker run --gpus all -it --rm \
-v "$PWD":/workspace -w /workspace \
-e LD_LIBRARY_PATH=/opt/nvidia/deepstream/deepstream/lib \
nvcr.io/nvidia/deepstream:9.0-samples-multiarch bashBy default [sink0] (file sink) is active and [sink1] (display) is disabled. To enable live display:
- Open
configs/config_deepstream_app.txtand setenable=1under[sink1](and optionallyenable=0under[sink0]to suppress the file output). - Pass the X server and DRI device into the container:
xhost +local:root
docker run --rm --gpus all \
-e DISPLAY=$DISPLAY \
-v /tmp/.X11-unix:/tmp/.X11-unix \
--device /dev/dri \
-v "$PWD":/workspace -w /workspace/configs \
-e LD_LIBRARY_PATH=/opt/nvidia/deepstream/deepstream/lib \
nvcr.io/nvidia/deepstream:9.0-samples-multiarch \
deepstream-app -c config_deepstream_app.txtThe files in this repository (configs, documentation) are released under the MIT License β see LICENSE.
This project depends on third-party components with separate licenses β see NOTICE for full details:
| Component | License | Notes |
|---|---|---|
| NVIDIA DeepStream SDK 9.0 | NVIDIA Proprietary | Runs inside the Docker container; not distributed here |
| Ultralytics YOLO-OBB weights | AGPL-3.0 | Commercial license available from Ultralytics; ONNX/engine is gitignored |
| DOTA dataset (demo imagery) | DOTA Academic License | assets/ images derived from DOTA tiles; academic use only |
Commercial use: if you deploy YOLO-OBB weights (YOLOv8-OBB or YOLO11-OBB, or a derived ONNX/TensorRT engine) in a commercial product, you must obtain a commercial license from Ultralytics. The configs and documentation in this repo are MIT and have no such restriction.
- DeepStream SDK docs β https://docs.nvidia.com/metropolis/deepstream/dev-guide/
- Ultralytics YOLO-OBB β https://docs.ultralytics.com/tasks/obb/
- DOTA dataset β https://captain-whu.github.io/DOTA/
