赞
踩
YOLOv8n部署到RK3588开发板全流程
小白博主,第一次写博客记录自己YOLOv8n部署RK3588开发板的全流程,记录下踩的所有坑,欢迎交流。
本篇主要参考博主@山水无移-ZhangQian的文章,如有需要,可自行查看参考。
欢迎各位小伙伴评论交流,同时也感谢昊哥、诚哥帮助,忠!义!
YOLOv8的模型训练参考可如下两篇文章,不做过多叙述:
一、第一篇
二、第二篇
备注:博主训练时,选用的预训练模型为YOLOv8n
.pt模型转.onnx模型
此处采用博主@山水无移-ZhangQian的转换方法
第一步:对YOLOv8/ultralytics_yolov8/ultralytics/nn/moduleshead.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即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模型,如下图所示:
在配置该环境前,需要先安装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模型放置在yolov8_rknn文件夹下
修改onnx2rknn_demo_ZQ.py中的类别名称
并将该文件夹下的各参数进行调整
然后执行yolov8_rknn下的onnx2rknn_demo_ZQ.py
python onnx2rknn_demo_ZQ.py
结果如下即为转换rknn成功:
检测效果图如下:
板端流程参考: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等等)后进行编译,生成可执行文件→执行可执行文件,生成板端检测结果。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。