RK3566/RK3588 yolov5部署(二)NPU冒烟测试及rknn_api使用

Rk3566/RK3588 yolov5部署(一)Ubuntu系统烧写及cmake安装

RK3566/RK3588 yolov5部署(三)yolov5单线程部署并查看NPU占用情况

RK3566/RK3588 YoloV5部署(四) yolov5多线程部署

本文提供rk3566和rk3588平台下mobilenet测试代码

rk3566:

通过网盘分享的文件:NPU_Test_rk3566
链接: https://pan.baidu.com/s/1-onBvnai-qZkGf8aECSpdA?pwd=3566 提取码: 3566 

rk3588:

 通过网盘分享的文件:NPU_Test_rk3588
链接: https://pan.baidu.com/s/1cv5Lpkf4slKMoxhudFeDcQ?pwd=3588 提取码: 3588 


不熟悉开发板系统烧写及cmake安装可以参考:Rk3566 yolov5部署(一)Ubuntu系统烧写及cmake安装(rk3588流程相同)

1.vscode 远程登陆开发板(以下演示使用rk3566,3588流程相同)

打开vscode 点击左侧扩展,搜索ssh,安装Remote - SSH插件

安装结束后点击左下角打开远程窗口按钮,选择连接到主机,点击添加新的主机

输入ssh orangepi@(ip地址 ifconfig查看),回车

点击第一个并在右下角弹出窗口选择连接

连接成功后右下角变成远程主机

在远程主机的vscode中进入代码文件夹,在当前文件夹中打开终端,输入

cmake -S . -B build
cmake --build build

编译完成后会在build文件夹下生成可执行文件,进入build文件夹,输入

./mobilenet ../weights/moblienetv3_demo.rknn ../images/cat_224x224.jpg

得到结果如下:

image文件夹下还有一张测试图片,可以输入以下命令测试

./mobilenet ../weights/moblienetv3_demo.rknn ../images/dog_224x224.jpg

有输出结果,则NPU可用

2.rknn_api

由于rknn_api用c语言开发,其使用面型对象思想,需要一个上下文传递信息(类似ffmpeg)

首先看src/main.cpp,从主函数开始

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

初始化 RKNN 上下文,加载 RKNN 模型

  • &ctx:指向rknn_context类型变量的指针,用于存储初始化后的上下文句柄。
  • model:指向 RKNN 模型数据的指针,通常由load_model函数加载得到。
  • model_len:RKNN 模型数据的长度(字节数)。
  • 0:这里是初始化标志,设为 0 表示使用默认配置。
  • NULL:用户自定义的配置参数,设为NULL表示使用默认配置。

若初始化成功,返回RKNN_SUCC(通常为 0);若失败,则返回对应的错误码。

ret = rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num));
ret = rknn_query(ctx, RKNN_QUERY_INPUT_ATTR, &(input_attrs[i]), sizeof(rknn_tensor_attr));
ret = rknn_query(ctx, RKNN_QUERY_OUTPUT_ATTR, &(output_attrs[i]), sizeof(rknn_tensor_attr));

查询 RKNN 模型的相关信息,像输入输出数量、输入输出张量属性等。

  • ctx:RKNN 上下文句柄,由rknn_init函数初始化得到。
  • RKNN_QUERY_IN_OUT_NUMRKNN_QUERY_INPUT_ATTRRKNN_QUERY_OUTPUT_ATTR:查询类型,分别表示查询输入输出数量、输入张量属性、输出张量属性。
  • &io_num&(input_attrs[i])&(output_attrs[i]):用于存储查询结果的结构体指针。
  • sizeof(io_num)sizeof(rknn_tensor_attr):存储查询结果的结构体的大小。

若查询成功,返回RKNN_SUCC;若失败,则返回对应的错误码。

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

为 RKNN 模型设置输入数据。

  • ctx:RKNN 上下文句柄。
  • io_num.n_input:输入数据的数量。
  • inputs:指向rknn_input类型数组的指针,该数组包含了输入数据的相关信息,如索引、数据类型、大小、格式、数据缓冲区指针等。
ret = rknn_run(ctx, nullptr);

执行 RKNN 模型的推理操作。

  • ctx:RKNN 上下文句柄。
  • nullptr:这里是推理时的额外参数,设为nullptr表示不使用额外参数。
ret = rknn_outputs_get(ctx, 1, outputs, NULL);

获取 RKNN 模型的输出结果。

  • ctx:RKNN 上下文句柄。
  • 1:要获取的输出结果的数量(这里写1是应为模型输出为1,输入一张图片,输出一个rknn_output类型结构体)。
  • outputs:指向rknn_output类型数组的指针,该数组用于存储输出结果的相关信息,如是否需要浮点型数据、数据缓冲区指针等。
  • NULL:这里是输出结果的额外参数,设为NULL表示不使用额外参数。

代码流程比较简单,不做解释了,之后在部署yolov5时会对rknn_api简单封装。

RK3566,和RK3588区别只在cmakelist文件中DEVICE_NAME变量名称(无关紧要)和weight文件夹下rknn模型文件不同 ,模型文件需要严格对应芯片型号。

附上main.cpp及cmakelists.txt

//引入opencv,rknn等库
#include "opencv2/core/core.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include "rknn_api.h" //rknn_api.h内部已经包含extern "C"

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

#include <fstream>
#include <iostream>

using namespace std;
using namespace cv;

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;
}

unsigned char* load_model(const char* model_path, int* model_len)
{
    FILE *fp = fopen(model_path,"rb");
    if(fp == nullptr)
    {
        cerr << "open model faile!" << endl;
        return NULL;
    }
    fseek(fp,0,SEEK_END);
    int model_size = ftell(fp);
    unsigned char *model = (unsigned char *)malloc(model_size);
    fseek(fp,0,SEEK_SET);
    if(model_size != fread(model,1,model_size,fp))
    {
        cerr << "read model faile!" << endl;
        return NULL;
    }
    *model_len = model_size;
    if(fp)
    {
        fclose(fp);
    }
    return model;
}

void dump_tensor_attr_info(rknn_tensor_attr *attr)
{
    cout << "index= " << attr->index << endl;
    cout << "name= " << attr->name << endl;
    cout << "n_dims= " << attr->n_dims << endl;
    cout << "dims= " ;
    for(int i = 0; i < attr->n_dims; i++)
    {
        cout << attr->dims[i] << " ";
    }
    cout << endl;
    cout << "n_elems= " << attr->n_elems << endl;
    cout << "size= " << attr->size << endl;
    cout << "fmt= " << get_format_string(attr->fmt) << endl;
    cout << "type= " << get_type_string(attr->type) << endl;
    cout << "qnt_type= " << get_qnt_type_string(attr->qnt_type) << endl;
    cout << "zp= " << attr->zp << endl;
    cout << "scale= " << attr->scale << endl;
}

int main(int argc, char* argv[])
{
    //设置输入图片宽,高,通道
    const int MODEL_INPUT_HEIGHT = 224;
    const int MODEL_INPUT_WIDTH = 224;
    const int MODEL_INPUT_CHANNLES = 3;

    rknn_context ctx = 0;
    int model_len = 0;
    unsigned char *model = 0;

    if (argc != 3)
    {
        cerr <<"Usage:"<< argv[0] << "<rknn model> <image_path>" << endl;
        return -1;
    }
    //读取图片并转换颜色通道顺序
    const char *img_path = argv[2];
    const char *model_path = argv[1];

    cv::Mat img = imread(img_path, cv::IMREAD_COLOR);
    if(!img.data)
    {
        cerr << "imread failed!" << endl;
        return -1;
    }

    if (img.cols != MODEL_INPUT_WIDTH || img.rows != MODEL_INPUT_HEIGHT)
    {
        cout << "resize image from " << img.cols << ":" << img.rows << " to " << MODEL_INPUT_WIDTH << ":" << MODEL_INPUT_HEIGHT << endl;
        cv::resize(img, img, cv::Size(MODEL_INPUT_WIDTH, MODEL_INPUT_HEIGHT), 0, 0, cv::INTER_LINEAR);
    }

    cv::Mat img_rgb;
    cv::cvtColor(img,img_rgb, cv::COLOR_BGR2RGB);

    model = load_model(model_path, &model_len);
//rknn模型初始化
    int ret = rknn_init(&ctx, model, model_len,0,NULL);
    if(ret < 0)
    {
        cerr << "rknn init fail!" << endl;
        return -1;
    }
//查询模型输入输出数量(mobilenet输入输出都是1)
    rknn_input_output_num io_num;
    ret = rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num));
    if(ret != RKNN_SUCC)
    {
        cerr << "rknn query num fail!" << endl;
        return -1;
    }
    cout << "model input num: " << io_num.n_input << "output num: " << io_num.n_output << endl;

    rknn_tensor_attr input_attrs[io_num.n_input];
    memset(input_attrs, 0, sizeof(input_attrs));
//查询输入输出张量的具体信息
    for(int i = 0; i < io_num.n_input; i++)
    {
        input_attrs[i].index = i;
        ret = rknn_query(ctx, RKNN_QUERY_INPUT_ATTR, &(input_attrs[i]), sizeof(rknn_tensor_attr));
        if(ret != RKNN_SUCC)
        {
            cerr << "rknn query input_attr fail!" << endl;
            return -1;
        }
        dump_tensor_attr_info(&(input_attrs[i]));
    }

    rknn_tensor_attr output_attrs[io_num.n_output];
    memset(output_attrs, 0, sizeof(output_attrs));

    for(int i = 0; i < io_num.n_output; i++)
    {
        output_attrs[i].index = i;
        ret = rknn_query(ctx, RKNN_QUERY_OUTPUT_ATTR, &(output_attrs[i]), sizeof(rknn_tensor_attr));
        if(ret != RKNN_SUCC)
        {
            cerr << "rknn query output_attr fail!" << endl;
            return -1;
        }
        dump_tensor_attr_info(&(output_attrs[i]));
    }
//插入输入数据
    rknn_input inputs[1];
    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;
    inputs[0].buf = img_rgb.data;

    ret = rknn_inputs_set(ctx, io_num.n_input, inputs);
    if(ret < 0 )
    {
        cerr << "rknn_inputs_set fail!" << endl;
        return -1;
    }

    cout << "rknn_running ..." << endl;
    ret = rknn_run(ctx, nullptr);
    if(ret < 0 )
    {
        cerr << "rknn_run fail!" << endl;
        return -1;
    }
//获取输出结果
    rknn_output outputs[1];
    memset(outputs, 0, sizeof(outputs));
    outputs[0].want_float = 1;
    ret = rknn_outputs_get(ctx, 1, outputs, NULL);
    if (ret < 0)
    {
        cerr << "rknn_outputs_get fail!" << endl;
        return -1;
    }

    uint32_t MaxClass[5];
    float fMaxProb[5];
    float *buffer = (float *)outputs[0].buf;
    uint32_t sz = outputs[0].size / 4;
    rknn_GetTop(buffer, fMaxProb, MaxClass, sz, 5); 

    cout << " --- Top5 ---" << endl;

    for (int i = 0; i < 5; i++)
    {
        cout << MaxClass[i] << ":" << fMaxProb[i] << endl;
    }

    ret = rknn_outputs_release(ctx, 1, outputs);
    if (ret < 0)
    {
        cerr << "rknn_outputs_release fail!" << endl;
        return -1;
    }
    else if (ctx > 0)
    {
        rknn_destroy(ctx);
    }
    if(model)
    {
        free(model);
    }
    return 0;
}
cmake_minimum_required(VERSION 3.11 FATAL_ERROR)
project(rk3566_mobilenet VERSION 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 "RK3566")
set(RKNN_API_PATH ${CMAKE_CURRENT_SOURCE_DIR}/librknn_api)
set(RKNN_API_INCLUDE_PATH ${RKNN_API_PATH}/include)
set(RKNN_API_LIB_PATH ${RKNN_API_PATH}/${LIB_ARCH}/librknnrt.so)
set(3RDPARTY_PATH ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty)
set(OpenCV_DIR ${3RDPARTY_PATH}/opencv/opencv-linux-${LIB_ARCH}/share/OpenCV)
find_package(OpenCV REQUIRED)
message(STATUS "    include path: ${OpenCV_INCLUDE_DIRS}")
include_directories(
    ${OpenCV_INCLUDE_DIRS}
    ${RKNN_API_INCLUDE_PATH}
)
add_executable(mobilenet src/main.cpp)
target_link_libraries(mobilenet
        ${RKNN_API_LIB_PATH}
        ${OpenCV_LIBS}
)

### 正点原子 RK3588 在 VSCode 上的开发环境配置 #### 配置概述 为了在 Visual Studio Code (VSCode) 中高效地开发基于正点原子 RK3588 的项目,需要完成一系列工具链和插件的安装与配置。这包括但不限于设置交叉编译器、调试工具以及集成开发环境的相关扩展。 --- #### 工具链准备 正点原子提供了完整的开发套件支持,其中包括适用于 RK3588 平台的工具链文件[^2]。开发者可以从官方文档或资源网站获取预构建的 GCC 工具链,并将其路径添加到系统的 `PATH` 环境变量中以便于后续操作。 对于 Linux 用户而言,推荐通过以下命令验证工具链是否可用: ```bash arm-linux-gnueabihf-gcc --version ``` 如果未找到该命令,则需手动指定其位置并更新 `.bashrc` 文件中的 PATH 变量: ```bash export PATH=$PATH:/path/to/toolchain/bin source ~/.bashrc ``` --- #### 安装必要的 VSCode 扩展 以下是几个常用的扩展及其作用: 1. **C/C++ Extension Pack**: 提供 IntelliSense 支持,帮助自动补全代码语法。 2. **Remote Development**: 如果目标设备连接至远程服务器上运行,此扩展允许直接访问远程主机上的文件系统。 3. **Platform IO IDE**: 虽然主要用于微控制器编程,但对于嵌入式 Linux 应用程序同样适用[^1]。 可以通过打开 Extensions Marketplace (`Ctrl+Shift+X`) 来搜索上述名称进行安装。 --- #### 创建工作区结构 建议按照标准目录布局创建一个新的 VSCode 项目空间。例如: ``` rk3588_project/ ├── src/ # 存放源代码文件夹 │ └── main.c # 主入口函数实现 ├── include/ # 头文件存放处 └── build/ # 编译输出产物存储区域 ``` 随后初始化 Git 版本控制系统(可选),便于版本管理: ```bash git init . ``` --- #### 配置 launch.json 和 tasks.json 为了让调试更加顺畅,还需要定义两个重要 JSON 文件——launch.json 和 tasks.json。 ##### Debug Configuration (launch.json) 用于描述如何启动 GDB 或其他调试客户端的过程。示例如下所示: ```json { "version": "0.2.0", "configurations": [ { "name": "(gdb) Launch", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/build/main.out", // 替换为目标 ELF 文件实际路径 "args": [], "stopAtEntry": false, "cwd": "${fileDirname}", "environment": [], "externalConsole": true, "MIMode": "gdb", "setupCommands": [ { "text": "-enable-pretty-printing" } ], "preLaunchTask": "Build" } ] } ``` ##### Build Task Definition (tasks.json) 负责执行 Make 命令或其他脚本来生成最终进制镜像。样例如下: ```json { "version": "2.0.0", "tasks": [ { "label": "Build", "type": "shell", "command": "/usr/bin/make", "options": { "cwd": "${workspaceFolder}" }, "problemMatcher": ["$gcc"] } ] } ``` 以上两份配置均应放置于 `.vscode/` 下面作为隐藏子目录存在。 --- #### 测试与优化 完成初步设定之后,尝试编写一段简单的测试程序以确认整个流程无误。比如打印字符串或者控制 GPIO 引脚状态变化等功能演示即可满足需求。 另外值得注意的是,在某些情况下可能还会遇到终端显示宽度不足的问题影响菜单配置界面正常渲染效果;此时可以参照相关提示调整窗口尺寸大小直至适配为止[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值