首先,使用rknn_toolkit2工具将训练好的模型转换为RKNN模型,然后就可以开始使用RKNPU2实现推理了

通用api的使用流程

1. 初始化阶段

  • 调用 rknn_init接口创建 rknn_context对象并加载RKNN模型

2. 模型信息获取

  • 调用 rknn_query接口查询模型输入输出属性、推理时间、SDK版本等信息

3. 推理执行阶段

  • 调用 rknn_inputs_set设置模型输入数据

  • 调用 rknn_run执行模型推理

  • 调用 rknn_outputs_get获取推理输出数据

4. 后处理与资源释放

  • 对输出数据进行后处理

  • 调用 rknn_outputs_release释放输出资源

  • 调用 rknn_destroy释放 rknn_context及相关资源

#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_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[])
{
    char *model_path = argv[1];  //从命令行参数获取模型路径
    char *image_path = argv[2];  //从命令行参数获取图像路径

    //初始化RKNN上下文并加载模型
    rknn_context context;
    rknn_init(&context, model_path, 0, 0, NULL);
    
    //查询模型输入输出数量
    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);

    //读取并预处理输入图像
    cv::Mat img = cv::imread(image_path);
    cv::cvtColor(img, img, cv::COLOR_BGR2RGB);

    //设置模型输入数据
    rknn_input inputs[1];
    memset(inputs, 0, sizeof(rknn_input));  //清空输入结构体
    inputs[0].index = 0;  //输入索引
    inputs[0].buf = img.data;  //图像数据指针
    inputs[0].size = img.rows * img.cols * img.channels() * sizeof(uint8_t);  //输入数据大小
    inputs[0].pass_through = 0;  //启用数据预处理
    inputs[0].type = RKNN_TENSOR_UINT8;  //数据类型为无符号8位整数
    inputs[0].fmt = RKNN_TENSOR_NHWC;  //数据格式为NHWC
    rknn_inputs_set(context, io_num.n_input, inputs);  //设置输入数据

    //执行模型推理
    rknn_run(context, NULL);

    //获取推理输出结果
    rknn_output outputs[1];    
    memset(outputs, 0, sizeof(outputs));  //清空输出结构体
    outputs[0].index = 0;  //输出索引
    outputs[0].is_prealloc = 0;  //由SDK自动分配内存
    outputs[0].want_float = 1;  //输出浮点数格式
    rknn_outputs_get(context, 1, outputs, NULL);  //获取输出数据

    //后处理:获取Top5分类结果
    for (int i = 0; i < io_num.n_output; i++) 
    {
        uint32_t MaxClass[5];  //存储Top5类别索引
        float    fMaxProb[5];  //存储Top5概率值
        float*   buffer = (float*)outputs[i].buf;  //输出数据缓冲区
        uint32_t sz     = outputs[i].size / 4;  //输出数据大小(浮点数个数)

        //获取Top5结果
        rknn_GetTop(buffer, fMaxProb, MaxClass, sz, 5);

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

    //释放输出资源
    rknn_outputs_release(context, 1, outputs);

    //销毁RKNN上下文,释放所有相关资源
    rknn_destroy(context);
    
    return 0;
}

编译生成 install文件后后

install文件就是我们要放在开发板上运行的文件夹

使用adb拷贝过去 adb push ./install /

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

对于一些已经生成 install 然后移动了位置,报错的项目,可以 rm -rf build # 删除整个 build 目录

然后重新build一下

0拷贝api的使用流程

不使用0拷贝:

[CPU内存] --拷贝--> [NPU内部内存] --推理--> [NPU内部内存] --拷贝--> [CPU内存]

两次数据拷贝(输入一次,输出一次):

  • 在嵌入式设备(如RK3588)上,CPU ↔ NPU 的拷贝是 性能瓶颈。

  • 拷贝耗时可能比推理本身还长

0拷贝:

[NPU共享内存] --直接使用--> [推理] --直接访问--> [NPU共享内存]

memcpy()把图像数据 直接写入 input_mem[0]->virt_addr(NPU内存)。所以要提前rknn_create_mem(),直接分配 NPU可访问的内存。这块内存 同时能被CPU和NPU访问(共享内存)。

  1. 初始化阶段

    1. rknn_init():创建RKNN上下文并加载模型

    2. rknn_query(RKNN_QUERY_INPUT_ATTR/OUTPUT_ATTR):查询输入输出tensor属性(包括维度、数据类型等)

  2. 内存准备阶段(关键区别点)

    1. rknn_create_mem():显式创建输入输出内存空间

    2. 输入内存大小根据input_attr.size_with_stride确定

    3. 输出内存大小根据output_attr.n_elems * sizeof(float)计算

    4. memcpy():将图像数据拷贝到已创建的输入内存

  3. 内存绑定阶段

    1. rknn_set_io_mem()将创建的内存与RKNN上下文绑定

    2. 需要明确指定tensor的数据类型(如UINT8/FLOAT32)

    3. 绑定后NPU直接使用这些内存区域

  4. 推理执行阶段

    1. rknn_run():执行模型推理

    2. 由于已预先绑定内存,无需在推理时额外拷贝数据

  5. 结果处理阶段

    1. 直接从绑定的输出内存读取结果(output_mem[0]->virt_addr

    2. 使用rknn_GetTopN()处理原始输出数据

  6. 资源释放阶段

    1. rknn_destroy_mem():释放创建的输入输出内存

    2. rknn_destroy():销毁RKNN上下文

对比传统API的主要优势:

  1. 避免了推理过程中额外的数据拷贝

  2. 内存生命周期由开发者显式控制

  3. 支持更灵活的内存布局(如带stride的输入)

  4. 适用于需要高性能的场景

#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;

//获取概率最高的topN分类结果
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;

  //初始化topN结果数组
  for (i = 0; i < topNum; ++i) {
    pfMaxProb[i] = -FLT_MAX;
    pMaxClass[i] = -1;
  }

  //遍历找出概率最高的topN类别
  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模型上下文
  rknn_context context;
  rknn_init(&context, model_path, 0, 0, NULL);//返回rknn_context句柄,等后续api调用

  //读取并预处理输入图像
  cv::Mat img = cv::imread(image_path);
  cv::cvtColor(img, img, cv::COLOR_BGR2RGB);//BGR->RGB(深度学习模型使用)

  //查询模型输入输出tensor属性
  rknn_tensor_attr input_attr[1],output_attr[1];
  memset(input_attr,0,sizeof(rknn_tensor_attr));  //清空输入属性结构体
  memset(output_attr,0,sizeof(rknn_tensor_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));  //获取输出属性

  //0拷贝内存管理,1、为输入输出数据申请NPU可访问的内存,避免额外拷贝
  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;
  memcpy(input_mem[0]->virt_addr, input_data, input_attr[0].size_with_stride);

  //设置输入输出内存属性并绑定到RKNN上下文,NPU会直接使用这些内存进行推理
  input_attr[0].type = RKNN_TENSOR_UINT8;  //设置输入数据类型
  output_attr[0].type = RKNN_TENSOR_FLOAT32;  //设置输出数据类型
  rknn_set_io_mem(context, input_mem[0], input_attr);  //绑定输入内存
  rknn_set_io_mem(context, output_mem[0], output_attr);  //绑定输出内存

  //执行模型推理
  rknn_run(context, NULL);

  //获取并输出top5分类结果
  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;  //实际输出的topN数量

  rknn_GetTopN(buffer, fMaxProb, MaxClass, sz, topNum);  //计算topN结果

  //打印分类结果
  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(context, input_mem[0]);  //释放输入内存
  rknn_destroy_mem(context, output_mem[0]);  //释放输出内存

  //销毁RKNN上下文
  rknn_destroy(context);
  
  return 0;
}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐