yolov8目标检测onnx推理及后处理实现

文章介绍了如何将YOLOv8的预训练PyTorch模型转换为ONNX格式,并进行推理测试。YOLOv8模型取消了anchor和bbox的置信度,输出形状不同于之前的YOLO系列。在推理后,通过提取最大分类概率作为bbox的置信度,以便使用非极大值抑制进行目标检测。文章提供了从ONNX模型获取结果并进行NMS处理的Python代码示例。
该文章已生成可运行项目,

         使用onnx进行yolov8模型推理测试。首先从YOLOv8开源地址下载预训练模型,由于测试在CPU上进行,就只下载最小的YOLOv8n模型。

 YOLOv8n预训练模型为pytorch的pt格式,大小为6.2M,下载完成后,通过pytorch转换为onnx。转换脚本:

import torch
net = torch.load('yolov8n.pt', map_location='cpu')
net.eval()
dummpy_input = torch.randn(1, 3, 640, 640)
torch.onnx.export(net, dummpy_input, 'yolov8n.onnx', export_params=True,
                  input_names=['input'],
                  output_names=['output'])

        完成模型转换后,接下来进行onnx推理测试。编写推理脚本前可以通过netron工具查看模型输入输出,可以看到yolov8输入为[1,3,640,640],输入为[1,84,8400]。

         YOLOv8输出shape跟yolo之前系列模型(YOLOv5输出为[25200,85]),有较大差异,查找一番后,发现yolov8在两个方面做了调整,一是取消了anchor(因为每个anchor对应3个bbox),因此总的bbox数降低三倍;二是取消了bbox的置信度,将bbox置信度与分类融合。

         为了复用之前YOLO系列的后处理代码(非极大值抑制),需要将YOLOv8输出结果进行处理,将分类预测中的最大值提取出来作为bbox置信度。将推理结果转换为[1,8400,85]形式。

 

pred_class = pred[..., 4:]
pred_conf = np.max(pred_class, axis=-1)
pred = np.insert(pred, 4, pred_conf, axis=-1)

测试图片:

 测试结果:

完整的推理脚本:

import onnxruntime as rt
import numpy as np
import cv2
import  matplotlib.pyplot as plt


def nms(pred, conf_thres, iou_thres): 
    conf = pred[..., 4] > conf_thres
    box = pred[conf == True] 
    cls_conf = box[..., 5:]
    cls = []
    for i in range(len(cls_conf)):
        cls.append(int(np.argmax(cls_conf[i])))
    total_cls = list(set(cls))  
    output_box = []  
    for i in range(len(total_cls)):
        clss = total_cls[i] 
        cls_box = []
        for j in range(len(cls)):
            if cls[j] == clss:
                box[j][5] = clss
                cls_box.append(box[j][:6])
        cls_box = np.array(cls_box)
        box_conf = cls_box[..., 4]  
        box_conf_sort = np.argsort(box_conf) 
        max_conf_box = cls_box[box_conf_sort[len(box_conf) - 1]]
        output_box.append(max_conf_box) 
        cls_box = np.delete(cls_box, 0, 0) 
        while len(cls_box) > 0:
            max_conf_box = output_box[len(output_box) - 1]  
            del_index = []
            for j in range(len(cls_box)):
                current_box = cls_box[j]  
                interArea = getInter(max_conf_box, current_box)  
                iou = getIou(max_conf_box, current_box, interArea)  
                if iou > iou_thres:
                    del_index.append(j)  
            cls_box = np.delete(cls_box, del_index, 0)  
            if len(cls_box) > 0:
                output_box.append(cls_box[0])
                cls_box = np.delete(cls_box, 0, 0)
    return output_box


def getIou(box1, box2, inter_area):
    box1_area = box1[2] * box1[3]
    box2_area = box2[2] * box2[3]
    union = box1_area + box2_area - inter_area
    iou = inter_area / union
    return iou


def getInter(box1, box2):
    box1_x1, box1_y1, box1_x2, box1_y2 = box1[0] - box1[2] / 2, box1[1] - box1[3] / 2, \
                                         box1[0] + box1[2] / 2, box1[1] + box1[3] / 2
    box2_x1, box2_y1, box2_x2, box2_y2 = box2[0] - box2[2] / 2, box2[1] - box1[3] / 2, \
                                         box2[0] + box2[2] / 2, box2[1] + box2[3] / 2
    if box1_x1 > box2_x2 or box1_x2 < box2_x1:
        return 0
    if box1_y1 > box2_y2 or box1_y2 < box2_y1:
        return 0
    x_list = [box1_x1, box1_x2, box2_x1, box2_x2]
    x_list = np.sort(x_list)
    x_inter = x_list[2] - x_list[1]
    y_list = [box1_y1, box1_y2, box2_y1, box2_y2]
    y_list = np.sort(y_list)
    y_inter = y_list[2] - y_list[1]
    inter = x_inter * y_inter
    return inter


def draw(img, xscale, yscale, pred):
    img_ = img.copy()
    if len(pred):
        for detect in pred:
            detect = [int((detect[0] - detect[2] / 2) * xscale), int((detect[1] - detect[3] / 2) * yscale),
                      int((detect[0]+detect[2] / 2) * xscale), int((detect[1]+detect[3] / 2) * yscale)]
            img_ = cv2.rectangle(img, (detect[0], detect[1]), (detect[2], detect[3]), (0, 255, 0), 1)
    return img_


if __name__ == '__main__':
    height, width = 640, 640
    img0 = cv2.imread('1.jpg')
    x_scale = img0.shape[1] / width
    y_scale = img0.shape[0] / height
    img = img0 / 255.
    img = cv2.resize(img, (width, height))
    img = np.transpose(img, (2, 0, 1))
    data = np.expand_dims(img, axis=0)
    sess = rt.InferenceSession('yolov8n.onnx')
    input_name = sess.get_inputs()[0].name
    label_name = sess.get_outputs()[0].name
    pred = sess.run([label_name], {input_name: data.astype(np.float32)})[0]
    pred = np.squeeze(pred)
    pred = np.transpose(pred, (1, 0))
    pred_class = pred[..., 4:]
    pred_conf = np.max(pred_class, axis=-1)
    pred = np.insert(pred, 4, pred_conf, axis=-1)
    result = nms(pred, 0.3, 0.45)
    ret_img = draw(img0, x_scale, y_scale, result)
    ret_img = ret_img[:, :, ::-1]
    plt.imshow(ret_img)
    plt.show()

 

本文章已经生成可运行项目
### YOLOv8目标检测通过ONNX执行推理 YOLOv8 是一种先进的实时对象检测框架,支持多种任务类型,包括旋转目标检测。为了实现基于 ONNX推理流程,可以按照以下方法操作。 #### 1. 导出模型至 ONNX 格式 首先需要将训练好的 YOLOv8 模型导出为 ONNX 文件格式。可以通过 Ultralytics 提供的工具完成此过程: ```python from ultralytics import YOLO # 加载预训练模型或自定义模型 model = YOLO('yolov8n.pt') # 或者 'path_to_custom_model.pt' # 将模型导出为 ONNX 格式 success = model.export(format='onnx', simplify=True, opset=12) ``` 上述代码会生成一个 `.onnx` 文件,该文件可用于后续的推理阶段[^1]。 --- #### 2. 使用 ONNX Runtime 进行推理 一旦获得了 ONNX 文件,就可以利用 `onnxruntime` 库加载并运行推理。下面是一个 Python 示例代码片段来演示这一过程: ```python import cv2 import numpy as np import onnxruntime as ort def preprocess_image(image_path, input_size=(640, 640)): image = cv2.imread(image_path) resized = cv2.resize(image, input_size) normalized = resized / 255.0 transposed = np.transpose(normalized, (2, 0, 1)) expanded = np.expand_dims(transposed, axis=0).astype(np.float32) return expanded def postprocess_output(output_data): predictions = output_data[0].squeeze() boxes = predictions[:, :4] scores = predictions[:, 4:] class_ids = np.argmax(scores, axis=1) confidences = np.max(scores, axis=1) filtered_indices = np.where(confidences > 0.5)[0] final_boxes = [] final_class_ids = [] final_confidences = [] for i in filtered_indices: box = boxes[i] class_id = class_ids[i] confidence = confidences[i] final_boxes.append(box.tolist()) final_class_ids.append(class_id.item()) final_confidences.append(confidence.item()) return final_boxes, final_class_ids, final_confidences if __name__ == "__main__": session = ort.InferenceSession("yolov8n.onnx") # 替换为你自己的 .onnx 路径 input_name = session.get_inputs()[0].name output_names = [output.name for output in session.get_outputs()] image_path = "test.jpg" # 输入图像路径 input_tensor = preprocess_image(image_path) outputs = session.run(output_names, {input_name: input_tensor}) boxes, classes, confidences = postprocess_output(outputs) print(f"Detected objects: {len(boxes)}") for idx, box in enumerate(boxes): print(f"Object {idx}: Class={classes[idx]}, Confidence={confidences[idx]:.2f}, Box={box}") ``` 这段代码展示了如何读取输入图片、对其进行预处理、调用 ONNX 模型进行推理以及解析输出数据[^2]。 --- #### 3. 可视化结果 对于可视化部分,可以借助 OpenCV 来绘制边界框和类别标签: ```python def draw_bounding_boxes(image, boxes, classes, confidences, colors=None): if colors is None: colors = [(np.random.randint(0, 255), np.random.randint(0, 255), np.random.randint(0, 255)) for _ in range(len(set(classes)))] for box, cls, conf in zip(boxes, classes, confidences): color = colors[int(cls)] x_min, y_min, x_max, y_max = map(int, box[:4]) label = f"{cls} ({conf:.2f})" cv2.rectangle(image, (x_min, y_min), (x_max, y_max), color, thickness=2) cv2.putText(image, label, (x_min, y_min - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, color, 2) return image image = cv2.imread(image_path) result_image = draw_bounding_boxes(image.copy(), boxes, classes, confidences) cv2.imshow("Result", result_image) cv2.waitKey(0) cv2.destroyAllWindows() ``` 以上代码实现了在原始图像上叠加预测的目标框及其分类信息的功能[^3]。 --- #### 总结 本教程介绍了从准备环境到最终可视化的整个工作流,涵盖了模型转换、推理设置及后处理逻辑等内容。希望这些内容能够帮助理解 YOLOv8ONNX 部署的相关技术要点。
评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TheMatrixs

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值