跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++AI算法

YOLO11 模型 C++ 部署:ONNX 导出、NMS 判别与推理实战

YOLO11 模型 C++ 部署流程涵盖 ONNX 导出配置、NMS 节点判别及推理环境搭建。核心在于使用 nms=True 参数导出 end2end 模型,并通过输出张量形状验证是否包含 NonMaxSuppression 算子。针对无 NMS 的原始头模型,需在 C++ 端手动实现后处理逻辑,同时解决 CMake 配置及依赖库兼容性问题以确保稳定运行。

dehua dong发布于 2026/1/6更新于 2026/6/1121 浏览
YOLO11 模型 C++ 部署:ONNX 导出、NMS 判别与推理实战

YOLO11 模型 C++ 部署:ONNX 导出、NMS 判别与推理实战

1. 现象与本质

现象:C++ 推理出现'满屏框'或'0 框'。 根因:导出的 ONNX 多为不带 NMS 的原始头,输出形状常见 [1, 84, 8400](84=4+80),模型不会帮你做阈值与 NMS;C++ 若不做后处理,就会'满屏框'。

在这里插入图片描述

相对地,带 NMS 的 ONNX(end2end) 会直接输出最终框,形状常见 [N,6] 或 [1,N,6](x1 y1 x2 y2 score class),C++ 只需画框。


2. NMS 是什么?为什么一定要做?

YOLO 在多尺度(如 80×80、40×40、20×20)上为每个网格点预测很多候选框。同一目标附近会出现大量位置相近、得分也不低的候选。NMS(Non-Maximum Suppression,非极大值抑制)的任务就是:在重叠的候选中,只保留那个最靠谱的(分数最高),把与之高度重叠的重复框删掉。

2.1 NMS 是怎么做的(硬 NMS 伪代码)
  • IoU(交并比):IoU(A,B) = Area(A∩B) / Area(A∪B) ∈ [0, 1]
  • 硬 NMS 两步:
    1. 按置信度降序排序;
    2. 依次取最高分框,删除与它 IoU 大于阈值(如 0.45)的后续框。
# boxes: [N, 4] in x1y1x2y2
# scores: [N]
# thr_conf: 置信度阈值(如 0.25)
# thr_iou: NMS IoU 阈值(如 0.45)
keep = []
idxs = argsort(scores, descending=True)
while idxs not empty:
    i = idxs[0]
    keep.append(i)
    ious = IoU(boxes[i], boxes[idxs[1:]])
    idxs = idxs[1:][ious <= thr_iou]
return keep

常见做法是按类别分别做 NMS(class-wise NMS)。Ultralytics 默认如此。

2.2 一个极小数值例子
idxscorebox(x1,y1,x2,y2)
00.90(10,10,50,50)
10.85(12,12,49,49)
20.60(200,200,260,260)
30.40(205,205,258,258)
  • 0 与 1 重叠很高 → 留 0,抑制 1
  • 2 与 3 接近 → 留 2,抑制 3 最终仅保留 idx=0、idx=2 两个框

3. 最佳做法:导出'带 NMS'的 ONNX

必须从 .pt 权重导出;不能在现有 .onnx 上'打开 NMS'。

from ultralytics import YOLO
m = YOLO(r"path/to/best.pt")  # 训练得到的 .pt 或 yolov8n.pt
m.export(format="onnx", imgsz=640,  # 与训练一致 dynamic=False, simplify=True,  # 安装了 onnxsim 就 True,没有也行 nms=True,  # ★★★ 关键:将 NonMaxSuppression 写进图里 conf=0.25,  # 内置置信阈 iou=0.45,  # 内置 NMS IoU opset=12  # 12/13/16 均可,保持一致即可)

导出成功后,继续下一节检查是否真的带 NMS。


4. 如何判断是否'带 NMS'

4.1 Python(最快)
import onnxruntime as ort
sess = ort.InferenceSession("best.onnx", providers=["CPUExecutionProvider"])
print([(o.name, o.shape) for o in sess.get_outputs()])  # 看到 [..., 6] / [..., 7] => 带 NMS
# 看到 84/85 出现在任一维 => 原始头(不带 NMS)

在这里插入图片描述

4.2 C++(不依赖 Python)
#include <onnxruntime_cxx_api.h>
#include <iostream>
#include <vector>
#include <filesystem>

static std::string Shape2Str(const std::vector<int64_t>& v){
  std::string s = "[";
  for(size_t i = 0; i < v.size(); ++i){
    s += std::to_string(v[i]);
    if(i + 1 < v.size()) s += ',';
  }
  return s += ']';
}

static bool LikeNMS(const std::vector<int64_t>& shp){
  return shp.size() >= 2 && (shp.back() == 6 || shp.back() == 7);
}

static bool LikeRaw(const std::vector<int64_t>& shp){
  for(auto d : shp)
    if(d == 84 || d == 85) return true;
  return false;
}

static void InspectOutputs(const Ort::Session& sess){
  const size_t n = sess.GetOutputCount();
  bool bAnyN = false, bAnyR = false;
  for(size_t i = 0; i < n; ++i){
    Ort::AllocatorWithDefaultOptions alloc;
    auto name = sess.GetOutputNameAllocated(i, alloc);
    auto shp = sess.GetOutputTypeInfo(i).GetTensorTypeAndShapeInfo().GetShape();
    std::cout << "out[" << i << "] " << (name ? name.get() : "(null)") << " shape=" << Shape2Str(shp) << "\n";
    bAnyN |= LikeNMS(shp);
    bAnyR |= LikeRaw(shp);
  }
  if(bAnyN) std::cout << "[detect] ✅ 带 NMS 的 end2end 输出\n";
  else if(bAnyR) std::cout << "[detect] ⚠️ 原始头(不带 NMS)\n";
  else std::cout << "[detect] ℹ️ 非常见形状,建议用 Netron 检查是否含 NonMaxSuppression\n";
}

int main(int argc, char** argv){
  if(argc < 2){
    std::cout << "Usage:\n " << argv[0] << " <onnx_model>\n";
    return 0;
  }
  std::filesystem::path strOnnxPath = argv[1];
  if(!std::filesystem::exists(strOnnxPath)){
    std::cerr << "[error] model not found: " << strOnnxPath;
    return 1;
  }
  // ... truncated
}

5. 输出张量的物理含义

(此处省略后续内容,基于原文逻辑推断)

6. 最小 C++ 示例(带 NMS 的 ONNX)

(此处省略后续内容)

7. 如果手上只有'原始头'ONNX(84/85)怎么办?

(此处省略后续内容)

8. vcpkg 无 ORT Config.cmake:CMake 兜底

(此处省略后续内容)

9. 常见坑位与速查

(此处省略后续内容)

10. Checklist

(此处省略后续内容)

附录:NMS 变体与阈值选择

(此处省略后续内容)

目录

  1. YOLO11 模型 C++ 部署:ONNX 导出、NMS 判别与推理实战
  2. 1. 现象与本质
  3. 2. NMS 是什么?为什么一定要做?
  4. 2.1 NMS 是怎么做的(硬 NMS 伪代码)
  5. boxes: [N, 4] in x1y1x2y2
  6. scores: [N]
  7. thr_conf: 置信度阈值(如 0.25)
  8. thr_iou: NMS IoU 阈值(如 0.45)
  9. 2.2 一个极小数值例子
  10. 3. 最佳做法:导出“带 NMS”的 ONNX
  11. 4. 如何判断是否“带 NMS”
  12. 4.1 Python(最快)
  13. 看到 84/85 出现在任一维 => 原始头(不带 NMS)
  14. 4.2 C++(不依赖 Python)
  15. 5. 输出张量的物理含义
  16. 6. 最小 C++ 示例(带 NMS 的 ONNX)
  17. 7. 如果手上只有“原始头”ONNX(84/85)怎么办?
  18. 8. vcpkg 无 ORT Config.cmake:CMake 兜底
  19. 9. 常见坑位与速查
  20. 10. Checklist
  21. 附录:NMS 变体与阈值选择
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Java 后端 Web API 开发实战:从架构到部署
  • 超越代码生成器:深度解析 Triton-Copilot 人机协同设计哲学
  • Vue3 中方法调用失效的排查与修复方案
  • Claude AI 注册流程及海外手机号验证解决方案
  • 西门子 S7-1200 PLC 与爱普生机器人 Modbus TCP 通讯配置
  • 机器人送料机械手设计方案
  • FastAPI:Python 高性能 Web 框架核心特性解析
  • Function Call、MCP 与 Agent:大模型能力扩展的核心机制解析
  • Python pip 配置国内镜像源方法(清华/阿里云/中科大)
  • Claude Code 本地化配置指南:接入魔搭社区 API 实现国产模型调用
  • AI 赋能原则 10 解读:政府 2.0 与公共智能系统建设
  • AI 上下文优化实战:解决过载与不足的平衡之道
  • 操作系统智能助手 OS Copilot 新功能测评
  • MCP 协议详解:与 Function Call 的区别及使用方式
  • Stable Diffusion 模型加载报错:CheckpointLoaderSimple 错误修复
  • 使用 Go 与 DeepSeek V3.2 构建智能日志分析系统
  • 基孔肯雅热流行风险地区 WebGIS 分类与可视化实战
  • N_m3u8DL-RE 流媒体下载工具使用指南
  • 通用 FPGA 开发流程详解:从设计定义到板级验证
  • TRAE vs Qoder vs Cursor vs Copilot:主流 AI 编程工具深度横评

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

  • 随机西班牙地址生成器

    随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online