当前位置: 首页 > article >正文

RKNPU2从入门到实践 ---- 【9】使用RKNPU2的C API接口将RKNN模型部署在RK3588开发板上

注:作者使用的平台为Ubuntu20.04虚拟系统,开发板为RK3588,开发板上面的系统为Ubuntu22.04。 

前言

      本博文我们要学习使用 RKNPU2 提供的 C API 接口将RKNN模型部署在RK3588开发板上,完成测试图片在开发板上的推理工作。C API接口可以根据帧数据的更新方式分为通用API和零拷贝API。而这一篇博文主要介绍通用 API 接口。

项目文件包

项目文件包以百度网盘链接的形式给出
链接:https://pan.baidu.com/s/1n9M3BwMKDO3NhyfJzBvORQ 
提取码:1234
,整体文件夹如下图所示:

进入到该文件夹中,如下图所示:

一、cmake架构

      由于在后续程序编写的过程中,会涉及到一些第三方库,且瑞芯微提供的例程都是以cmake自动化构建工具来生成可执行文件、库和其他构建目标的。
      因此,在此之前,我们需要先了解一下cmake架构。
      打开Ubuntu虚拟系统,打开终端,创建一个work目录,用来存放后续的cmake工程。

然后将 01_Cmake工程示例 中的 00_example文件夹拷贝到work目录下。

使用 vscode 软件打开 work 这个工程。
打开work工程,如下图所示:

该工程下有两个目录,一个是model目录,另一个是src目录。
model目录存放了测试图片以及适用于RK3568和RK3588的RKNN模型。
src目录下存放了要编译的源码。
build.sh 脚本文件中设置了一些基本的环境变量,以及开始cmake的构建。

注意编译器的路径:

交叉编译器设置步骤如下所示:

1 安装 gcc 交叉编译器,拷贝 gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu.tar.gz 到 Ubuntu虚拟系统 的/usr/local/arm64/目录下,这里拷贝的路径要和作者保持一致,后面要用到交叉编译器的绝对路径。如下图所示:

2 解压交叉编译器压缩包 tar -vxf gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu.tar.gz ,解压完成后即可!


      CMakeLists.txt 文件表示cmake构建的配置文件,如下所示:

在上图中(CMakeLists.txt 文件中),我们截取第11、17行的内容,如下图所示:


我们发现,在设置第三方库rknn_api、opencv的CMAKE_SOURCE_DIR时,需要用到3rdparty这个文件夹。
因此我们将3rdparty这个文件夹放至work目录下,如下图所示。


这一步结束后,work目录下的情况如下图所示:

接下来我们运行build.sh脚本进行cmake工程的构建,运行结果如下:

生成新的目录,如下所示:

      从上图可以看到,多出了build目录和install目录,build目录用来存放工程构建过程中生成的中间文件。install目录用来存放编译完成之后的可执行程序、运行所用到的库以及RKNN模型和推理图片。
      到这里,关于cmake工程结构就结束了,接下来我们来学习通用API部署RKNN模型。

二、通用API部署RKNN模型

RKNPU2通用API使用流程如下所示: 

2.1 前奏工作 

回到vscode软件中,在work工程目录中创建 01_resnet18 目录。

将 00_example目录下的四个文件拷贝到 01_resnet18 目录下,如下所示:


首先,将CmakeLists.txt中的项目名称由:

修改为:

然后将main.cc代码清空,根据RKNPU2通用API使用流程图从零开始编写代码:

程序编写到该步骤(第7行)时可能会报错,这是因为我们还没有添加rknn的头文件。
我们在3rdparty目录中找到rknn头文件rknn_api.h所在的文件夹include的路径,如下图所示:

点击复制路径,然后使用快捷键 ctrl+shift+p 打开搜索框搜索
C/C++: Edit ConFigurations(JSON) ,如下所示:

如果输入C/C++: Edit ConFigurations(JSON)后并没有上图中红色框中的内容弹出,大概率是你的vscode中没有装C/C++ debug拓展,此时需要去拓展库装debug,如下图所示:

进入到 josn 文件后,内容如下所示:

我们需要将刚刚复制的RKNN库文件的路径添加在如下所示的位置中:

添加之后,返回代码处添加rknn头文件,如下图所示:

此时我们发现rknn_context从原先的灰色变为高亮色了,如下图所示:

这说明已经配置成功了。
接下来,就要按流程图进行编写代码了,请看下面

2.2 第一步:调用rknn_init接口创建rknn_context对象、加载 RKNN模型

2.2.1 rknn_init API函数介绍 

rknn_init 初始化函数将创建 rknn_context 对象、加载 RKNN 模型以及根据 flag 和 rknn _init_extend 结构体执行特定的初始化行为。

示例代码如下:

2.2.2 实际代码编写

      在实际代码编写中,调用rknn_init函数时,flag 和rknn_init_extend 目前用不到,因此将flag参数赋值为0,将rknn_init_extend赋值为NULL。

2.3 第二步:调用rknn_query接口查询获取到模型输入输出属性、推理时间、SDK版本等信息

2.3.1 rknn_query API函数介绍

具体介绍后续更新!!在本项目中,确实也用到了这个接口函数,如下图所示:

但由于介绍起来内容较多,因此后续以博文的形式单独介绍这个接口函数!!

2.4 第三步:调用rknn_inputs_set接口设置模型的输入数据

2.4.1 opencv读取输入数据

根据下图中的步骤配置好opencv库文件的路径,如下图所示: 


添加opencv的头文件,如下图所示:

使用opencv读取要推理的图像,如下图所示:

至此,opencv部分就结束了。

2.4.2 rknn_inputs_set API介绍

      通过 rknn_inputs_set 函数可以设置模型的输入数据。该函数能够支持多个输入,其中每个输入是 rknn_input 结构体对象,在传入之前用户需要设置该对象。(注:RV1106/RV1103 不支持这个接口)

示例代码如下:

2.4.3 rknn_inputs_set 实际代码编写

 

2.5 第四步:调用rknn_run接口执行模型推理

2.5.1 rknn_run API介绍

      rknn_run 函数将执行一次模型推理,调用之前需要先通过 rknn_inputs_set 函数或者零拷贝的接口设置输入数据。

示例代码如下:

2.5.2 rknn_run 接口实际代码编写
 ​​​​​​

 

2.6 第五步:调用rknn_outputs_get接口获取模型推理的输出数据

2.6.1 API介绍

      rknn_outputs_get 函数可以获取模型推理的输出数据。该函数能够一次获取多个输出数据。 其中每个输出是 rknn_output 结构体对象,在函数调用之前需要依次创建并设置每个 rknn_output 对象。
      对于输出数据的 buffer 存放可以采用两种方式:一种是用户自行申请和释放,此时 rknn_output 对象的 is_prealloc 需要设置为 1,并且将 buf 指针指向用户申请的 buffer;另一种是由 rknn 来进行分配,此时 rknn_output 对象的 is_prealloc 设置为 0 即可,函数执行之后 buf 将指向输出数据。(注:RV1106/RV1103 不支持这个接口) 


示例代码如下:

2.6.2 实际代码编写


      至此,rknn模型推理图像的过程就已经完成了,输出数据会保存到output结构体中的buffer成员之中。 为了得到我们常见的概率信息,还需要经过后处理部分,后处理的代码如下:

static int rknn_GetTop(float* pfProb, float* pfMaxProb, uint32_t* pMaxClass, uint32_t outputCount, uint32_t topNum)
{
  uint32_t i, j;

#define MAX_TOP_NUM 20
  if (topNum > MAX_TOP_NUM)
    return 0;

  memset(pfMaxProb, 0, sizeof(float) * topNum);
  memset(pMaxClass, 0xff, sizeof(float) * topNum);

  for (j = 0; j < topNum; j++) {
    for (i = 0; i < outputCount; i++) {
      if ((i == *(pMaxClass + 0)) || (i == *(pMaxClass + 1)) || (i == *(pMaxClass + 2)) || (i == *(pMaxClass + 3)) ||
          (i == *(pMaxClass + 4))) {
        continue;
      }

      if (pfProb[i] > *(pfMaxProb + j)) {
        *(pfMaxProb + j) = pfProb[i];
        *(pMaxClass + j) = i;
      }
    }
  }

  return 1;
}



   // Post Process
  for (int i = 0; i < io_num.n_output; i++) 
  {
    uint32_t MaxClass[5];
    float    fMaxProb[5];
    float*   buffer = (float*)output[i].buf;
    uint32_t sz     = output[i].size / 4;

    rknn_GetTop(buffer, fMaxProb, MaxClass, sz, 5);

    printf(" --- Top5 ---\n");
    for (int i = 0; i < 5; i++) {
      printf("%3d: %8.6f\n", MaxClass[i], fMaxProb[i]);
    }
  }


 

后处理完成之后,就需要释放前面所创建的资源了。请看下面。

2.7 第六步:调用rknn_outputs_release接口释放推理输出的相关资源

2.7.1 rknn_outputs_release API介绍

rknn_outputs_release 函数将释放 rknn_outputs_get 函数得到的输出的相关资源。 


示例代码如下所示:

2.7.2 实际代码编写


2.8 第七步:调用rknn_destroy释放传入的rknn_context及其相关资源

2.8.1 rknn_destroy API介绍

rknn_destroy 函数将释放传入的 rknn_context 及其相关资源。 


示例代码如下:

2.8.2 实际代码编写

到此,使用通用 API 加载RKNN模型并推理的程序就编写完成了。 

2.8.3 最终代码

整体代码如下所示:

#include<stdio.h>
#include "rknn_api.h"
#include "opencv2/core.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
using namespace cv;

static int rknn_GetTop(float* pfProb, float* pfMaxProb, uint32_t* pMaxClass, uint32_t outputCount, uint32_t topNum)
{
  uint32_t i, j;

#define MAX_TOP_NUM 20
  if (topNum > MAX_TOP_NUM)
    return 0;

  memset(pfMaxProb, 0, sizeof(float) * topNum);
  memset(pMaxClass, 0xff, sizeof(float) * topNum);

  for (j = 0; j < topNum; j++) {
    for (i = 0; i < outputCount; i++) {
      if ((i == *(pMaxClass + 0)) || (i == *(pMaxClass + 1)) || (i == *(pMaxClass + 2)) || (i == *(pMaxClass + 3)) ||
          (i == *(pMaxClass + 4))) {
        continue;
      }

      if (pfProb[i] > *(pfMaxProb + j)) {
        *(pfMaxProb + j) = pfProb[i];
        *(pMaxClass + j) = i;
      }
    }
  }

  return 1;
}


int main(int argc, char *argv[]){
  /*要求程序传入的第一个参数为RKNN模型,第二个参数为要推理的图片*/
  char *model_path = argv[1];
  char *image_path = argv[2];
  /*调用rknn_init接口将RKNN模型的运行环境和相关信息赋予到context变量中*/
  rknn_context context;
  rknn_init(&context,model_path,0,0,NULL);

  /*使用opencv读取要推理的图像数据*/
  cv::Mat img = cv::imread(image_path); 
  /*使用cvtColor进行通道转换*/
  cv::cvtColor(img,img,cv::COLOR_BGR2RGB);

  /*调用rknn_query接口查询tensor输入输出个数*/
  rknn_input_output_num io_num;
  rknn_query(context,RKNN_QUERY_IN_OUT_NUM,&io_num,sizeof(io_num));
  printf("model input num:%d,output num:%d\n",io_num.n_input,io_num.n_output);

  /*调用rknn_inputs_set接口设置输入数据*/
  rknn_input input[1];
  memset(input,0,sizeof(rknn_input));
  input[0].index = 0;
  input[0].buf = img.data;
  input[0].size = img.rows*img.cols*img.channels()*sizeof(uint8_t);
  input[0].pass_through = 0;
  input[0].type = RKNN_TENSOR_UINT8;
  input[0].fmt = RKNN_TENSOR_NHWC;
  rknn_inputs_set(context,1,input);

  /*调用rknn_run接口进行模型推理*/
  rknn_run(context,NULL);
  /*调用rknn_outputs_get接口获取模型推理结果*/
  rknn_output output[1]; 
  memset(output,0,sizeof(rknn_output));
  output[0].index = 0;
  output[0].is_prealloc = 0;
  output[0].want_float = 1; // 表示将输出数据转换为浮点类型
  rknn_outputs_get(context,1,output,NULL);

   // Post Process
  for (int i = 0; i < io_num.n_output; i++) 
  {
    uint32_t MaxClass[5];
    float    fMaxProb[5];
    float*   buffer = (float*)output[i].buf;
    uint32_t sz     = output[i].size / 4;

    rknn_GetTop(buffer, fMaxProb, MaxClass, sz, 5);

    printf(" --- Top5 ---\n");
    for (int i = 0; i < 5; i++) {
      printf("%3d: %8.6f\n", MaxClass[i], fMaxProb[i]);
    }
  }


  /*调用rknn_outputs_release接口释放推理输出的相关资源*/
  rknn_outputs_release(context,1,output);

  /*调用rknn_destory接口销毁context变量*/
  rknn_destroy(context);

  return 0;
}



2.9 运行build.sh文件进行cmake工程的构建 

      运行build.sh前后注意观察该文件夹内容变化,左图为没有运行build.sh文件之前的,右图为运行build.sh文件之后的。 


运行结束后终端输出信息如下:

      我们可以看到运行build.sh文件后多出了两个目录,一个build目录和install目录。install目录就是我们要放在开发板上运行测试的文件夹。这在博文刚开始的时候已经介绍过了,这里就不再赘述。

2.10 启动开发板、将生成的install目录拷贝到开发板系统上

2.10.1 开发板与电脑相连 

      将开发板与电脑连接好之后,启动开发板会在虚拟系统上弹出如下界面,按照下图选择并点击确定按键。

点击确定之后,若得到如下图:

      即在虚拟系统任务栏处出现了手机的标识,那么就说明开发板的adb工具已经成功连接至虚拟系统上了。

2.10.2 将生成的install目录拷贝到开发板系统上

      在这一操作中,有很多种方式,例如:用优盘拷贝等,但在这里,有一种更为简单的方式,即使用开发板的adb工具。
打开终端,如下图所示:

进入到 01_resnet18 目录,如下图所示:

使用adb push [xxx] [xxx] ,将install目录拷贝到开发板系统的根目录上,如下图所示:

      我们可以使用 adb shell 命令来进入到开发板的系统中,并查看install目录是否已经拷贝完成,如下图所示:

发现install已经放至开发板系统的根目录上去了。
进入 install目录中,如下图:

进入resnet18_Linux目录下:

接下来,使用./resnet18运行模型,第一个参数为rknn模型的路径,第二个参数为要推理的图片路径,如下所示:

当准备输入model时,会自动弹到如下界面的形式,并继续输入第二个参数剩余部分即可:

输入完成之后,按下回车键,得到运行结果:

我们看到812号的值(具体是什么值,目前有争议,等待后续更新)最大,而812号正是太空飞船,故推理成功。


http://www.kler.cn/a/287760.html

相关文章:

  • 什么是 C++ 内联函数?它的作用是什么?
  • java实现代码沙盒(docker-java)
  • Pandas进行周期与时间戳转换
  • 神经网络的正则化(一)
  • react 中 useCallback Hook 作用
  • 【Framework系列】UnityEditor调用外部程序详解
  • 当敏捷开发遇上AI
  • Go发布自定义包
  • 华为云征文|遥遥领先的华为云Flexus云服务器X它来了~~~~
  • vrrp协议,主备路由器的选举
  • 关于VUE3开发频繁引入ref,reactive,computed等基础函数。
  • mac电脑里面的 磁盘分区,容器,宗卷,宗卷组的理解和使用
  • 【HarmonyOS NEXT开发】鸿蒙开发环境准备,ArkTS基础语法入门
  • 【JavaScript + ES6】前端八股文面试题
  • 笔记:如何使用Process Explorer分析句柄泄露溢出问题
  • 【高校科研前沿】三峡大学黄进副教授等人在环境科学Top期刊JCP发文:人类活动如何在气候变化下影响和降低生态敏感性:以中国长江经济带为例
  • react + ts + material-ui V5版本的table封装
  • css-50 Projects in 50 Days(2)
  • SAP2 - 系统管理课程 System Administration Course
  • redisson异步解锁
  • Tower for Mac Git客户端管理软件
  • 450. 删除二叉搜索树中的节点
  • 跟李沐学AI:序列模型
  • STM32单片机和ARM有什么区别?
  • vue之函数式组件
  • git diff命令详解