赞
踩
在本篇教程中,博主将记录国庆假期前在RK3568上部署分割算法的步骤以及代码。首先说一下,RK3568这个开发板本身的算力大概是0.8T(在实际开发中还会用到额外的计算卡,额外的计算卡后面文章再说,本篇文章主要记录在RK3568上的部署过程)。
1、这步不是很难,我之前也写过BiSeNet的教程,官方提供的代码也很好理解,并且提供了onnx模型的导出代码。教程--从零开始使用BiSeNet(语义分割)网络训练自己的数据集_计算机幻觉的博客-CSDN博客为了从图片分割出我们想要的特征,我们采用BiSeNet作为分割模型,并且在自己制作的数据集上进行训练测试。注:训练是在linux环境下的,Win下训练可能会有点问题。_bisenethttps://blog.csdn.net/qq_39149619/article/details/131882664?spm=1001.2014.3001.55012、将导出的onnx代码进行rknn转换,RK3568需要用到rknn-toolkit2,该环境的安装之前也写过:
RKNN-ToolKit2 1.5.0安装教程_rknn安装-CSDN博客由于种种原因需要用到开发版RK3568,需要预先安装RKNN-Toolkit2进行模型转化等,博主安装的版本是1.5.0,Ubuntu版本是20.04,python版本3.6。1、原本准备采取docker安装,但是文件有点大再加上网速不行,于是我们采用pip方法进行安装。,直接点击下载,得到rknn-toolkit2-master.zip,并且解压到任意文件夹中。_rknn安装https://blog.csdn.net/qq_39149619/article/details/131694631?spm=1001.2014.3001.5501转换代码:mean_values和std_values需要根据自己训练集修改
- from rknn.api import RKNN
-
- ONNX_MODEL = '/home/zw/Prg/Pycharm/file/RKNN3568/onnx/yolov5-seg/best_480x480.onnx'
- platform = "rk3568"
- RKNN_MODEL = '/home/zw/Prg/Pycharm/file/RKNN3568/rknn/BiSeNetV2/BiSeNetv2_320x320_min4_{}_out_opt.rknn'.format(platform)
-
-
- if __name__ == '__main__':
-
-
- # Create RKNN object
- rknn = RKNN(verbose=False)
-
- # pre-process config
- print('--> config model')
-
- rknn.config(mean_values=[82.9835, 93.9795, 82.1893], std_values=[54.02, 54.804, 54.0225], target_platform='rk3568') #BiSeNet
-
-
- print('done')
-
- # Load tensorflow model
- print('--> Loading model')
- ret = rknn.load_onnx(model=ONNX_MODEL, outputs=['preds']) # 这里一定要根据onnx模型修改
- if ret != 0:
- print('Load onnx model failed!')
- exit(ret)
- print('done')
-
- # Build model
- print('--> Building model')
- ret = rknn.build(do_quantization=False, dataset='/home/zw/Prg/Pycharm/file/RKNN3568/dataset.txt')
- if ret != 0:
- print('Build rkmodel failed!')
- exit(ret)
- print('done')
-
- # rknn.export_rknn_precompile_model(RKNN_MODEL)
- rknn.export_rknn(RKNN_MODEL)
-
- rknn.release()
1、首先,从官网下载rknpu2相关文件,官网地址:GitHub - rockchip-linux/rknpu2,该文件包含了rknn相关的接口文件以及提供的示例代码。同时,我们创建bisenetv2的例子,如下图:
model存放转换好的rknn文件,main是推理C++代码。
2、想在3568上部署,需要对程序进行编译,详细的参考官方提供的说明pdf,讲解的简单易懂,这里就不多说。编译需要用到交叉编译工具,这里提供下载地址:Firefly-Linux / prebuilts / gcc / linux-x86 / aarch64 / gcc-buildroot-9.3.0-2020.03-x86_64_aarch64-rockchip-linux-gnu · GitLab
下载到任意位置即可,打开build-linux_RK3566_RK3568.sh文件,修改入下:gcc替换成你自己的地址。
- #!/bin/bash
- set -e
-
- TARGET_SOC="rk356x"
-
- # for aarch64
- # GCC_COMPILER=aarch64-linux-gnu
- export TOOL_CHAIN=/home/zw/Downloads/gcc-buildroot-9.3.0-2020.03-x86_64_aarch64-rockchip-linux-gnu-firefly
- GCC_COMPILER=/home/zw/Downloads/gcc-buildroot-9.3.0-2020.03-x86_64_aarch64-rockchip-linux-gnu-firefly/bin/aarch64-rockchip-linux-gnu
- export LD_LIBRARY_PATH=${TOOL_CHAIN}/lib64:$LD_LIBRARY_PATH
- export CC=${GCC_COMPILER}-gcc
- export CXX=${GCC_COMPILER}-g++
-
- ROOT_PWD=$( cd "$( dirname $0 )" && cd -P "$( dirname "$SOURCE" )" && pwd )
-
- # build
- BUILD_DIR=${ROOT_PWD}/build/build_linux_aarch64
-
- if [[ ! -d "${BUILD_DIR}" ]]; then
- mkdir -p ${BUILD_DIR}
- fi
-
- cd ${BUILD_DIR}
- cmake ../.. \
- -DTARGET_SOC=${TARGET_SOC} \
- -DCMAKE_C_COMPILER=${GCC_COMPILER}-gcc \
- -DCMAKE_CXX_COMPILER=${GCC_COMPILER}-g++
- make -j4
- make install
- cd -
cmakelist文件爱你没啥要改的,修改好自己项目名称即可:
- cmake_minimum_required(VERSION 3.4.1)
-
- project(rknn_bisenetv2_demo)
-
- set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
-
- # rknn api
- if(TARGET_SOC STREQUAL "rk356x")
- set(RKNN_API_PATH ${CMAKE_SOURCE_DIR}/../../runtime/RK356X/${CMAKE_SYSTEM_NAME}/librknn_api)
- elseif(TARGET_SOC STREQUAL "rk3588")
- set(RKNN_API_PATH ${CMAKE_SOURCE_DIR}/../../runtime/RK3588/${CMAKE_SYSTEM_NAME}/librknn_api)
- else()
- message(FATAL_ERROR "TARGET_SOC is not set, ref value: rk356x or rk3588")
- endif()
-
- if (CMAKE_SYSTEM_NAME STREQUAL "Android")
- set(RKNN_RT_LIB ${RKNN_API_PATH}/${CMAKE_ANDROID_ARCH_ABI}/librknnrt.so)
- else()
- if (CMAKE_C_COMPILER MATCHES "aarch64")
- set(LIB_ARCH aarch64)
- else()
- set(LIB_ARCH armhf)
- endif()
- set(RKNN_RT_LIB ${RKNN_API_PATH}/${LIB_ARCH}/librknnrt.so)
- endif()
- include_directories(${RKNN_API_PATH}/include)
- include_directories(${CMAKE_SOURCE_DIR}/../3rdparty)
-
- # opencv
- if (CMAKE_SYSTEM_NAME STREQUAL "Android")
- set(OpenCV_DIR ${CMAKE_SOURCE_DIR}/../3rdparty/opencv/OpenCV-android-sdk/sdk/native/jni/abi-${CMAKE_ANDROID_ARCH_ABI})
- else()
- if(LIB_ARCH STREQUAL "armhf")
- set(OpenCV_DIR ${CMAKE_SOURCE_DIR}/../3rdparty/opencv/opencv-linux-armhf/share/OpenCV)
- else()
- set(OpenCV_DIR ${CMAKE_SOURCE_DIR}/../3rdparty/opencv/opencv-linux-aarch64/share/OpenCV)
- endif()
- endif()
- find_package(OpenCV REQUIRED)
-
- set(CMAKE_INSTALL_RPATH "lib")
-
- add_executable(rknn_bisenetv2_demo
- src/main.cc
- )
-
- target_link_libraries(rknn_bisenetv2_demo
- ${RKNN_RT_LIB}
- ${OpenCV_LIBS}
- )
-
- # install target and libraries
- set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/install/rknn_bisenetv2_demo_${CMAKE_SYSTEM_NAME})
- install(TARGETS rknn_bisenetv2_demo DESTINATION ./)
-
- install(DIRECTORY model DESTINATION ./)
- install(PROGRAMS ${RKNN_RT_LIB} DESTINATION lib)
3、废话不多说了,直接提供C++代码,需要注意的是代码中的图像尺寸这里写死了,根据自己需要来修改代码即可。
- #include <stdio.h>
- #include <stdint.h>
- #include <stdlib.h>
- #include <queue>
- #include "rknn_api.h"
- #include "opencv2/core/core.hpp"
- #include "opencv2/imgproc/imgproc.hpp"
- #include "opencv2/highgui/highgui.hpp"
- #include <chrono>
- #include <sys/time.h>
- using namespace cv;
- using namespace std;
-
- /* 打印结构体rknn_tensor_attr所代表的张量信息,这个结构体包含了关于张量的信息;
- rknn_tensor_attr *attr:指向 rknn_tensor_attr 结构体的指针,这个结构体包含了关于张量的信息。
- %d 整数 %s 字符串 %f 浮点数
- */
- void printRKNNTensor(rknn_tensor_attr *attr) {
- printf("index=%d name=%s n_dims=%d dims=[%d %d %d %d] n_elems=%d size=%d "
- "fmt=%d type=%d qnt_type=%d fl=%d zp=%d scale=%f\n",
- attr->index, // 张量的索引
- attr->name, // 张量的名字
- attr->n_dims, //张量的维度数
- attr->dims[3], attr->dims[2],
- attr->dims[1], attr->dims[0], // 张量在每个维度上的大小
- attr->n_elems, // 张量中元素的总数
- attr->size, // 张量的大小
- 0,
- attr->type, // 张量的数据类型
- attr->qnt_type, // 张量的量化类型
- attr->fl, // 浮点层(与量化有关)
- attr->zp, //零点(与量化有关)
- attr->scale); //缩放值(与量化有关)
- }
-
- /*
- 这段代码是一个用于后处理的函数,主要是将一个输入数组 input0 中的数据转换成伪彩色图像,
- 然后将伪彩色图像与原始图像 resize_img 进行融合,
- 最后保存三张图像:伪彩色图像、原始图像、和融合后的图像。
- int:函数返回一个整数值作为结果。
- float *input0:指向 float 类型的数组,存储了后处理前的数据。
- cv::Mat resize_img:OpenCV 中的 cv::Mat 类型,代表原始图像。
- */
- int post_process_u8(float *input0,cv::Mat resize_img,int w,int h){
- //将 float 类型的数组转换为 int 类型的向量 vec_host_scores,并将 input0 数组中的数据逐个添加到这个向量中。
-
- std::vector<int> vec_host_scores;
- for(int i=0;i<w*h;i++){
- vec_host_scores.emplace_back(input0[i]);
- }
- /*
- 根据预设的 num_class 值(256),生成颜色映射表 color_map,
- 用于将 input0 中的整数值映射为伪彩色值。
- 在这里,每个整数值被视为一个类别标签,然后将其转换为对应的伪彩色值。
- 这个过程是通过位操作来实现的,根据 input0 中的整数值生成对应的 R、G、B 分量值。
- */
- int num_class = 256;//提取到外面 只执行一次即可,自己改吧
- vector<int> color_map(num_class * 3);
- for (int i = 0; i < num_class; i++) {
- int j = 0;
- int lab = i;
- while (lab) {
- color_map[i * 3] |= ((lab >> 0 & 1) << (7 - j));
- color_map[i * 3 + 1] |= (((lab >> 1) & 1) << (7 - j));
- color_map[i * 3 + 2] |= (((lab >> 2) & 1) << (7 - j));
- j += 1;
- lab >>= 3;
- }
- }
- /*
- 创建一个 cv::Mat 类型的 pseudo_img 对象,用于存储生成的伪彩色图像。
- 在这里,该图像的尺寸与输入的 w 和 h 相同,通道数为 3(代表 RGB 颜色通道)。
- 用于创建大小为 w x h 的 cv::Mat 对象 pseudo_img 并将所有像素设置为黑色。
- */
- cv::Mat pseudo_img(w, h, CV_8UC3, cv::Scalar(0, 0, 0));
- for (int r = 0; r < w; r++) {
- for (int c = 0; c < h; c++) {
- int idx = vec_host_scores[r*h + c];
- pseudo_img.at<Vec3b>(r, c)[0] = color_map[idx * 3];
- pseudo_img.at<Vec3b>(r, c)[1] = color_map[idx * 3 + 1];
- pseudo_img.at<Vec3b>(r, c)[2] = color_map[idx * 3 + 2];
- }
- }
- cv::Mat result;
- cv::Mat resize_result;
- cv::addWeighted(resize_img, 0.4, pseudo_img, 0.6, 0, result, 0);
- cv::resize(result, resize_result, cv::Size(640, 480));
- // cv::imshow("pseudo_img", pseudo_img);
- cv::imwrite("pseudo_img.jpg", pseudo_img);
- // cv::imshow("bgr", resize_img);
- cv::imwrite("resize_img.jpg", resize_img);
- // cv::imshow("result", result);
- cv::imwrite("result.jpg", resize_result);
- // cv::waitKey(0);
- return 0;
- }
-
- double __get_us(struct timeval t) { return (t.tv_sec * 1000000 + t.tv_usec); }
-
- int main(int argc, char **argv) {
- const char *img_path = argv[2];
- const char *model_path = argv[1];
- const char *post_process_type = "fp";//fp
- struct timeval start_time, stop_time;
- // const int target_width = 960;
- // const int target_height = 720;
-
- if (argc != 3) {
- printf("Usage: %s <rknn model> <image_path> \n", argv[0]);
- return -1;
- }
-
- // Load image
- cv::Mat bgr = cv::imread(img_path);
- if (!bgr.data) {
- printf("cv::imread %s fail!\n", img_path);
- return -1;
- }
- cv::Mat rgb;
- //BGR->RGB
- cv::cvtColor(bgr, rgb, cv::COLOR_BGR2RGB);
-
- //调整rgb图像的大小
- cv::Mat img_resize;
- cv::resize(rgb,img_resize,cv::Size(320,320));
- int width=img_resize.cols; //获取原始bgr图像的大小
- int height=img_resize.rows;
-
-
- // Load model
- FILE *fp = fopen(model_path, "rb");
- if (fp == NULL) {
- printf("fopen %s fail!\n", model_path);
- return -1;
- }
- fseek(fp, 0, SEEK_END); // 将文件指针移动到文件的末尾
- int model_len = ftell(fp); // 然后使用 ftell(fp) 获取当前文件指针的位置,即文件的大小
- void *model = malloc(model_len); //分配大小为 model_len 字节的内存块,并将内存块的起始地址保存在指针变量 model 中
- fseek(fp, 0, SEEK_SET); //是将文件指针重新设置到文件的开头,以便后续读取文件数据或执行其他操作。
- if (model_len != fread(model, 1, model_len, fp)) { //model用于存储从文件中读取的数据,model_len这个参数指定要读取的数据的总字节数
- printf("fread %s fail!\n", model_path);
- free(model);
- return -1;
- }
-
- /*
- 定义了一个 rknn_context 类型的变量 ctx,并初始化为0。
- rknn_context 是 RKNN 提供的一个上下文对象,用于执行模型推理的各种操作。
- 通过将其初始化为0,表示暂时没有创建 RKNN 上下文。
- */
- rknn_context ctx = 0;
-
- /*
- rknn_init 函数用于创建 RKNN 上下文,并将模型数据加载到上下文中。它的参数如下:
- ctx: 这是一个指向 rknn_context 的指针的地址,通过传递指针的地址,函数可以在内部分配内存并创建一个新的 RKNN 上下文,
- 并将其地址存储在 ctx 变量中;
- model:这是之前通过 fread 从模型文件中读取的模型数据的指针。它包含了要加载到 RKNN 上下文的模型数据;
- rknn_init 函数执行成功后,会返回一个非负值,表示初始化成功,并将 RKNN 上下文的地址存储在 ctx 变量中。
- 如果初始化失败,返回值将是一个负数,表示初始化失败的错误码。
- */
- int ret = rknn_init(&ctx, model, model_len, RKNN_FLAG_COLLECT_PERF_MASK, NULL);
- if (ret < 0) {
- printf("rknn_init fail! ret=%d\n", ret);
- return -1;
- }
-
- /* Query sdk version
- 查询 Rockchip Neural Network Toolkit(RKNN)的 SDK 版本和驱动版本,并输出它们的信息。
- */
- rknn_sdk_version version;
- ret = rknn_query(ctx, RKNN_QUERY_SDK_VERSION, &version,
- sizeof(rknn_sdk_version));
- if (ret < 0) {
- printf("rknn_init error ret=%d\n", ret);
- return -1;
- }
- printf("sdk version: %s driver version: %s\n", version.api_version,
- version.drv_version);
-
-
- /* Get input,output attr
- 查询模型的输入和输出数量,并将结果输出到控制台。
- io_num,用于存储模型的输入和输出数量信息;rknn_query 函数来查询模型的输入和输出数量
- */
- rknn_input_output_num io_num;
- ret = rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num));
- if (ret < 0) {
- printf("rknn_init error ret=%d\n", ret);
- return -1;
- }
- printf("model input num: %d, output num: %d\n", io_num.n_input,
- io_num.n_output);
-
- /*
- 查询模型的输入属性,并将输入属性信息打印到控制台,memset 函数将 input_attrs 数组的内存清零,以确保所有属性初始值为0。
- */
- 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 < 0) {
- printf("rknn_init error ret=%d\n", ret);
- return -1;
- }
- printRKNNTensor(&(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));
- printRKNNTensor(&(output_attrs[i]));
- }
-
- /*
- 确定模型的输入格式(NCHW或NHWC)以及输入的宽度、高度和通道数,并将这些信息输出到控制台。
- */
- int input_channel = 3;
- int input_width = 0;
- int input_height = 0;
- if (input_attrs[0].fmt == RKNN_TENSOR_NCHW) {
- printf("model is NCHW input fmt\n");
- input_width = input_attrs[0].dims[0];
- input_height = input_attrs[0].dims[1];
- printf("input_width=%d input_height=%d\n", input_width, input_height);
- } else {
- printf("model is NHWC input fmt\n");
- input_width = input_attrs[0].dims[2];
- input_height = input_attrs[0].dims[1];
- printf("input_width=%d input_height=%d\n", input_width, input_height);
- }
-
- printf("model input height=%d, width=%d, channel=%d\n", input_height, input_width,
- input_channel);
-
-
- /* Init input tensor
- 准备模型推理所需的输入数据
- */
- rknn_input inputs[1]; //定义了一个 rknn_input 数组 inputs,其中包含一个元素
- memset(inputs, 0, sizeof(inputs)); // 使用 memset 函数将 inputs 数组的内存清零,以确保所有属性初始值为0。
- // 设置 inputs[0] 的属性。
- inputs[0].index = 0; //将 index 设置为0,表示这是模型的第一个输入。
- //将输入数据的指针 img_resize.data 赋值给 inputs[0].buf。这表示输入数据的实际内容存储在 img_resize.data 中,而 inputs[0].buf 指向该数据。
- inputs[0].buf = img_resize.data;
- inputs[0].type = RKNN_TENSOR_UINT8; //表示输入数据的数据类型为无符号8位整数(uint8)。
- // 将 inputs[0].size 设置为输入数据的大小,即输入数据的宽度 (input_width)、高度 (input_height) 和通道数 (input_channel) 的乘积。这表示输入数据的总字节数。
- inputs[0].size = input_width * input_height * input_channel;
- inputs[0].fmt = RKNN_TENSOR_NHWC; //表示输入数据的格式为 NHWC
- inputs[0].pass_through = 0; //表示在输入数据到达模型之前,不对输入数据进行任何处理。
-
- /* Init output tensor
- 用于进行模型的推理(inference)过程,将输入数据输入到模型中并获取输出结果。
- */
- rknn_output outputs[io_num.n_output]; //定义了一个 rknn_output 数组 outputs,用于存储模型的输出数据
- memset(outputs, 0, sizeof(outputs));
-
- //want_float 属性表示是否希望输出结果为浮点数(float)。将它设置为1表示希望输出为浮点数。这通常在需要对输出进行后处理时使用。
- for (int i = 0; i < io_num.n_output; i++) {
- outputs[i].want_float = 1;
- }
-
- printf("img.cols: %d, img.rows: %d\n", img_resize.cols, img_resize.rows);
- // auto t1=std::chrono::steady_clock::now(); //记录当前时间,用于计算推理时间
- //rknn_inputs_set 函数用于将输入数据绑定到 RKNN 上下文,以便进行推理。
- gettimeofday(&start_time, NULL);
- rknn_inputs_set(ctx, io_num.n_input, inputs);
- ret = rknn_run(ctx, NULL); //执行模型的推理过程
- if (ret < 0) {
- printf("ctx error ret=%d\n", ret);
- return -1;
- }
- ret = rknn_outputs_get(ctx, io_num.n_output, outputs, NULL); //用于从 RKNN 上下文中获取输出数据
- //毫秒级
- // auto t2=std::chrono::steady_clock::now(); //获取当前时间
- // double dr_ms=std::chrono::duration<double,std::milli>(t2-t1).count(); //计算推理时间
- gettimeofday(&stop_time, NULL);
- printf("once run use %f ms\n", (__get_us(stop_time) - __get_us(start_time)) / 1000);
- // printf("%lf ms\n",dr_ms);
- if (ret < 0) {
- printf("outputs error ret=%d\n", ret);
- return -1;
- }
- // rknn_perf_detail perf_detail;
- // ret = rknn_query(ctx, RKNN_QUERY_PERF_DETAIL, &perf_detail, sizeof(perf_detail));
- // printf("Perf detail:\n");
- // printf("process_detil : %s",perf_detail.perf_data);
-
- // printf(&perf_detail);
-
- /* Post process
- 后处理(post-process)模型输出;
- out_scales 和 out_zps,用于存储输出数据的缩放因子和零点偏移值
- */
- std::vector<float> out_scales;
- std::vector<uint8_t> out_zps;
- for (int i = 0; i < io_num.n_output; ++i) {
- out_scales.push_back(output_attrs[i].scale);
- out_zps.push_back(output_attrs[i].zp);
- }
- gettimeofday(&start_time, NULL);
- //通过比较 post_process_type 的值是否等于 "fp",来确定是否进行后处理。如果 post_process_type 是 "fp",则调用 post_process_u8 函数进行后处理。
- if (strcmp(post_process_type, "fp") == 0) {
- post_process_u8((float *) outputs[0].buf,img_resize,
- 320, 320);
- }
- gettimeofday(&stop_time, NULL);
- printf("process use %f ms\n", (__get_us(stop_time) - __get_us(start_time)) / 1000);
-
- /*
- 用于释放模型推理过程中获取的输出数据的内存,以便避免内存泄漏。
- */
- ret = rknn_outputs_release(ctx, io_num.n_output, outputs);
-
- if (ret < 0) {
- printf("rknn_query fail! ret=%d\n", ret);
- goto Error;
- }
- /*
- 错误处理部分,当在前面的代码执行过程中发生错误时,将会跳转到 Error 标签处进行错误处理。
- */
- Error:
- if (ctx > 0)
- rknn_destroy(ctx);
- if (model)
- free(model);
- if (fp)
- fclose(fp);
- return 0;
- }
4、运行sh文件,开始编译
编译成功!
将编译好的可执行文件(install下的文件)全部送入到板端任意位置,执行以下命令即可(根据自己的路经修改):
./rknn_bisenetv2_demo ./model/RK3566_RK3568/.rknn ./image
后续等加入3T的计算棒之后,速度应该会更快。
点个赞呗!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。