模型部署——RKNN C API开发板上落地部署RKNN模型(附代码 详细图文教程)

欢迎学习RKNN系列相关文章,从模型转换、精度分析,评估到部署,推荐好资源:
一、Ubuntu系统上安装rknn-toolkit
二、使用rknn-toolkit将Pytorch模型转为RKNN模型
三、RKNN模型的评估和推理测试
四、RKNN模型量化精度分析及混合量化提高精度
五、RKNN模型性能评估和内存评估
六、rknn-toolkit-lite2部署RKNN模型到开发板上(python版)
七、RKNN C API开发板上落地部署RKNN模型
八、RKNN零拷贝API开发板落地部署RKNN模型


在这里插入图片描述

一、源码包准备

官网提供的开发文档和demo代码链接为:RKNN C API

本教程配套的源码包获取方法文章末扫码到公众号「视觉研坊」中回复关键字:RKNN C API部署RKNN模型。获取下载链接。

下载解压后的样子如下:

在这里插入图片描述

RKNN模型在跟目录下的model文件夹中,还有测试图片,如下:

在这里插入图片描述

关于RKNN C API的详细使用说明,都在源码包中的开发文档中。

RKNN C API不同硬件平台支持如下:

在这里插入图片描述

二、测试CMake工程构建

在Ubuntu系统中打开我提供的源码包中example文件,我自己使用的是VScode编译软件。如何在Ubuntu系统中安装VScode软件,推荐教程:Linux上安装VScode软件

2.1 拷贝交叉编译器

在编辑器中打开example文件夹中的build.sh文件,如下:

在这里插入图片描述
在这里插入图片描述

注意上面build.sh文件中的GCC_COMPILER参数,需要将交叉编译器的路径拷贝到此处,下面是将我提供源码包中Cross compiler文件夹下的压缩文件拷贝到arm64文件夹下,具体如下:

在这里插入图片描述

拷贝完后使用下面命令进行解压,,如下:

tar -vxf gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu.tar.gz

在这里插入图片描述

解压后得到的文件如下:

在这里插入图片描述

2.2 CMake构建的配置文件

CMake构建的配置文件如下,下面框起来的部分都不用修改,保持默认:

在这里插入图片描述
在这里插入图片描述

2.3 运行构建脚本

上面都准备好后,进入到example目录下,运行构建脚步,如下:

./build.sh

在这里插入图片描述

2.4 CMake构建结果

下面是运行上面脚本后的结果,在example目录下生成了builed和install两个文件夹,如下:

在这里插入图片描述

三、添加第三方库

3.1 添加rknn_api库

先按下快捷键ctrl+shift+p打开搜索框,输入编辑配置,点击c/c++:编辑配置(JSON),如下:

在这里插入图片描述

打开后是一个c_cpp_properties.json文件,将源码包中3rdparty/librknn_api/include的路径复制到此文件中,复制后ctrl+s保存,如下:

在这里插入图片描述

3.2 添加opencv库

同上在c_cpp_properties.json文件中,添加opencv文件夹下的include文件夹路径,记得保存,如下:

在这里插入图片描述

四、推理主文件

推理主文件为源码包resnet18/src目录下的main.cc脚本,此脚本中我写了很详细的注释。

4.1 修改项目名

在正式构建前需要先修改CMakeLists.txt文件下的project参数,如下:

在这里插入图片描述

4.2 推理主文件代码

下面代码对应源码包中的main.cc脚步,具体代码如下:

#include <stdio.h>
#include "rknn_api.h"
#include "opencv2/core/core.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include <string.h>

using namespace cv;   //将命名空间,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);   // 第一个参数为上面的context地址

  /*使用opencv读取要推理的图像数据*/
  cv::Mat img = cv::imread(image_path);
  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];                        //输入数组为1
  memset(input,0,sizeof(rknn_input));//使用memset格式化结构体变量  将input中的成员变量都设置为0
  input[0].index = 0;                         //对该结构体中的成员变量进行填写
  input[0].buf = img.data;                    // 设置输入数据指针 
  input[0].size = img.rows * img.cols * img.channels() * sizeof(uint8_t);//设置输入大小,即图片长宽    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 context对象,第二个参数为输入数据的个数,第三个参数为上面定义的input结构体变量

  /*调用rknn_run接口进行模型推理*/
  rknn_run(context,NULL);                     // 第一个参数为rknn context对象,第二个参数为扩展参数。

  /*调用rknn_outputs_get接口获取模型推理结果*/    // 推理结果会存放到output中的buf中
  rknn_output output[1];                      // 定义一个rknn_output结构体变量    由于rknn模型的输出只有一个,所以数组数量为1
  memset(output,0,sizeof(rknn_output));       // 使用memset初始化结构体  下面时设置结构体成员变量
  output[0].index = 0;
  output[0].is_prealloc = 0;                  //表示有rknn来分配输出数据buff的存放
  output[0].want_float = 1;                   //表示将输出数据转换为浮点类型
  rknn_outputs_get(context,1,output,NULL);    // 第一个参数为context对象,第二个参数为输出数据的格式,第三个参数为output结构体变量,第四个参数为扩展参数

  // Post Process   后处理
  for (int i = 0; i < io_num.n_output; i++)   // 打印推理前5名的类别
  {
    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;

}

4.3 C API解析

上面代码中用到的C API接口详细说明见下。

4.3.1 rknn_init

在这里插入图片描述
在这里插入图片描述

4.3.2 rknn_query

在这里插入图片描述

4.3.3 rknn_inputs_set

在这里插入图片描述

4.3.4 rknn_run

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

在这里插入图片描述

4.3.5 rknn_outputs_get

在这里插入图片描述
在这里插入图片描述

4.4 CMake构建

在终端进入到resnet18目录下,运行build.sh脚本,如下:

在这里插入图片描述

4.5 构建结果

运行上面build.sh后,在resnet18目录下生成了buile和install两个文件,关于这两个文件的详细解析,见上面2.4。

在这里插入图片描述

五、开发板上部署

5.1 程序文件发送

通过上面的操作,已经得到最终的install文件夹,现需要将install文件夹拷贝到开发板上,可以通过优盘拷贝,也可以通过命令发送,命令发送方式见下。

先通过rknn_server连通开发板,连通后用过adb将install文件发送到开发板的根目录上。

adb push install /

在这里插入图片描述

5.2 进入开发板终端

发送完程序文件后通过下面命令,直接进入开发板的可视化终端:

adb shell

在这里插入图片描述

5.3 执行程序

在开发板上进入到install目录下,执行下面命令运行推理程序,执行程序时需要通过命令方式传入三个参数,第一个是要执行的程序,第二个是RKNN模型路径,第三个是测试图片路径。

resnet18 model/RK3588/resnet18.rknn ./model/space_shuttle_224.jpg

在这里插入图片描述

5.4 程序运行结果

运行上面程序后,输出如下所示:

在这里插入图片描述

对照imagenet1000标签.txt文件,可以看出,推理结果正确,如下:

在这里插入图片描述

六、总结

以上就是RKNN C API开发板上落地部署RKNN模型的详细流程,希望能帮到你快速上手!

总结不易,多多支持!

感谢您阅读到最后!关注公众号「视觉研坊」,获取干货教程、实战案例、技术解答、行业资讯!

  • 9
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 17
    评论
A: 首先,需要安装CUDA和CUDNN的相关库,并下载yolov5的代码仓库。然后,按照以下步骤来编写部署在nvidia开发板上的yolov5模型推理代码。 1. 导入必要的头文件 ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <dirent.h> #include <errno.h> #include <fcntl.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/time.h> #include <cuda_runtime_api.h> #include <cublas_v2.h> #include <curand.h> #include <cudnn.h> #include "darknet.h" ``` 2. 定义模型类和相关参数 ```c typedef struct{ network net; float *output; float *input; cudaStream_t stream; } yolov5; #define BATCH 1 #define DEVICE 0 #define NMS_THRESH 0.45 #define CONF_THRESH 0.25 #define CLASS_NUM 80 #define ANCHOR_NUM 3 #define INPUT_H 640 #define INPUT_W 640 #define CLASS_NAME_FILE "./coco.names" #define WEIGHTS_FILE "./yolov5s.weights" ``` 3. 加载模型 ```c void load_model(yolov5 *model, char *classes_path, char *weights_path){ // 加载类别名 char **names = get_labels(classes_path); // 加载模型 model->net = parse_network_cfg(cfg_path); if(weights_path){ load_weights(&model->net, weights_path); } // 设置输入层 model->input = (float *)calloc(model->net.batch * model->net.inputs, sizeof(float)); cudaMalloc((void **)&model->output, model->net.batch * model->net.outputs * sizeof(float)); cudaStreamCreate(&model->stream); set_batch_network(&model->net, BATCH); model->net.layers[model->net.n - 1].classes = CLASS_NUM; model->net.layers[model->net.n - 1].anchor_num = ANCHOR_NUM; model->net.layers[model->net.n - 1].confidence_thresh = CONF_THRESH; model->net.layers[model->net.n - 1].nms_thresh = NMS_THRESH; model->net.layers[model->net.n - 1].mask = (int *)calloc(ANCHOR_NUM, sizeof(int)); for(int i = 0; i < ANCHOR_NUM; i++) model->net.layers[model->net.n - 1].mask[i] = i; srand(time(NULL)); } ``` 4. 推理函数 ```c void yolov5_inference(yolov5 *model, char *img_path){ // 加载图片 image img = load_image_color(img_path, 0, 0); // 缩放图片 image sized = resize_image(img, INPUT_W, INPUT_H); // 将数据写入输入层 fill_cuda_data(sized.data, INPUT_H * INPUT_W * 3, model->input, INPUT_H * INPUT_W * 3 * BATCH, model->stream); // 进行推理 forward_network(&model->net, model->input); // 获取输出结果 get_network_boxes(&model->net, img.w, img.h, CONF_THRESH, model->net.layers[model->net.n - 1].mask, 0, 1, model->output, 1, &model->net.layers[model->net.n - 1], model->net.classes, model->net.outputs, 1); // 进行非极大值抑制 do_nms_sort(model->net.hold_cpu, model->net.hold_gpu, model->net.batch, model->net.layers[model->net.n - 1].classes, model->net.layers[model->net.n - 1].w, model->net.layers[model->net.n - 1].h, model->output, model->net.layers[model->net.n - 1].nms, CLASS_NUM, NMS_THRESH); // 输出结果 draw_detections(img, model->net.hold_cpu, model->net.darknet_gpu, model->net.layers[model->net.n - 1], CLASS_NUM, names, WINDOW_WIDTH, WINDOW_HEIGHT, 0); } ``` 5. main函数 ```c int main(int argc, char **argv){ yolov5 model; // 加载模型 load_model(&model, CLASS_NAME_FILE, WEIGHTS_FILE); // 进行推理 yolov5_inference(&model, argv[1]); // 释放资源 cudaFree(model.input); cudaFree(model.output); cudaStreamDestroy(model.stream); free_network(model.net); return 0; } ``` 以上就是用C语言编写的在nvidia开发板部署yolov5模型推理代码
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

视觉研坊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值