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
- A trained model (
best.pt), regular or OBB — see How to train a YOLO object detection model with Roboflow and Ultralytics or How to train a YOLO OBB model and get the center and angle of detections pip install torch torchvision ultralytics opencv-python numpy
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)
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: try0first (the usual default camera); switch to1or 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_yback 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
- How to train a YOLO object detection model with Roboflow and Ultralytics — train the model used here
- How to train a YOLO OBB model and get the center and angle of detections — building and reading an OBB model
- How to run a trained YOLO model on a webcam and save detections with OpenCV — drawing and saving detections with OpenCV
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)?.