RK3588部署及其RKNPU工具链使用学习

RKNPU 推理框架

RKNPU 发展历程

推理软件框架

RKNPU 硬件层:

AXI 接口:

AXI 主接口【用来和内存进行连接,从中获取模型、图像的相关参数和数据】(AXI Master Interface)用于从连接到 SoC AXI 互联的内存中获取数据。AXI 是 一种高性能、低延迟、可扩展的总线接口,常用于连接处理器和外设,并支持多个主设备和从 设备。AXI 主接口通常用于从内存中获取数据,例如从 DRAM 或其他存储器中读取程序和数据, 并将其传输到处理器或其他外设中进行处理和计算。

AHB 接口:
  		AHB 从接口【用于访问寄存器,从而对RKNPU进行配置、调试和测试】(AHB Slave Interface)用于访问寄存器进行配置、调试和测试。AHB 是一种标 准化的系统总线接口,通常用于连接处理器、内存和外设等硬件电路。AHB 从接口通常用于访问寄存器,例如控制和配置处理器、外设和其他硬件电路的参数和状态,以实现系统的配置、 调试和测试。
卷积神经网络加速单元(CNA)

(1)卷积预处理控制器

卷积预处理控制器是 CNA 中用于预处理卷积计算的硬件单元,可以对输入的模型权重进行 解压缩之后加载进 NPU 的内部缓冲区,并且可以判断零节点加速运算速度,最后将要推理的数据加载进 NPU 的内部缓冲区中。

(2)NPU 内部缓存区(Internal Buffer)

NPU 内部缓存区是 CNA 中用于存储中间计算结果的缓存区。它可以高效地存储和管理卷积 神经网络中的各种数据,包括输入数据、卷积核、卷积结果等。NPU 内部缓存区采用了多级缓 存和数据重用技术,可以高效地利用计算资源和存储资源,从而进一步提高计算速度和效率。

(3)序列控制器(Sequence Controller)

序列控制器是 CNA 中用于控制卷积计算序列的硬件单元。它可以根据卷积神经网络的结构和参数,自动地配置和控制 CNA 中各个硬件单元的工作模式和参数。序列控制器还可以实现卷积计算的并行化和流水化,从而提高计算速度和效率。

(4)乘加运算单元(Multiply-Accumulate Unit,MAC)

乘加运算单元是 CNA 中用于执行卷积计算的硬件单元。它可以对输入数据和卷积核进行乘 法和累加运算,从而得到卷积计算结果。乘加运算单元采用了高度并行的设计,可以同时执行多个卷积计算操作,从而大大提高计算速度和效率。

(5)累加器(Accumulator)

累加器是 CNA 中用于累加卷积计算结果的硬件单元。它可以高效地累加卷积计算结果,从 而得到最终的输出结果。累加器可以采用多种精度,可以适应不同的计算精度要求。

数据处理单元(Data Processing Unit,DPU)

数据处理单元(Data Processing Unit)主要处理单个数据的计算,例如 Leaky ReLU、ReLU、 ReluX、Sigmoid、Tanh 等。它还提供了一些功能,例如 Softmax、转置、数据格式转换等。

  数据处理单元是一种硬件电路,用于加速神经网络的计算过程。它通常被用于处理前向计 算过程中的单个数据,例如卷积层和全连接层中的激活函数计算。不同的激活函数需要不同的 计算操作,例如 ReLU 需要计算 max(0,x),Sigmoid 需要计算 1/(1+exp(-x)),而数据处理单元可 以通过硬件电路来实现这些计算操作,从而提高神经网络的计算性能和效率。

  除了激活函数计算之外,数据处理单元还提供了一些其他的函数,例如 Softmax、转置、 数据格式转换等。这些函数通常被用于神经网络模型的构建和优化过程中,例如将模型的输出 转换为概率分布、重新排列张量的维度、将数据从一种格式转换为另一种格式等。
平面处理单元(Planar Processing Unit,PPU)

主要提供对数据处理单元的输出数据进行平面操作的功能,例如平均池化、最大值池化、最小值池化等。平面处理单元可以通过硬件电路来实现这些操作,从而提高神经网 络的计算性能和效率。

RKNPU 驱动层:

RKNPU 驱动层是连接上层应用和 RKNPU 硬件的桥梁。驱动层的主要作用是将应用程序需要推理的内容提交给 RKNPU 进行计算,从而加速神经网络的训练和推理过程。具体来说, 驱动层需要完成以下任务:

1. 硬件初始化:包括设置寄存器,分配内存等操作;
2. 数据传输:将数据从主机内存中传输到 NPU 内存中,在计算完成后还需将计算结果传输回主机内存。
3. 计算任务调度:驱动层需要根据应用程序需求,管理和分配 RKNPU 的计算资源,确保多个计算任务之间不会相互干扰。

RKNPU 应用层

RKNPU 应用层由 AI 应用程序、RKNN API 以及运行时所需要的库所组成。通过调用瑞芯微提供好的 API 接口进行 AI 应用的开发,瑞芯微分别提供了 C 语言和 Python 语言这两种 API 帮助开发者进行嵌入式设备部署,Python 语言提供的接口较为简单, 旨在帮助用户进行前期的模型检测、测试以及应用调试,而要想得到更好的效果从而真正应用到实际项目中就要使用 C 的 API 接口。

而无论是由 C API 接口还是 Python 的 API 接口编写的应用程序,要想实际运行都需要相应 的动态库,动态库包含这些 API 的具体实现,这些动态库由瑞芯微所提供。而我们只需要根据 瑞芯微所提供的 API 编写对应的应用程序即可。

RKNN 模型

RKNN(Rockchip Neural Network)是瑞芯微公司开发的一种神经网络模型格式,它可以将常见的深度学习模型转换为适用于瑞芯微的 AI 加速器 RKNPU 的模型格式。RKNN 模型的优点是在保证精度的同时,可以实现高效的推理和低功耗的计算。

RKNN 模型的文件格式:使用自定义的文件格式来表示神经网络模型。它将神经网络模型划分为两个部分:静态部分和动态部分。静态部分包括模型的网络结构和权重,而动态部分包括输入输出的形状和类型等信息。使用这种格式可以减少模型的存储空间和加载时间。

RKNN 模型的转换工具:为了其他常见深度学习模型转换成 RKNN 模型,瑞芯微提供了一个转换工具 RKNN-Toolkit2。该工具支持将 TensorFlow、Caffe、MXNet 等框架训练出来的模型转换为 RKNN 模型,并且支持对模型进行量化、融合等优化操作,以提高运行效率。

RKNN 模型的部署和推理:RKNN 模型可以通过 RKNPU 硬件进行高效的推理。在部署 RKNN 模型时,开发者需要使用 RKNPU 提供的 SDK,并调用相应的 API 接口来加载和运行 RKNN 模型。 由于 RKNPU 硬件的优化,RKNN 模型可以实现高效的推理和低功耗的计算。

RKNN 的工具链介绍

RKNN 软件栈整体介绍

RKNN-Toolkit2 功能介绍

RKNN-Toolkit2是为用户提供在计算机上进行模型转换、推理和性能评估的开发套件,RKNN-Toolkit2的主要框图如下:

通过该工具提供的Python接口可以便捷地完成以下功能:

**1. 模型转换:**支持将PyTorch、ONNX、TensorFlow、TensorFlow Lite、Caffe、DarkNet等模型转为 RKNN模型。

**2. 量化功能:**支持将浮点模型量化为定点模型,并支持混合量化。

**3. 模型推理:**将RKNN模型分发到指定的NPU设备上进行推理并获取推理结果;或在计算机上仿真

NPU运行RKNN模型并获取推理结果。

**4. 性能和内存评估:**将RKNN模型分发到指定NPU设备上运行,以评估模型在实际设备上运行时的性

能和内存占用情况。

5. 量化精度分析:该功能将给出模型量化后每一层推理结果与浮点模型推理结果的余弦距离和欧氏距

离,以便于分析量化误差是如何出现的,为提高量化模型的精度提供思路。

**6. 模型加密功能:**使用指定的加密等级将RKNN模型整体加密。

仓库:https://github.com/airockchip/rknn-toolkit2

  • docs 目录:包含了 RKNN-Toolkit2 的使用文档
  • rknn-toolkit2 目录:其中的 examples 目录包含了 RKNN-Toolkit2 的一些示例代码;其中的 packages 目录提供了 不同 python 版本的安装包,对应 X86 平台的 Linux 操作系统,其是安装 在 PC 端 X86 平台 linux 系统上的。
  • rknn_toolkit_lite2 目录:RKNN Toolkit lite2 相较于RKNN-Toolkit2 多 了 一 个 lite 后缀 , 可以简单的 理 解 为 RKNN-Toolkit2 的阉割版,只保留了推理的功能,可以帮助用户在开发板端进行模型的初步部署和测试。二者的区别如下:(1)RKNN-Toolkit2 提供的安装包架构为 X86_64,运行在日常使用的 PC 电脑上,一般情况下安装在虚拟机 ubuntu 上,而 RKNN-Toolkit lite2 提供的安装包架构为 aarch64,运行在 RK3568 或者 RK3588 开发板上;(2)RKNN-Toolkit2 可以进行模型转换、推理和性能评估,而 RKNN-Toolkit lite2 只保留了推理功能,用于模型的初步部署和调试。

RKNPU2-SDK

其 examples 目录包含了 RKNPU2 SDK 的一些示例代码和第三方库。通过这些示例代码,可以 帮助我们更快速的掌握 RKNPU2 所提供的一些 C API 接口和使用流程。

  runtime目录用于存放API运行时依赖的动态库和头文件,并且提供了一个名为rknn_server 的可执行程序,该服务启动后可用于 PC 端和板端进行通信,在后面可以通过 RKNN-Toolkit2 模拟器用于 PC 端连板推理。

  RKNPU2 SDK 和 RKNN-Toolkit lite2 要实现的功能相同,都是在开发板部署 RKNN 模型,然 后进行推理,只是相较于 RKNN-Toolkit lite2 提供了更多的接口,调用起来较为复杂,但可以获得更好的运行效果。

RKNPU 的学习过程总结

开发环境搭建

PC 端采用 虚拟机上的 Ubunt20.04 系统

通过 pip 方式安装

安装 anconda

通过 conda 创建虚拟环境

conda create -n RKNN-Toolkit2 python=3.8
conda activate RKNN-Toolkit2

安装 RKNN-Toolkit2 依赖库

下载 rknn-toolkit2 的仓库:git clone https://github.com/airockchip/rknn-toolkit2/tree/master

进入到以下路径中:

选择你对应的 python 版本,执行如下命令:

pip install -r requirements_cp38-2.2.0.txt

安装 RKNN-Toolkit2

进入到以下路径中:

选择你对应的 python 版本,执行如下命令:

pip install rknn_toolkit2-2.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

验证 RKNN-Toolkit2 是否安装成功

 python
>>> from rknn.api import RKNN

板端使用 RK3588,安装的是 aarch64 的 linux 系统

板端 NPU 环境装备

NPU 驱动版本确认
cat /sys/kernel/debug/rknpu/version

Rockchip的固件均自带RKNPU驱动。若以上命令均查询不到NPU驱动版本,则可能为第三方固件未安装RKNPU驱动。

NPU 连板环境确认

RKNN-Toolkit2的连板调试功能依赖板端的RKNN Server程序,该程序是一个运行在开发板上的后台代理服务,用于接收PC通过USB传输过来的命令和数据,然后调用相应的运行时接口,并返回相应结果给PC。所以在做连板调试前需要确认开发板是否已启动RKNN Server程序。

在板端执行如下命令:

ps | grep rknn_server

若正常启动,则结果如下:

若没有查询到RKNN Server进程ID,则执行以下命令手动启动RKNN Server后再进行查询。

nohup /usr/bin/rknn_server >/dev/null&

RKNN Server 安装和更新

RKNN-Toolkit2 的连板调试功能要求板端已安装 RKNPU2 环境,并且启动 RKNN Server 服务。以下是RKNPU2 环境中的2个基本概念:

  1. RKNN Server: 运行在开发板上的后台代理服务,用于接收PC通过 USB 传输过来的数据,然后执行板端 Runtime 对应的接口,并返回结果给 PC。
  2. RKNPU2 Runtime 库:
    1. librknnrt.so: 是用于 RK3562 / RK3566 / RK3568 / RK3576 / RK3588 板端的 Runtime 库。
    2. librknnmrt.so: 是用于 RV1103 / RV1103B / RV1106 / RV1106B板端的 Runtime 库。

RKNN 的模型评估工具

模型精度分析

查看量化模型推理结果是否有问题,可以使用** rknn.accuracy_analysis() **接口进行精度分析,查看每层算子的精度。

精度分析接口 rknn.accuracy_analysis() 参数如下:

  • inputs:输入文件路径列表(格式包括 jpg 、 png 、 bmp 和 npy )。
  • output_dir:分析结果保存目录,默认值为’ ./snapshot '。
  • target:目标硬件平台。默认值为 None ,使用仿真器进行精度分析。如果设置了target(RK3562 /RK3566 / RK3568 / RK3588 / RK3576 / RV1103 / RV1103B / RV1106 / RV1106B),则会获取运行时每一层的结果,并进行精度分析。
  • device_id:设备编号。默认值为 None 。若有设置target则选择唯一一台设备进行精度分析。如果电脑连接多台设备时,需要指定相应的设备ID。

simulator_error:

entire: 从输入层到当前层 simulator 结果与 golden 结果对比的余弦距离和欧氏距离。

single: 当前层使用 golden 输入时,simulator 结果与 golden 结果对比的余弦距离和欧氏距离。

runtime_error:

entire: 从输入层到当前层板端实际推理结果与 golden 结果对比的余弦距离和欧氏距离。

single: 当前层使用 golden 输入时,板端当前层实际推理结果与 golden 结果对比的余弦距离和欧氏距离。

注:

  1. 精度分析工具会保存模型在推理时网络每一层的输出结果,因此如果模型太大且开发板上存储容量不够会导致运行失败。
  2. .通过 rknn.load_rknn() 加载RKNN模型后,因RKNN模型缺失原始模型信息,因此无法使用模型精度分析功能。精度分析工具只能通过rknn.load_onnx(model=ONNX_MODEL),加载 onnx 模型来构建 rknn,才可以使用精度分析工具。

模型性能分析

接口 rknn.eval_perf() 会输出当前的硬件频率并获取模型的性能评估结果, fix_freq 参数表示是否需要尝试对

硬件(包括CPU/NPU/DDR)定频,如果要对硬件定频则设置成 True ,否则设置成 False ,默认值为True 。若初始化时 rknn.init_runtime() 的 perf_debug 参数设置为 True ,将输出每一层的耗时情况和总耗时情况,若为 False 则只输出总耗时情况。

ret = rknn.init_runtime(target=platform, perf_debug=True)
perf_detail = rknn.eval_perf()

以RK3588为例,执行eval_perf后输出的硬件频率如下(不同固件支持的频率可能会有所差异),其中,由于RK3588 CPU是大小核架构,CPU频率栏会输出多个频率值,单位为KHz,而NPU和DDR频率单位都是Hz。

性能评估结果如下:

模型的内存评估

接口 rknn.eval_memory() 可以获取模型的内存消耗评估结果。初始化时 rknn.init_runtime() 的 eval_mem 参数设

置为 True ,将输出各部分内存消耗情况。

ret = rknn.init_runtime(target=platform, eval_mem=True)
memory_detail = rknn.eval_memory()

模型内存评估结果如下:

板端实际部署

部署步骤

训练模型转 ONNX 模型

当浮点模型训练收敛后,设置模型为评估状态model.eval()。定义好模型的输入数据,即可进行 onnx 模型导出。

 self.model.eval()
        #导出onnx模型,for rk3588
        #dummy_input = torch.randn(1, 3, 640, 640, requires_grad=True).cuda()
        dummy_input = torch.randn(1, 3, 512, 512, requires_grad=True).cuda()
        torch.onnx.export(self.model,  # 模型
                          dummy_input,  # 模型输入
                          "DB_witnn_512x512.onnx",  # 输出文件名
                          export_params=True,  # 是否导出训练后的参数
                          opset_version=12,  # ONNX版本
                          do_constant_folding=True,  # 是否执行常量折叠优化
                          input_names=['images'],  # 输入名
                          output_names=['output'],  # 输出名
                         )

ONNX 模型转 RKNN 模型

通过 pc 端搭建好 RKNN-Toolkit2 工具环境,将 onnx 模型转换成板端能执行的 RKNN 模型。

根据模型确定转换参数,模型转换时,"rknn.config()"和"rknn.build()"接口会影响模型转换结果。

  1. 可以参考以下基本步骤进行模型转换:
1. 准备量化数据,提供dataset.txt文件。
2. 确定模型要使用的NPU平台,如RK3566、RV1106等,并填写rknn.config()接口中的target_platform参数。
3. 当输入是3通道的图像,且量化数据采用的是图片格式(如jpg、png格式)时,需要确认模型的输入是RGB还是BGR,以决定rknn.config()接口中quant_img_RGB2BGR参数的值。
4. 确认模型训练时候的归一化参数,以决定rknn.config接口中的mean_values和std_values参数的值。
5. 确认模型输入的尺寸信息,填入load接口相应参数中,如rknn.load_pytorch()接口中的input_size_list参数。
6. 确认模型要量化比特数,以决定rknn.config()接口中的quantized_dtype参数的值。不对模型进行量化或加载的是已量化模型时可以忽略此步骤。
7. 确认模型量化时使用的量化算法,以决定rknn.config()接口中quantized_algorithm参数的值。不对模型进行量化或加载已量化模型时可以忽略此步骤。
8. 确认是否对模型进行量化,以决定rknn.build()接口中do_quantization参数的值。选择对模型进行量化时,需要额外填写rknn.build()接口中的dataset参数,指定量化正数据。
  1. 量化数据数据的格式和要求:

因为由 onnx 模型转换到 rknn 模型,会进行网络权重的量化操作,所以在转换时需要提供量化校正的数据。

量化校正数据的格式有两种选择:一种是图片格式(jpg,png),RKNN-Toolkit2会调用OpenCV接口进行读取;另一种是npy格式,RKNN-Toolkit2会调用numpy接口进行读取。对于非RGB/BGR图片输入的模型,建议使用numpy的npy格式提供量化数据。

多输入模型dataset.txt文件的填写方式:

模型量化需要用dataset.txt文件指定量化数据的路径。规则为一行作为一组输入,模型存在多输入时,多个输入写在同一行,并用空格隔开。eg:

单输入模型:

sampleA.npy
sampleB.npy

3 个输入模型:

sampleA_in0.npy sampleA_in1.npy sampleA_in2.npy
sampleB_in0.npy sampleB_in1.npy sampleB_in2.npy

代码示例:

import os
import urllib
import traceback
import time
import sys
import numpy as np
import cv2
from rknn.api import RKNN

# Model from https://github.com/airockchip/rknn_model_zoo
ONNX_MODEL = 'DB_witnn_512x512.onnx'
RKNN_MODEL = 'DB_witnn_fp16_512x512.rknn'
IMG_PATH = 'IMG_0059.JPG'
DATASET = './dataset.txt'

QUANTIZE_ON = False
IMG_SIZE = 512


if __name__ == '__main__':

    # Create RKNN object
    rknn = RKNN(verbose=True)

    # pre-process config
    print('--> Config model')
    rknn.config(mean_values=[[0, 0, 0]], std_values=[[229, 224, 225]],quant_img_RGB2BGR=False, target_platform='rk3588')
    print('done')

    # Load ONNX model
    print('--> Loading model')
    ret = rknn.load_onnx(model=ONNX_MODEL)
    if ret != 0:
        print('Load model failed!')
        exit(ret)
    print('done')

    # Build model
    print('--> Building model')
    ret = rknn.build(do_quantization=QUANTIZE_ON, dataset=DATASET)
    if ret != 0:
        print('Build model failed!')
        exit(ret)
    print('done')

    # Export RKNN model
    print('--> Export rknn model')
    ret = rknn.export_rknn(RKNN_MODEL)
    if ret != 0:
        print('Export rknn model failed!')
        exit(ret)
    print('done')

    # Init runtime environment
    print('--> Init runtime environment')
    ret = rknn.init_runtime()
    
    if ret != 0:
        print('Init runtime environment failed!')
        exit(ret)
    print('done')

    # Set inputs
    img = cv2.imread(IMG_PATH)
    # img, ratio, (dw, dh) = letterbox(img, new_shape=(IMG_SIZE, IMG_SIZE))
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img = cv2.resize(img, (IMG_SIZE, IMG_SIZE))

    # Inference
    print('--> Running model')
    img2 = np.expand_dims(img, 0)
    outputs = rknn.inference(inputs=[img2], data_format=['nhwc'])
    print("#"*100)
    print(outputs[0].shape)
    print('done')

    # post process
    print(type(outputs[0]))

    out_mask=outputs[0][0, 0, :, :]
    print(out_mask.shape)

    img_1 = cv2.imwrite("DB_model__fp16_out_512x512.png",out_mask*255)
    print('Save results to DB_model_out.jpg!')

    rknn.release()

PC 端加载 RKNN 模型在模拟器上运行

可参考上一节示例代码,在 onnx 转成 rknn 模型后,同时也构建了 rknn 的 runtime,这时便可以在 pc 端的模拟器上进行推理。模拟器推理: RKNN-Toolkit2 在 Linux x86_64 平台提供模拟器功能,可以在没有开发板的情况下进行模型推理,获取推理结果。(该功能输出结果未必与连板或板端一致,更推荐使用连板推理或板端推理)。

PC 端连接板子(RK3588) 运行

**连板推理:**指在开发板已连接电脑的情况下,调用 RKNN-Toolkit2 的 Python API 推理模型,获取推理结果。使用连板推理时,RKNN-Toolkit2会与板端的RKNN Server进行通信,通信时会将模型、模型的输入由PC端传至板端,随后调用RKNPU2 C API进行模型推理,板端推理完成后将结果回传至PC端。

import urllib
import time
import sys
import numpy as np
import cv2
from rknn.api import RKNN

RKNN_MODEL = 'yolov5s_relu_rk3588.rknn'
IMG_PATH = 'bus.jpg'
OBJ_THRESH = 0.25
NMS_THRESH = 0.45
IMG_SIZE = 640


CLASSES = ['person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light',
           'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow',
           'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee',
           'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard',
           'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple',
           'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch',
           'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone',
           'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear',
           'hair drier', 'toothbrush']


def xywh2xyxy(x):
    # Convert [x, y, w, h] to [x1, y1, x2, y2]
    y = np.copy(x)
    y[:, 0] = x[:, 0] - x[:, 2] / 2  # top left x
    y[:, 1] = x[:, 1] - x[:, 3] / 2  # top left y
    y[:, 2] = x[:, 0] + x[:, 2] / 2  # bottom right x
    y[:, 3] = x[:, 1] + x[:, 3] / 2  # bottom right y
    return y


def process(input, mask, anchors):

    anchors = [anchors[i] for i in mask]
    grid_h, grid_w = map(int, input.shape[0:2])

    box_confidence = input[..., 4]
    box_confidence = np.expand_dims(box_confidence, axis=-1)

    box_class_probs = input[..., 5:]

    box_xy = input[..., :2]*2 - 0.5

    col = np.tile(np.arange(0, grid_w), grid_w).reshape(-1, grid_w)
    row = np.tile(np.arange(0, grid_h).reshape(-1, 1), grid_h)
    col = col.reshape(grid_h, grid_w, 1, 1).repeat(3, axis=-2)
    row = row.reshape(grid_h, grid_w, 1, 1).repeat(3, axis=-2)
    grid = np.concatenate((col, row), axis=-1)
    box_xy += grid
    box_xy *= int(IMG_SIZE/grid_h)

    box_wh = pow(input[..., 2:4]*2, 2)
    box_wh = box_wh * anchors

    box = np.concatenate((box_xy, box_wh), axis=-1)

    return box, box_confidence, box_class_probs


def filter_boxes(boxes, box_confidences, box_class_probs):
    """Filter boxes with box threshold. It's a bit different with origin yolov5 post process!

    # Arguments
        boxes: ndarray, boxes of objects.
        box_confidences: ndarray, confidences of objects.
        box_class_probs: ndarray, class_probs of objects.

    # Returns
        boxes: ndarray, filtered boxes.
        classes: ndarray, classes for boxes.
        scores: ndarray, scores for boxes.
    """
    boxes = boxes.reshape(-1, 4)
    box_confidences = box_confidences.reshape(-1)
    box_class_probs = box_class_probs.reshape(-1, box_class_probs.shape[-1])

    _box_pos = np.where(box_confidences >= OBJ_THRESH)
    boxes = boxes[_box_pos]
    box_confidences = box_confidences[_box_pos]
    box_class_probs = box_class_probs[_box_pos]

    class_max_score = np.max(box_class_probs, axis=-1)
    classes = np.argmax(box_class_probs, axis=-1)
    _class_pos = np.where(class_max_score >= OBJ_THRESH)

    boxes = boxes[_class_pos]
    classes = classes[_class_pos]
    scores = (class_max_score* box_confidences)[_class_pos]

    return boxes, classes, scores


def nms_boxes(boxes, scores):
    """Suppress non-maximal boxes.

    # Arguments
        boxes: ndarray, boxes of objects.
        scores: ndarray, scores of objects.

    # Returns
        keep: ndarray, index of effective boxes.
    """
    x = boxes[:, 0]
    y = boxes[:, 1]
    w = boxes[:, 2] - boxes[:, 0]
    h = boxes[:, 3] - boxes[:, 1]

    areas = w * h
    order = scores.argsort()[::-1]

    keep = []
    while order.size > 0:
        i = order[0]
        keep.append(i)

        xx1 = np.maximum(x[i], x[order[1:]])
        yy1 = np.maximum(y[i], y[order[1:]])
        xx2 = np.minimum(x[i] + w[i], x[order[1:]] + w[order[1:]])
        yy2 = np.minimum(y[i] + h[i], y[order[1:]] + h[order[1:]])

        w1 = np.maximum(0.0, xx2 - xx1 + 0.00001)
        h1 = np.maximum(0.0, yy2 - yy1 + 0.00001)
        inter = w1 * h1

        ovr = inter / (areas[i] + areas[order[1:]] - inter)
        inds = np.where(ovr <= NMS_THRESH)[0]
        order = order[inds + 1]
    keep = np.array(keep)
    return keep


def yolov5_post_process(input_data):
    masks = [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
    anchors = [[10, 13], [16, 30], [33, 23], [30, 61], [62, 45],
               [59, 119], [116, 90], [156, 198], [373, 326]]

    boxes, classes, scores = [], [], []
    for input, mask in zip(input_data, masks):
        b, c, s = process(input, mask, anchors)
        b, c, s = filter_boxes(b, c, s)
        boxes.append(b)
        classes.append(c)
        scores.append(s)

    boxes = np.concatenate(boxes)
    boxes = xywh2xyxy(boxes)
    classes = np.concatenate(classes)
    scores = np.concatenate(scores)

    nboxes, nclasses, nscores = [], [], []
    for c in set(classes):
        inds = np.where(classes == c)
        b = boxes[inds]
        c = classes[inds]
        s = scores[inds]

        keep = nms_boxes(b, s)

        nboxes.append(b[keep])
        nclasses.append(c[keep])
        nscores.append(s[keep])

    if not nclasses and not nscores:
        return None, None, None

    boxes = np.concatenate(nboxes)
    classes = np.concatenate(nclasses)
    scores = np.concatenate(nscores)

    return boxes, classes, scores


def draw(image, boxes, scores, classes):
    """Draw the boxes on the image.

    # Argument:
        image: original image.
        boxes: ndarray, boxes of objects.
        classes: ndarray, classes of objects.
        scores: ndarray, scores of objects.
        all_classes: all classes name.
    """
    print("{:^12} {:^12}  {}".format('class', 'score', 'xmin, ymin, xmax, ymax'))
    print('-' * 50)
    for box, score, cl in zip(boxes, scores, classes):
        top, left, right, bottom = box
        top = int(top)
        left = int(left)
        right = int(right)
        bottom = int(bottom)

        cv2.rectangle(image, (top, left), (right, bottom), (255, 0, 0), 2)
        cv2.putText(image, '{0} {1:.2f}'.format(CLASSES[cl], score),
                    (top, left - 6),
                    cv2.FONT_HERSHEY_SIMPLEX,
                    0.6, (0, 0, 255), 2)

        print("{:^12} {:^12.3f} [{:>4}, {:>4}, {:>4}, {:>4}]".format(CLASSES[cl], score, top, left, right, bottom))

def letterbox(im, new_shape=(640, 640), color=(0, 0, 0)):
    # Resize and pad image while meeting stride-multiple constraints
    shape = im.shape[:2]  # current shape [height, width]
    if isinstance(new_shape, int):
        new_shape = (new_shape, new_shape)

    # Scale ratio (new / old)
    r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])

    # Compute padding
    ratio = r, r  # width, height ratios
    new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
    dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]  # wh padding

    dw /= 2  # divide padding into 2 sides
    dh /= 2

    if shape[::-1] != new_unpad:  # resize
        im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
    top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
    left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
    im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)  # add border
    return im, ratio, (dw, dh)


if __name__ == '__main__':
     # Create RKNN object
    rknn = RKNN(verbose=True)

    # Load RKNN model
    print('--> Loading model')
    ret = rknn.load_rknn(RKNN_MODEL)
    if ret != 0:
        print('Load model failed!')
        exit(ret)
    print('done')
    
    # Init runtime environment
    print('--> Init runtime environment')
    ret = rknn.init_runtime(target='rk3588')
    if ret != 0:
        print('Init runtime environment failed!')
        exit(ret)
    print('done')
    
    # Set inputs
    img = cv2.imread(IMG_PATH)
    # img, ratio, (dw, dh) = letterbox(img, new_shape=(IMG_SIZE, IMG_SIZE))
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img = cv2.resize(img, (IMG_SIZE, IMG_SIZE))

    # Inference
    print('--> Running model')
    img2 = np.expand_dims(img, 0)
    outputs = rknn.inference(inputs=[img],data_format=['nhwc'])
    rknn.val_perf()

    input0_data = outputs[0]
    input1_data = outputs[1]
    input2_data = outputs[2]

    input0_data = input0_data.reshape([3, -1] + list(input0_data.shape[-2:]))
    input1_data = input1_data.reshape([3, -1] + list(input1_data.shape[-2:]))
    input2_data = input2_data.reshape([3, -1] + list(input2_data.shape[-2:]))

    input_data = list()
    input_data.append(np.transpose(input0_data, (2, 3, 0, 1)))
    input_data.append(np.transpose(input1_data, (2, 3, 0, 1)))
    input_data.append(np.transpose(input2_data, (2, 3, 0, 1)))

    boxes, classes, scores = yolov5_post_process(input_data)

    img_1 = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
    if boxes is not None:
       draw(img_1, boxes, scores, classes)

    cv2.imwrite("pc_board_out.png",img_1)
    cv2.imshow("img", img_1)
    c = cv2.waitKey(10000)

    cv2.destroyAllWindows()

常见问题:

  1. 板端的RKNN Server 没有开启

在板端输入以下命令进行查看:

 ps | grep rknn_server

手动开启板端的 RKNN Server服务:

nohup /usr/bin/rknn_server >/dev/null&

在板端通过 python 部署

和在 PC 端通过RKNN-Toolkit2 模拟器进行推理不同的是,在板端推理是通过RKNN-Toolkit_lite2 进行推理的。RKNN-Toolkit2 和RKNN-Toolkit_lite2 的区别请看章节 1.3.2 处的介绍。

import numpy as np
import cv2
from rknnlite.api import RKNNLite

#RKNN_MODEL = 'yolov5s_relu.rknn'
RKNN_MODEL = 'yolov5s.rknn'
IMG_PATH = 'bus.jpg'

QUANTIZE_ON = True

OBJ_THRESH = 0.25
NMS_THRESH = 0.45
IMG_SIZE = 640

CLASSES = ("person", "bicycle", "car", "motorbike ", "aeroplane ", "bus ", "train", "truck ", "boat", "traffic light",
           "fire hydrant", "stop sign ", "parking meter", "bench", "bird", "cat", "dog ", "horse ", "sheep", "cow", "elephant",
           "bear", "zebra ", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee", "skis", "snowboard", "sports ball", "kite",
           "baseball bat", "baseball glove", "skateboard", "surfboard", "tennis racket", "bottle", "wine glass", "cup", "fork", "knife ",
           "spoon", "bowl", "banana", "apple", "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza ", "donut", "cake", "chair", "sofa",
           "pottedplant", "bed", "diningtable", "toilet ", "tvmonitor", "laptop	", "mouse	", "remote ", "keyboard ", "cell phone", "microwave ",
           "oven ", "toaster", "sink", "refrigerator ", "book", "clock", "vase", "scissors ", "teddy bear ", "hair drier", "toothbrush ")

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def xywh2xyxy(x):
    # Convert [x, y, w, h] to [x1, y1, x2, y2]
    y = np.copy(x)
    y[:, 0] = x[:, 0] - x[:, 2] / 2  # top left x
    y[:, 1] = x[:, 1] - x[:, 3] / 2  # top left y
    y[:, 2] = x[:, 0] + x[:, 2] / 2  # bottom right x
    y[:, 3] = x[:, 1] + x[:, 3] / 2  # bottom right y
    return y

def process(input, mask, anchors):

    anchors = [anchors[i] for i in mask]
    grid_h, grid_w = map(int, input.shape[0:2])

    box_confidence = sigmoid(input[..., 4])
    box_confidence = np.expand_dims(box_confidence, axis=-1)

    box_class_probs = sigmoid(input[..., 5:])

    box_xy = sigmoid(input[..., :2])*2 - 0.5

    col = np.tile(np.arange(0, grid_w), grid_w).reshape(-1, grid_w)
    row = np.tile(np.arange(0, grid_h).reshape(-1, 1), grid_h)
    col = col.reshape(grid_h, grid_w, 1, 1).repeat(3, axis=-2)
    row = row.reshape(grid_h, grid_w, 1, 1).repeat(3, axis=-2)
    grid = np.concatenate((col, row), axis=-1)
    box_xy += grid
    box_xy *= int(IMG_SIZE/grid_h)

    box_wh = pow(sigmoid(input[..., 2:4])*2, 2)
    box_wh = box_wh * anchors

    box = np.concatenate((box_xy, box_wh), axis=-1)

    return box, box_confidence, box_class_probs

def filter_boxes(boxes, box_confidences, box_class_probs):
    """Filter boxes with box threshold. It's a bit different with origin yolov5 post process!

    # Arguments
        boxes: ndarray, boxes of objects.
        box_confidences: ndarray, confidences of objects.
        box_class_probs: ndarray, class_probs of objects.

    # Returns
        boxes: ndarray, filtered boxes.
        classes: ndarray, classes for boxes.
        scores: ndarray, scores for boxes.
    """
    boxes = boxes.reshape(-1, 4)
    box_confidences = box_confidences.reshape(-1)
    box_class_probs = box_class_probs.reshape(-1, box_class_probs.shape[-1])

    _box_pos = np.where(box_confidences >= OBJ_THRESH)
    boxes = boxes[_box_pos]
    box_confidences = box_confidences[_box_pos]
    box_class_probs = box_class_probs[_box_pos]

    class_max_score = np.max(box_class_probs, axis=-1)
    classes = np.argmax(box_class_probs, axis=-1)
    _class_pos = np.where(class_max_score >= OBJ_THRESH)

    boxes = boxes[_class_pos]
    classes = classes[_class_pos]
    scores = (class_max_score* box_confidences)[_class_pos]

    return boxes, classes, scores


def nms_boxes(boxes, scores):
    """Suppress non-maximal boxes.

    # Arguments
        boxes: ndarray, boxes of objects.
        scores: ndarray, scores of objects.

    # Returns
        keep: ndarray, index of effective boxes.
    """
    x = boxes[:, 0]
    y = boxes[:, 1]
    w = boxes[:, 2] - boxes[:, 0]
    h = boxes[:, 3] - boxes[:, 1]

    areas = w * h
    order = scores.argsort()[::-1]

    keep = []
    while order.size > 0:
        i = order[0]
        keep.append(i)

        xx1 = np.maximum(x[i], x[order[1:]])
        yy1 = np.maximum(y[i], y[order[1:]])
        xx2 = np.minimum(x[i] + w[i], x[order[1:]] + w[order[1:]])
        yy2 = np.minimum(y[i] + h[i], y[order[1:]] + h[order[1:]])

        w1 = np.maximum(0.0, xx2 - xx1 + 0.00001)
        h1 = np.maximum(0.0, yy2 - yy1 + 0.00001)
        inter = w1 * h1

        ovr = inter / (areas[i] + areas[order[1:]] - inter)
        inds = np.where(ovr <= NMS_THRESH)[0]
        order = order[inds + 1]
    keep = np.array(keep)
    return keep


def yolov5_post_process(input_data):
    masks = [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
    anchors = [[10, 13], [16, 30], [33, 23], [30, 61], [62, 45],
               [59, 119], [116, 90], [156, 198], [373, 326]]

    boxes, classes, scores = [], [], []
    for input, mask in zip(input_data, masks):
        b, c, s = process(input, mask, anchors)
        b, c, s = filter_boxes(b, c, s)
        boxes.append(b)
        classes.append(c)
        scores.append(s)

    boxes = np.concatenate(boxes)
    boxes = xywh2xyxy(boxes)
    classes = np.concatenate(classes)
    scores = np.concatenate(scores)

    nboxes, nclasses, nscores = [], [], []
    for c in set(classes):
        inds = np.where(classes == c)
        b = boxes[inds]
        c = classes[inds]
        s = scores[inds]

        keep = nms_boxes(b, s)

        nboxes.append(b[keep])
        nclasses.append(c[keep])
        nscores.append(s[keep])

    if not nclasses and not nscores:
        return None, None, None

    boxes = np.concatenate(nboxes)
    classes = np.concatenate(nclasses)
    scores = np.concatenate(nscores)

    return boxes, classes, scores


def draw(image, boxes, scores, classes):
    """Draw the boxes on the image.

    # Argument:
        image: original image.
        boxes: ndarray, boxes of objects.
        classes: ndarray, classes of objects.
        scores: ndarray, scores of objects.
        all_classes: all classes name.
    """
    for box, score, cl in zip(boxes, scores, classes):
        top, left, right, bottom = box
        print('class: {}, score: {}'.format(CLASSES[cl], score))
        print('box coordinate left,top,right,down: [{}, {}, {}, {}]'.format(top, left, right, bottom))
        top = int(top)
        left = int(left)
        right = int(right)
        bottom = int(bottom)

        cv2.rectangle(image, (top, left), (right, bottom), (255, 0, 0), 2)
        cv2.putText(image, '{0} {1:.2f}'.format(CLASSES[cl], score),
                    (top, left - 6),
                    cv2.FONT_HERSHEY_SIMPLEX,
                    0.6, (0, 0, 255), 2)


def letterbox(im, new_shape=(640, 640), color=(0, 0, 0)):
    # Resize and pad image while meeting stride-multiple constraints
    shape = im.shape[:2]  # current shape [height, width]
    if isinstance(new_shape, int):
        new_shape = (new_shape, new_shape)

    # Scale ratio (new / old)
    r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])

    # Compute padding
    ratio = r, r  # width, height ratios
    new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
    dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]  # wh padding

    dw /= 2  # divide padding into 2 sides
    dh /= 2

    if shape[::-1] != new_unpad:  # resize
        im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
    top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
    left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
    im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)  # add border
    return im, ratio, (dw, dh)


if __name__ == '__main__':
    import datetime as dt

    # Create RKNN object
    rknn = RKNNLite(verbose=True)

    # load RKNN model
    print('--> Load RKNN model')
    ret = rknn.load_rknn(RKNN_MODEL)

    # Init runtime environment
    print('--> Init runtime environment')
    ret = rknn.init_runtime(core_mask=RKNNLite.NPU_CORE_0_1_2)  #使用0 1 2三个NPU核心
    #ret = rknn.init_runtime('rk3588')
    if ret != 0:
        print('Init runtime environment failed!')
        exit(ret)
    print('done')

    # Set inputs
    img = cv2.imread(IMG_PATH)
    # img, ratio, (dw, dh) = letterbox(img, new_shape=(IMG_SIZE, IMG_SIZE))
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img = cv2.resize(img, (IMG_SIZE, IMG_SIZE))

    # Inference
    print('--> Running model')
    img2 = np.expand_dims(img, 0)
    outputs = rknn.inference(inputs=[img2], data_format=['nhwc'])
    np.save('./onnx_yolov5_0.npy', outputs[0])
    np.save('./onnx_yolov5_1.npy', outputs[1])
    np.save('./onnx_yolov5_2.npy', outputs[2])
    print('done')

    # post process
    input0_data = outputs[0]
    input1_data = outputs[1]
    input2_data = outputs[2]

    input0_data = input0_data.reshape([3, -1] + list(input0_data.shape[-2:]))
    input1_data = input1_data.reshape([3, -1] + list(input1_data.shape[-2:]))
    input2_data = input2_data.reshape([3, -1] + list(input2_data.shape[-2:]))

    input_data = list()
    input_data.append(np.transpose(input0_data, (2, 3, 0, 1)))
    input_data.append(np.transpose(input1_data, (2, 3, 0, 1)))
    input_data.append(np.transpose(input2_data, (2, 3, 0, 1)))

    boxes, classes, scores = yolov5_post_process(input_data)

    img_1 = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
    if boxes is not None:
        draw(img_1, boxes, scores, classes)
        cv2.imwrite('result.jpg', img_1)
        print('Save results to result.jpg!')

    rknn.release()

在板端通过 C API 进行部署

板端常见的一些操作

CPU、NPU 定频操作

  • 查看当前 cpu 频率
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
  • 查看 cpu 可用频率
cat /sys/devices/system/cpu/cpufreq/policy0/scaling_available_frequencies

查看 NPU 的占用率

sudo cat /sys/kernel/debug/rknpu/load
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值