AOI 实战第六篇:.onnx 到 C++ 的最后一公里

opset / IR / FP16 / dynamic batch,导出参数一个都不能错


开头:C++ 加载 ONNX 报错,你查了三天

你有没有过这种经历?

训练了一个精度 99% 的模型,导出了 ONNX。产线同事说:「C++ 加载报错。」

你查了三天,发现是 IR 版本过高(IR=11),旧版 ORT 不支持。锁定 IR=8 后,问题解决。

.onnx 训好了不算完,C++ 能跑才是真正的交付。

AOI 项目有一套统一的导出规范:opset=12、IR=8、FP16、dynamic batch。每个 ONNX 文件带时间戳和元信息。

不是因为 ONNX 有多难------而是因为导出参数一个都不能错。


一、AOI 项目的导出规范

统一参数

参数 推荐值 说明
format "onnx" 导出格式
opset 12 操作集版本
ir_version 8 内部版本号(手动锁定)
half True FP16
dynamic True 动态 batch
imgsz 与训练一致 输入尺寸

为什么要锁定 IR?

复制代码
新版 ultralytics 可能写出过高 IR 版本(如 IR=11)
旧版 ORT(C++ 端)可能不支持高版本 IR
解决方案:导出后手动锁定 IR=8

二、AOI 项目的导出脚本

核心函数

python 复制代码
def export_compatible_onnx(weights, *, out_dir, run_id, imgsz=224, half=True):
    """
    导出 C++ 链路可用的 ONNX
    """
    model = YOLO(str(weights))
    
    # 1. 导出 ONNX
    exported = model.export(
        format="onnx",
        imgsz=imgsz,
        dynamic=True,
        half=half,
        opset=12,
    )
    
    # 2. 锁定 IR 版本
    onnx_model = onnx.load(str(exported))
    onnx_model.ir_version = 8  # 锁定 IR=8
    onnx.save(onnx_model, str(exported))
    
    # 3. 产出文件管理
    stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    tagged = f"{run_id}_opset12_ir8"
    
    # 主产出:带时间戳,不覆盖
    stamped = out_dir / f"{tagged}_{stamp}.onnx"
    shutil.copy2(exported, stamped)
    
    # 指针文件:可覆盖
    latest = out_dir / f"{tagged}_latest.onnx"
    shutil.copy2(exported, latest)
    
    # 元信息
    meta = out_dir / f"{tagged}_{stamp}.txt"
    meta.write_text(f"run_id={run_id}\nonnx={stamped}\nir_version=8\nopset=12")
    
    return stamped

文件命名规范

复制代码
{run_id}_opset12_ir8_{timestamp}.onnx  # 主产出,不覆盖
{run_id}_opset12_ir8_latest.onnx       # 指针文件,可覆盖
{run_id}_opset12_ir8_{timestamp}.txt   # 元信息

三、AOI 项目的三种导出场景

场景1:极性分类 ONNX

python 复制代码
# 极性分类
exported = export_compatible_onnx(
    weights="runs/classify/train-yolo12n-cls-cpp224v2/weights/best.pt",
    out_dir=Path("onnx"),
    run_id="yolo12n-cls-cpp224v2",
    imgsz=224,
    half=True,
)
# 产出:yolo12n-cls-cpp224v2_opset12_ir8_20260808_113805.onnx

场景2:缺陷分类 ONNX

python 复制代码
# 缺陷分类 V4
exported = export_compatible_onnx(
    weights="runs/train/defect_cls_v4_safe/weights/best.pt",
    out_dir=Path("onnx"),
    run_id="defect_cls_v4",
    imgsz=224,
    half=True,
)
# 产出:defect_cls_v4_opset12_ir8_20260808_123740.onnx

场景3:灯组检测 ONNX

python 复制代码
# 灯组检测(已锁定 IR=8,跳过重新锁定)
exported = export_compatible_onnx(
    weights="runs/detect/train-yolo11s/weights/best.pt",
    out_dir=Path("onnx"),
    run_id="yolo11s-det",
    imgsz=640,
    half=True,
)
# 产出:yolo11s-det_opset12_ir8_20260808_123812.onnx

四、ONNX 文件管理

防覆盖策略

复制代码
错误做法:
  直接导出到 model.onnx
  下次导出覆盖了上次的结果

正确做法:
  带时间戳的主产出:model_20260808_113805.onnx(不覆盖)
  指针文件:model_latest.onnx(可覆盖)
  元信息:model_20260808_113805.txt

元信息记录

复制代码
run_id=yolo12n-cls-cpp224v2
onnx=onnx/yolo12n-cls-cpp224v2_opset12_ir8_20260808_113805.onnx
ir_version=8
opset=12

用途:追溯每个 ONNX 文件的来源和参数。


五、避坑指南

坑1:IR 版本过高

python 复制代码
# 错误:用默认 IR 版本
exported = model.export(format="onnx")  # IR=11,旧 ORT 加载失败!

# 正确:手动锁定 IR 版本
onnx_model = onnx.load(str(exported))
onnx_model.ir_version = 8  # 锁定 IR=8
onnx.save(onnx_model, str(exported))

坑2:imgsz 与训练不一致

python 复制代码
# 错误:训练用 224,导出用 640
model.train(imgsz=224)
model.export(imgsz=640)  # 输入尺寸不一致,推理结果错误!

# 正确:导出尺寸与训练一致
model.train(imgsz=224)
model.export(imgsz=224)

坑3:覆盖已有 ONNX

python 复制代码
# 错误:导出到已有文件,覆盖了旧版本
shutil.move(exported, "model.onnx")  # 覆盖了!

# 正确:带时间戳,不覆盖
stamped = f"model_{timestamp}.onnx"
shutil.move(exported, stamped)

坑4:不记录元信息

现象 :ONNX 文件太多,不知道哪个是哪个版本。

解法 :每个 ONNX 文件带 .txt 元信息。

坑5:FP16 精度损失未验证

python 复制代码
# 错误:直接用 FP16 导出,不验证精度
exported = model.export(format="onnx", half=True)  # 可能有精度损失!

# 正确:用 FP32 对比验证
exported_fp16 = model.export(format="onnx", half=True)
exported_fp32 = model.export(format="onnx", half=False)
# 对比两个模型的精度,确认 FP16 没有精度损失

AOI 项目优秀实践

  • 统一导出规范:opset=12、IR=8、FP16、dynamic batch
  • 时间戳防覆盖:每个 ONNX 文件带时间戳,不会覆盖旧版本
  • 元信息记录 :每个 ONNX 文件有对应的 .txt 元信息

AOI 项目可改进之处

  • 缺少导出后精度验证工具:没有自动对比 FP16 和 FP32 精度
  • 缺少多格式导出支持:只支持 ONNX,不支持 TensorRT / OpenVINO
  • 缺少导出日志:没有记录每次导出的参数和结果

结尾

说回那个「C++ 加载 ONNX 报错」的故事。

后来我定了一套导出规范:opset=12、IR=8、FP16、dynamic batch。每个 ONNX 文件带时间戳和元信息。产线同事再也没报过加载错误。

不是因为 ONNX 有多难------而是因为导出参数一个都不能错。


翻翻你的项目:

  1. 你的 ONNX 导出用的是哪个 opset 版本?
  2. 你的 IR 版本是多少?旧版 ORT 能加载吗?
  3. 你的 ONNX 文件有元信息记录吗?

评论区说说你在 ONNX 导出上踩过最深的坑。


觉得有用?

收藏这篇,下次导出 ONNX 时翻出来对照。转发给你团队里那个天天被 ONNX 折磨的同事。


下期预告:

训练报告 99%,产线上只有 80%?

裁图验证 / Holdout 盲测 / 整板 E2E,三级验证体系全拆解。

下一篇,我们聊三级验证体系------「训练准 不等于 产线准」。


本文是《AOI 芯片焊盘缺陷检测实战》系列第六篇。

相关推荐
硅谷秋水1 小时前
Qwen-Drive-1.0:迈向自动驾驶视觉-语言基础模型的第一步
人工智能·深度学习·机器学习·语言模型·自动驾驶
wuminyu1 小时前
Kafka配置TLS/SSL加密传输时零拷贝失效分析
java·linux·c语言·jvm·c++
影视飓风TIM1 小时前
C++ 智能指针:auto_ptr / unique_ptr / shared_ptr / weak_ptr 原理与使用
开发语言·c++
这张生成的图像能检测吗1 小时前
(论文速读)基于两阶段多模式深度学习的轮胎表面缺陷自动检测及严重程度分类系统
人工智能·深度学习·计算机视觉·数据采集·检测系统·缺陷检测分类·轮胎缺陷
一木 之林1 小时前
李沐《动手学深度学习》知识卡合集:从 Softmax 回归到房价实战(第24-50集)
开发语言·c++·人工智能
无敌的牛2 小时前
大模型推理理解
人工智能·深度学习
keyipatience2 小时前
5种IO模型与阻塞IO,select,poll,epoll,LT和ET模式
linux·服务器·网络·数据结构·c++·算法
汉克老师2 小时前
CSP-J 初赛(以满分为目标):第三十八课《数学与逻辑①——排列还是组合?先搞清楚“选”与“排”》
c++·csp-j·小学生·学c++编程
qeen872 小时前
【C++】智能指针介绍
开发语言·c++·笔记·指针