← Back to list

How To Use Custom Or Official YOLOv8 Object Detection Model In ONNX Format

In this article, we’ll see how to use any pretrained or custom YOLOv8 object detection model in a well known open format known as ONNX…

Zain Ul haq · 2024-07-08 22:34 · 17 claps · 5.9 min read
#computer-vision #export-yolov8 #onnx-runtime #object-detection
Open on Medium ↗

How To Use Custom Or Official YOLOv8 Object Detection Model In ONNX Format

In this article, we’ll see how to use any pretrained or custom YOLOv8 object detection model in a well known open format known as ONNX (Open Neural Network Exchange). Utilizing this format offers an advantage that it can be used in deployment in multiple programming languages without depending on official Ultralytics module.

In this write up, I’ll use official YOLOv8n model available here but the method will also be applicable to any custom YOLOv8 model converted to ONNX format.

First of all we need to convert our trained model with .pt extension to onnx format using the following code.

from ultralytics import YOLO

model_path = 'path/to/yolov8n.pt'

model = YOLO(model_path)

model.export(format='onnx', opset = 12, imgsz =[640,640])

Ensure you have already installed the ultralytics module before running the above code. Once the onnx file is generated, we can define all the classes that our model can detect. In my case, it is the pre-trained model trained over coco dataset and can identify 80 classes.

with open('coco-classes.txt') as file:
    content = file.read()
    classes = content.split('\n')

del classes[-1]

print(classes) # Let's print classes list

The above code snippet on execution gives the following output,

['person', 'bicycle', 'car', 'motorbike', 'aeroplane', 'bus', 'train', 'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'sofa', 'pottedplant', 'bed', 'diningtable', 'toilet', 'tvmonitor', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush']

For inference, we can read the image using OpenCV.

# read the image
image = cv2.imread('bicycle.jpg')

# YOLOv8 need RGB image
img = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)
img_height,img_width = img.shape[:2]

Since OpenCV read image in BGR format whereas YOLO expects an RGB format, so we converted our image into RGB and also store image dimensions for later use. But that’s not all! Before YOLO can work its magic, there are some additional image processing required. Let’s take a look at the image’s shape.

print(img.shape)
(420, 620, 3)

Our image is a 3 channel RGB image with width and height of 620 and 420 respectively. In Contrast, our YOLOv8 model expect an image of size (640, 640) and the channel information before the image size.

# resize image to get the desired size (640,640) for inference
img = cv2.resize(img,(640,640))

# change the order of image dimension from (640,640,3) to (3,640,640)
img = img.transpose(2,0,1)

Finally, to feed the dnn module with our image we need an extra dimension at the 0th index that tells the module how many images we are providing at a time. Additionally, our image pixels range from 0 to 255. They must be scaled to change this range to 0–1 before inference.

# add an extra dimension at index 0
img = img.reshape(1,3,640,640)

# scale to 0-1
img = img/255.0

Finally, our image is ready for inference. To run the inference with our onnx model we can use readNetFromONNX() or readNet() method from DNN module.

# read the trained onnx model
net = cv2.dnn.readNetFromONNX('yolov8n.onnx')  # readNet() also works

# feed the model with processed image
net.setInput(img)

# run the inference
out = net.forward()

After running the inference we obtain an output matrix containing the model’s predictions as shown in above code. To understand how to extract the valuable information it holds, let’s first print the shape of this output matrix.

print(out.shape)
(1, 84, 8400)

The matrix shape of (1, 84, 8400) indicates 8400 detections each with 84 parameters. This is because our YOLOv8 model is designed to always predict up to 8400 objects in an image. It’s important to note that not all detections will be accurate, we’ll need to filter based on confidence scores later. Here 84 at the first index corresponds to the number of parameters for each detection. This includes the bounding box coordinates (x1, y1, x2, y2) and confidence scores for 80 different classes. This structure might differ for custom models. The number of confidence scores depends on the number of classes your model is trained on. For example, if YOLOv8 is trained to detect 1 class, there would be 5 parameters instead of 84. For 2 classes there will be 6 on the first index and so on. We can simply remove the 1 at 0th index as it just tells that a single image being processed by model.

results = out[0]

Now transposing the matrix to get the shape (8400, 84) for our ease.

results = results.transpose()

As mentioned above, each detection includes a confidence score for each class. To identify the class an object or detection most likely belongs to, we simply find the class with the highest confidence score. Additionally, to remove the detections with all the confidences lower than given threshold we can use the following function.

def filter_Detections(results, thresh = 0.5):
    # if model is trained on 1 class only
    if len(results[0]) == 5:
        # filter out the detections with confidence > thresh
        considerable_detections = [detection for detection in results if detection[4] > thresh]
        considerable_detections = np.array(considerable_detections)
        return considerable_detections

    # if model is trained on multiple classes
    else:
        A = []
        for detection in results:

            class_id = detection[4:].argmax()
            confidence_score = detection[4:].max()

            new_detection = np.append(detection[:4],[class_id,confidence_score])

            A.append(new_detection)

        A = np.array(A)

        # filter out the detections with confidence > thresh
        considerable_detections = [detection for detection in A if detection[-1] > thresh]
        considerable_detections = np.array(considerable_detections)

        return considerable_detections
results = filter_Detections(results)

Once we get the useful result by excluding useless parameters, we can print the shape to better understand our results.

print(results.shape)
(45, 6)

Looks like now we have 45 detections each having 6 parameters. They are bounding box top left (x1, y1) and bottom right (x2, y2) coordinates, class id and confidence value. Before we go ahead, let’s have a look on the picture i run the inference on.

Looking at this picture one can easily tells that this picture does not contains 45 objects at all. The reason our resultant matrix still containing so many detections because multiple detections pointing to same object. To address this, we can apply a well known technique called Non-Maximum Suppression (NMS). NMS acts as a filter, selecting the best detections among those potentially referring to the same object. It achieves this by considering two key metrics which are confidence value (how certain the model is about the detection) and Intersection over Union (IOU). Additionally, we’ll need to rescale the remaining detections back to their original scale. This is because our model has output the detection for an image of size 640x640 which is not the size of our original image.

def NMS(boxes, conf_scores, iou_thresh = 0.55):

    #  boxes [[x1,y1, x2,y2], [x1,y1, x2,y2], ...]

    x1 = boxes[:,0]
    y1 = boxes[:,1]
    x2 = boxes[:,2]
    y2 = boxes[:,3]

    areas = (x2-x1)*(y2-y1)

    order = conf_scores.argsort()

    keep = []
    keep_confidences = []

    while len(order) > 0:
        idx = order[-1]
        A = boxes[idx]
        conf = conf_scores[idx]

        order = order[:-1]

        xx1 = np.take(x1, indices= order)
        yy1 = np.take(y1, indices= order)
        xx2 = np.take(x2, indices= order)
        yy2 = np.take(y2, indices= order)

        keep.append(A)
        keep_confidences.append(conf)

        # iou = inter/union

        xx1 = np.maximum(x1[idx], xx1)
        yy1 = np.maximum(y1[idx], yy1)
        xx2 = np.minimum(x2[idx], xx2)
        yy2 = np.minimum(y2[idx], yy2)

        w = np.maximum(xx2-xx1, 0)
        h = np.maximum(yy2-yy1, 0)

        intersection = w*h

        # union = areaA + other_areas - intesection
        other_areas = np.take(areas, indices= order)
        union = areas[idx] + other_areas - intersection

        iou = intersection/union

        boleans = iou < iou_thresh

        order = order[boleans]

        # order = [2,0,1]  boleans = [True, False, True]
        # order = [2,1]

    return keep, keep_confidences

def rescale_back(results,img_w,img_h):
    cx, cy, w, h, class_id, confidence = results[:,0], results[:,1], results[:,2], results[:,3], results[:,4], results[:,-1]
    cx = cx/640.0 * img_w
    cy = cy/640.0 * img_h
    w = w/640.0 * img_w
    h = h/640.0 * img_h
    x1 = cx - w/2
    y1 = cy - h/2
    x2 = cx + w/2
    y2 = cy + h/2

    boxes = np.column_stack((x1, y1, x2, y2, class_id))
    keep, keep_confidences = NMS(boxes,confidence)
    print(np.array(keep).shape)
    return keep, keep_confidences

Applying the above functions on our result.

rescaled_results, confidences = rescale_back(results, img_width, img_height)

Here ‘rescaled_results’ contains the bounding box (x1, y1, x2, y2) and class id while ‘confidences stores’ the corresponding confidence scores.

Finally we are ready to visualize these results on our image.

for res, conf in zip(rescaled_results, confidences):

    x1,y1,x2,y2, cls_id = res
    cls_id = int(cls_id)
    x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
    conf = "{:.2f}".format(conf)
    # draw the bounding boxes
    cv2.rectangle(image,(int(x1),int(y1)),(int(x2),int(y2)),(255,0,255),1)
    cv2.putText(image,classes[cls_id]+' '+conf,(x1,y1-17),
                cv2.FONT_HERSHEY_SCRIPT_COMPLEX,1,(255,0,255),1)


메타데이터
post_id
ca8f055643df
slug
how-to-use-custom-or-official-yolov8-object-detection-model-in-onnx-format-ca8f055643df
url
https://medium.com/@zain.18j2000/how-to-use-custom-or-official-yolov8-object-detection-model-in-onnx-format-ca8f055643df
canonical_url
https://medium.com/@zain.18j2000/how-to-use-custom-or-official-yolov8-object-detection-model-in-onnx-format-ca8f055643df
author_url
https://medium.com/@zain.18j2000
status
ok
fetched_at
2026-08-29 03:21:47