# How to train a YOLO OBB model and get the center and angle of detections

**URL:** <https://www.robotexchange.io/t/how-to-train-a-yolo-obb-model-and-get-the-center-and-angle-of-detections/4702>\
**Category:** 💡 How-to\
**Tags:** Python, machine-learning, RealSense-camera, yolo, roboflow, oriented-bounding-boxes\
**Created:** [September 2, 2026, 12:17am UTC](https://www.robotexchange.io/t/how-to-train-a-yolo-obb-model-and-get-the-center-and-angle-of-detections/4702 "2026-09-02T00:17:08Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![thijs](https://www.robotexchange.io/user_avatar/www.robotexchange.io/thijs/32/4_2.png) [@thijs](https://www.robotexchange.io/u/thijs)\
**Post date:** [September 2, 2026, 12:17am UTC](https://www.robotexchange.io/t/how-to-train-a-yolo-obb-model-and-get-the-center-and-angle-of-detections/4702/1 "2026-09-02T00:17:08Z")

</div>

_Based on contributions by sarahstam, 20129556._

A regular YOLO bounding box is always axis-aligned, so it can’t tell you how an object is rotated. An **oriented bounding box (OBB)** model fits a rotated rectangle to each object instead, so you also get its angle — useful when a robot needs to pick up an object at the correct orientation.

## What you need

- A Roboflow account (see [How to annotate and label images in Roboflow for YOLO training](https://www.robotexchange.io/t/how-to-annotate-and-label-images-in-roboflow-for-yolo-training/4699) for the general annotation workflow)
- Ultralytics (`pip install ultralytics`)
- Google Colab (free), or an [Ultralytics HUB](https://hub.ultralytics.com/) account as an alternative
- OpenCV (`pip install opencv-python`) to run and visualise the trained model

## Steps

### 1. Annotate with the polygon tool

Create a Roboflow project and, for each object, use the **smart polygon** tool (not the plain rectangle tool) to trace its actual outline. Roboflow uses the polygon shape to work out the object’s orientation later.

 ![image](https://www.robotexchange.io/uploads/default/original/2X/d/d7afc438f0e266f5c9103d28187e298afa661f7e.jpeg)

Use 70+ images if you can — more images generally give a more reliable model.

### 2. Export the dataset in OBB format

In the **Generate** tab, export your dataset with format **“YOLOv8 Oriented Bounding Boxes”** and copy the download link (or the Python download snippet).

 ![image](https://www.robotexchange.io/uploads/default/original/2X/2/297f5d5937704f99962f72c7ee5460e42631e1ef.png)

### 3. Train the model

You can train an OBB model either directly in Colab with Ultralytics, or via Ultralytics HUB.

#### Option A: Google Colab with Ultralytics

```python
%pip install ultralytics

!wget YOUR_ROBOFLOW_ZIP_LINK -O roboflow.zip # paste your copied dataset link here

!mkdir -p /content/datasets
!unzip -o roboflow.zip -d /content/datasets

from ultralytics import YOLO
import os
os.environ['WANDB_MODE'] = 'disabled'

model = YOLO("yolo11n-obb.pt")
results = model.train(data="/content/datasets/data.yaml", epochs=200, imgsz=640)

```

> ⚠ Check: if your exported dataset doesn’t already contain a separate `valid/` split, you may be tempted to copy the `train/` folder into `valid/` just to satisfy `data.yaml`. Don’t do this for real evaluation — validating on the same images the model trained on gives misleadingly good metrics. Instead re-export from Roboflow with a proper train/valid split (step 8 of [How to annotate and label images in Roboflow for YOLO training](https://www.robotexchange.io/t/how-to-annotate-and-label-images-in-roboflow-for-yolo-training/4699)).

Once training finishes, find your model in the Colab file browser under `runs/obb/train/weights/best.pt` and download it.

#### Option B: Ultralytics HUB

1. Go to [hub.ultralytics.com](https://hub.ultralytics.com/) and create a dataset, uploading your Roboflow OBB export.
2. Set the dataset’s task to **OBB**.
3. If HUB complains your dataset doesn’t match its expected sample layout, unzip your Roboflow export locally, rename files/folders to match exactly what HUB expects, and re-zip it before uploading.
4. Open the dataset and click **Train model**. HUB generates a ready-to-run Google Colab notebook — follow it to train.
5. If training fails because a folder can’t be found, your dataset layout is still wrong — go back to step 3.
6. When training finishes, find your model under `runs/obb/train/weights/best.pt` in the Colab files panel and download `best.pt`.

### 4. Run the model and read center, size, and rotation

`results[0].obb.xywhr` gives, for every detection, the box centre (x, y), width, height, and rotation in **radians** :

```python
import math
import cv2
from ultralytics import YOLO

MODEL_PATH = r"path/to/best.pt"
model = YOLO(MODEL_PATH)

def detect_obb(image, confidence_threshold=0.8):
    results = model(image, conf=confidence_threshold)
    return results

def get_obb_info(results, annotated_image):
    """Return a list of [x_center, y_center, rotation_degrees] for every detection,
    and draw a dot at the centre of each one."""
    detections = []
    for result in results:
        for x, y, w, h, r in result.obb.xywhr.cpu().numpy():
            x_center, y_center = int(x), int(y)
            rotation_deg = int(r * 180 / math.pi)
            cv2.circle(annotated_image, (x_center, y_center), radius=5, color=(0, 0, 255), thickness=-1)
            detections.append([x_center, y_center, rotation_deg])
    return detections

results = detect_obb(image)
annotated_image = results[0].plot() # draws the rotated boxes
detections = get_obb_info(results, annotated_image)
print(detections)

cv2.imshow("OBB detections", annotated_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

```

> If your camera outputs raw Bayer-pattern data rather than a normal BGR/RGB image, convert it first with something like `cv2.cvtColor(image, cv2.COLOR_BAYER_RG2RGB)` before passing it to the model. Most webcams already give you a regular BGR image and don’t need this step.

### 5. Using an Intel RealSense camera (optional)

If your setup uses a RealSense camera for combined colour and depth, you can feed its colour stream into the same model:

```python
import math
import cv2
import numpy as np
import pyrealsense2 as rs
from ultralytics import YOLO

model = YOLO("path/to/best.pt", task="obb")

pipeline = rs.pipeline()
config = rs.config()
config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30)
config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30)

align = rs.align(rs.stream.color)
pipeline.start(config)

PERSIST = True
CONFIDENCE_THRESHOLD = 0.5
IMAGE_SIZE = 640

try:
    while True:
        frames = pipeline.wait_for_frames()
        aligned_frames = align.process(frames)

        depth_frame = aligned_frames.get_depth_frame()
        color_frame = aligned_frames.get_color_frame()
        if not depth_frame or not color_frame:
            continue

        depth_image = np.asanyarray(depth_frame.get_data())
        color_image = np.asanyarray(color_frame.get_data())
        depth_colormap = cv2.applyColorMap(cv2.convertScaleAbs(depth_image, alpha=0.5), cv2.COLORMAP_JET)

        results = model.track(color_image, persist=PERSIST, conf=CONFIDENCE_THRESHOLD, imgsz=IMAGE_SIZE)
        yolo_image = results[0].plot()

        for result in results:
            for x, y, w, h, r in result.obb.xywhr.cpu().numpy():
                x_center, y_center = int(x), int(y)
                rotation_deg = int(r * 180 / math.pi)
                cv2.circle(yolo_image, (x_center, y_center), 5, (0, 0, 255), -1)
                print(f"centre=({x_center},{y_center}) rotation={rotation_deg} deg")

        cv2.imshow("frame", yolo_image)
        cv2.imshow("depth", depth_colormap)

        if cv2.waitKey(1) == 27: # Esc
            break
finally:
    pipeline.stop()
    cv2.destroyAllWindows()

```

## Troubleshooting

- **Ultralytics HUB rejects your dataset** : your unzipped Roboflow export’s folder/file names must match HUB’s expected sample layout exactly — check spelling and casing.
- **Rotation values look wrong** : `xywhr` returns rotation in radians, not degrees. Convert with `rotation * 180 / math.pi` (and make sure `import math` is present).
- **Validation accuracy looks suspiciously perfect** : check you’re not validating on the same images used for training (see the ⚠ Check note above).

## Related

- [How to annotate and label images in Roboflow for YOLO training](https://www.robotexchange.io/t/how-to-annotate-and-label-images-in-roboflow-for-yolo-training/4699) — annotate images in Roboflow
- [How to augment images and OBB labels to expand a YOLO training set](https://www.robotexchange.io/t/how-to-augment-images-and-obb-labels-to-expand-a-yolo-training-set/4704) — augment images and OBB labels to grow your dataset
- [How to run YOLOv8 or YOLOv8-OBB detection within a region of interest (ROI)](https://www.robotexchange.io/t/how-to-run-yolov8-or-yolov8-obb-detection-within-a-region-of-interest-roi/4703) — run OBB (or regular) detection within a region of interest

* * *

_Rewritten and consolidated (Sept 2026) from the original student how-to’s: [How to make a YOLO model with orientated bounding boxes to find the center and angle of the bounding box](https://www.robotexchange.io/t/how-to-make-a-yolo-model-with-orientated-bounding-boxes-to-find-the-center-and-angle-of-the-bounding-box/3322), [How to use orientated Bounding Boxes (obb) using Yolo](https://www.robotexchange.io/t/how-to-use-orientated-bounding-boxes-obb-using-yolo/3049)._
