1. YOLOv8n模型ONNX导出与验证
任务目标
将YOLOv8n目标检测模型导出为ONNX格式,并验证导出模型的输出张量形状与数值正确性,确保ONNX模型与PyTorch原始模型在相同输入下推理结果一致。
环境依赖
需要安装以下依赖:
pip install ultralytics onnx onnxruntime
完整实现代码
import torch
import numpy as np
import onnx
import onnxruntime as ort
from ultralytics import YOLO
# 1. 加载YOLOv8n预训练模型
model = YOLO('yolov8n.pt')
# 2. 导出为ONNX格式(动态batch,固定输入尺寸640x640)
export_path = model.export(format='onnx', imgsz=640, dynamic=True, simplify=True)
print(f'ONNX模型已保存至: {export_path}')
# 3. 构造测试输入(与训练预处理一致:RGB、0-255、BCHW)
dummy_input = np.random.randint(0, 255, size=(1, 3, 640, 640), dtype=np.uint8).astype(np.float32)
# 4. 获取PyTorch模型输出(作为基准)
model.eval()
with torch.no_grad():
torch_output = model.predict(source=dummy_input, imgsz=640, verbose=False)
# 取第一个检测结果的原始输出张量(形状: [1, 84, 8400])
torch_preds = torch_output[0].boxes.data.cpu().numpy() # [N, 6] 格式: x1,y1,x2,y2,conf,cls
# 5. 使用ONNX Runtime进行推理
ort_session = ort.InferenceSession(export_path)
onnx_input = {ort_session.get_inputs()[0].name: dummy_input}
onnx_output = ort_session.run(None, onnx_input)[0] # 形状: [1, 84, 8400]
# 6. 验证输出张量形状
print(f'ONNX输出形状: {onnx_output.shape}')
assert onnx_output.shape == (1, 84, 8400), f'形状不匹配: {onnx_output.shape}'
# 7. 验证数值正确性(将ONNX输出转换为与PyTorch相同的检测框格式)
# ONNX输出格式: [batch, 4+80, num_anchors] = [1, 84, 8400]
# 需要解码为xyxy格式并与PyTorch结果对比
def decode_onnx_output(output, conf_thres=0.25):
"""将ONNX原始输出解码为检测框列表"""
output = output[0] # [84, 8400]
num_classes = 80
boxes = []
for i in range(output.shape[1]):
scores = output[4:, i]
class_id = np.argmax(scores)
confidence = scores[class_id]
if confidence < conf_thres:
continue
cx, cy, w, h = output[:4, i]
x1 = cx - w / 2
y1 = cy - h / 2
x2 = cx + w / 2
y2 = cy + h / 2
boxes.append([x1, y1, x2, y2, confidence, class_id])
return np.array(boxes) if boxes else np.empty((0, 6))
onnx_boxes = decode_onnx_output(onnx_output)
# 8. 对比检测结果(允许坐标微小误差)
if len(onnx_boxes) > 0 and len(torch_preds) > 0:
# 按置信度排序后取前5个框对比
onnx_boxes = onnx_boxes[onnx_boxes[:, 4].argsort()[::-1]][:5]
torch_preds = torch_preds[torch_preds[:, 4].argsort()[::-1]][:5]
print(f'PyTorch检测框数量: {len(torch_preds)}, ONNX检测框数量: {len(onnx_boxes)}')
print('前5个检测框对比(x1,y1,x2,y2,conf,cls):')
for i in range(min(5, len(onnx_boxes), len(torch_preds))):
diff = np.abs(onnx_boxes[i] - torch_preds[i])
print(f'框{i+1} 坐标最大差异: {diff[:4].max():.4f}, 置信度差异: {diff[4]:.6f}')
assert diff[:4].max() < 1.0, f'坐标差异过大: {diff}'
assert diff[4] < 0.01, f'置信度差异过大: {diff}'
print('数值验证通过:ONNX与PyTorch输出一致')
else:
print('警告:未检测到足够的目标框,无法进行数值对比')
print('请更换测试输入或降低置信度阈值后重试')
运行与验证方法
运行步骤:
- 确保已安装依赖并下载yolov8n.pt预训练权重(首次运行会自动下载)
- 直接执行上述Python脚本
- 观察控制台输出
通过条件:
- 脚本无异常退出,打印“ONNX模型已保存至”路径
- 输出张量形状断言通过,打印
ONNX输出形状: (1, 84, 8400) - 若检测到目标框,坐标最大差异小于1像素且置信度差异小于0.01,打印“数值验证通过”
- 若未检测到目标框,脚本会给出警告提示,此时应更换输入图像或降低置信度阈值重新验证
关键说明
ONNX导出的输出为原始预测张量[1, 84, 8400],其中84=4(边界框坐标)+80(COCO类别数),8400为不同尺度特征图上的锚点总数。PyTorch的model.predict()返回的是经过NMS后处理的检测结果,因此验证时需要将ONNX输出解码为相同格式再对比。本案例使用随机噪声作为输入,仅用于验证模型结构正确性和数值一致性,实际部署时应使用真实图像测试。