YOLO 模型导出
更新: 2026/3/2 字数: 0 字 时长: 0 分钟
📌 移动端强烈推荐使用 NCNN 格式,性能更优,资源占用更低,具体参阅ONNX 和 NCNN 性能对比。
YOLO26
支持以下两种导出 NCNN 方式:
📌 说明:模型转换阶段支持优化处理,详见 模型优化。
YOLO13
⚠️ 必须使用 YOLO13 源码环境,才能顺利导出 ONNX 和 NCNN 模型。
支持以下两种导出 NCNN 方式:
📌 说明:模型转换阶段支持优化处理,详见 模型优化。
YOLO12
支持以下两种导出 NCNN 方式:
📌 说明:模型转换阶段支持优化处理,详见 模型优化。
YOLO11
支持以下两种导出 NCNN 方式:
📌 说明:模型转换阶段支持优化处理,详见 模型优化。
YOLOv10
⚠️ 必须使用 YOLOv10 源码环境,并完成 1 处源码修改后,方可正常导出 ONNX 和 NCNN 模型。
支持以下两种导出 NCNN 方式:
📌 说明:模型转换阶段支持优化处理,详见 模型优化。
修改 1 处源码
⚠️ 以下修改仅用于模型转换,不可用于训练或推理。
为避免频繁修改源码版本,建议:
- 使用 YOLO26 环境进行训练
- 使用修改后的 YOLOv10 环境专门用于导出 NCNN
修改位置 :ultralytics/nn/modules/head.py
定位到 class v10Detect(Detect):(约第497行),将 v10Detect 类 完整替换 为以下实现:
class v10Detect(Detect):
max_det = 300
def __init__(self, nc=80, ch=()):
super().__init__(nc, ch)
c3 = max(ch[0], min(self.nc, 100)) # channels
self.cv3 = nn.ModuleList(nn.Sequential(nn.Sequential(Conv(x, x, 3, g=x), Conv(x, c3, 1)), \
nn.Sequential(Conv(c3, c3, 3, g=c3), Conv(c3, c3, 1)), \
nn.Conv2d(c3, self.nc, 1)) for i, x in enumerate(ch))
self.one2one_cv2 = copy.deepcopy(self.cv2)
self.one2one_cv3 = copy.deepcopy(self.cv3)
def forward(self, x):
if self.export:
return self.forward_export(x)
one2one = self.forward_feat([xi.detach() for xi in x], self.one2one_cv2, self.one2one_cv3)
if not self.export:
one2many = super().forward(x)
if not self.training:
one2one = self.inference(one2one)
if not self.export:
return {"one2many": one2many, "one2one": one2one}
else:
assert (self.max_det != -1)
boxes, scores, labels = ops.v10postprocess(one2one.permute(0, 2, 1), self.max_det, self.nc)
return torch.cat([boxes, scores.unsqueeze(-1), labels.unsqueeze(-1).to(boxes.dtype)], dim=-1)
else:
return {"one2many": one2many, "one2one": one2one}
def forward_export(self, x):
results = []
for i in range(self.nl):
dfl = self.one2one_cv2[i](x[i]).permute(0, 2, 3, 1).contiguous()
cls = self.one2one_cv3[i](x[i]).permute(0, 2, 3, 1).contiguous()
cls = cls.sigmoid()
results.append(torch.cat((cls, dfl), -1))
return tuple(results)
def bias_init(self):
super().bias_init()
"""Initialize Detect() biases, 警告: requires stride availability."""
m = self # self.model[-1] # Detect() module
# cf = torch.bincount(torch.tensor(np.concatenate(dataset.labels, 0)[:, 0]).long(), minlength=nc) + 1
# ncf = math.log(0.6 / (m.nc - 0.999999)) if cf is None else torch.log(cf / cf.sum()) # nominal class frequency
for a, b, s in zip(m.one2one_cv2, m.one2one_cv3, m.stride): # from
a[-1].bias.data[:] = 1.0 # box
b[-1].bias.data[: m.nc] = math.log(5 / m.nc / (640 / s) ** 2) # cls (.01 objects, 80 classes, 640 img)YOLOv9
支持以下两种导出 NCNN 方式:
📌 说明:模型转换阶段支持优化处理,详见 模型优化。
YOLOv8
支持以下两种导出 NCNN 方式:
📌 说明:模型转换阶段支持优化处理,详见 模型优化。
环境说明
- 使用 YOLO11 / YOLO26 源码环境**:可直接进行模型转换,无需修改源码。
- 使用 原始 YOLOv8 源码环境:若采用分步转换方式,导出 ONNX 前需修改 2 处源码。
修改 2 处源码
⚠️ 以下修改仅用于模型转换,不可用于训练或推理。为避免反复修改源码,建议统一使用 YOLO26 环境进行导出。
修改位置 1:ultralytics/nn/modules/block.py
定位到 class C2f(nn.Module)(约第205行),完整替换为以下代码:
class C2f(nn.Module):
# CSP Bottleneck with 2 convolutions
def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion
super().__init__()
self.c = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, 2 * self.c, 1, 1)
self.cv2 = Conv((2 + n) * self.c, c2, 1) # optional act=FReLU(c2)
self.m = nn.ModuleList(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n))
def forward(self, x):
# y = list(self.cv1(x).chunk(2, 1))
# y.extend(m(y[-1]) for m in self.m)
# return self.cv2(torch.cat(y, 1))
print('forward C2f')
x = self.cv1(x)
x = [x, x[:, self.c:, ...]]
x.extend(m(x[-1]) for m in self.m)
x.pop(1)
return self.cv2(torch.cat(x, 1))
def forward_split(self, x):
y = list(self.cv1(x).split((self.c, self.c), 1))
y.extend(m(y[-1]) for m in self.m)
return self.cv2(torch.cat(y, 1))修改位置 2:ultralytics/nn/modules/head.py
定位到 class Detect(nn.Module)(约第20行),完整替换为以下代码:
class Detect(nn.Module):
# YOLOv8 Detect head for detection models
dynamic = False # force grid reconstruction
export = False # export mode
shape = None
anchors = torch.empty(0) # init
strides = torch.empty(0) # init
def __init__(self, nc=80, ch=()): # detection layer
super().__init__()
self.nc = nc # number of classes
self.nl = len(ch) # number of detection layers
self.reg_max = 16 # DFL channels (ch[0] // 16 to scale 4/8/12/16/20 for n/s/m/l/x)
self.no = nc + self.reg_max * 4 # number of outputs per anchor
self.stride = torch.zeros(self.nl) # strides computed during build
c2, c3 = max((16, ch[0] // 4, self.reg_max * 4)), max(ch[0], self.nc) # channels
self.cv2 = nn.ModuleList(
nn.Sequential(Conv(x, c2, 3), Conv(c2, c2, 3), nn.Conv2d(c2, 4 * self.reg_max, 1)) for x in ch)
self.cv3 = nn.ModuleList(nn.Sequential(Conv(x, c3, 3), Conv(c3, c3, 3), nn.Conv2d(c3, self.nc, 1)) for x in ch)
self.dfl = DFL(self.reg_max) if self.reg_max > 1 else nn.Identity()
def forward(self, x):
shape = x[0].shape # BCHW
for i in range(self.nl):
x[i] = torch.cat((self.cv2[i](x[i]), self.cv3[i](x[i])), 1)
if self.training:
return x
elif self.dynamic or self.shape != shape:
self.anchors, self.strides = (x.transpose(0, 1) for x in make_anchors(x, self.stride, 0.5))
self.shape = shape
print('forward Detect')
pred = torch.cat([xi.view(shape[0], self.no, -1) for xi in x], 2).permute(0, 2, 1)
return pred
def bias_init(self):
# Initialize Detect() biases, 警告: requires stride availability
m = self # self.model[-1] # Detect() module
# cf = torch.bincount(torch.tensor(np.concatenate(dataset.labels, 0)[:, 0]).long(), minlength=nc) + 1
# ncf = math.log(0.6 / (m.nc - 0.999999)) if cf is None else torch.log(cf / cf.sum()) # nominal class frequency
for a, b, s in zip(m.cv2, m.cv3, m.stride): # from
a[-1].bias.data[:] = 1.0 # box
b[-1].bias.data[:m.nc] = math.log(5 / m.nc / (640 / s) ** 2) # cls (.01 objects, 80 classes, 640 img)YOLOv7
⚠️ 必须在 YOLOv7 源码环境 下操作,才能顺利导出 ONNX 和 NCNN 模型。
导出 ONNX 模型 在项目根目录打开 Anaconda Prompt,激活 yolov7 环境后,执行以下命令:
python export.py --weights yolov7-tiny.pt --img-size 640 640 --batch-size 1 --device cpu --grid --simplify⚠️ 使用此方法导出的 ONNX 模型 不能直接用于 NCNN,如果要生成 NCNN 模型,需要用其他导出方式。
说明:
- 将
yolov7-tiny.pt模型导出为 ONNX 格式 - 输入图片尺寸为 640×640
- batch size 为 1
- 使用 CPU 进行导出
--grid和--simplify用于保证模型结构兼容和优化
导出 NCNN 模型
待补充……
YOLOv6
待补充……
YOLOv5
1️⃣ 源码环境说明
- ✅ 可直接进行模型转换
- ⚠️ 只能导出 目标检测 模型
- ❌ 实例分割、图片分类、旋转检测需用 原始 YOLOv5 源码环境
原始 YOLOv5 源码环境
- ✅ 可导出 目标检测 / 实例分割 / 图片分类 / 旋转检测
2️⃣ YOLO11 / YOLO26 源码环境导出 NCNN 的方式
⚠️ 提示:导出的 NCNN 模型加载时需指定 version: 26
$yolo.load({
binPath: "...",
paramPath: "...",
classesPath: "...",
task: 0,
version: 26, // YOLO26方式加载
device: 0,
})3️⃣ 原始 YOLOv5 源码环境导出命令示例
- 目标检测 ONNX
python export.py --weights yolov5n.pt --img 640 640 --batch 1 --simplify --include onnx⚠️ 导出的 ONNX 不能转 NCNN
- 分类模型 ONNX
python export.py --weights yolov5n-cls.pt --img 224 224 --batch 1 --simplify --include onnx✅ 导出后可用 onnx2ncnn 转 NCNN
- 分割模型 ONNX
python export.py --weights yolov5n-seg.pt --img 640 640 --batch 1 --simplify --include onnx⚠️ 导出后 不能 转 NCNN
- 旋转模型 ONNX / NCNN
待补充
YOLOx
待补充……
模型导出
导出 ONNX
在源码环境中,将 .pt 模型直接导出为 ONNX 格式。
适用版本及任务
YOLOv8 / YOLO11 / YOLO26
支持多种任务:目标检测、实例分割、图像分类、姿态/关键点估计、旋转框检测
✅ 可在 YOLO26 源码环境 下直接导出YOLOv5 / YOLOv9 / YOLOv12
支持任务:目标检测
✅ 可在 YOLO26 源码环境 下直接导出YOLOv10 / YOLOv13
支持任务:目标检测
✅ 需使用对应版本的源码环境才能导出YOLOX / YOLOv5 / YOLOv6 / YOLOv7
支持任务:各自原始训练任务
❌ 必须使用对应版本的源码环境,且导出脚本与此处不同(详见对应章节)
如果是 YOLOv8 源码环境,需要修改 2 处源码后才能导出 YOLOv8 模型,但是修改源码后无法训练,所以推荐用 YOLO26 源码环境导出模型。
安装 onnx 依赖
pip install onnx onnxruntime onnxsim -i https://pypi.tuna.tsinghua.edu.cn/simple在项目根目录下新建文件:yolo26_export_onnx.py
# coding:utf-8
# 作者 :Bot.js
# 开发时间:2025/10/26 下午4:34
from ultralytics import YOLO
model = YOLO(r"G:\website\yolo\ultralytics-8.3.163\test\pt\yolo26n.pt")
# 导出为 ONNX
model.export(
format='onnx',
opset=12, # ONNX 算子集版本
simplify=True, # 启用简化优化(重要!)
imgsz=(640, 320), # 输入图像尺寸 (height, width)
batch=1, # 批次大小
dynamic=False # 禁用动态尺寸
)执行脚本后,将在模型同级目录下生成 .onnx 文件。
🔍 用 Netron 查看模型结构:https://netron.app/ (拖入文件即可)
导出 NCNN
在源码环境中,将 .pt 模型直接导出为 NCNN 格式。
适用版本及任务
YOLOv8 / YOLO11 / YOLO26
支持多种任务:目标检测、实例分割、图像分类、姿态/关键点估计、旋转框检测
✅ 可在 YOLO26 源码环境 下直接导出YOLOv5 / YOLOv9 / YOLOv12
支持任务:目标检测
✅ 可在 YOLO26 源码环境 下直接导出YOLOv10 / YOLOv13
支持任务:目标检测
✅ 需使用对应版本的源码环境才能导出YOLOX / YOLOv5 / YOLOv6 / YOLOv7
支持任务:各自原始训练任务
❌ 必须使用对应版本的源码环境,且导出脚本与此处不同(详见对应章节)
安装 pnnx 依赖
pip install pnnx -i https://pypi.tuna.tsinghua.edu.cn/simple在项目根目录下新建文件:yolo26_export_ncnn.py
# coding:utf-8
# 作者 :Bot.js
# 开发时间:2025/10/26 下午4:34
from ultralytics import YOLO
model = YOLO(r"G:\website\yolo\ultralytics-8.3.163\test\pt\yolo26n.pt")
# 导出为 NCNN
model.export(
format='ncnn',
opset=12, # ONNX 算子集版本
simplify=True, # 启用简化优化(重要!)
imgsz=(640, 320), # 输入图像尺寸 (height, width)
batch=1, # 批次大小
dynamic=False # 禁用动态尺寸
)执行脚本后,将在模型同级目录下生成一个文件夹,文件夹名称由原模型文件名加 _ncnn_model 构成,例如 yolo26n.pt 导出后生成 yolo26n_ncnn_model,其中包含 model.ncnn.bin 和 model.ncnn.param 两个文件。
ONNX 转 NCNN
如果已将 .pt 模型导出为 ONNX,也可以通过 pnnx 或 onnx2ncnn 工具进一步转换为 NCNN 格式。
此方法适用于需要对 ONNX 进行中间处理或优化的场景。
pnnx
pnnx 是 NCNN 官方推荐的转换工具,兼容性更好,功能更完整。
开源地址:pnnx GitHub
适用版本
- YOLOv8 / YOLO11 / YOLO26(目标检测 、 实例分割 、图像分类 、姿态 / 关键点估计 、旋转框检测)
- YOLOv9 / YOLOv10 / YOLOv12 / YOLOv13(目标检测)
安装 pnnx 依赖
pip install pnnx -i https://pypi.tuna.tsinghua.edu.cn/simple如果不使用 pip 安装,如何手动安装 pnnx?
可以手动下载并解压获得 pnnx.exe。建议将 pnnx.exe 放置在待转换模型所在目录,或添加至系统 PATH 环境变量,以便在终端中直接执行 pnnx 命令。
- 国外下载地址:pnnx-20260112-windows.zip
- 国内下载地址:pnnx-20260112-windows.zip
通过 CMD 命令转换
pnnx yolo26n.onnx inputshape=[1,3,640,320]f32 device=cpu说明:
inputshape:输入张量尺寸f32:数据类型device=cpu:使用 CPU 转换
通过 Python 脚本转换
# coding:utf-8
import subprocess
def pnnx_ncnn(onnx_path, pnnx_exe, input_shape=[1, 3, 640, 320], dtype="f32", device="cpu"):
# 把列表自动转换成pnnx需要的字符串格式(如 [1,3,640,320] → "[1,3,640,320]")
input_shape_str = f"[{','.join(map(str, input_shape))}]"
# 拼接完整命令(包含f32、cpu)
cmd = [pnnx_exe, onnx_path, f"inputshape={input_shape_str}{dtype}", f"device={device}"]
# 执行命令并输出结果
result = subprocess.run(cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
encoding="utf-8", # 指定utf-8编码
)
print("执行命令:", " ".join(cmd)) # 可选:打印实际执行的命令,方便核对
print(result.stdout)
if result.returncode != 0:
print(f"转换失败: {result.stderr}")
else:
print("转换成功!")
# 直接配置参数运行
if __name__ == "__main__":
PNNX_EXE = r"E:\application\anaconda3\envs\yolo26\Scripts\pnnx.exe" # pnnx.exe 路径
ONNX_PATH = r"G:\test\yolo26n.onnx" # onnx 模型文件
pnnx_ncnn(ONNX_PATH, PNNX_EXE, input_shape=[1, 3, 640, 640], dtype="f32", device="cpu")转换完成后,生成的文件将与原始模型文件在同一目录下,且文件名与源文件相同,只是后缀不同,分别为 .ncnn.param 和 .ncnn.bin,例如:
yolo26n.ncnn.paramyolo26n.ncnn.bin
这两个文件是 NCNN 模型格式所需的文件,部署到移动端时,只需要这两个文件。
onnx2ncnn
onnx2ncnn 为早期转换工具,适用于部分旧版模型。
适用模型
- YOLOv5 / YOLOv6 / YOLOv7 / YOLOX
- YOLOv8(仅支持:目标检测、实例分割、图像分类)
⚠️ 不支持:姿态 / 关键点估计 / 旋转框检测 如需转换这些任务,推荐使用 pnnx。
下载onnx2ncnn,下载后解压得到onnx2ncnn.exe 程序
通过 CMD 命令(同级目录)
假设文件位于同级目录下,目录结构如下:
G:\
├─📁 test
│ ├─📄 yolov8n.onnx
│ └─📄 onnx2ncnn.exe在该目录下打开命令提示符(CMD),执行:
onnx2ncnn.exe yolov8n.onnx yolov8n.param yolov8n.bin通过 CMD 命令(非同级目录)
如果文件不在同级目录中,可以使用完整路径:
G:\test\onnx2ncnn.exe G:\test\yolov8n.onnx G:\test\yolov8n.param G:\test\yolov8n.bin通过 Python 脚本转换
你还可以使用 Python 脚本来自动化转换过程,脚本会生成与源文件同名的 .param 和 .bin 文件。
# coding:utf-8
# 作者: Bot.js
# 功能: onnx -> ncnn(param/bin),自动同名同目录
import os
import subprocess
def onnx2ncnn(onnx_path, onnx2ncnn_exe):
assert onnx_path.lower().endswith(".onnx"), "输入必须是 .onnx"
onnx_path = os.path.abspath(onnx_path)
base, _ = os.path.splitext(onnx_path)
param_path = base + ".param"
bin_path = base + ".bin"
cmd = [
onnx2ncnn_exe,
onnx_path,
param_path,
bin_path
]
print("执行:")
print(" ".join(cmd))
result = subprocess.run(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
print("----- stdout -----")
print(result.stdout)
if result.returncode != 0:
print("----- stderr -----")
print(result.stderr)
raise RuntimeError("onnx2ncnn failed")
return param_path, bin_path
if __name__ == "__main__":
ONNX2NCNN_EXE = r"G:\test\onnx2ncnn.exe"
ONNX_PATH = r"G:\test\yolov8n.onnx"
onnx2ncnn(ONNX_PATH, ONNX2NCNN_EXE)无论使用 CMD 命令 还是 Python 脚本,转换完成后,源文件 yolov8n.onnx 将生成以下两个文件:
yolov8n.paramyolov8n.bin
这两个文件是 NCNN 模型格式所需的文件,部署到移动端时,只需要这两个文件。
ONNX 和 NCNN 性能对比
以输入尺寸 320 为例:
| 格式 | 推理耗时 | 特点 |
|---|---|---|
| ONNX | 60 ~ 150 ms | 通用性强,跨平台兼容 |
| NCNN | 20 ~ 50 ms | 专为移动端优化,速度更快 |
性能说明
- NCNN 为腾讯开源的高性能移动端推理框架
- 针对 ARM 架构深度优化
- 内存占用更低
- 更适合手机自动化、游戏识别、验证码识别等实时场景
📌 结论:移动端部署推荐使用 NCNN 模型
模型优化
整体推理性能通常可提升约 10% ~ 30%
待补充……
NCNN 输入输出层名称
将 ONNX 模型转换为 NCNN 格式后,每个 NCNN 模型都会包含固定的**输入层(Input)和输出层(Output)**名称及结构。
Bot.js Pro 内置的 YOLO 模型加载器会根据 version 自动匹配对应版本的输入输出层名称进行推理。
本对照表用于:
- 了解各 YOLO 版本的默认输入输出层名称
- 理解模型的输入输出层结构
- 在推理异常、闪退或无法检测到目标时作为排查参考
查看模型输入输出层
由于不同训练方式或导出参数可能导致层名称不同,建议使用模型结构查看工具确认:
推荐工具:
可查看:
- 输入层名称
- 输出层名称
- 网络结构
模型加载或推理异常排查
通常情况下,你无需手动关心输入输出层名称。
当出现以下情况时,建议查阅本表:
- 加载模型时崩溃或闪退
- 模型加载成功但无识别结果
- 推理结果异常
- 使用自定义训练或第三方导出的模型
模型无法正常推理的最常见原因是 version 参数与实际模型格式不匹配。
推荐尝试的 version 顺序(按匹配概率降序):
26 → 13 → 12 → 11 → v8 → v5
示例:
$yolo.load({
modelPath: "...",
paramPath: "...",
classPath: "...",
task: 0,
version: 13, // 尝试不同的 version 值
});选择与实际模型输入输出层名称匹配的 version 即可正常推理。详细配置说明参见 YOLO 加载配置。
对照表
目标检测
| 版本 | 输入层名称 | 输出层名称 | 备注 |
|---|---|---|---|
| YOLO26 | in0 | out0 | |
| YOLO13 | in0 | out0 | |
| YOLO12 | in0 | out0 | |
| YOLO11 | in0 in0 | out0、out1、out2 out0 | 导出方式不同 |
| YOLOv10 | in0 | out0、out1、out2 | |
| YOLOv9 | images | output0 | |
| YOLOv8 | in0 images images | out0 output output0 | 导出方式不同 |
| YOLOv7 | in0 images | out0、out1、out2 output、288、302 | 导出名称不固定 |
| YOLOv6 | images | output | |
| YOLOv5 | in0 images images images images | out0、out1、out2 output、353、367 output、376、401 output、781、801 其他名称 | 不固定 部分为数字名称 |
| YOLOX | images | output |
实例分割
| 版本 | 输入层名称 | 输出层名称 | 备注 |
|---|---|---|---|
| YOLO26 | in0 | out0、out1 | |
| YOLO11 | in0 in0 | out0、out1、out2 out0、out1 | 导出方式不同 |
| YOLOv8 | images images | output、seg output0、output1 | 导出方式不同 |
| YOLOv5 | images images images images | output、385、405、seg output、619、639、seg output、736、756、seg 其他名称 | 名称不固定 部分为数字名称 |
图片分类
| 版本 | 输入层名称 | 输出层名称 | 备注 |
|---|---|---|---|
| YOLO26 | in0 images | out0 output0 | 导出方式不同 |
| YOLO11 | in0 images | out0 output0 | 导出方式不同 |
| YOLOv8 | in0 images | out0 output0 | 导出方式不同 |
| YOLOv5 | images | output0 |
姿态估计
| 版本 | 输入层名称 | 输出层名称 | 备注 |
|---|---|---|---|
| YOLO26 | in0 | out0 | |
| YOLO11 | in0 in0 | out0、out1 out0 | 导出方式不同 |
| YOLOv8 | in0 in0 | out0、out1 out0 | 导出方式不同 |
旋转检测
| 版本 | 输入层名称 | 输出层名称 | 备注 |
|---|---|---|---|
| YOLO26 | in0 | out0 | |
| YOLO11 | in0 in0 | out0、out1 out0 | 导出方式不同 |
| YOLOv8 | in0 in0 images | out0、out1 out0 output0 | 导出方式不同 |
| YOLOv5 | input | prob |
