赣州市网站建设_网站建设公司_Windows Server_seo优化
2026/1/16 20:18:26 网站建设 项目流程

文章目录

  • 前言
  • 一、基础环境
  • 二、ultralytics ==8.0.5 (yolov8量化)
    • 1、检测
    • 2、分割
  • 三、最新版本ultralytics(yolov8量化)
    • 1、onnx2ncnn
    • 2、pnnx2ncn
  • 四、最新版本ultralytics(yolo11量化)
    • 1、onnx2ncnn
    • 2、pnnx2ncnn

前言

本篇属于【基于NCNN搭建从0到1完整版】自定义算法搭建Android APP工程的延伸篇,主要针对YOLOv8检测、分割等转换流程和中间遇到的问题做一个记录,另外对于ultralytics当前最新版本yolov8、yolo11进行摸索记录(方法可行)。

教程比较傻瓜式,大家按照步骤走,基本都可以量化成功。

注:由于在量化过程中会改动ultralytics包部分源码,所以一定要将量化和训练环境给区分开


想直接拿货的小伙伴,百度网盘可直接下载:
yolov8s量化结果 提取码: 2wia
yolov11量化结果 提取码: twva

一、基础环境

  1. 我的环境
    Windows搭建虚拟环境、python== 3.8.20、onnx== 1.17.0、onnxruntime== 1.19.2、onnxsim== 0.4.36、onnxslim==0.1.80、ultralytics
    onnx2ncnn.exe脚本 提取码: jafy
  2. 打开Anaconda Powershell Prompt 创建conda环境
conda create -n your_name python==3.8.20 -y
conda activate your_name

在这里插入图片描述
3. 下载指定版本ultralytics,指定版本预训练权重、onnx相关依赖包

# ultralytics 下载包
pip install ultralytics==8.0.50 -i https://pypi.tuna.tsinghua.edu.cn/simple
# onnx相关依赖包
pip install onnx==1.17.0 onnxruntime==1.19.2 onnxslm==0.4.36 onnxslim==0.1.80 -i https://pypi.tuna.tsinghua.edu.cn/simple
#可选,若使用gpu推理,可下载torch相关
pip install torch==1.8.1+cu111 torchvision==0.9.1+cu111 torchaudio==0.8.1 -f https://download.pytorch.org/whl/torch_stable.html

onnx、onnxruntime、onnxsim、onnxslim区别?
onnx:读取、保存ONNX模型文件、结构查看和验证。
onnxruntime:运行ONNX模型的推理引擎,通常转化和推理都会用到i。
onnxsim:优化和简化ONNX模型,融合相邻操作(如Conv+BN+ReLU),通常在转化时用到,推理不会用到。
onnxslim:剪枝和量化ONNX模型(模型剪枝、FP32→INT8),通常在转换时会用到。
在这里插入图片描述

二、ultralytics ==8.0.5 (yolov8量化)

注:这个版本属于比较老旧的版本,在搭建工程时,参考了一些网上旧版本的教程,进行了记录。

1、检测

  1. 预训练权重下载,并用搭建的环境,创建infer.py进行推理
wget https://github.com/ultralytics/ultralytics/releases/download/v8.0.4/yolov8s.pt
from ultralytics import YOLO
#加载预训练模型(会自动下载 yolov8n.pt)
model = YOLO("yolov8s.pt")
#预测图片
results = model.predict(
source="bus.jpg",       # 输入源:图片/视频/目录/摄像头(0)
conf=0.5,                    # 置信度阈值
save=True,                # 保存结果
show=True,              # 显示结果(适用于Jupyter Notebook)
device="cuda:0"     # 使用GPU(改为 "cpu" 则用CPU))
#打印结果
for result in results:
print(result.boxes)  # 检测到的边界框信息

搭建的环境是没有问题的
在这里插入图片描述

  1. 在转换前,我们需要从ultralytics安装包中修改两个地方:C2F和Detect层。
    C2F: 在C2f模块中,之前使用了split(),可能是考虑ncnn架构对该算子不支持的问题,改成了切片操作Slice x[:, :self.c, …]。
#路径\path\envs\ncnn\Lib\site-packages\ultralytics\nn\modules.C2F
class C2f(nn.Module):
#原始实现(8.0.5)
def forward(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))
#修改后的实现:
def forward(self, x):
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))
	Detect:在Detect模块中,同样也是对一些复杂算子进行了优化,方便导出onnx模型。
#路径\path\envs\ncnn\Lib\site-packages\ultralytics\nn\modules.Detect
#Class Detect:
#原始实现(8.0.5)
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
box, cls = torch.cat([xi.view(shape[0], self.no, -1) for xi in x], 2).split((self.reg_max * 4, self.nc), 1)
dbox = dist2bbox(self.dfl(box), self.anchors.unsqueeze(0), xywh=True, dim=1) * self.strides
y = torch.cat((dbox, cls.sigmoid()), 1)
return y if self.export else (y, x)
#修改后的实现:
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
  1. 编写pt2onnx.py脚本,用于转成onnx模型
from ultralytics import YOLO
model = YOLO("yolov8s.pt")
success = model.export(format="onnx", opset=12, simplify=True)
  1. 执行onnx2ncnn.exe,生成param和bin
.\onnx2ncnn.exe .\yolov8s.onnx .\yolov8s.param .\yolov8s.bin
  1. 将量化好的模型放置到\path\app\src\main\assets下面,在源码模型load部分进行更改,同时一定要在推理部分看好模型的输入和输出,我们使用onnx2ncnn工具时,导出的模型IO为images/output0
    在这里插入图片描述在这里插入图片描述在这里插入图片描述
  2. 我们编译好放置到手机上进行推理。
    在这里插入图片描述

2、分割

  1. 预训练权重下载,并用搭建的环境,创建infer.py进行推理
wget https://github.com/ultralytics/ultralytics/releases/download/v8.0.4/yolov8s-seg.pt
  1. 在转换前,我们需要从ultralytics安装包中修改三个地方:C2F、Detect和Seg层。
    C2F: 在C2f模块,改动参考检测即可。
    Detect层:
pred = torch.cat([xi.view(shape[0], self.no, -1) for xi in x], 2).permute(0, 2, 1)
#更改为:
pred = torch.cat([xi.view(shape[0], self.no, -1) for xi in x], 2)

Seg层:主要是为了调整输出格式,适配ncnn。

##路径\path\envs\ncnn\Lib\site-packages\ultralytics\nn\modules.Segment
##class Segment(nn.Module):
##更改前
def forward(self, x):
p = self.proto(x[0])  # mask protos
bs = p.shape[0]  # batch size
mc = torch.cat([self.cv4[i](x[i]).view(bs, self.nm, -1) for i in range(self.nl)], 2)  # mask coefficients
x = self.detect(self, x)
if self.training:
return x, mc, p
return (torch.cat([x, mc], 1), p) if self.export else (torch.cat([x[0], mc], 1), (x[1], mc, p))
##更改后
def forward(self, x):
p = self.proto(x[0])  # mask protos
bs = p.shape[0]  # batch size
mc = torch.cat([self.cv4[i](x[i]).view(bs, self.nm, -1) for i in range(self.nl)], 2)  # mask coefficients
x = self.detect(self, x)
if self.training:
return x, mc, p
return (torch.cat([x, mc], 1).permute(0,2,1),p.view(bs,self.nm, -1)) if self.export else (torch.cat([x[0], mc], 1), (x[1], mc, p))
  1. 同样也是利用检测pt2onnx.py脚本,用于转成onnx模型
from ultralytics import YOLO
model = YOLO("yolov8s-seg.pt")
success = model.export(format="onnx", opset=12, simplify=True)
  1. 编写pt2onnx.py脚本,用于转成onnx模型
.\onnx2ncnn.exe .\yolov8s-seg.onnx .\yolov8s-seg.param .\yolov8s-seg.bin
  1. 将量化好的模型放置到\path\app\src\main\assets下面,在源码模型load部分进行更改,同时一定要在推理部分看好模型的输入和输出,我们使用onnx2ncnn工具时,导出的模型IO为images/output0在这里插入图片描述在这里插入图片描述
    6. 我们编译好放置到手机上进行推理。
    在这里插入图片描述

三、最新版本ultralytics(yolov8量化)

1、onnx2ncnn

  1. 下载最新ultralytics和权重
pip install -U ultralytics-i https://pypi.tuna.tsinghua.edu.cn/simple
#检测
wget https://github.com/ultralytics/assets/releases/download/v8.3.0/yolov8s.pt
#分割
wget https://github.com/ultralytics/assets/releases/download/v8.3.0/yolov8s-seg.pt
  1. 同样也是从ultralytics库中,修改对应模块,进行改动。但是在对应包里并没有找到modules.py,整体结构进行了调整,也是找了好久,终于给捋明白找到了!!
    检测更改:
#路径\path\envs\ncnn\Lib\site-packages\ultralytics\nn\modules\block.py
#class C2f(nn.Module):
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""Forward pass through C2f layer."""
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))
#改为:
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = self.cv1(x)                     # [B, 2c, H, W]
x = [x, x[:, self.c:, ...]]         # [2c] 和 后一半[c]
x.extend(m(x[-1]) for m in self.m)  # 叠加 n 个 Bottleneck 输出
x.pop(1)                            # 去掉多余分支,通道数保持 (2 + n) * c
return self.cv2(torch.cat(x, 1))
#路径\path\envs\ncnn\Lib\site-packages\ultralytics\nn\modules\head.py 
#class Detect(nn.Module):
def forward(self, x: list[torch.Tensor]) -> list[torch.Tensor] | tuple:
"""Concatenate and return predicted bounding boxes and class probabilities."""
if self.end2end:
return self.forward_end2end(x)
for i in range(self.nl):
x[i] = torch.cat((self.cv2[i](x[i]), self.cv3[i](x[i])), 1)
if self.training:  # Training path
return x
y = self._inference(x)
return y if self.export else (y, x)
#改为:
def forward(self, x: list[torch.Tensor]) -> list[torch.Tensor] | tuple:
"""Concatenate and return predicted bounding boxes and class probabilities."""
if self.end2end:
return self.forward_end2end(x)
for i in range(self.nl):
x[i] = torch.cat((self.cv2[i](x[i]), self.cv3[i](x[i])), 1)
if self.training:  # Training path
return x
# 量化优化:导出模式下直接返回原始预测 [B, no, N],不做解码(适配NCNN等推理引擎)
if self.export:
shape = x[0].shape  # BCHW
pred = torch.cat([xi.view(shape[0], self.no, -1) for xi in x], 2).permute(0, 2, 1)
return pred
y = self._inference(x)
return y if self.export else (y, x)

分割更改:

#路径\path\envs\ncnn\Lib\site-packages\ultralytics\nn\modules\block.py
#class C2f(nn.Module):
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""Forward pass through C2f layer."""
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))
#改为:
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = self.cv1(x)                     # [B, 2c, H, W]
x = [x, x[:, self.c:, ...]]         # [2c] 和 后一半[c]
x.extend(m(x[-1]) for m in self.m)  # 叠加 n 个 Bottleneck 输出
x.pop(1)                            # 去掉多余分支,通道数保持 (2 + n) * c
return self.cv2(torch.cat(x, 1))
#路径\path\envs\ncnn\Lib\site-packages\ultralytics\nn\modules\head.py 
#class Detect(nn.Module):
pred = torch.cat([xi.view(shape[0], self.no, -1) for xi in x], 2).permute(0, 2, 1)
#改为:
pred = torch.cat([xi.view(shape[0], self.no, -1) for xi in x], 2)
#路径\path\envs\ncnn\Lib\site-packages\ultralytics\nn\modules\head.py 
#class Segment(nn.Module):
def forward(self, x: list[torch.Tensor]) -> tuple | list[torch.Tensor]:
"""Return model outputs and mask coefficients if training, otherwise return outputs and mask coefficients."""
p = self.proto(x[0])  # mask protos
bs = p.shape[0]  # batch size
mc = torch.cat([self.cv4[i](x[i]).view(bs, self.nm, -1) for i in range(self.nl)], 2)  # mask coefficients
x = Detect.forward(self, x)
if self.training:
return x, mc, p
return (torch.cat([x, mc], 1), p) if self.export else (torch.cat([x[0], mc], 1), (x[1], mc, p))
def forward(self, x: list[torch.Tensor]) -> tuple | list[torch.Tensor]:
"""Return model outputs and mask coefficients if training, otherwise return outputs and mask coefficients."""
p = self.proto(x[0])  # mask protos
bs = p.shape[0]  # batch size
mc = torch.cat([self.cv4[i](x[i]).view(bs, self.nm, -1) for i in range(self.nl)], 2)  # mask coefficients
x = Detect.forward(self, x)
if self.training:
return x, mc, p
# 量化优化:导出模式下调整输出格式以适配NCNN(permute输出,view proto)
if self.export:
return (torch.cat([x, mc], 1).permute(0, 2, 1), p.view(bs, self.nm, -1))
return (torch.cat([x[0], mc], 1), (x[1], mc, p))

2、pnnx2ncn

目前该方法比较主流,主要参考nihui大佬相关教程,只跑了检测的流程,关于分割,旋转框等其参考以下博客:
2025年YOLOv8检测/分割/姿态估计/分类/旋转目标的ncnn精致实现流程

  1. 搭建环境,下载相关依赖。
conda create -n your_name python==3.8.20 -y
conda activate your_name
pip  install -U ultralytics pnnx ncnn
#检测
wget https://github.com/ultralytics/assets/releases/download/v8.3.0/yolov8s.pt
#分割
wget https://github.com/ultralytics/assets/releases/download/v8.3.0/yolov8s-seg.pt
  1. 导出 yolov8 torchscript
yolo export model=yolov8s.pt format=torchscript
  1. 转换静态尺寸输入的 torchscript
 pnnx yolov8s.torchscript
  1. 手工修改 pnnx 模型脚本:yolo11n_pnnx.py
修改前:
v_165 = v_142.reshape(1, 144, 6400)
v_166 = v_153.reshape(1, 144, 1600)
v_167 = v_164.reshape(1, 144, 400)
v_168 = torch.cat((v_165, v_166, v_167), dim=2)
修改后:
v_165 = v_142.reshape(1, 144, -1).transpose(1,2)
v_166 = v_153.reshape(1, 144, -1).transpose(1,2)
v_167 = v_164.reshape(1, 144, -1).transpose(1,2)
v_168 = torch.cat((v_165, v_166, v_167), dim=1)
return v_168
  1. 重新导出 yolov8s torchscript
python -c 'import yolov8s_pnnx; yolov8s_pnnx.export_torchscript()'
  1. 动态尺寸转换新 torchscript
pnnx yolov8s_pnnx.py.pt inputshape=[1,3,640,640] inputshape2=[1,3,320,320]
  1. 在同级目录下会生成yolov8s_pnnx.py.ncnn.param、yolov8s_pnnx.py.ncnn.bin
    注:利用pnnx方法转出来的输入/输出和onnx2ncnn有所区别,,导出的模型IO为in0/out0

四、最新版本ultralytics(yolo11量化)

1、onnx2ncnn

目前用这个方法转出来yolo11,推理不出结果,在网上查了下好像yolo11新增attention模块,导致onnx2ncnn兼容性不足,可以用下面这种方法转。

2、pnnx2ncnn

主要参考nihui大佬相关教程,只跑了检测的流程,关于分割,旋转框等其参考以下博客:
2025年YOLO11检测/分割/姿态估计/分类/旋转目标的ncnn精致实现流程

  1. 搭建环境,下载相关依赖。
conda create -n your_name python==3.8.20 -y
conda activate your_name
pip  install -U ultralytics pnnx ncnn
#检测
wget https://github.com/ultralytics/assets/releases/download/v8.3.0/yolo11s.pt
  1. 导出 yolo11 torchscript
yolo export model=yolov11s.pt format=torchscript
  1. 转换静态尺寸输入的 torchscript
pnnx yolo11s.torchscript
  1. 修改 pnnx 模型脚本,yolo11s_pnnx.py,支持动态尺寸输入
#修改前:
v_235 = v_204.reshape(1, 144, 6400)
v_236 = v_219.reshape(1, 144, 1600)
v_237 = v_234.reshape(1, 144, 400)
v_238 = torch.cat((v_235, v_236, v_237), dim=2)
#修改后:
v_235 = v_204.reshape(1, 144, -1).transpose(1,2)
v_236 = v_219.reshape(1, 144, -1).transpose(1,2)
v_237 = v_234.reshape(1, 144, -1).transpose(1,2)
v_238 = torch.cat((v_235, v_236, v_237), dim=1)
return v_238
#area attention 修改前:
v_95 = self.model_10_m_0_attn_qkv_conv(v_94)
v_96 = v_95.reshape(1, 4, 128, 400)
v_97, v_98, v_99 = torch.split(tensor=v_96, dim=2, split_size_or_sections=(32,32,64))
v_100 = torch.transpose(v_97, dim0=-2, dim1=-1)
v_101 = torch.matmul(v_100, other=v_98)
v_102 = (v_101 * 0.176777)
v_103 = F.softmax(v_102, dim=-1)
v_104 = torch.transpose(v_103, dim0=-2, dim1=-1)
v_105 = torch.matmul(v_99, other=v_104)
v_106 = v_105.reshape(1, 256, 20, 20)
v_107 = v_99.reshape(1, 256, 20, 20)
v_108 = self.model_10_m_0_attn_pe_conv(v_107)
v_109 = (v_106 + v_108)
v_110 = self.model_10_m_0_attn_proj_conv(v_109)
#area attention 修改后:
v_95 = self.model_10_m_0_attn_qkv_conv(v_94)
v_96 = v_95.reshape(1, 4, 128, -1)
v_97, v_98, v_99 = torch.split(tensor=v_96, dim=2, split_size_or_sections=(32,32,64))
v_100 = torch.transpose(v_97, dim0=-2, dim1=-1)
v_101 = torch.matmul(v_100, other=v_98)
v_102 = (v_101 * 0.176777)
v_103 = F.softmax(v_102, dim=-1)
v_104 = torch.transpose(v_103, dim0=-2, dim1=-1)
v_105 = torch.matmul(v_99, other=v_104)
v_106 = v_105.reshape(1, 256, v_95.size(2), v_95.size(3))
v_107 = v_99.reshape(1, 256, v_95.size(2), v_95.size(3))
v_108 = self.model_10_m_0_attn_pe_conv(v_107)
v_109 = (v_106 + v_108)
v_110 = self.model_10_m_0_attn_proj_conv(v_109)
  1. 重新导出 yolov11 torchscript
python -c 'import yolo11s_pnnx; yolov8s_pnnx.export_torchscript()'
  1. 动态尺寸转换新 torchscript
pnnx yolo11s_pnnx.py.pt inputshape=[1,3,640,640] inputshape2=[1,3,320,320]
  1. 在同级目录下会生成yolo11s_pnnx.py.ncnn.param、yolo11s_pnnx.py.ncnn.bin

注:利用pnnx方法转出来的输入/输出和onnx2ncnn有所区别,,导出的模型IO为in0/out0

整理不易,欢迎一键三连
送你们一条美丽的--分割线--

⛵⛵⭐⭐

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询