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
相关产品推荐
相关产品推荐

