1. 为什么要在Windows上折腾ONNX转NCNN?

如果你已经跟着之前的教程,在PyCharm里把YOLOv5模型跑起来了,甚至用自己的数据训练了一个不错的.pt权重文件,那恭喜你,你已经成功了一大半。但模型训练好只是第一步,就像你炒好了一盘菜,还得找个合适的盘子端上桌。对于很多移动端,特别是Android应用来说,NCNN就是这个“盘子”——一个为移动端极致优化的高性能神经网络推理框架。

你可能会问,我直接在服务器上用PyTorch或者ONNX Runtime推理不香吗?对于手机App来说,真不行。移动端环境资源有限,对模型大小、推理速度、功耗都有苛刻的要求。NCNN由腾讯优图实验室开源,专门为移动平台设计,没有复杂的依赖,推理效率非常高,是很多AI应用落地到手机端的首选。而ONNX(Open Neural Network Exchange)则像一个“中转站”,它定义了一个通用的模型格式,让PyTorch、TensorFlow等不同框架训练的模型可以互相转换。所以,我们的路径很清晰:PyTorch (.pt) -> ONNX -> NCNN。

在Windows上做这个转换,尤其对Android开发友好。很多深度学习的训练和初步验证工作都是在Windows上完成的,如果能直接在熟悉的Windows环境里完成模型转换,生成最终能塞进Android项目的.param和.bin文件,会省去很多跨系统操作的麻烦。我自己在项目里就经常这么干,在Windows上调试好转换流程,确保模型输出无误后,再把文件扔给Android同事集成,效率提升非常明显。

接下来,我会手把手带你走通两条路:一条是**“懒人福音”在线转换法**,最快5分钟搞定;另一条是**“硬核玩家”本地编译法**,虽然步骤多,但能让你彻底搞懂背后的原理,并且能自定义一些优化选项。两种方法我都会详细拆解,包括你可能遇到的每一个坑和我的填坑经验。

2. 第一步:从PyTorch的.pt到通用的ONNX

万事开头难,但这一步其实相当简单。YOLOv5的作者们已经把导出脚本写好了,我们只需要“按图索骥”。

2.1 找到并配置导出脚本

打开你的YOLOv5项目文件夹,找到 models/export.py 这个文件。用任何文本编辑器或者PyCharm打开它。我们需要关注的核心是 run 函数里的参数。这里我建议你直接修改源代码里的默认值,而不是每次通过命令行传参,这样更直观。

找到类似下面的代码块(不同版本的YOLOv5可能略有差异,但核心参数不变):

parser.add_argument('--weights', type=str, default='./yolov5s.pt', help='weights path')
parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='image size')
parser.add_argument('--batch-size', type=int, default=1, help='batch size')
parser.add_argument('--dynamic', action='store_true', help='dynamic ONNX axes')

你需要修改的主要是 --weights 参数。如果你用的是官方预训练模型,比如 yolov5s.pt,确保这个文件在项目根目录下,或者给出它的绝对路径。更常见的情况是使用自己训练的模型。假设你训练的模型叫 best.pt,我强烈建议你把它复制到YOLOv5项目的根目录下,然后将 default 改为 './best.pt'。这样能避免很多因路径问题导致的“FileNotFoundError”。

关于 --dynamic 参数,如果你希望导出的ONNX模型支持动态的输入尺寸(比如不固定为640x640),可以将其设置为 True。但对于初次转换和移动端部署,我建议先使用固定的 --img-size(如默认的640),这样结构更简单,出错的概率更低。

2.2 运行导出与常见问题排查

配置好后,在PyCharm里右键点击 export.py 选择运行,或者在终端切换到项目目录执行 python models/export.py --weights ./best.pt。

如果一切顺利,你会在终端看到详细的导出日志,最后在项目根目录下生成 best.onnx 文件。但新手最容易在这里卡住,我遇到最多的问题是缺少依赖包。

比如,你可能会看到报错:ModuleNotFoundError: No module named 'onnx' 或者 ‘coremltools’ not found。别慌,这太正常了。export.py 脚本会尝试导出ONNX、TorchScript、CoreML等多种格式,但我们只需要ONNX。所以,我们只需要安装ONNX相关的包。

打开你的Anaconda Prompt(如果你用了虚拟环境,请先激活你的深度学习环境),运行:

pip install onnx onnx-simplifier

通常只安装 onnx 就足够了。安装完成后,再次运行导出脚本。这次应该就能成功看到生成的 .onnx 文件了。有时候还会提示 onnxruntime 或者 protobuf 的版本警告,只要不影响最终文件生成,可以暂时忽略。一个重要的检查:用Netron(一个超好用的模型可视化工具,后面会讲到)打开生成的 best.onnx,确认模型结构输入是 images,输出是你期望的检测框和类别信息。这一步能提前发现很多模型定义上的问题。

3. 方法一:在线转换工具(最快上手)

拿到ONNX文件后,我们首先尝试最快捷的方法——在线转换。这适合想快速验证、或者对C++编译环境感到头疼的朋友。

3.1 使用ConvertModel一站式转换

访问 ConvertModel 网站。这个工具支持非常多的模型格式互转,我们找到从ONNX到NCNN的选项。

页面的操作非常直观:点击上传你的 best.onnx 文件,选择输出格式为 NCNN,然后点击转换按钮。稍等片刻,它就会帮你完成转换并提供两个文件:best.param(模型结构文件)和 best.bin(模型权重文件)的下载。

听起来完美无缺?但根据我的经验,直接用这个工具转换YOLOv5的ONNX模型,十有八九会失败。你会看到转换进度条卡住,或者最终报错。这是因为YOLOv5模型结构中有一个特殊的 Focus 模块(在早期版本中),标准的ONNX转换器可能无法正确处理。不过别担心,即使转换“成功”下载了文件,我们还需要进行一个关键的手动修改,这个修改步骤是绕不开的。

3.2 手动修复Param文件:理解YOLOv5Focus

无论在线转换是否直接报错,我们拿到的 .param 文件很可能无法直接被NCNN加载。核心问题就在于 Focus 操作。在YOLOv5中,Focus 模块的作用是对输入图片进行切片和拼接,以实现一种无参的下采样,提升小目标检测能力。但一些旧的ONNX转换链或NCNN版本没有原生支持这个算子。

怎么办?手动替换它。 首先,用文本编辑器(推荐VS Code或Notepad++)打开下载的 best.param 文件。文件开头几行定义了模型的输入和第一层操作。你会看到开头可能是一系列 Split、Slice、Crop 和 Concat 算子,它们共同实现了 Focus 的功能。我们的目标是将这一坨算子替换成NCNN能识别的 YoloV5Focus 层。

你需要做的是:

  1. 找到第一个 Convolution(卷积层)出现的位置。
  2. 将 Input 行之后,到第一个 Convolution 行之前的所有层定义(通常就是那些 Split、Crop等)全部删除。
  3. 在 Input 行下面,添加一行新的层定义。这行怎么写呢?这里有个小技巧。在删除前,找到第一个 Concat 算子的输出名。例如,你看到一行 Concat /model.0/Concat_output_0 1 1 ...,那么输出名就是 /model.0/Concat_output_0。记下它。
  4. 添加的行格式为:YoloV5Focus focus 1 1 images /model.0/Concat_output_0。这里,images 是你的输入blob名字(通常就是 images),后面的 /model.0/Concat_output_0 就是你刚才记下的输出名。

修改后,你的param文件开头应该像这样:

7767517
4 5
Input images 0 1 images
YoloV5Focus focus 1 1 images /model.0/Concat_output_0
Convolution Conv ...
...

如何验证修改是否正确? 再次祭出神器 Netron。打开网站,上传你修改后的 .param 文件。如果可视化图中,开头的复杂结构变成了一个清晰的 YoloV5Focus 模块,并且整个模型图看起来整洁流畅,恭喜你,修改成功了!这个文件现在就可以和 .bin 文件一起,用于Android端的NCNN推理了。

4. 方法二:本地编译转换工具(彻底掌控)

如果你追求极致的控制感,或者在线转换总是出怪问题,那么本地搭建转换环境是最好的选择。这条路稍微复杂,需要安装VS2019、CMake并编译一些工具,但一旦搭建好,以后转换任何模型都会非常方便。

4.1 搭建Windows编译环境:VS2019与CMake

首先,去微软官网下载安装 Visual Studio 2019 Community(社区版,免费)。在安装时,工作负载务必勾选 “使用C++的桌面开发”,右边可选的组件里,把 “Windows 10 SDK” 和 “用于Windows的C++ CMake工具” 也选上。这一步是提供编译所需的编译器(MSVC)和基础库。

接着,安装 CMake。CMake是一个跨平台的编译配置工具。去CMake官网下载Windows平台的安装包(.msi格式),安装时记得勾选 “Add CMake to the system PATH for all users”,这样就能在命令行直接用了。安装完成后,打开命令提示符(CMD)或 PowerShell,输入 cmake --version,如果能显示版本号,说明安装成功。

4.2 编译Protobuf与NCNN

我们需要编译两个核心库:Protobuf(Google的数据序列化工具,NCNN用它来解析模型)和NCNN本身。

第一步:编译Protobuf 3.4.0。 为什么是指定版本?因为NCNN对Protobuf的版本比较敏感,3.4.0是一个经过广泛测试的稳定版本。下载Protobuf 3.4.0的源码压缩包,解压到一个没有中文和空格的路径下,比如 D:\libs\protobuf-3.4.0。

接下来是关键操作:以管理员身份打开 “Developer Command Prompt for VS 2019”(在开始菜单里找)。这个命令行环境已经配置好了VS的编译工具链。

依次执行以下命令(注意替换 <protobuf-root-dir> 为你的实际路径):

cd D:\libs\protobuf-3.4.0
mkdir build-vs2019
cd build-vs2019
cmake -G"NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=%cd%/install -Dprotobuf_BUILD_TESTS=OFF -Dprotobuf_MSVC_STATIC_RUNTIME=OFF ../cmake
nmake
nmake install

这个过程会持续几分钟。如果 cmake 命令成功,会生成一堆文件;nmake 命令会开始编译,满屏滚动代码;nmake install 会将编译好的头文件和库文件复制到 build-vs2019/install 目录下。看到这三个命令都顺利完成没有红色报错,Protobuf就搞定了。

第二步:编译NCNN。 去NCNN的GitHub仓库下载最新的Release源码,同样解压到无中文空格的路径,如 D:\libs\ncnn。

再次在VS2019开发者命令行中,执行:

cd D:\libs\ncnn
mkdir build-vs2019
cd build-vs2019
cmake -G"NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=%cd%/install -DProtobuf_INCLUDE_DIR=D:\libs\protobuf-3.4.0\build-vs2019\install\include -DProtobuf_LIBRARIES=D:\libs\protobuf-3.4.0\build-vs2019\install\lib\libprotobuf.lib -DProtobuf_PROTOC_EXECUTABLE=D:\libs\protobuf-3.4.0\build-vs2019\install\bin\protoc.exe -DNCNN_VULKAN=OFF ..
nmake
nmake install

这里最重要的就是 cmake 命令里那三个 -DProtobuf_xxx 的参数,必须正确指向你刚才编译安装的Protobuf的路径。-DNCNN_VULKAN=OFF 表示我们不编译Vulkan支持(如果不需要GPU加速的话)。同样,等待 nmake 编译完成。

编译成功后,宝藏就藏在 ncnn/build-vs2019/tools/onnx 目录里!你会找到一个 onnx2ncnn.exe 文件,这就是我们梦寐以求的本地转换工具。

4.3 执行本地转换与参数修改

转换就简单了。为了方便,你可以把 onnx2ncnn.exe 和你生成的 best.onnx 文件放在同一个文件夹里。

在文件资源管理器里,按住Shift键并右键点击该文件夹空白处,选择“在此处打开Powershell窗口”。然后运行命令:

.\onnx2ncnn.exe best.onnx best.param best.bin

瞬间,best.param 和 best.bin 就生成了。但是,注意看命令行输出! 很大概率你会看到几行警告,比如 “Unsupported slice step !” 等。这几乎就是明示:转换出来的param文件里的 Focus 结构需要修复。

没错,和方法一一样,你需要用文本编辑器打开这个新生成的 best.param 文件,执行完全相同的“删除冗余算子,添加YoloV5Focus层”的操作。修改完成后,同样建议用Netron打开检查一下。至此,本地转换也大功告成。

5. 两种方法对比与实战选择建议

走完了两条路,我们来复盘一下,到底该怎么选。

在线转换工具(ConvertModel)的优缺点:

  • 优点:极致简单,无需配置任何本地环境,有网页就能操作。适合快速原型验证、模型简单或者不想折腾环境的同学。
  • 缺点:1. 网络依赖强,大模型上传下载耗时。2. 隐私问题,你的模型文件会上传到第三方服务器。3. 对非常见算子或复杂模型的支持可能不佳,比如YOLOv5的Focus问题仍需手动干预。4. 无法进行更深入的优化(如层融合、量化等)。

本地编译转换的优缺点:

  • 优点:1. 完全离线,安全可控。2. 转换速度快,尤其是对于大模型。3. 可以使用NCNN提供的更多工具,如 ncnnoptimize 对模型进行优化,进一步提升推理速度。4. 一次搭建环境,终身受益,后续转换任何模型都方便。
  • 缺点:环境搭建过程相对复杂,需要一定的耐心和排错能力,可能会遇到编译器版本、依赖库路径等问题。

我的实战建议是: 对于刚接触的小伙伴,可以先用在线工具走通流程,重点理解 .param 文件的手动修改过程。当你成功修改并能在Netron里看到正确结构时,你对NCNN模型格式就有了最直观的认识。 当你需要频繁转换模型,或者需要对模型进行优化时,果断搭建本地环境。虽然前期需要一两个小时折腾,但后面会节省大量时间,而且能让你更深入地理解整个部署工具链。我在团队里就维护了一套配置好的虚拟环境镜像,新同事来了直接导入,五分钟就能开始转换,非常高效。

6. 转换后的验证与Android端集成前瞻

生成 .param 和 .bin 文件后,千万别急着往Android项目里塞。在Windows端先做一次推理验证,可以排除90%的模型问题。

NCNN提供了简单的C++示例代码。你可以写一个简单的测试程序,用OpenCV读取一张图片,预处理后喂给NCNN模型,然后看输出的检测框是否合理。这个过程能验证:1. 模型加载是否正确;2. 前处理(缩放、归一化、BGR2RGB等)是否和训练时一致;3. 后处理(解析输出层、非极大抑制NMS)逻辑是否正确。

如果Windows上测试通过,那么集成到Android就只剩下环境适配的问题了。Android上主要就是配置NCNN的Android AAR库,或者直接编译NCNN的so库,然后在JNI层调用相同的推理代码。预处理和后处理的逻辑可以完全从Windows测试代码移植过来。

我踩过的一个坑是数据类型的对齐。训练时图片可能是0-1浮点数,而移动端为了速度可能用0-255整数,这个归一化尺度一定要搞清楚。另一个是输出层的顺序,YOLOv5不同版本输出层的排列可能稍有不同,务必用Netron看清输出层的名字和形状,确保你的后处理代码抓取的是正确的输出数据。

模型转换和部署是个细活,每一步的细节都可能导致最终结果天差地别。但只要你按照上面的步骤,耐心地走一遍,遇到问题多查查NCNN的GitHub Issues,基本上都能解决。当你第一次在自己手机上实时跑起YOLOv5检测时,那种成就感绝对是值得的。好了,转换的坑基本上都给你标出来了,接下来就动手试试吧,遇到具体问题,欢迎随时交流。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐