YOLOv8n部署RK3588开发板全流程(pt→onnx→rknn模型转换、板端后处理检测)

YOLOv8n部署到RK3588开发板全流程

文章目录

  • 前言
  • 一、模型训练
  • 二、配置用于pt模型转onnx模型的环境
  • 三、pt→onnx模型转换
    • Opset与dilation介绍
  • 四、配置onnx转rknn模型的虚拟环境
  • 五、onnx转rknn模型
  • 六、RK3588板端部署


前言

更新:此训练、转换、部署流程已有新版本,可移步此篇文章:【YOLOv8n部署至RK3588】模型训练→转换rknn→部署全流程,该文是完全按照瑞芯微最新demo进行部署,效果更好,模型转换的流程步骤更精简明确。

小白博主,第一次写博客记录自己YOLOv8n部署RK3588开发板的全流程,记录下踩的所有坑,欢迎交流。
本篇主要参考博主@山水无移-ZhangQian的文章,如有需要,可自行查看参考。

欢迎各位小伙伴评论交流,同时也感谢昊哥、诚哥帮助


一、模型训练

YOLOv8的模型训练参考可如下两篇文章,不做过多叙述:
一、第一篇
二、第二篇

备注:博主训练时,选用的预训练模型为YOLOv8n

二、配置用于pt模型转onnx模型的环境

  1. 在由pt模型转onnx模型过程中,最好是新创建一个环境,避免在YOLOv8下路径出错等bug问题。
  2. conda create -n newpt2onnx python=3.8 创建模型转换的环境
  3. conda activate newpt2onnx 激活该环境
  4. 在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两个文件。
  5. 环境配置完成

三、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等等)后进行编译,生成可执行文件→执行可执行文件,生成板端检测结果。

评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值