news 2026/10/24 2:18:57

YOLO 训练好的模型怎么在 QT 下使用?从 ONNX 到 DirectML GPU 加速的完整落地

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLO 训练好的模型怎么在 QT 下使用?从 ONNX 到 DirectML GPU 加速的完整落地

YOLO 训练好的模型怎么在 QT 下使用?从 ONNX 到 DirectML GPU 加速的完整落地

YOLO 训练这步现在很省心。Ultralytics 一条命令,best.pt转best.onnx,几分钟的事。

麻烦的是下一步。

模型交到桌面端,怎么用?我们的场景很典型:产线检测工具要发给产线工程师,对方机器上不能要求装 Python,更不能要求每台机器都有 N 卡。最后用 VS2019 + Qt 5.14.2 + ONNX Runtime(DirectML 版)做了个纯 C++ 的检测工具YoloDetQt,导入模型、批量检测、统计导出,一条链路走完,全程零 Python 依赖。

先看跑起来的效果。PCB 缺陷检测,六类缺陷,GPU 加速下单张 24ms:

模型导入之后,类别名、输入尺寸、执行设备全是自动解析的。换别的模型,一行代码不用动:

下面把整个过程摊开讲:工程怎么配,推理代码长什么样,CPU 和 GPU 的配置差在哪,以及当时为什么没走 CUDA 而是选了 DirectML。


整体路线:pt 是进不了 Qt 的,先转 onnx

一件事先说死:Qt/C++ 端加载不了.pt。别在这上面浪费时间,标准做法就两步。

第一步在训练环境里导出:

fromultralyticsimportYOLO model=YOLO("best.pt")model.export(format="onnx",imgsz=640)# 得到 best.onnx

第二步是这篇文章的正题。

推理后端我们踩过一次坑。一开始试的 OpenCV DNN,想着能少带一个依赖库,结果 YOLO26 导出的计算图,OpenCV 4.14 的 legacy 导入器和 5.0 的新导入器都啃不动。换 ONNX Runtime,一次通过。而且后来发现它还有个更大的好处——DirectML 版本自带 GPU 加速,不需要 CUDA。这个后面单独说。


工程配置

整个工程就两样依赖:Qt5 管界面,onnxruntime 管推理。图像读写和画框全用 Qt 自带的 QImage 和 QPainter,连 OpenCV 都没带。

onnxruntime 的运行库直接随工程自带,目录长这样:

YoloDetQt/ ├── YoloDetQt.pro # qmake 工程入口 ├── CMakeLists.txt # CMake 入口(跟 .pro 二选一) ├── build_msvc.bat # 一键构建脚本 ├── deps/ │ └── onnxruntime/ # DirectML 版 ONNX Runtime │ ├── include/ # onnxruntime_cxx_api.h 等头文件 │ ├── lib/ # onnxruntime.lib │ └── bin/ # onnxruntime.dll + DirectML.dll ★ └── src/ ├── YoloDetector.* # 推理封装,本文核心 ├── DetectWorker.* # 批量推理后台线程 ├── MainWindow.* # 主界面 └── ...

bin 里那个 DirectML.dll,是 GPU 加速的关键,必须和 exe 待在同一目录。记住这句话,后面有个坑就是它挖的。

qmake 配置没什么花头,头文件路径、链接库、构建完拷 DLL,三件事:

# ---- onnxruntime(随工程自带)---- ORT_ROOT = $$PWD/deps/onnxruntime INCLUDEPATH += $$ORT_ROOT/include LIBS += -L$$ORT_ROOT/lib -lonnxruntime win32 { # 源码是 UTF-8,不加这个 MSVC 会按本地代码页解析,中文字符串乱码 QMAKE_CXXFLAGS += /utf-8 # 构建完成后把三个 DLL 拷到 exe 目录 QMAKE_POST_LINK += $$QMAKE_COPY \"$$ORT_ROOT/bin/onnxruntime.dll\" \"$$OUT_PWD\" QMAKE_POST_LINK += $$QMAKE_COPY \"$$ORT_ROOT/bin/onnxruntime_providers_shared.dll\" \"$$OUT_PWD\" QMAKE_POST_LINK += $$QMAKE_COPY \"$$ORT_ROOT/bin/DirectML.dll\" \"$$OUT_PWD\" }

CMake 那边同理,链接onnxruntime.lib,POST_BUILD 里拷同样三个 DLL,就不重复贴了。

顺带一句,/utf-8这个选项建议所有 Qt + MSVC 工程都无脑加上,不然界面中文乱码,这个坑不值得踩第二遍。

发布的时候,exe 旁边要带齐这几个文件:

文件作用缺了会怎样
onnxruntime.dll推理引擎本体程序起不来
onnxruntime_providers_shared.dllEP 共享库部分提供者加载失败
DirectML.dllDirectML 后端静默回退 CPU,不报任何错
Qt5*.dll / platforms/ 等Qt 运行时界面起不来

表格里第三行要展开讲讲。DirectML.dll 缺失的时候程序不会崩、不会报错,只是悄悄用 CPU 跑,一切功能照常。我们就被这个静默回退坑过一次——工具发出去用了两天,同事说他那边怎么这么慢,一看执行设备,CPU。查下来是构建时 DLL 没拷过去。

从那以后我们在模型信息栏加了一行"执行设备"的显示。GPU 配置到底生没生效,界面上得能直接看出来,不能靠猜:

核心代码

推理封装成一个YoloDetector类,对上层就两个接口:load()和detect()。核心代码不到一百行。

加载模型,GPU 和 CPU 的分岔口就在这

DirectML 的 EP 入口函数,DML 版的 onnxruntime.dll 里是导出了的,但公共头文件没声明,得自己补一行 extern “C”。然后加载时先试着走 GPU,不行就落回 CPU:

extern"C"OrtStatus*ORT_API_CALLOrtSessionOptionsAppendExecutionProvider_DML(OrtSessionOptions*options,intdevice_id);boolYoloDetector::load(constQString&onnxPath,QString*error){m_env=std::make_unique<Ort::Env>(ORT_LOGGING_LEVEL_WARNING,"YoloDetQt");automakeOptions=[nThreads](){Ort::SessionOptions opts;opts.SetIntraOpNumThreads(nThreads);// CPU 线程数,默认 min(4, 核数)opts.SetGraphOptimizationLevel(ORT_ENABLE_ALL);returnopts;};booldmlOk=false;if(m_useGpu){// 先试 GPU (DirectML)try{Ort::SessionOptions opts=makeOptions();OrtStatus*st=OrtSessionOptionsAppendExecutionProvider_DML(opts,0);if(!st){m_session=std::make_unique<Ort::Session>(*m_env,onnxPath.toStdWString().c_str(),opts);dmlOk=true;}}catch(conststd::exception&){m_session.reset();// DML 不可用,落回 CPU}}if(!dmlOk){// CPU 路径Ort::SessionOptions opts=makeOptions();m_session=std::make_unique<Ort::Session>(*m_env,onnxPath.toStdWString().c_str(),opts);}m_info.provider=dmlOk?"GPU (DirectML)":"CPU";// ... 解析输入输出形状、类别名}

这就是 CPU 和 GPU 的全部配置差异了。同一个 DLL,同一份代码,跑在哪个设备只取决于中间那一行AppendExecutionProvider_DML。CPU 路径什么都不用加,默认就是 CPU EP。

这个写法还有个白送的好处:DML 注册失败或者显卡不支持,会自动落到 CPU 上接着跑。工具发到任何一台 Windows 机器上都能用,区别只是快慢。

预处理:跟训练侧对不齐,全白搭

推理结果对不对,预处理说了算。这里必须和 Ultralytics 训练、验证时的处理严格一致:等比缩放、短边补 114 的灰边、RGB、除 255、NCHW。少对齐一项,精度就对不上。

// letterbox:等比缩放,短边补 114 灰边constfloatr=qMin(float(H)/ih,float(W)/iw);constintnh=qRound(ih*r),nw=qRound(iw*r);constinttop=(H-nh)/2,left=(W-nw)/2;QImage resized=image.convertToFormat(QImage::Format_RGB888).scaled(nw,nh,Qt::IgnoreAspectRatio,Qt::SmoothTransformation);// NCHW 浮点输入,padding 区保持 114 灰std::vector<float>input(size_t(W)*H*3,114.f/255.f);for(inty=0;y<nh;++y){constuchar*line=resized.constScanLine(y);float*rRow=input.data()+size_t(top+y)*W+left;for(intx=0;x<nw;++x){rRow[x]=line[x*3+0]/255.f;// R 平面rRow[x+plane]=line[x*3+1]/255.f;// G 平面rRow[x+2*plane]=line[x*3+2]/255.f;// B 平面}}

前向

Ort::MemoryInfo mem=Ort::MemoryInfo::CreateCpu(OrtArenaAllocator,OrtMemTypeDefault);constint64_tshape[4]={1,3,H,W};Ort::Value inputTensor=Ort::Value::CreateTensor<float>(mem,input.data(),input.size(),shape,4);constchar*inName="images";// 从 session 读出的实际输入输出名constchar*outName="output0";autot0=std::chrono::steady_clock::now();std::vector<Ort::Value>outputs=m_session->Run(Ort::RunOptions{nullptr},&inName,&inputTensor,1,&outName,1);autot1=std::chrono::steady_clock::now();// t1 - t0 就是单张推理耗时

写法跟 Python 端的session.run()一一对应,用过 Python 推理的人看一眼就懂。

解码和 NMS

YOLO26 的输出布局是[1, 4+nc, A],A 是锚点数,640 输入对应 8400。前 4 行是cx,cy,w,h,后面每行一个类别的分数,没有 objectness。

解码流程文字描述就够:遍历锚点取各类别最大分数,过 conf 阈值,按类别做贪心 NMS,最后把坐标经 letterbox 逆变换还原回原图,也就是(x - pad) / scale这么一下。

有两个细节容易翻车。

一是布局要自适应。YOLOv5 导出的图带 objectness(5+nc),YOLOv8/26 不带(4+nc),我们按rows == nc + 5自动区分,两类模型通吃。

二是类别名不用硬编码。Ultralytics 导出时把names写进了 ONNX 的metadata_props,手工解析 protobuf 把它取出来就行,多少个类别都能正确显示。

检测出来的效果,框按类别配色,置信度标在框上:

CPU 和 GPU 差多少

配置上的差异前面已经说了,就一行代码加一个 DLL。那性能呢?

我们在 RTX 3050 上跑了 100 张 640×640 的 PCB 图。CPU 四线程,单张 286.6ms,折合 3.5 FPS;换 DirectML 走 GPU,单张 24.1ms,41.4 FPS。十一倍多的加速。

比数字更重要的是另一件事:两边逐图比对过,检测出的目标数、类别分布完全一致。换后端没有引入任何数值偏差,这点验完我们才敢把 GPU 开关默认打开。

界面上的切换就是个勾选框,切完重载模型生效:

检测结果会缓存在内存里,改置信度阈值只重新解码、不重新推理,一百张图刷一遍不到一百毫秒。调阈值的时候这个体验差距非常大:

为什么不是 CUDA

这是选型时被问得最多的问题。我们的结论:分发给别人的 Windows 桌面工具,DirectML 就是最优解。CUDA 适合自家机房、硬件全可控的场景。

理由展开说。

最直接的一条,CUDA 只认 N 卡。DirectML 建在 DirectX 12 上,NVIDIA、AMD、Intel 全覆盖,办公本的核显也算数。我们发出去的工具,产线上一半机器是没有独显的办公机——走 CUDA 的话这些机器直接不可用,除非再维护一条 CPU 路径,等于两套逻辑两套测试。

第二条是部署。CUDA 方案的依赖清单:显卡驱动版本要匹配,CUDA Runtime 要装或者要带,cuDNN 要装或者要带,onnxruntime-gpu 的版本还得跟 CUDA、cuDNN 三者对齐。任何一台机器上组合不对,就是一串CUDA_ERROR_XXX,远程排查能把人折腾死。DirectML 呢?一个 DirectML.dll,18MB,放 exe 旁边,完事。驱动要求宽松到"支持 DX12 就行",近十年的卡全满足。

第三条,性能。说实话这是我最开始担心的,实测之后不担心了。24ms 一张、41 FPS 的吞吐,产线单张检测场景绰绰有余。DirectML 作为通用后端,极限性能确实比不过同卡深度调优的 CUDA + TensorRT,但那是服务端批处理该操心的事,一个桌面工具犯不着。

第四条算是保险:ONNX Runtime 的 DirectML EP 是微软官方维护的执行提供者,不是社区魔改,失败回退 CPU 的行为也是框架原生的。接入成本一行代码,鲁棒性白送。

一句话收尾:CUDA 快,但只快在 N 卡上;DirectML 稍慢一点,可它让工具在任何一台 Windows 机器上都跑得起来。桌面工具要的是发出去就能用,不是单机极限性能。


最后的建议

整个过程压缩一下就五步:导出 onnx,工程里带上 directml 版的 onnxruntime,写推理封装(加载、letterbox、Run、解码),跟 Python 端逐图比对验证,部署时 DLL 拷齐。

但真正想留给大家的是前面那个教训:**一定要在 UI 上把执行设备显示出来。**DirectML 缺 DLL 的时候是静默回退的,没有这个显示,你以为在用 GPU,其实一直在用 CPU,还纳闷为什么加速没效果。


工程环境:VS2019 + Qt 5.14.2(msvc2017_64)+ ONNX Runtime 1.24.4 DirectML 版;测试模型 Ultralytics YOLO26m(PCB 缺陷 6 类);测试硬件 RTX 3050。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/24 2:18:55

船舶封闭空间火灾中的烟气风险与船用材料抑烟设计

提到船舶安全&#xff0c;大多数人首先想到的是阻燃。但对于船舱、走廊、客舱这些相对封闭的空间来说&#xff0c;火灾带来的风险并不只有火焰。浓烟会影响人员视线&#xff0c;也可能增加疏散和救援的难度。在船上&#xff0c;烟的威胁有时比火本身更直接。封闭空间放大了烟的…

作者头像 李华
网站建设 2026/10/24 2:18:55

【办公类-38-02】20240305 班级微信优质指导交流(word单元格插入统一大小的微信截图,用文字替换方式修改基本信息、Word模版上传)

一、背景需求搭档问我要中2班上学期的资料&#xff0c;我把最后的微信优质指导做完&#xff0c;就打包发送了。因为旧代码和旧Word都卡死在坏掉的电脑里&#xff0c;所以要重做模版二、Word模版制作本次把Word模版上传CSDN&#xff0c;备份三、代码 微信优质家教指导(一页)自动…

作者头像 李华
网站建设 2026/10/24 2:16:52

画面还在动,现场却已过去:一次内窥镜视频卡顿的排查笔记

“帧率一直显示60&#xff0c;为什么操作的人说画面慢半拍&#xff1f;” 这是一次虚构的工程排查。没有患者&#xff0c;没有临床操作&#xff0c;测试对象是一套实验室视频原型与运动目标。故障很有迷惑性&#xff1a;界面不卡&#xff0c;录像可用&#xff0c;监控数字也漂…

作者头像 李华
网站建设 2026/10/24 2:15:01

Linux:应用层协议详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/24 2:14:38

三步把 yoRadio 接入 Home Assistant:MQTT 完整打通

三步把 yoRadio 接入 Home Assistant&#xff1a;MQTT 完整打通 【免费下载链接】yoradio Web-radio based on ESP32-audioI2S library 项目地址: https://gitcode.com/GitHub_Trending/yo/yoradio 电台能放歌了&#xff0c;但切台还得走到机器前按按钮&#xff0c;或者…

作者头像 李华