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

2023-12-27 20:36

本文主要是介绍四、RK3588-Mobilenet直接推理(C++版本),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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 20if (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;                                        // 设置模型输入格式:NHWCinputs[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));// 设置模型输出类型为floatoutputs[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是因为模型输出类型为floatrknn_GetTop(buffer, fMaxProb, MaxClass, sz, 5); // 获取模型输出的Top5printf(" --- 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权限试试

这篇关于四、RK3588-Mobilenet直接推理(C++版本)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/544226

相关文章

Nginx进行平滑升级的实战指南(不中断服务版本更新)

《Nginx进行平滑升级的实战指南(不中断服务版本更新)》Nginx的平滑升级(也称为热升级)是一种在不停止服务的情况下更新Nginx版本或添加模块的方法,这种升级方式确保了服务的高可用性,避免了因升... 目录一.下载并编译新版Nginx1.下载解压2.编译二.替换可执行文件,并平滑升级1.替换可执行文件

C++11范围for初始化列表auto decltype详解

《C++11范围for初始化列表autodecltype详解》C++11引入auto类型推导、decltype类型推断、统一列表初始化、范围for循环及智能指针,提升代码简洁性、类型安全与资源管理效... 目录C++11新特性1. 自动类型推导auto1.1 基本语法2. decltype3. 列表初始化3

C++11右值引用与Lambda表达式的使用

《C++11右值引用与Lambda表达式的使用》C++11引入右值引用,实现移动语义提升性能,支持资源转移与完美转发;同时引入Lambda表达式,简化匿名函数定义,通过捕获列表和参数列表灵活处理变量... 目录C++11新特性右值引用和移动语义左值 / 右值常见的左值和右值移动语义移动构造函数移动复制运算符

在macOS上安装jenv管理JDK版本的详细步骤

《在macOS上安装jenv管理JDK版本的详细步骤》jEnv是一个命令行工具,正如它的官网所宣称的那样,它是来让你忘记怎么配置JAVA_HOME环境变量的神队友,:本文主要介绍在macOS上安装... 目录前言安装 jenv添加 JDK 版本到 jenv切换 JDK 版本总结前言China编程在开发 Java

C++中detach的作用、使用场景及注意事项

《C++中detach的作用、使用场景及注意事项》关于C++中的detach,它主要涉及多线程编程中的线程管理,理解detach的作用、使用场景以及注意事项,对于写出高效、安全的多线程程序至关重要,下... 目录一、什么是join()?它的作用是什么?类比一下:二、join()的作用总结三、join()怎么

C++中全局变量和局部变量的区别

《C++中全局变量和局部变量的区别》本文主要介绍了C++中全局变量和局部变量的区别,全局变量和局部变量在作用域和生命周期上有显著的区别,下面就来介绍一下,感兴趣的可以了解一下... 目录一、全局变量定义生命周期存储位置代码示例输出二、局部变量定义生命周期存储位置代码示例输出三、全局变量和局部变量的区别作用域

C++中assign函数的使用

《C++中assign函数的使用》在C++标准模板库中,std::list等容器都提供了assign成员函数,它比操作符更灵活,支持多种初始化方式,下面就来介绍一下assign的用法,具有一定的参考价... 目录​1.assign的基本功能​​语法​2. 具体用法示例​​​(1) 填充n个相同值​​(2)

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

C++中NULL与nullptr的区别小结

《C++中NULL与nullptr的区别小结》本文介绍了C++编程中NULL与nullptr的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编... 目录C++98空值——NULLC++11空值——nullptr区别对比示例 C++98空值——NUL

C++ Log4cpp跨平台日志库的使用小结

《C++Log4cpp跨平台日志库的使用小结》Log4cpp是c++类库,本文详细介绍了C++日志库log4cpp的使用方法,及设置日志输出格式和优先级,具有一定的参考价值,感兴趣的可以了解一下... 目录一、介绍1. log4cpp的日志方式2.设置日志输出的格式3. 设置日志的输出优先级二、Window