资讯动态

Qt调用神经网络实战:Windows下ONNX Runtime集成与部署指南

发布时间:2026/10/21 21:34:33 来源:尧图企业网站定制
把训练好的模型塞进Windows桌面程序是很多Qt开发者迟早要面对的需求。模型训练阶段大家几乎都在Python环境里折腾可一旦要交给终端用户、做成一个能双击运行的exe问题就来了怎么让C/Qt程序把神经网络跑起来还得跑得稳、跑得快、部署不闹心。这篇文章就围绕“Window下Qt调用神经网络”这个场景把方案选型、环境配置、核心推理代码、线程处理、部署避坑一路讲透所有步骤都是我实际踩过坑之后整理出来的可以直接对着抄。先说结论Qt里调用神经网络最省心的路子是用ONNX Runtime的C接口。模型先用Python训练好、导出成ONNX格式然后Qt程序直接加载这个文件做推理。整套流程下来不需要在客户端额外装Python环境不依赖庞大的训练框架产出的exe加DLL就能分发。下文所有内容都围绕这套方案展开附带我实测过的CMake配置和完整推理代码。1. 方案选型为什么最终选了ONNX Runtime1.1 摆在面前的几条路Qt项目里要跑神经网络主流选项其实就三个LibTorchPyTorch的C前端、TensorFlow C API、ONNX Runtime。我当初在选型的时候分别都调研试过一遍各有各的坑。LibTorch的完整包解压出来好几个GB光链接这一步就能把人劝退。而且LibTorch对MSVC版本、C标准都很挑剔Debug和Release配置稍有不对就是一串看不懂的链接错误。TensorFlow C API更折腾官方没有直接提供预编译的Windows二进制想用要么自己用CMake从源码编译——那个体量和时间成本正常情况下你根本跑不完。两个方案试下来我都想放弃直接在Qt里接神经网络了。最后转到ONNX Runtime一下子清爽很多。它本身就是微软开源的跨平台推理引擎官方直接提供Windows平台的预编译库解压出来include、lib目录清清楚楚CMake里加几行配置就能用。更重要的是PyTorch和TensorFlow训练好的模型都能导出成ONNX格式转换工具成熟不用为了某个框架把自己绑死。1.2 ONNX Runtime解决的核心痛点选择ONNX Runtime本质上是想解决三个痛点。第一个痛点是客户端环境依赖。如果走Python方案客户端得装Python还得把所有依赖包一并打包这意味着用户的机器上多了一整套解释器。用ONNX Runtime最终交付的只是一个原生DLL和Qt自己的DLL放在一起就行。第二个痛点是性能损耗。Python调用模型有解释器开销进程间通信也费时间而ONNX Runtime直接以C库的形式嵌入进程推理就是一次本地函数调用性能明显更好。官方还做了算子融合和内存优化CPU上的推理速度通常比直接在PyTorch里跑还快一些。第三个痛点是跨平台。Qt应用本身讲究跨平台ONNX Runtime的Windows、Linux、macOS版本都有代码层面调用接口基本一致。今天在Windows上写完的推理代码以后要移植到Linux改动量非常小。1.3 整体架构设计选型定了之后整体架构就很清晰了。模型训练在Python环境完成训练好后用torch.onnx.export导出为.onnx文件。Qt应用通过ONNX Runtime加载这个文件把用户选择的图片读进来做预处理、转为张量调用Ort::Session::Run完成推理最后把输出结果映射回业务层展示。这里要特别强调一个原则推理代码不要沾UI线程。神经网络推理再快在小模型上也要几十毫秒大模型要几百毫秒甚至更久直接放在按钮点击事件里会把界面卡死。所以我的架构里单独封装了一个推理服务类跑在子线程里通过信号槽把结果回传给主界面。这个细节后面专门讲。2. 环境配置Windows下Qt ONNX Runtime的完整搭建2.1 获取ONNX Runtime库ONNX Runtime官方发布页面会提供多个预编译包Windows环境下我们关注两个版本CPU版和GPU版。GPU版要额外装显卡驱动和CUDA/DirectML运行时如果目标机器显卡型号不可控建议先老老实实用CPU版。CPU版对机器零要求普通办公电脑都能跑只是大模型上速度会慢一些。下载时也要注意架构64位程序用x64包32位用x86包。我项目里用的x64的1.16.3版本解压出来的目录结构大概是这样onnxruntime-win-x64-1.16.3/ ├── include/ │ ├── onnxruntime_c_api.h │ ├── onnxruntime_cxx_api.h │ ├── onnxruntime_cxx_inline.h │ └── ... ├── lib/ │ └── onnxruntime.lib └── bin/ └── onnxruntime.dllinclude里有完整的C头文件lib目录下的onnxruntime.lib是链接用的导入库bin目录里的onnxruntime.dll是运行时真正加载的动态库。需要把所有头文件路径、导入库和DLL都招呼到项目里。注意ONNX Runtime的DLL分Release和Debug吗答案是预编译包里就一份不区分。不管Qt程序是Debug还是Release链接的都是同一个lib。这一点和很多原生库不太一样反倒是省了Debug下还要单独换一份DLL的精力。2.2 CMake集成配置接下来是真正的工程配置。我习惯用CMake管理Qt项目在CMakeLists.txt里加入ONNX Runtime相关的路径就行。核心配置如下cmake_minimum_required(VERSION 3.16) project(QtOnnxDemo) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) # Qt模块 find_package(Qt6 REQUIRED COMPONENTS Widgets Gui) # ONNX Runtime路径按自己实际解压位置修改 set(ONNX_RUNTIME_DIR D:/third_party/onnxruntime-win-x64-1.16.3) include_directories(${ONNX_RUNTIME_DIR}/include) link_directories(${ONNX_RUNTIME_DIR}/lib) add_executable(QtOnnxDemo main.cpp MainWindow.cpp MainWindow.h InferenceEngine.cpp InferenceEngine.h ) target_link_libraries(QtOnnxDemo PRIVATE Qt6::Widgets Qt6::Gui onnxruntime )这里面有个关键的坑link_directories指定的目录要确保能找到onnxruntime.lib而运行时要保证onnxruntime.dll在exe同目录下或者在你的PATH环境变量里。团队协作时每个成员的机器上库路径可能不一样我建议把ONNX Runtime目录放进项目仓库里的third_party子目录用相对路径引用这样就避免了路径不一致的问题。如果你的项目还在用Qt5find_package那行改成find_package(Qt5 COMPONENTS Widgets Gui)即可其他配置不变。2.3 MSVC运行时和位数一致性Windows下最容易翻车的不是CMake配置本身而是编译器选项和位数不匹配。首先是位数。ONNX Runtime x64包只能给x64程序用。如果在Visual Studio里把解决方案平台设成了Win32链接时会报“无法解析的外部符号”但这个错误很具有迷惑性——因为这通常被解读为代码问题实际上根本不是。开发Qt程序首选x64因为ONNX Runtime的CPU包也只提供x64版本同时也提供Win32包但需要自己确认对应位数。其次是MSVC运行时。ONNX Runtime预编译库是用MT静态链接还是MD动态链接编译的官方文档明确说它的DLL依赖最新版微软Visual C运行时所以你的Qt程序需要使用MD运行时默认就是MD。如果你项目里改了运行库设置强烈建议保持默认。另外一直在使用老版本MSVC的话可能要安装对应的Visual C Redistributable否则启动时弹窗“找不到vcruntime140.dll”连程序都跑不起来。2.4 头文件和DLL的部署策略开发环境配置好只是第一步真正的麻烦在发布环节。Qt程序的发布本来就要打包各种插件DLL现在多了onnxruntime.dll部署策略得提前想清楚。我的习惯是把onnxruntime.dll拷贝到exe同级的目录里。如果使用了Qt的windeployqt工具它会自动补齐Qt相关的DLL但不会帮你处理onnxruntime.dll所以这一步必须手动操作。项目里我写了个脚本先跑windeployqt再手动copy onnxruntime.dll进输出目录两条命令一气呵成windeployqt build/Release/QtOnnxDemo.exe copy D:\third_party\onnxruntime-win-x64-1.16.3\bin\onnxruntime.dll build\Release\有些团队喜欢把所有第三方DLL放到单独的bin目录再设置PATH这样也可以但给用户的分发包越简单越好DLL和exe放同一目录是最不容易出幺蛾子的方案。3. 核心推理代码从加载模型到输出结果3.1 模型导出PyTorch到ONNX这一步在Python环境完成相对简单但还有些要注意的地方。我以一个分类模型为例import torch import torchvision model torchvision.models.resnet18(pretrainedTrue) model.eval() # 导出时需要给一个虚拟输入来定义张量形状 dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, resnet18.onnx, input_names[input], output_names[output], dynamic_axes{ input: {0: batch_size}, output: {0: batch_size} }, opset_version17 )这里有两个点直接决定后面Qt代码怎么写。第一个是dummy_input的尺寸它定义了模型的输入形状导出后输入张量就是[1, 3, 224, 224]分别代表batch大小、通道数、高、宽。如果模型是其他尺寸后续预处理时必须匹配。第二个是dynamic_axes我这里把batch维度设成了动态意思是推理时batch可以不是1想一次跑多张图也可以。但动态轴会牺牲一点推理性能不是特别需要就不要加。导出后建议用Netron工具打开看一下模型结构重点确认输入名、输入类型、输出名和输出形状。名字后续在C代码里要用形状决定预处理怎么写这两个信息不能凭记忆。3.2 ONNX Runtime核心调用流程在Qt里调用ONNX Runtime核心是封装一个推理引擎类。这个类负责三件事加载模型、预处理图片、执行推理。我通常会单独建一个InferenceEngine类和UI层彻底解耦这样既能被主界面调用也方便以后做单元测试和复用。先看加载模型和会话配置的代码// InferenceEngine.h #ifndef INFERENCEENGINE_H #define INFERENCEENGINE_H #include opencv2/opencv.hpp #include onnxruntime_cxx_api.h #include QString #include QImage #include vector #include string class InferenceEngine { public: InferenceEngine(); ~InferenceEngine(); bool loadModel(const QString modelPath, bool useGpu false); std::vectorfloat classifyImage(const QImage image); private: cv::Mat qImageToMat(const QImage image); void preprocess(cv::Mat image, int targetWidth, int targetHeight); std::shared_ptrOrt::Session session_; Ort::Env env_; Ort::MemoryInfo memoryInfo_; std::vectorstd::string inputNames_; std::vectorstd::string outputNames_; }; #endif构造函数里初始化环境变量。Ort::Env是ONNX Runtime的全局环境参数里有个日志级别我一般设置成ORT_LOGGING_LEVEL_WARNING只有在推理出现问题时才打印信息正常运行时免去刷屏InferenceEngine::InferenceEngine() : env_(ORT_LOGGING_LEVEL_WARNING, qt_onnx_inference) { memoryInfo_ Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault); } bool InferenceEngine::loadModel(const QString modelPath, bool useGpu) { Ort::SessionOptions sessionOptions; // 优化级别如果模型结构固定可以升到全部优化 sessionOptions.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL); if (useGpu) { // 如果需要GPU需链接onnxruntime的GPU版本并启用CUDA // sessionOptions.AppendExecutionProvider_CUDA(0); } try { session_ std::make_sharedOrt::Session( env_, modelPath.toStdString().c_str(), sessionOptions); // 读取输入输出名称和形状 Ort::AllocatorWithDefaultOptions allocator; inputNames_.clear(); outputNames_.clear(); for (size_t i 0; i session_-GetInputCount(); i) { auto name session_-GetInputNameAllocated(i, allocator); inputNames_.push_back(name.get()); } for (size_t i 0; i session_-GetOutputCount(); i) { auto name session_-GetOutputNameAllocated(i, allocator); outputNames_.push_back(name.get()); } } catch (const Ort::Exception e) { // 记录日志返回false return false; } return session_ ! nullptr; }这里有一个细节值得注意GetInputNameAllocated返回的是智能指针我用name.get()取出来拷到std::string里避免后面作用域结束指针释放后字符串失效。这个坑我踩过一次报错时提示“invalid input name”排查半天才发现是这片内存已经被释放了。3.3 图像预处理QImage到张量的无缝衔接图像预处理是最容易出错的环节因为推理模型的输入是纯数据而Qt里面拿到的是QImage。这两者之间有三层差异格式、尺寸、数值范围。先说格式。QImage有ARGB32、RGB888、Gray8等格式而模型输入基本都是RGB三通道浮点数。我的处理思路是先把QImage转成cv::Mat利用OpenCV成熟的处理函数完成统一缩放和归一化。虽然引入OpenCV多了一个依赖但图像处理这块实在太省心了完全不亏。QImage转cv::Mat的代码cv::Mat InferenceEngine::qImageToMat(const QImage image) { QImage img image.convertToFormat(QImage::Format_RGB888); cv::Mat mat(img.height(), img.width(), CV_8UC3, const_castuchar*(img.bits()), static_castsize_t(img.bytesPerLine())); return mat.clone(); // 深拷贝防止QImage销毁 }对QImage用convertToFormat固定成RGB888是很保险的做法。很多代码直接按照ARGB32去构造Mat一旦输入图片格式变化就会读到错误数据。然后bytesPerLine不能漏QImage每行的字节数可能不是width3刚好对齐而cv::Mat默认的行宽就是width3。如果不补上这个参数图像就会出现“斜线”花屏。接着是预处理核心三步缩放、转换通道顺序、归一化void InferenceEngine::preprocess(cv::Mat image, int targetWidth, int targetHeight) { cv::resize(image, image, cv::Size(targetWidth, targetHeight)); // OpenCV默认是BGR顺序模型要求是RGB需要置换通道 cv::cvtColor(image, image, cv::COLOR_BGR2RGB); // 转换为float并归一化到[0,1] image.convertTo(image, CV_32FC3, 1.0 / 255.0); }关于归一化不一定都是除以255。有些模型会用ImageNet的均值和标准差做标准化公式是(x - mean) / std。这个必须看模型训练时的预处理方式不能想当然。训练给的数据是怎么处理的推理就必须一模一样不然精度会莫名下降。为了高效执行应该把预处理后的数据按照模型的张量布局填进输入buffer。常见布局是NCHW也就是按batch、通道、高、宽的顺序排列。也就是说224x224的RGB图内存里应该依次是全部R通道的224x224像素再是G通道再是B通道。而cv::Mat里是RGBRGBRGB交替排列的HWC布局需要手动拆开重排std::vectorfloat InferenceEngine::matToNCHW(const cv::Mat image) { std::vectorfloat inputTensor(image.total() * 3); // 拆通道填充 for (int c 0; c 3; c) { for (int h 0; h image.rows; h) { for (int w 0; w image.cols; w) { cv::Vec3f pixel image.atcv::Vec3f(h, w); inputTensor[c * image.rows * image.cols h * image.cols w] pixel[c]; } } } return inputTensor; }这段代码虽然遍历三层循环但224x224的尺寸也就是十几万次操作在推理的几毫秒开销面前可以忽略。当然如果要求极致性能可以用OpenCV的blobFromImage函数一行搞定。3.4 执行推理session.Run的完整调用预处理完终于到了真正的推理环节。图片转张量、输入输出名称都准备好之后调用ONNX Runtime的Run方法std::vectorfloat InferenceEngine::classifyImage(const QImage image) { if (!session_) { return {}; } cv::Mat mat qImageToMat(image); preprocess(mat, 224, 224); // 224是模型输入尺寸 std::vectorfloat input matToNCHW(mat); // 构造输入张量 std::arrayint64_t, 4 inputShape {1, 3, 224, 224}; Ort::Value inputTensor Ort::Value::CreateTensorfloat( memoryInfo_, input.data(), input.size(), inputShape.data(), inputShape.size()); std::vectorconst char* inputNames {inputNames_[0].c_str()}; std::vectorconst char* outputNames {outputNames_[0].c_str()}; std::vectorOrt::Value outputTensors; try { outputTensors session_-Run( Ort::RunOptions{nullptr}, inputNames.data(), inputTensor, 1, outputNames.data(), 1); } catch (const Ort::Exception e) { // 记录日志返回空结果 return {}; } // 提取输出数据 Ort::Value outputTensor outputTensors.front(); float* outputData outputTensor.GetTensorMutableDatafloat(); size_t outputSize outputTensor.GetTensorTypeAndShapeInfo().GetElementCount(); return std::vectorfloat(outputData, outputData outputSize); }有个地方很容易错Run函数的参数顺序上面代码里的两个1分别是输入张量数量和输出张量数量输出名数组长度必须和输出数量一致。如果模型只有一个输入一个输出这样写没问题多输入多输出时必须小心名称顺序要和session加载的模型定义完全一致。3.5 线程方案别让界面卡成狗前面的推理代码跑起来最快暴露的问题就是按钮按下去界面锁死几秒钟。这是典型的把重任务放到了UI线程导致的。经验是推理越快的模型越容易忽略这个问题等模型变大一点半小时就能让用户觉得程序坏了。正确的做法是把推理封装到QtConcurrent等后台线程里跑通过信号把结果带回主线程。我习惯在MainWindow里定义信号signals: void inferenceFinished(const QVectorfloat results);然后在槽函数里触发异步任务void MainWindow::onClassifyClicked() { // 先把图片结果缓存好避免界面上临时的纹理或缓存数据在线程中访问 auto image m_currentImage; QtConcurrent::run([this, image]() { std::vectorfloat result m_inferenceEngine-classifyImage(image); // 这里跨线程发射信号Qt会自动排队到主线程 QVectorfloat qtResult(result.begin(), result.end()); emit inferenceFinished(qtResult); }); }注意图片要先拷贝一份不要在线程里去访问m_currentImage这种共享成员。虽然QImage本身是隐式共享的但一旦它在主线程里被修改比如用户又选择了一张新图后台线程就会读到脏数据。我在实践中踩过这个坑界面上选图后立刻点“识别”结果程序偶尔崩溃排查半天才发现是QImage的引用计数问题。提前拷贝一下成本很低但能避免这类偶发性崩溃。拿到推理结果后在主线程的inferenceFinished槽里更新界面比如显示分类标签、置信度条。至此一个完整的“Qt窗口加载图片、后台推理、回显结果”流程就跑通了。4. 部署与运行时的常见故障排查4.1 能编译不能运行DLL缺失这是Windows下所有动态库方案都会遇到的经典问题。程序在Visual Studio里能编译过但双击exe直接弹窗“找不到onnxruntime.dll”或“找不到vcruntime140.dll”。DLL缺失的根源很简单链接阶段通过onnxruntime.lib找到了函数的符号信息但运行阶段加载器找不到对应的DLL文件。处理方法就是把onnxruntime.dll拷贝到exe同级目录。有时候还会连带缺别的DLL比如VC运行库组件Windows老版本系统上尤其常见。推荐打包时带上Visual C Redistributable的安装包或者选用应用本地部署的方式把所有运行库DLL一并拷进发布目录。建议开发期就把调试工作目录设置成exe输出目录并确认ONNX Runtime的bin目录也被加入系统PATH能省去不少来回拷贝的动作。4.2 推理结果全是对不上预处理不一致如果程序能跑、结果不对十有八九是预处理环节和模型训练时不匹配。常见的预处理不一致包括通道顺序反了训练时用RGB推理时给的是BGR结果精度直接崩坏归一化参数不对训练时减均值除以标准差推理时只除以255缩放方式和分辨率不一致训练时是短边等比缩放再中心裁剪推理时不加裁剪直接拉伸。办法只有一个导模型之前把那个导出的原始Python推理脚本打开把每一步预处理代码原样翻译成C的opencv调用逐行对照。4.3 推理很慢忘记一次性CPU线程ONNX Runtime默认会使用CPU的多核资源但如果你在代码里设置了sessionOptions.SetIntraOpNumThreads必须注意别设成1或者过低否则模型推理会慢得让人怀疑人生。还有一个性能杀手是调试模式。Debug版程序的推理速度比Release版慢好几倍因为ONNX Runtime自身的DLL虽然是一样的但你的Qt程序Debug版的图像预处理代码慢同时调试器本身也会影响性能。发布性能测试务必用Release版。实测下来同一个ResNet18模型Release版CPU推理大约60ms一帧Debug版能拖到300多毫秒。所以真要跑性能测试一律用Release版。4.4 偶发崩溃资源生命周期和线程安全上面提到过的输入名称字符串生命周期问题是一个隐蔽的崩溃源。另一个常见崩溃是QImage在线程里被释放后底层数据还在被模型引用。这是因为cv::Mat和QImage都管理内部缓冲区深拷贝没做好内存被提前释放一旦主线程继续访问就是野指针。对策就是在qImageToMat里做clone深拷贝。线程安全方面同一时间只有一个线程调用session_-Run是最稳妥的。如果想多线程并发推理需要使用多个会话实例或者确认模型支持多线程环境共享。我项目里做的并发请求数量很少直接串行推理就满足需求了最好不引入复杂的并发场景。下面是常见报错的速查表贴出来给读者参考报错现象可能原因排查方向启动提示缺少onnxruntime.dll动态库未放入exe目录将bin目录中的onnxruntime.dll拷到exe同级链接器报LNK2019位数不匹配或库路径错误确认x64/x86一致、link_directories路径存在推理崩溃提示invalid input name输入名称字符串被提前释放确保std::string生命周期覆盖Run调用结果全错或置信度极低预处理不一致对照Python推理脚本检查通道顺序、归一化参数界面卡顿数秒推理在UI线程执行改用QtConcurrent后台推理Debug版性能极差Debug模式运行用Release版测试和发布5. 项目结构建议与工程化管理实际做项目时合理的代码组织能帮你少踩很多坑。推荐把推理相关的东西独立成模块与界面代码严格分离。我通常的目录结构是这样QtOnnxDemo/ ├── CMakeLists.txt ├── third_party/ │ └── onnxruntime-win-x64-1.16.3/ │ ├── include/ │ ├── lib/ │ └── bin/ ├── src/ │ ├── main.cpp │ ├── MainWindow.h/cpp │ ├── InferenceEngine.h/cpp │ └── models/ │ └── resnet18.onnxONNX模型文件也放项目里的models目录下运行时通过相对路径加载。但要注意相对路径的基准目录是exe所在位置不是源码目录。比如exe在build/Release里模型在源码目录的models下那就得把模型拷贝到build/Release/models下。我习惯在CMake里加一个自定义命令自动拷贝add_custom_command(TARGET QtOnnxDemo POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/src/models $TARGET_FILE_DIR:QtOnnxDemo/models )发布时还要把onnxruntime.dll一起拷贝可以在同一个POST_BUILD里做也可以放在专门的部署脚本里。还有一个实用技巧写一个简单的模型加载状态管理。模型文件可能很大加载耗时几十毫秒如果用户在界面上反复点击加载按钮会创建多个会话实例导致内存飙升。我一般用一个布尔标志位控制重复加载并在界面上给出加载状态提示避免误操作。6. 性能优化经验整个流程跑通之后可以再花点时间做一些性能调优收获一般都不错。第一个优化点是会话复用。同一个模型加载一次长期持有session不要每次识别都重新加载。模型加载本身是个开销很大的操作包含解析网络结构、算子布局优化等。我见过有开发者把loadModel放在每次识别的函数里结果一个大模型光加载就要一两秒界面体验可想而知。第二个优化点是调整图优化级别。SetGraphOptimizationLevel有ORT_ENABLE_BASIC、ORT_ENABLE_EXTENDED、ORT_ENABLE_ALL三档。模型结构固定且不涉及动态输入时建议直接用ORT_ENABLE_ALLONNX Runtime会做算子融合、内存复用等优化实测ResNet18能再快10%左右。第三个优化点是预热。第一次调用session_-Run时运行时会做一些初始化工作包括内存申请、内核编译等速度通常会比后续调用慢很多。所以在程序启动后可以拿一张纯黑图或者占位图提前跑一次推理把热身耗在加载阶段后续用户点击识别时就快了。第四个优化点关乎用户体感UI上可以先显示“识别中”的提示再启动推理这样哪怕推理要几十毫秒也不会显得程序没响应两三百毫秒的推理也完全可接受。写在最后的补充在Qt里调用神经网络最让我意外的是难点不在于“调用神经网络”这个操作本身而在于让推理代码和Qt生态平滑共存。线程模型、图像格式转换、DLL部署、预处理一致性这些看起来不起眼的细节才是真正拉开体验差距的地方。按这套方案搭建从零到能跑通一个真实模型的路径已经很成熟了剩下的事情就是拿你实际训练好的模型替换进来做相应预处理适配。如果后续想在Windows上做更极致的性能测试可以关注GPU版本的ONNX Runtime把CUDA或DirectML执行提供程序开启速度和CPU版完全不在一个量级。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑