You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

YOLOv8导出ONNX后OpenCV DNN检测类别ID始终为0的问题

问题描述

已成功使用YOLOv8训练自定义猫狗数据集,通过官方YOLO接口测试可正确识别cat类别,但将模型导出为ONNX格式后,使用OpenCV DNN模块检测时,检测框、置信度及结果图像均正常,却始终将cat识别为dog(class_id固定为0)。

训练代码

from ultralytics import YOLO

# Load a model
model = YOLO('yolov8s.pt')  # load a pretrained model (recommended for training)

# Train the model
model.train(
    name='MyModel',
    data='/root/work/images/data.yaml',
    epochs=100,
    imgsz=640
)

官方接口测试结果(正确识别)

python3 yolo_test.py

cat
[ 344  808  572 1208]
[ 458 1008  227  400]
[    0.84057]

ONNX导出代码

from ultralytics import YOLO

model = YOLO('/root/work/MyModel/weights/best.pt')
# Export the model
model.export(format='onnx', opset=12)

OpenCV DNN测试结果(错误识别)

['dog']
[0.8799546]
[array([364, 802, 206, 416])]

OpenCV检测代码(predict_cv2.py)

import cv2
import numpy as np
import requests
from PIL import Image

# Load Model
net = cv2.dnn.readNet('/root/work/MyModel/weights/best.onnx')

INPUT_WIDTH = 640
INPUT_HEIGHT = 640

CLASESS_YOLO = ['dog','cat']

inputs = ['https://example.com/cat.jpg']

response = requests.get(inputs[0])
cv2_img = cv2.imdecode(np.array(bytearray(response.content), dtype=np.uint8), -1)
blob = cv2.dnn.blobFromImage(cv2_img, 1/255.0, (INPUT_WIDTH, INPUT_HEIGHT), swapRB=True, crop=False)
net.setInput(blob)
preds = net.forward()

preds = preds.transpose((0, 2, 1))

# Extract output detection
class_ids, confs, boxes = list(), list(), list()

image_height, image_width, _ = cv2_img.shape
x_factor = image_width / INPUT_WIDTH
y_factor = image_height / INPUT_HEIGHT

min_conf = .3

rows = preds[0].shape[0]

for i in range(rows):
    row = preds[0][i]
    conf = row[4]
    
    classes_score = row[4:]
    _,_,_, max_idx = cv2.minMaxLoc(classes_score)
    class_id = max_idx[1] 

    if (classes_score[class_id] > min_conf):
        confs.append(conf)
        label = CLASESS_YOLO[int(class_id)]
        print(class_id)
        class_ids.append(label)
        #extract boxes
        x, y, w, h = row[0].item(), row[1].item(), row[2].item(), row[3].item() 
        left = int((x - 0.5 * w) * x_factor)
        top = int((y - 0.5 * h) * y_factor)
        width = int(w * x_factor)
        height = int(h * y_factor)
        box = np.array([left, top, width, height])
        boxes.append(box)
        
r_class_ids, r_confs, r_boxes = list(), list(), list()

indexes = cv2.dnn.NMSBoxes(boxes, confs, min_conf, min_conf-0.1) 

for i in indexes:
    r_class_ids.append(class_ids[i])
    r_confs.append(confs[i])
    r_boxes.append(boxes[i])

print(r_class_ids)
print(r_confs)
print(r_boxes)

for i in indexes:
    box = boxes[i]
    left = box[0]
    top = box[1]
    width = box[2]
    height = box[3]
    
    cv2.rectangle(cv2_img, (left, top), (left + width, top + height), (0,255,0), 3)
result_image = Image.fromarray(cv2.cvtColor(cv2_img, cv2.COLOR_BGR2RGB))
result_image.save('/root/work/image_cv2.jpg')

错误原因及修复方案

1. 核心错误:类别得分截取范围错误

YOLOv8的ONNX输出格式为:[cx, cy, w, h, obj_conf, cls1_conf, cls2_conf, ...],其中:

  • 前4位是检测框的中心坐标、宽高
  • 第5位(索引4)是目标存在置信度(obj_conf)
  • 从第6位(索引5)开始才是各个类别的置信度得分

你错误地将row[4:]作为类别得分,把obj_conf也包含在内,导致cv2.minMaxLoc会把obj_conf的位置(索引0)识别为最高分,对应class_id=0,因此始终输出dog。

修复代码:

# 将原代码的这一行
classes_score = row[4:]
# 修改为
classes_score = row[5:]

2. 置信度计算逻辑错误

YOLOv8中检测框的最终置信度应为目标存在置信度 × 类别置信度,而非直接使用obj_conf。你当前仅用row[4]作为置信度,会导致置信度值不准确。

修复代码:

# 将原代码的
conf = row[4]
...
if (classes_score[class_id] > min_conf):
    confs.append(conf)
# 修改为
obj_conf = row[4]
classes_score = row[5:]
...
cls_conf = classes_score[class_id]
final_conf = obj_conf * cls_conf
if (final_conf > min_conf):
    confs.append(final_conf)

3. 类别索引获取方式不够可靠

使用cv2.minMaxLoc处理一维数组容易出现索引混淆,改用numpy的argmax更直接高效。

修复代码:

# 将原代码的
_,_,_, max_idx = cv2.minMaxLoc(classes_score)
class_id = max_idx[1]
# 修改为
class_id = np.argmax(classes_score)

完整修复后的循环代码片段

for i in range(rows):
    row = preds[0][i]
    obj_conf = row[4]
    classes_score = row[5:]
    
    class_id = np.argmax(classes_score)
    cls_conf = classes_score[class_id]
    final_conf = obj_conf * cls_conf

    if (final_conf > min_conf):
        confs.append(final_conf)
        label = CLASESS_YOLO[int(class_id)]
        print(class_id)
        class_ids.append(label)
        #extract boxes
        x, y, w, h = row[0].item(), row[1].item(), row[2].item(), row[3].item() 
        left = int((x - 0.5 * w) * x_factor)
        top = int((y - 0.5 * h) * y_factor)
        width = int(w * x_factor)
        height = int(h * y_factor)
        box = np.array([left, top, width, height])
        boxes.append(box)

内容的提问来源于stack exchange,提问作者Lorenzo Garuti

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 00:09:54