四、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++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

c++中std::placeholders的使用方法

《c++中std::placeholders的使用方法》std::placeholders是C++标准库中的一个工具,用于在函数对象绑定时创建占位符,本文就来详细的介绍一下,具有一定的参考价值,感兴... 目录1. 基本概念2. 使用场景3. 示例示例 1:部分参数绑定示例 2:参数重排序4. 注意事项5.

使用C++将处理后的信号保存为PNG和TIFF格式

《使用C++将处理后的信号保存为PNG和TIFF格式》在信号处理领域,我们常常需要将处理结果以图像的形式保存下来,方便后续分析和展示,C++提供了多种库来处理图像数据,本文将介绍如何使用stb_ima... 目录1. PNG格式保存使用stb_imagephp_write库1.1 安装和包含库1.2 代码解

C++实现封装的顺序表的操作与实践

《C++实现封装的顺序表的操作与实践》在程序设计中,顺序表是一种常见的线性数据结构,通常用于存储具有固定顺序的元素,与链表不同,顺序表中的元素是连续存储的,因此访问速度较快,但插入和删除操作的效率可能... 目录一、顺序表的基本概念二、顺序表类的设计1. 顺序表类的成员变量2. 构造函数和析构函数三、顺序表

使用C++实现单链表的操作与实践

《使用C++实现单链表的操作与实践》在程序设计中,链表是一种常见的数据结构,特别是在动态数据管理、频繁插入和删除元素的场景中,链表相比于数组,具有更高的灵活性和高效性,尤其是在需要频繁修改数据结构的应... 目录一、单链表的基本概念二、单链表类的设计1. 节点的定义2. 链表的类定义三、单链表的操作实现四、

java中不同版本JSONObject区别小结

《java中不同版本JSONObject区别小结》本文主要介绍了java中不同版本JSONObject区别小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们... 目录1. FastjsON2. Jackson3. Gson4. org.json6. 总结在Jav

使用C/C++调用libcurl调试消息的方式

《使用C/C++调用libcurl调试消息的方式》在使用C/C++调用libcurl进行HTTP请求时,有时我们需要查看请求的/应答消息的内容(包括请求头和请求体)以方便调试,libcurl提供了多种... 目录1. libcurl 调试工具简介2. 输出请求消息使用 CURLOPT_VERBOSE使用 C