四、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

相关文章

利用c++判断水仙花数并输出示例代码

《利用c++判断水仙花数并输出示例代码》水仙花数是指一个三位数,其各位数字的立方和恰好等于该数本身,:本文主要介绍利用c++判断水仙花数并输出的相关资料,文中通过代码介绍的非常详细,需要的朋友可以... 以下是使用C++实现的相同逻辑代码:#include <IOStream>#include <vec

基于C++的UDP网络通信系统设计与实现详解

《基于C++的UDP网络通信系统设计与实现详解》在网络编程领域,UDP作为一种无连接的传输层协议,以其高效、低延迟的特性在实时性要求高的应用场景中占据重要地位,下面我们就来看看如何从零开始构建一个完整... 目录前言一、UDP服务器UdpServer.hpp1.1 基本框架设计1.2 初始化函数Init详解

C++ 右值引用(rvalue references)与移动语义(move semantics)深度解析

《C++右值引用(rvaluereferences)与移动语义(movesemantics)深度解析》文章主要介绍了C++右值引用和移动语义的设计动机、基本概念、实现方式以及在实际编程中的应用,... 目录一、右值引用(rvalue references)与移动语义(move semantics)设计动机1

python版本切换工具pyenv的安装及用法

《python版本切换工具pyenv的安装及用法》Pyenv是管理Python版本的最佳工具之一,特别适合开发者和需要切换多个Python版本的用户,:本文主要介绍python版本切换工具pyen... 目录Pyenv 是什么?安装 Pyenv(MACOS)使用 Homebrew:配置 shell(zsh

C++ move 的作用详解及陷阱最佳实践

《C++move的作用详解及陷阱最佳实践》文章详细介绍了C++中的`std::move`函数的作用,包括为什么需要它、它的本质、典型使用场景、以及一些常见陷阱和最佳实践,感兴趣的朋友跟随小编一起看... 目录C++ move 的作用详解一、一句话总结二、为什么需要 move?C++98/03 的痛点⚡C++

详解C++ 存储二进制数据容器的几种方法

《详解C++存储二进制数据容器的几种方法》本文主要介绍了详解C++存储二进制数据容器,包括std::vector、std::array、std::string、std::bitset和std::ve... 目录1.std::vector<uint8_t>(最常用)特点:适用场景:示例:2.std::arra

C++构造函数中explicit详解

《C++构造函数中explicit详解》explicit关键字用于修饰单参数构造函数或可以看作单参数的构造函数,阻止编译器进行隐式类型转换或拷贝初始化,本文就来介绍explicit的使用,感兴趣的可以... 目录1. 什么是explicit2. 隐式转换的问题3.explicit的使用示例基本用法多参数构造

C++,C#,Rust,Go,Java,Python,JavaScript的性能对比全面讲解

《C++,C#,Rust,Go,Java,Python,JavaScript的性能对比全面讲解》:本文主要介绍C++,C#,Rust,Go,Java,Python,JavaScript性能对比全面... 目录编程语言性能对比、核心优势与最佳使用场景性能对比表格C++C#RustGoJavapythonjav

C++打印 vector的几种方法小结

《C++打印vector的几种方法小结》本文介绍了C++中遍历vector的几种方法,包括使用迭代器、auto关键字、typedef、计数器以及C++11引入的范围基础循环,具有一定的参考价值,感兴... 目录1. 使用迭代器2. 使用 auto (C++11) / typedef / type alias

C++ scoped_ptr 和 unique_ptr对比分析

《C++scoped_ptr和unique_ptr对比分析》本文介绍了C++中的`scoped_ptr`和`unique_ptr`,详细比较了它们的特性、使用场景以及现代C++推荐的使用`uni... 目录1. scoped_ptr基本特性主要特点2. unique_ptr基本用法3. 主要区别对比4. u