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 芯片焊盘缺陷检测实战》系列第六篇。

相关推荐
xx_xxxxx_16 分钟前
论文阅读-RoTTA
论文阅读·人工智能·深度学习·机器学习
无忧.芙桃23 分钟前
C++语言原理与实践(十):vector类的底层实现
开发语言·c++·算法
无名猿28 分钟前
std::variant 完全指南:类型安全的 union 与 std::visit 用法
c++·stl·标准库·现代c++
乐迪信息30 分钟前
AI防爆摄像机,监测港口船舶航行偏航隐患
大数据·人工智能·深度学习·算法·计算机视觉
无名猿1 小时前
C++ 迭代器五分类与失效场景汇总:一张表避开所有 UB
c++·stl·标准库·踩坑记录
0+1111 小时前
算法 --模拟
c++·算法·leetcode
打工仔折腾 AI1 小时前
从BPE到SentencePiece:Transformer分词原理与Python实战对比
android·人工智能·python·深度学习·langchain·transformer·ai agent 实战
hetao17338371 小时前
2026-10-01~02 hetao1733837 的刷题记录
c++·算法
郝学胜-神的一滴2 小时前
AI 编程智能体 03:拆解当下主流智能体能力
开发语言·c++·人工智能·python·程序人生·游戏
YOLO数据集集合3 小时前
大模型融合YOLO铁路要素缺陷分析系统 | 铁路缺陷检测 YOLO DeepSeek 大语言模型 智能巡检 9141期
人工智能·yolo·目标检测·语言模型·铁路缺陷·轨道缺陷