四、RK3588-Mobilenet直接推理(C++版本)

发布时间:2023年12月27日

1.前言

????????RKNN(Rockchip Neural Network)是一种用于嵌入式设备的深度学习推理框架,提供了一个端到端的解决方案,用于将训练好的深度学习模型转换为在嵌入式设备上运行的可执行文件。RKNN在Rockchip NPU(神经网络处理器)平台上运行,提供了模型转换、推理和性能评估的开发套件。

????????RKNN-Toolkit2是一个开发套件,它为用户提供了在PC和Rockchip NPU平台上进行模型转换、推理和性能评估的Python接口。用户可以通过这个工具进行模型转换、量化功能、模型推理、性能和内存评估以及量化精度分析等多种操作。

2.下载代码

????????代码链接:mobilenet

3.RKNN的C++代码解释

3.1CmakeLists文件

# 设置最低版本号
cmake_minimum_required(VERSION 3.11 FATAL_ERROR)
# 设置项目名称
project(rk3588-demo VERSION 0.0.1 LANGUAGES CXX)

# 输出系统信息
message(STATUS "System: ${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_VERSION}")

# 设置编译器
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 设置库架构
set(LIB_ARCH "aarch64")
set(DEVICE_NAME "RK3588")

#  rknn_api 文件夹路径
set(RKNN_API_PATH ${CMAKE_CURRENT_SOURCE_DIR}/librknn_api)
#  rknn_api include 路径
set(RKNN_API_INCLUDE_PATH ${RKNN_API_PATH}/include)
#  rknn_api lib 路径
set(RKNN_API_LIB_PATH ${RKNN_API_PATH}/${LIB_ARCH}/librknnrt.so)

# 寻找OpenCV库,使用自定义的OpenCV_DIR
set(3RDPARTY_PATH ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty)
set(OpenCV_DIR ${3RDPARTY_PATH}/opencv/opencv-linux-${LIB_ARCH}/share/OpenCV)
find_package(OpenCV REQUIRED)
# 输出OpenCV信息
message(STATUS "    include path: ${OpenCV_INCLUDE_DIRS}")

# 用来搜索头文件的目录
include_directories(
    ${OpenCV_INCLUDE_DIRS}
    ${RKNN_API_INCLUDE_PATH}
)

# 测试NPU:rknn mobilenet 
add_executable(mobilenet src/mobilenet_change.cpp)

# 链接库
target_link_libraries(mobilenet
        ${RKNN_API_LIB_PATH}
        ${OpenCV_LIBS}
)

3.2 源文件mobilenet_change.cpp

????????RKNN通用API接口流程图

(1) 导入相应的库

????????cmake文件中已经链接opencv和rknn的相关库,同时下载的zip文件中已经包含相关的库,不用再安装和配置。

#include "opencv2/core/core.hpp"

#include "opencv2/imgcodecs.hpp"

#include "opencv2/imgproc.hpp"

#include "rknn_api.h"

#include <stdint.h>

#include <stdio.h>

#include <stdlib.h>

#include <sys/time.h>

#include <fstream>

#include <iostream>

using namespace std;

using namespace cv;

(2) 主函数中设置相关参数

// 模型的输入3*224*224(chw)

const int MODEL_IN_WIDTH = 224;

const int MODEL_IN_HEIGHT = 224;

const int MODEL_IN_CHANNELS = 3;

/***

(1) 上下文变量将在后续的代码中用于管理 RKNN 模型的执行。初始化为0可能是为了在后续代码中检查 ctx 的状态,如果 ctx 的值不为0,说明成功创建了 RKNN 上下文。

(2) 上下文是用于执行模型推理的对象,它包含了模型的状态、输入输出张量等信息。通过上下文,你可以加载模型、设置输入数据、执行推理,以及获取输出结果。

(3) 后续的模型释放内存会用到

***/

rknn_context ctx = 0;

int ret;

// ====model_len :加载模型时需要知道模型的大小,以便为模型数据分配足够的内存空间。通过一个参数(通常是一个指向整数的指针)来获取模型的大小。这个参数可以用来在加载模型的过程中获取模型的实际大小 ====

int model_len = 0;

unsigned char *model;

// 模型的路径和图片的路径

const char *model_path = "/home/ubuntu/1.npu_test/weights/mobilenet_v1.rknn";

const char *img_path = "/home/ubuntu/1.npu_test/images/cat_224x224.jpg";

(3) 读取图片进行前处理

????????之前的opencv c++版本应该已经熟悉了

// ======================= 读取图片 ===================

cv::Mat orig_img = imread(img_path, cv::IMREAD_COLOR);

if (!orig_img.data)

{

printf("cv::imread %s fail!\n", img_path);

return -1;

}

// ===========OpenCV默认读的图片BGR 转成 RGB===========

cv::Mat orig_img_rgb;

cv::cvtColor(orig_img, orig_img_rgb, cv::COLOR_BGR2RGB);

// ===========将图片的形状Resize成224*224 ===========

cv::Mat img = orig_img_rgb.clone();

if (orig_img.cols != MODEL_IN_WIDTH || orig_img.rows != MODEL_IN_HEIGHT)

{

cv::resize(orig_img, img, cv::Size(MODEL_IN_WIDTH, MODEL_IN_HEIGHT), 0, 0, cv::INTER_LINEAR);

}

(4) 初始化rknn模型

// ======================= 初始化RKNN模型 ===================

// 输入参数:模型文件名,模型大小

// 返回值:模型数据指针

// load_model调用函数

model = load_model(model_path, &model_len);

// 初始化RKNN模型

// 输入:ctx:模型句柄,model:模型数据指针,model_len:模型大小,flag:0,reserverd:NULL

// 返回值:<0:失败

ret = rknn_init(&ctx, model, model_len, 0, NULL);

if (ret < 0)

{

printf("rknn_init fail! ret=%d\n", ret);

return -1;

}

// ======================= 获取模型输入输出信息 ===================

// ********** 输入输出数量 **********

rknn_input_output_num io_num;

// 使用rknn_query函数获取模型输入输出数量

ret = rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num));

if (ret != RKNN_SUCC)

{

printf("rknn_query fail! ret=%d\n", ret);

return -1;

????????load_model调用函数:从文件中读取rknn二进制模型数据

// 参数:filename:模型文件名,model_size:模型大小

// 返回值:模型数据指针

static unsigned char *load_model(const char *filename, int *model_size)

{

FILE *fp = fopen(filename, "rb");

if (fp == nullptr)

{

printf("fopen %s fail!\n", filename);

return NULL;

}

fseek(fp, 0, SEEK_END);

int model_len = ftell(fp);

unsigned char *model = (unsigned char *)malloc(model_len); // 申请模型大小的内存,返回指针

fseek(fp, 0, SEEK_SET);

if (model_len != fread(model, 1, model_len, fp))

{

printf("fread %s fail!\n", filename);

free(model);

return NULL;

}

*model_size = model_len;

if (fp)

{

fclose(fp);

}

return model;

}

(4) 设置模型输入

// ======================= 设置模型输入 ===================

// 使用rknn_input结构体存储模型输入信息, 表示模型的一个数据输入,用来作为参数传入给 rknn_inputs_set 函数

rknn_input inputs[1];

// 初始化,将inputs中前sizeof(inputs)个字节用0替换

memset(inputs, 0, sizeof(inputs));

inputs[0].index = 0; // 设置模型输入索引

inputs[0].type = RKNN_TENSOR_UINT8; // 设置模型输入类型

inputs[0].size = img.cols * img.rows * img.channels() * sizeof(uint8_t); // 设置模型输入大小

inputs[0].fmt = RKNN_TENSOR_NHWC; // 设置模型输入格式:NHWC

inputs[0].buf = img.data; // 设置模型输入数据

// 使用rknn_inputs_set函数设置模型输入

// 输入:ctx:模型句柄,io_num.n_input:模型输入数量,inputs:模型输入信息

// 返回值:<0:失败

ret = rknn_inputs_set(ctx, io_num.n_input, inputs);

if (ret < 0)

{

printf("rknn_input_set fail! ret=%d\n", ret);

return -1;

}

(5) 模型推理和获取结果

// ======================= 推理 ===================

printf("rknn_run\n");

// 使用rknn_run函数运行RKNN模型

// 输入:ctx:模型句柄,nullptr:保留参数

// 返回值:<0:失败

ret = rknn_run(ctx, nullptr);

if (ret < 0)

{

printf("rknn_run fail! ret=%d\n", ret);

return -1;

}

// ======================= 获取模型输出 ===================

// 使用rknn_output结构体存储模型输出信息

rknn_output outputs[1];

// 初始化,将outputs中前sizeof(outputs)个字节用0替换

memset(outputs, 0, sizeof(outputs));

// 设置模型输出类型为float

outputs[0].want_float = 1;

// 使用rknn_outputs_get函数获取模型输出

// 输入:ctx:模型句柄,1:模型输出数量,outputs:模型输出信息,nullptr:保留参数

// 返回值:<0:失败

ret = rknn_outputs_get(ctx, 1, outputs, NULL);

if (ret < 0)

{

printf("rknn_outputs_get fail! ret=%d\n", ret);

return -1;

}

(6) 后处理

// ======================= 后处理 ===================

// 遍历模型所有输出

for (int i = 0; i < io_num.n_output; i++)

{

uint32_t MaxClass[5];

float fMaxProb[5];

float *buffer = (float *)outputs[i].buf; // 模型输出数据

uint32_t sz = outputs[i].size / 4; // 模型输出大小,除以4是因为模型输出类型为float

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]);

}

}

(7) 释放内存

// ======================= 释放输出缓冲区 ===================

// 释放rknn_outputs_get获取的输出

// 输入:ctx:模型句柄,1:模型输出数量,outputs:模型输出信息(数组)

// 返回值:<0:失败,>=0:成功

rknn_outputs_release(ctx, 1, outputs);

if (ret < 0)

{

printf("rknn_outputs_release fail! ret=%d\n", ret);

return -1;

}

else if (ctx > 0)

{

// ======================= 释放RKNN模型 ===================

rknn_destroy(ctx);

}

// ======================= 释放模型数据 ===================

if (model)

{

free(model);

}

return 0;

(8)完整代码

/*-------------------------------------------
                Includes
-------------------------------------------*/
#include "opencv2/core/core.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include "rknn_api.h"

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

#include <fstream>
#include <iostream>

using namespace std;
using namespace cv;

/*-------------------------------------------
                  Functions
-------------------------------------------*/


// 从文件中读取rknn二进制模型数据
// 参数:filename:模型文件名,model_size:模型大小
// 返回值:模型数据指针
static unsigned char *load_model(const char *filename, int *model_size)
{
  FILE *fp = fopen(filename, "rb");
  if (fp == nullptr)
  {
    printf("fopen %s fail!\n", filename);
    return NULL;
  }
  fseek(fp, 0, SEEK_END);
  int model_len = ftell(fp);
  unsigned char *model = (unsigned char *)malloc(model_len); // 申请模型大小的内存,返回指针
  fseek(fp, 0, SEEK_SET);
  if (model_len != fread(model, 1, model_len, fp))
  {
    printf("fread %s fail!\n", filename);
    free(model);
    return NULL;
  }
  *model_size = model_len;
  if (fp)
  {
    fclose(fp);
  }
  return model;
}


//mobilenet后处理
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;
}

/*-------------------------------------------
                  Main Function
-------------------------------------------*/

int main()
{
  //模型的输入3*224*224(chw)
  const int MODEL_IN_WIDTH = 224;
  const int MODEL_IN_HEIGHT = 224;
  const int MODEL_IN_CHANNELS = 3;
  /***
    (1) 上下文变量将在后续的代码中用于管理 RKNN 模型的执行。初始化为0可能是为了在后续代码中检查 ctx 的状态,如果 ctx 的值不为0,说明成功创建了 RKNN 上下文。
    (2) 上下文是用于执行模型推理的对象,它包含了模型的状态、输入输出张量等信息。通过上下文,你可以加载模型、设置输入数据、执行推理,以及获取输出结果。
    (3) 后续的模型释放内存会用到

  ***/

  rknn_context ctx = 0;
  int ret;
  // ====model_len :加载模型时需要知道模型的大小,以便为模型数据分配足够的内存空间。通过一个参数(通常是一个指向整数的指针)来获取模型的大小。这个参数可以用来在加载模型的过程中获取模型的实际大小 ====
  int model_len = 0;
  unsigned char *model;
  // 模型的路径和图片的路径
  // const char *model_path = argv[1];
  // const char *img_path = argv[2];
  const char *model_path = "/home/ubuntu/1.npu_test/weights/mobilenet_v1.rknn";
  const char *img_path = "/home/ubuntu/1.npu_test/images/cat_224x224.jpg";


  // ======================= 读取图片 ===================
  cv::Mat orig_img = imread(img_path, cv::IMREAD_COLOR);
  if (!orig_img.data)
  {
    printf("cv::imread %s fail!\n", img_path);
    return -1;
  }
  // ===========OpenCV默认读的图片BGR 转成 RGB===========
  cv::Mat orig_img_rgb;
  cv::cvtColor(orig_img, orig_img_rgb, cv::COLOR_BGR2RGB);

  // ===========将图片的形状Resize成224*224 ===========
  cv::Mat img = orig_img_rgb.clone();
  if (orig_img.cols != MODEL_IN_WIDTH || orig_img.rows != MODEL_IN_HEIGHT)
  {
    cv::resize(orig_img, img, cv::Size(MODEL_IN_WIDTH, MODEL_IN_HEIGHT), 0, 0, cv::INTER_LINEAR);
  }

  // ======================= 初始化RKNN模型 ===================

  // 输入参数:模型文件名,模型大小
  // 返回值:模型数据指针
  // load_model调用函数
  model = load_model(model_path, &model_len);
  // 初始化RKNN模型
  // 输入:ctx:模型句柄,model:模型数据指针,model_len:模型大小,flag:0,reserverd:NULL
  // 返回值:<0:失败
  ret = rknn_init(&ctx, model, model_len, 0, NULL);
  if (ret < 0)
  {
    printf("rknn_init fail! ret=%d\n", ret);
    return -1;
  }

  // ======================= 获取模型输入输出信息 ===================
  // ********** 输入输出数量 **********
  rknn_input_output_num io_num;
  // 使用rknn_query函数获取模型输入输出数量
  ret = rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num));
  if (ret != RKNN_SUCC)
  {
    printf("rknn_query fail! ret=%d\n", ret);
    return -1;
  }


    // ======================= 设置模型输入 ===================
    // 使用rknn_input结构体存储模型输入信息, 表示模型的一个数据输入,用来作为参数传入给 rknn_inputs_set 函数
    rknn_input inputs[1];
    // 初始化,将inputs中前sizeof(inputs)个字节用0替换
    memset(inputs, 0, sizeof(inputs));
    inputs[0].index = 0;                                                     // 设置模型输入索引
    inputs[0].type = RKNN_TENSOR_UINT8;                                      // 设置模型输入类型
    inputs[0].size = img.cols * img.rows * img.channels() * sizeof(uint8_t); // 设置模型输入大小
    inputs[0].fmt = RKNN_TENSOR_NHWC;                                        // 设置模型输入格式:NHWC
    inputs[0].buf = img.data;                                                // 设置模型输入数据

    // 使用rknn_inputs_set函数设置模型输入
    // 输入:ctx:模型句柄,io_num.n_input:模型输入数量,inputs:模型输入信息
    // 返回值:<0:失败
    ret = rknn_inputs_set(ctx, io_num.n_input, inputs);
    if (ret < 0)
    {
      printf("rknn_input_set fail! ret=%d\n", ret);
      return -1;
    }

    // ======================= 推理 ===================
    printf("rknn_run\n");
    // 使用rknn_run函数运行RKNN模型
    // 输入:ctx:模型句柄,nullptr:保留参数
    // 返回值:<0:失败
    ret = rknn_run(ctx, nullptr);
    if (ret < 0)
    {
      printf("rknn_run fail! ret=%d\n", ret);
      return -1;
    }

    // ======================= 获取模型输出 ===================
    // 使用rknn_output结构体存储模型输出信息
    rknn_output outputs[1];
    // 初始化,将outputs中前sizeof(outputs)个字节用0替换
    memset(outputs, 0, sizeof(outputs));
    // 设置模型输出类型为float
    outputs[0].want_float = 1;

    // 使用rknn_outputs_get函数获取模型输出
    // 输入:ctx:模型句柄,1:模型输出数量,outputs:模型输出信息,nullptr:保留参数
    // 返回值:<0:失败
    ret = rknn_outputs_get(ctx, 1, outputs, NULL);
    if (ret < 0)
    {
      printf("rknn_outputs_get fail! ret=%d\n", ret);
      return -1;
    }

    // ======================= 后处理 ===================
    // 遍历模型所有输出
    for (int i = 0; i < io_num.n_output; i++)
    {
      uint32_t MaxClass[5];
      float fMaxProb[5];
      float *buffer = (float *)outputs[i].buf;        // 模型输出数据
      uint32_t sz = outputs[i].size / 4;              // 模型输出大小,除以4是因为模型输出类型为float
      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_get获取的输出
    // 输入:ctx:模型句柄,1:模型输出数量,outputs:模型输出信息(数组)
    // 返回值:<0:失败,>=0:成功
    rknn_outputs_release(ctx, 1, outputs);
    if (ret < 0)
    {
      printf("rknn_outputs_release fail! ret=%d\n", ret);
      return -1;
    }
    else if (ctx > 0)
    {
      // ======================= 释放RKNN模型 ===================
      rknn_destroy(ctx);
    }
    // ======================= 释放模型数据 ===================
    if (model)
    {
      free(model);
    }
    return 0;
  }

4.执行结果

? ? ? ?4.1 执行命令

cmake -S . -B build

cmake --build build

cd build

./mobilenet

? ? ? ? 4.2 执行结果

????????如果初始化模型失败,加上root权限试试

文章来源:https://blog.csdn.net/weixin_43999691/article/details/135244146
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。