YOLOv8n部署到RK3588开发板全流程
文章目录
- 前言
- 一、模型训练
- 二、配置用于pt模型转onnx模型的环境
- 三、pt→onnx模型转换
- Opset与dilation介绍
- 四、配置onnx转rknn模型的虚拟环境
- 五、onnx转rknn模型
- 六、RK3588板端部署
前言
更新:此训练、转换、部署流程已有新版本,可移步此篇文章:【YOLOv8n部署至RK3588】模型训练→转换rknn→部署全流程,该文是完全按照瑞芯微最新demo进行部署,效果更好,模型转换的流程步骤更精简明确。
小白博主,第一次写博客记录自己YOLOv8n部署RK3588开发板的全流程,记录下踩的所有坑,欢迎交流。
本篇主要参考博主@山水无移-ZhangQian的文章,如有需要,可自行查看参考。
欢迎各位小伙伴评论交流,同时也感谢昊哥、诚哥帮助
一、模型训练
YOLOv8的模型训练参考可如下两篇文章,不做过多叙述:
一、第一篇
二、第二篇
备注:博主训练时,选用的预训练模型为YOLOv8n
二、配置用于pt模型转onnx模型的环境
- 在由pt模型转onnx模型过程中,最好是新创建一个环境,避免在YOLOv8下路径出错等bug问题。
- conda create -n newpt2onnx python=3.8 创建模型转换的环境
- conda activate newpt2onnx 激活该环境
- 在https://github.com/airockchip/ultralytics_yolov8上下载相关配置文件,操作如下,进入YOLOv8文件夹后,在终端输入:git clone https://github.com/airockchip/ultralytics_yolov8.git,clone完成后进入ultralytics_yolov8文件夹下。输入两个命令:
第一个命令:
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
完成后输入第二个命令:
pip install -e .
完成这两个命令后,主要是修改了源码的ultralytics/nn/modules/head.py和ultralytics/engine/exporter.py两个文件。 - 环境配置完成
三、pt→onnx模型转换
.pt模型转.onnx模型
此处采用博主@山水无移-ZhangQian的转换方法
第一步:对YOLOv8/ultralytics_yolov8/ultralytics/nn/modules/head.py文件进行修改:
将Detect类改成如下所示:
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=4, ch=()): # detection layer //这边我把nc改成了4,原来nc是80
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], min(self.nc, 100)) # 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):
"""Concatenates and returns predicted bounding boxes and class probabilities."""
shape = x[0].shape # BCHW
# if self.export and self.format == 'rknn':
# y = []
# for i in range(self.nl):
# y.append(self.cv2[i](x[i]))
# cls = torch.sigmoid(self.cv3[i](x[i]))
# cls_sum = torch.clamp(cls.sum(1, keepdim=True), 0, 1)
# y.append(cls)
# y.append(cls_sum)
# return y
# 导出 onnx 增加
y = []
for i in range(self.nl):
t1 = self.cv2[i](x[i])
t2 = self.cv3[i](x[i])
y.append(t1)
y.append(t2)
return y
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
x_cat = torch.cat([xi.view(shape[0], self.no, -1) for xi in x], 2)
if self.export and self.format in ('saved_model', 'pb', 'tflite', 'edgetpu', 'tfjs'): # avoid TF FlexSplitV ops
box = x_cat[:, :self.reg_max * 4]
cls = x_cat[:, self.reg_max * 4:]
else:
box, cls = x_cat.split((self.reg_max * 4, self.nc), 1)
dbox = dist2bbox(self.dfl(box), self.anchors.unsqueeze(0), xywh=True, dim=1) * self.strides
if self.export and self.format in ('tflite', 'edgetpu'):
# Normalize xywh with image size to mitigate quantization error of TFLite integer models as done in YOLOv5:
# https://github.com/ultralytics/yolov5/blob/0c8de3fca4a702f8ff5c435e67f378d1fce70243/models/tf.py#L307-L309
# See this PR for details: https://github.com/ultralytics/ultralytics/pull/1695
img_h = shape[2] * self.stride[0]
img_w = shape[3] * self.stride[0]
img_size = torch.tensor([img_w, img_h, img_w, img_h], device=dbox.device).reshape(1, 4, 1)
dbox /= img_size
y = torch.cat((dbox, cls.sigmoid()), 1)
return y if self.export else (y, x)
上面主要是对nc进行修改,初始为80类,我的检测类别有4类,所以将nc改成4,然后对forward进行如下修改。(可直接复制)
第二步:对YOLOv8/ultralytics_yolov8/ultralytics/enginn/model.py进行修改
将model.py中的 _load函数进行修改,加入模型导出功能,因为在执行后续model = YOLO(“xxx.pt”)的命令时,会从model中的_load方法构建模型,因此在此处加入模型导出功能可实现导出。
具体改动如下:
def _load(self, weights: str, task=None):
"""
Initializes a new model and infers the task type from the model head.
Args:
weights (str): model checkpoint to be loaded
task (str | None): model task
"""
suffix = Path(weights).suffix
if suffix == '.pt':
self.model, self.ckpt = attempt_load_one_weight(weights)
self.task = self.model.args['task']
self.overrides = self.model.args = self._reset_ckpt_args(self.model.args)
self.ckpt_path = self.model.pt_path
else:
weights = check_file(weights)
self.model, self.ckpt = weights, None
self.task = task or guess_model_task(weights)
self.ckpt_path = weights
self.overrides['model'] = weights
self.overrides['task'] = self.task
print("=========== onnx =========== ")
import torch
dummy_input = torch.randn(1, 3, 640, 640)
input_names = ["data"]
output_names = ["reg1", "cls1", "reg2", "cls2", "reg3", "cls3"]
torch.onnx.export(self.model, dummy_input, "./weights/mybestyolov8_opset9.onnx", verbose=False, input_names=input_names, output_names=output_names, opset_version=9)
print("======================== convert onnx Finished! .... ")
注意,此处 opset_version必须为9!如果≥10,也是可以成功转出onnx模型的,但是在后续的onnx转rknn模型时,会报错:Meet unsupported MaxPool attribute ‘dilations’!,此时用netron工具查看了自己转换的onnx,maxpool中有dilation属性,这个dilation属性是onnx opset10之后的新属性,因此我们在这里采用opset_version=9,避免出错。
Opset与dilation介绍
这里延伸一下,opset即Operator Set,是ONNX的操作集,定义了用于构建和表示深度学习模型的操作符(operations),定义了一组用于构建和表示深度学习模型的操作符(operations)。
同时,ONNX操作集随着ONNX规范的发展而不断更新和扩展。每个版本的操作集都可能包含新增的操作符、对现有操作符的改进或优化,以及对某些过时操作符的废弃。因此,在将模型转换为ONNX格式时,需要指定目标操作集的版本,以确保模型在目标平台上能够正确执行。
ONNX操作集包含了各种类型的操作符,涵盖了深度学习中的常见操作,如卷积(Convolution)、池化(Pooling)、激活函数(Activation Functions)、批归一化(Batch Normalization)等。这些操作符可以被组合起来构建复杂的深度学习模型。ONNX操作集具有良好的可扩展性,允许用户根据需要定义自定义操作符。
dilation参数用于控制池化窗口中元素之间的间距,即在池化过程中,不是连续地考虑窗口内的所有元素,而是根据dilation的值来跳过某些元素。当dilation=1时(默认值),池化窗口内的元素是连续考虑的;当dilation大于1时,池化窗口内的元素之间会有间隔。
举例:如果此时9×9的特征图,池化窗口大小为3×3,dilation为2,stride为1。那么,每个池化窗口仍然为3×3大小,里面共有九个参数,但实际上参与到最大值池化计算的参数只有四个,分别是左上角、右上角、左下角以及右下角的元素(是由dilation=2决定的)
opset9版本的onnx模型:
opset11版本的onnx模型:
第三步:在/YOLOv8/ultralytics_yolov8下新建tuili.py文件,内容如下:
from ultralytics import YOLO
model = YOLO("./weights/yolov8bestptmodel.pt")
results = model(task='detect', mode='predict', source='2022129_1_158.jpg', line_width=3, show=True, save=True, device='cpu')
第四步:运行tuili.py
python tuili.py
会出现各种报错,但是没关系,只要出现=========== onnx =========== 和======================== convert onnx Finished! … ,说明tuili.py中的model = YOLO("./weights/yolov8bestptmodel.pt")成功调用了def _load()函数,此时已成功转换出了onnx模型,如下图所示:
四、配置onnx转rknn模型的虚拟环境
在配置该环境前,需要先安装rknn_toolkit2,记住,如果是RK3588的开发板,最好安装1.3版本的rknn_toolkit2,否则容易出错。
其余版本的开发板可以自己多试试,看看结果。
整个rknn_toolkit2-1.3.0的安装流程按照如下博客:rknn_toolkit2-1.3.0安装
主要安装流程如下:先百度云下载RK_NPU_SDK_1.3.0,将RK_NPU_SDK_1.3.0文件下的rknn-toolkit2-1.3.0文件夹放至YOLOv8目录下
然后安装python3.6版本的虚拟环境:conda create -n rknn130version python=3.6
完成配置后,激活该环境
在rknn130version环境下安装rknn-toolkit2依赖:
sudo apt-get install libxslt1-dev zlib1g-dev libglib2.0 libsm6 libgl1-mesa-glx libprotobuf-dev gcc
cd rknn-toolkit2-1.3.0/doc
pip install -r requirements_cp36-1.3.0.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
如果用的是云服务器,在Ubuntu下可能会显示:Linux:bash: sudo: command not found。此时可参考此篇文章。
在终端输入:
apt-get update -y
apt-get install sudo
如果中途显示bfloat16安装失败,则手动安装一下numpy:pip install numpy==1.16.6
此时再重新安装rknn-toolkit2依赖
安装完依赖后,安装rknn-toolkit2,如下所示
cd rknn-toolkit2-1.3.0/packages
pip install rknn_toolkit2-1.3.0_11912b58-cp36-cp36m-linux_x86_64.whl
检查是否安装成功:
conda activate rknn130version
python
from rknn.api import RKNN
如下所示:
没有任何提示则安装成功。此时CTRL+Z,退出 Python 的交互式模式。
然后在YOLOv8目录下下载yolov8_rknn(此处代码为博主@山水无移-ZhangQian创作),git clone https://github.com/cqu20160901/yolov8n_onnx_tensorRT_rknn_horizon_dfl.git
将其中的yolov8_rknn放置YOLOv8目录下
五、onnx转rknn模型
将之前得到的onnx模型放置在yolov8_rknn文件夹下
修改onnx2rknn_demo_ZQ.py中的类别名称
并将该文件夹下的各参数进行调整
然后执行yolov8_rknn下的onnx2rknn_demo_ZQ.py
python onnx2rknn_demo_ZQ.py
结果如下即为转换rknn成功:
检测效果图如下:
六、RK3588板端部署
板端流程参考:https://github.com/cqu20160901/yolov8n_onnx_tensorRT_rknn_horizon_dfl (为博主山水无移-ZhangQian
所创)可以克隆到本地:git clone https://github.com/cqu20160901/yolov8n_rknn_Cplusplus_dfl.git
在main.cpp中修改模型地址、输入图片与输出地址后编译,再执行可执行文件,生成如下检测结果:
到此为止,完整流程已结束,所有流程包括:YOLOv8模型训练→PT转ONNX模型的环境部署→PT转ONNX→ONNX转RKNN模型的环境部署→ONNX转RKNN→在RK3588上修改main.cpp与配置参数(包括Makefile、CMakelist等等)后进行编译,生成可执行文件→执行可执行文件,生成板端检测结果。