How to run YOLOv8 or YOLOv8-OBB detection within a region of interest (ROI)

Based on contributions by elenavoicila, Mathijs.

If you only care about objects in part of the camera frame (e.g. a conveyor lane or a fixed pickup area), restricting detection to that region of interest (ROI) cuts down false positives from the rest of the image and can speed up inference. This works for both a regular YOLOv8 model and a YOLOv8-OBB model (see How to train a YOLO OBB model and get the center and angle of detections).

What you need

Choosing an ROI approach

There are two different ways to apply a region of interest, and they suit different situations:

  • Image-region ROI (crop before inference): crop the frame to the ROI first, then run the model only on that crop. This reduces the amount of image data the model has to process, which speeds up inference — but only use it if you’re confident the objects you want are always fully inside that region. If an object can be partially outside the ROI, cropping may cut it off.
  • Results ROI (filter after inference): run the model on the full frame, then keep only the detections whose centre point falls inside your ROI. This is more robust when you’re not sure exactly where objects will appear, at the cost of processing the whole frame every time.

The steps below cover the image-region (crop) approach in detail, since it’s the one most setups use, followed by the results-filtering alternative.

Steps: image-region ROI (crop before inference)

1. Install and import libraries

import cv2 as cv
import torch
from ultralytics import YOLO

2. Load the camera and model

camera_index = 1  # 0 is usually the default camera; try 0 if 1 doesn't work
model = YOLO(r"path/to/best.pt")
cap = cv.VideoCapture(camera_index)

3. Define the ROI

roi_x, roi_y, roi_w, roi_h = 515, 0, 250, 720  # x, y, width, height, in pixels

4. Capture and crop each frame

while True:
    ret, frame = cap.read()
    if not ret:
        break

    roi_frame = frame[roi_y:roi_y + roi_h, roi_x:roi_x + roi_w]

5. Run the model on the cropped frame

    results = model(roi_frame, verbose=False, conf=0.75)

6. Draw detections back onto the full frame

Detection coordinates are relative to the cropped roi_frame, so add roi_x/roi_y back before drawing on the original frame.

For a YOLOv8-OBB model:

    if results and results[0].obb:
        for obb in results[0].obb:
            vertices = obb.xyxyxyxy[0].cpu().numpy()
            vertices[:, 0] += roi_x
            vertices[:, 1] += roi_y
            points = vertices.astype(int)

            for j in range(len(points)):
                cv.line(frame, tuple(points[j]), tuple(points[(j + 1) % len(points)]), (0, 255, 0), 2)
            label = results[0].names[int(obb.cls[0])]
            cv.putText(frame, label, (points[0][0], points[0][1] - 10),
                       cv.FONT_HERSHEY_SIMPLEX, 0.9, (255, 255, 255), 2)

For a regular YOLOv8 model:

    if results:
        for box in results[0].boxes:
            x1, y1, x2, y2 = map(int, box.xyxy[0])
            cv.rectangle(frame, (x1 + roi_x, y1 + roi_y), (x2 + roi_x, y2 + roi_y), (0, 255, 0), 2)
            label = results[0].names[int(box.cls[0])]
            cv.putText(frame, label, (x1 + roi_x, y1 + roi_y - 10),
                       cv.FONT_HERSHEY_SIMPLEX, 0.9, (255, 255, 255), 2)

7. Draw the ROI outline and display the frame

    cv.rectangle(frame, (roi_x, roi_y), (roi_x + roi_w, roi_y + roi_h), (0, 255, 0), 2)
    cv.imshow("YOLOv8 Detection", frame)

    if cv.waitKey(1) == ord('q'):
        break

cap.release()
cv.destroyAllWindows()

Full code — OBB within ROI

import cv2 as cv
import torch
from ultralytics import YOLO

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

cap = cv.VideoCapture(camera_index)
roi_x, roi_y, roi_w, roi_h = 515, 0, 250, 720

while True:
    ret, frame = cap.read()
    if not ret:
        break

    roi_frame = frame[roi_y:roi_y + roi_h, roi_x:roi_x + roi_w]
    results = model(roi_frame, verbose=False, conf=0.75)

    if results and results[0].obb:
        for obb in results[0].obb:
            vertices = obb.xyxyxyxy[0].cpu().numpy()
            vertices[:, 0] += roi_x
            vertices[:, 1] += roi_y
            points = vertices.astype(int)

            for j in range(len(points)):
                cv.line(frame, tuple(points[j]), tuple(points[(j + 1) % len(points)]), (0, 255, 0), 2)
            label = results[0].names[int(obb.cls[0])]
            cv.putText(frame, label, (points[0][0], points[0][1] - 10),
                       cv.FONT_HERSHEY_SIMPLEX, 0.9, (255, 255, 255), 2)

    cv.rectangle(frame, (roi_x, roi_y), (roi_x + roi_w, roi_y + roi_h), (0, 255, 0), 2)
    cv.imshow("YOLOv8 OBB Detection", frame)

    if cv.waitKey(1) == ord('q'):
        break

cap.release()
cv.destroyAllWindows()

Full code — regular boxes within ROI

import cv2 as cv
import torch
from ultralytics import YOLO

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

cap = cv.VideoCapture(camera_index)
roi_x, roi_y, roi_w, roi_h = 515, 0, 250, 720

while True:
    ret, frame = cap.read()
    if not ret:
        break

    roi_frame = frame[roi_y:roi_y + roi_h, roi_x:roi_x + roi_w]
    results = model(roi_frame, verbose=False, conf=0.75)

    if results:
        for box in results[0].boxes:
            x1, y1, x2, y2 = map(int, box.xyxy[0])
            cv.rectangle(frame, (x1 + roi_x, y1 + roi_y), (x2 + roi_x, y2 + roi_y), (0, 255, 0), 2)
            label = results[0].names[int(box.cls[0])]
            cv.putText(frame, label, (x1 + roi_x, y1 + roi_y - 10),
                       cv.FONT_HERSHEY_SIMPLEX, 0.9, (255, 255, 255), 2)

    cv.rectangle(frame, (roi_x, roi_y), (roi_x + roi_w, roi_y + roi_h), (0, 255, 0), 2)
    cv.imshow("YOLOv8 Detection", frame)

    if cv.waitKey(1) == ord('q'):
        break

cap.release()
cv.destroyAllWindows()

Alternative: results ROI (filter after inference)

Instead of cropping, run the model on the full-resolution image and keep only detections whose centre lies inside the ROI. Use box.xywh (regular boxes) or box.xywhr (OBB) to get each detection’s centre:

from ultralytics import YOLO

model = YOLO(r"path/to/best.pt")
roi_x, roi_y, roi_w, roi_h = 515, 0, 250, 720

results = model("frame.jpg")  # or a live frame

filtered_results = []
for r in results:
    for box in r.boxes:
        box_center_x, box_center_y = box.xywh[0][:2]

        if (roi_x <= box_center_x <= roi_x + roi_w and
                roi_y <= box_center_y <= roi_y + roi_h):
            filtered_results.append(box)
            print(box.xywh)

:warning: Check: this filtering approach was contributed as an untested snippet in the original student post. The general idea (check each detection’s centre against the ROI bounds) is sound, but test it on your own setup before relying on it.

Troubleshooting

  • camera_index: try 0 first (the usual default camera); switch to 1 or higher if you have multiple cameras connected and get the wrong feed.
  • Detections drawn in the wrong place on the full frame almost always mean you forgot to add roi_x/roi_y back onto the cropped-frame coordinates.
  • If objects near the edge of your ROI get cut off or missed, switch to the results-ROI (filter-after-inference) approach instead of cropping.

Related


Rewritten and consolidated (Sept 2026) from the original student how-to’s: How to use YOLOv8 or YOLOv8 OBB within a ROI (Region of Interest)?.