RKNN模型部署——RKNN零拷贝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模型


在这里插入图片描述

一、零拷贝API

1.1 使用零拷贝API注意事项

数据格式要求:RKNN零拷贝API要求输入输出数据必须满足特定的格式要求,具体可参考RKNN官方文档。

内存分配:使用零拷贝API进行推理前,需要先分配好输入输出数据的内存空间,并通过rknn_create_mem等接口进行注册。

数据更新:在推理过程中,如果需要更新输入输出数据,需要先调用rknn_set_io_mem等接口进行更新,否则可能导致推理结果不正确。

兼容性:RKNN零拷贝API与RKNN C API不完全兼容,不能混合使用。

1.2 零拷贝API与C API的区别

RKNN零拷贝API与RKNN C API的主要区别如下:

在这里插入图片描述

1.3 选择API建议

如果对性能要求较高,建议使用RKNN零拷贝API。

如果需要使用RKNN C API提供的其他功能,则只能使用RKNN C API进行推理。

二、源码包获取

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

解压后的样子如下:

在这里插入图片描述

三、编译准备

关于CMake编译的详细过程,见我上一篇博文第二部分,链接为:CMake工程构建

四、RKNN零拷贝API

4.1 零拷贝API使用流程

相比上一篇的通用API,少了数据输入设置,获取输出数据相关步骤。

在这里插入图片描述

4.2 代码修改

使用我提供的源码包,修改最终运行程序的工程文件名见下:

在这里插入图片描述

还有一些可以自定义调整代码,在non_copy/src/main.cc脚本中。

4.3 代码

源码包中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;

static int rknn_GetTopN(float* pfProb, float* pfMaxProb, uint32_t* pMaxClass, uint32_t outputCount, uint32_t topNum)
{
  uint32_t i, j;
  uint32_t top_count = outputCount > topNum ? topNum : outputCount;

  for (i = 0; i < topNum; ++i) {
    pfMaxProb[i] = -FLT_MAX;
    pMaxClass[i] = -1;
  }

  for (j = 0; j < top_count; 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[])
{
  char *model_path = argv[1];
  char *image_path = argv[2];

  rknn_context context;                       // 创建rknn_context类型的变量
  rknn_init(&context,model_path,0,0,NULL);    // 第一个参数为上面的context地址,第二参数模型路径,

  /*读取图像*/
  cv::Mat img = cv::imread(image_path);
  cv::cvtColor(img,img,cv::COLOR_BGR2RGB);

  /*调用rknn_query接口查询输入输出tensor属性*/
  rknn_tensor_attr input_attr[1],output_attr[1];
  memset(input_attr,0,sizeof(rknn_tensor_attr));       //使用memset初始化input_attr
  memset(output_attr,0,sizeof(rknn_tensor_attr));        //使用memset初始化output_attr

  rknn_query(context,RKNN_QUERY_INPUT_ATTR,input_attr,sizeof(input_attr));           // 查看输入属性
  rknn_query(context,RKNN_QUERY_OUTPUT_ATTR,output_attr,sizeof(output_attr));        // 查看输出属性

  /*调用rknn_create_mem接口申请输入和输出数据内存*/
  rknn_tensor_mem *input_mem[1],*output_mem[1];//表示输入和输出内存信息的两个变量
  input_mem[0] = rknn_create_mem(context,input_attr[0].size_with_stride);       // 申请输入内存,表示补齐了无效像素后,实际存储图像数据所占用内存空间的大小
  output_mem[0] = rknn_create_mem(context,output_attr[0].n_elems*sizeof(float));               // 输出数据所占内存为输出数据的元素个数乘以每个元素所占的空间大小

  unsigned char *input_data = img.data;// 指向无符号char类型的指针
  memcpy(input_mem[0]->virt_addr,input_data,input_attr[0].size_with_stride);   // 第一个参数为目标内存地址,input_mem[0]->virt_addr表示input_mem[0]的虚拟地址,第二个参数为图像存放地址,第三个参数为复制内存大小

  /*调用rknn_set_io_mem让NPU使用上面申请到的内存*/
  input_attr[0].type = RKNN_TENSOR_UINT8;            // 对输入的tensor数据类型进行设置
  output_attr[0].type = RKNN_TENSOR_FLOAT32;         // 对输出的tensor数据类型进行设置
  rknn_set_io_mem(context,input_mem[0],input_attr);
  rknn_set_io_mem(context,output_mem[0],output_attr);

  /*调用rknn_run进行模型推理*/
  rknn_run(context,NULL);   // 第二个参数为扩展参数    模型推理结束后会将推理数据保存到上面设置的输出数据内存中,输出内容不是常见的概率值,需要进行后处理

  /*后处理*/
  uint32_t topNum = 5;
  uint32_t MaxClass[topNum];
  float    fMaxProb[topNum];
  float*   buffer = (float*)output_mem[0]->virt_addr;
  uint32_t sz = output_attr[0].n_elems;
  int      top_count = sz > topNum ? topNum : sz;
  rknn_GetTopN(buffer, fMaxProb, MaxClass, sz, topNum);
  printf("---- Top%d ----\n", top_count);
  for (int j = 0; j < top_count; j++) {
    printf("%8.6f - %d\n", fMaxProb[j], MaxClass[j]);
 }

 /*调用rknn_destroy_mem接口销毁申请的内存*/
 rknn_destroy_mem(context,input_mem[0]);    // 销毁输入数据内存
 rknn_destroy_mem(context,output_mem[0]);   // 销毁输出数据内存

 /*调用rknn_destory销毁context*/
 rknn_destroy(context);

  return 0;
}

4.4 API文档

上面代码中用到的一些API详细解析见下。

4.4.1 rknn_create_mem

在这里插入图片描述

4.4.2 rknn_query

在这里插入图片描述

4.4.3 rknn_set_io_mem

在这里插入图片描述
示例代码如下:
在这里插入图片描述

4.4.4 rknn_init

在这里插入图片描述

4.4.5 rknn_run

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

4.4.6 rknn_destroy_mem

在这里插入图片描述

4.4.7 rknn_destroy

在这里插入图片描述

更多的API接口详见源码包中的开发文档。

4.5 CMake构建

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

在这里插入图片描述

4.6 构建结果

运行上面build.sh后,在non_copy目录下生成了build和install两个文件,其中build文件用于存放工程构建过程中生成的中间文件,install文件用于存放编译完成后的可执行程序,运行所要用到的库,以及RKNN模型和图片。

在这里插入图片描述

五、开发板上部署

5.1 程序文件发送

将上面得到的install文件夹,通过命令方式发送到开发板子上,前提是本地电脑端已经连通开发板。

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

adb push install /

在这里插入图片描述

5.2 进入开发板终端

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

adb shell

在这里插入图片描述

5.3 执行程序及结果

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

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

在这里插入图片描述
对照imagenet1000标签.txt文件,可以看出,推理结果正确,如下:

在这里插入图片描述

六、总结

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

总结不易,多多支持!

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

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模型推理代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

视觉研坊

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

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

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

打赏作者

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

抵扣说明:

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

余额充值