四、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++ chrono简单使用场景示例详解

《C/C++chrono简单使用场景示例详解》:本文主要介绍C/C++chrono简单使用场景示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友... 目录chrono使用场景举例1 输出格式化字符串chrono使用场景China编程举例1 输出格式化字符串示

C++/类与对象/默认成员函数@构造函数的用法

《C++/类与对象/默认成员函数@构造函数的用法》:本文主要介绍C++/类与对象/默认成员函数@构造函数的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录名词概念默认成员函数构造函数概念函数特征显示构造函数隐式构造函数总结名词概念默认构造函数:不用传参就可以

C++类和对象之默认成员函数的使用解读

《C++类和对象之默认成员函数的使用解读》:本文主要介绍C++类和对象之默认成员函数的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、默认成员函数有哪些二、各默认成员函数详解默认构造函数析构函数拷贝构造函数拷贝赋值运算符三、默认成员函数的注意事项总结一

conda安装GPU版pytorch默认却是cpu版本

《conda安装GPU版pytorch默认却是cpu版本》本文主要介绍了遇到Conda安装PyTorchGPU版本却默认安装CPU的问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的... 目录一、问题描述二、网上解决方案罗列【此节为反面方案罗列!!!】三、发现的根本原因[独家]3.1 p

Redis指南及6.2.x版本安装过程

《Redis指南及6.2.x版本安装过程》Redis是完全开源免费的,遵守BSD协议,是一个高性能(NOSQL)的key-value数据库,Redis是一个开源的使用ANSIC语言编写、支持网络、... 目录概述Redis特点Redis应用场景缓存缓存分布式会话分布式锁社交网络最新列表Redis各版本介绍旧

C/C++中OpenCV 矩阵运算的实现

《C/C++中OpenCV矩阵运算的实现》本文主要介绍了C/C++中OpenCV矩阵运算的实现,包括基本算术运算(标量与矩阵)、矩阵乘法、转置、逆矩阵、行列式、迹、范数等操作,感兴趣的可以了解一下... 目录矩阵的创建与初始化创建矩阵访问矩阵元素基本的算术运算 ➕➖✖️➗矩阵与标量运算矩阵与矩阵运算 (逐元

IIS 7.0 及更高版本中的 FTP 状态代码

《IIS7.0及更高版本中的FTP状态代码》本文介绍IIS7.0中的FTP状态代码,方便大家在使用iis中发现ftp的问题... 简介尝试使用 FTP 访问运行 Internet Information Services (IIS) 7.0 或更高版本的服务器上的内容时,IIS 将返回指示响应状态的数字代

C/C++的OpenCV 进行图像梯度提取的几种实现

《C/C++的OpenCV进行图像梯度提取的几种实现》本文主要介绍了C/C++的OpenCV进行图像梯度提取的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录预www.chinasem.cn备知识1. 图像加载与预处理2. Sobel 算子计算 X 和 Y

C/C++和OpenCV实现调用摄像头

《C/C++和OpenCV实现调用摄像头》本文主要介绍了C/C++和OpenCV实现调用摄像头,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录准备工作1. 打开摄像头2. 读取视频帧3. 显示视频帧4. 释放资源5. 获取和设置摄像头属性

c/c++的opencv图像金字塔缩放实现

《c/c++的opencv图像金字塔缩放实现》本文主要介绍了c/c++的opencv图像金字塔缩放实现,通过对原始图像进行连续的下采样或上采样操作,生成一系列不同分辨率的图像,具有一定的参考价值,感兴... 目录图像金字塔简介图像下采样 (cv::pyrDown)图像上采样 (cv::pyrUp)C++ O