天空中

  • 2024-05-26
  • 回复了主题帖: #AI挑战营终点站#RV1106手写数字识别部署

    luyism 发表于 2024-5-26 13:32 将图像的数据复制到张量内存空间的时候是要求16字节对齐的,28字节一行的图像数据直接整个拷贝到里面不满足 ... 好的我修改一下,感谢感谢  

  • 回复了主题帖: 【AI挑战营终点站】应用落地:部署手写数字识别应用到幸狐RV1106开发板

    完成打卡#AI挑战营终点站#RV1106手写数字识别部署 https://bbs.eeworld.com.cn/thread-1282914-1-1.html

  • 2024-05-25
  • 发表了主题帖: #AI挑战营终点站#RV1106手写数字识别部署

    本帖最后由 天空中 于 2024-5-25 18:09 编辑 # RV1106数字识别 ## 简述 利用 RKMPI 库实现摄像头图像捕获、预处理、硬件编码,结合 opencv-mobile 进行图像处理, rtsp 推流,使用 VLC 软件拉取并观察图像。 ### 实现效果 * RKNN数字识别并标注 ## 操作流程 RKMPI获取图像 -> 使用 opencv-mobile 进行捕获 ->opencv-mobile 进行图像预处理框选数字 -> RKNN推理->结果反回 ## 涉及函数库简述 ### opencv-mobile 在嵌入式系统中opencv中很多模块无法使用如:opencv_gapi,opencv_videoio等模块,所以需要精简openncv。 opencv-mobile,一款体积仅有官方 1/10 的精简 OpenCV 库,并已经适配rv1106已支持 luckfox-pico MIPI CSI 摄像头和 rk-aiq/rga 硬件加速 [github库地址](https://github.com/nihui/opencv-mobile) ### RKNN模型转换并部署 RKNN-Toolkit2 工具在 PC 平台上提供 C 或 Python 接口,简化模型的部署和运行。用户可以通过该工具轻松完成以下功能:模型转换、量化、推理、性能和内存评估、量化精度分析以及模型加密。RKNN-Toolkit2的环境及安装在以前的文章中有详细讲述此文章不在赘述 ## 代码讲解 感谢 [knv](https://home.eeworld.com.cn/space-uid-1362174.html) 的代码分享,提供了思路和优化手段。 本代码参考文章[RKMPI 实例使用指南 | LUCKFOX WIKI](https://wiki.luckfox.com/zh/Luckfox-Pico/RKMPI-example#6-opencv-mobile-标注推流帧数实例) ### 将RKMPI获取的图像转化为opencv Mat型 ```c++ void *data = RK_MPI_MB_Handle2VirAddr(stVpssFrame.stVFrame.pMbBlk); //获取MPI数据帧对应内存缓冲块虚拟地址 cv::Mat frame(height,width,CV_8UC3, data);//转化为opencv Mat型为后序图像预处理做准备 ``` ### 图像预处理 ```c++ cv::Mat frame64; cv::cvtColor(frame, frame64, cv::COLOR_BGR2GRAY); //将彩色图转为灰度图 //图形二值化,此处没有使用固定阈值而是使用大津法做动态阈值处理增加了在不同光源下识别数字的稳定性 //使用THRESH_BINARY_INV模式将大于阈值的像素设为0小于阈值设为255这样就不用在进行像素反转的操作 cv::threshold(frame64, frame64, 100, 255, cv::THRESH_BINARY_INV | cv::THRESH_OTSU); ``` #### 寻找数字的轮廓并框选 ```c++ // 查找轮廓,声明变量来存储轮廓 std::vectorcontours; std::vector hierarchy; cv::findContours(frame64, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE, cv::Point()); //声明变量来存储框选出来的矩形 cv::Rect rect; int out_num = 10; for (int i = 0; i < contours.size(); i++) { rect = cv::boundingRect(contours); //在寻找的轮廓中拟合矩形 //通过矩形的长度宽度和长宽比筛出错误的矩形 if (rect.width < 100 && rect.width > 10 && (rect.height/rect.width)>1 && rect.height virt_addr, roi.data, 28*28*1); //运行模型 inference_NUM_model(&rknn_app_ctx, out_num); } } ``` 本代码没有对图像进行滤波操作,因为经过测试滤波操作在光线稳定的情况下并不会影响数字的识别率并且滤波操作会遍历整张图像降低帧率所以舍去滤波操作。 ## 模型初始化部分和模型推理部分使用RKNN零拷贝 API 注:rv1106/rv1103仅支持零拷贝 API RKNN零拷贝API是Rockchip神经网络处理库(RKNN, Rockchip Neural Network)提供的一组功能,旨在优化数据传输效率,减少内存复制操作,从而提高基于Rockchip平台的深度学习应用的性能。在传统的内存操作中,数据在不同内存区域或不同进程间传递时,经常需要进行数据的复制,这一过程会消耗时间和系统资源。而零拷贝技术则尽量避免这些不必要的复制操作。 ### 模型初始化 ```c++ //rknn上下文结构体变量 typedef struct { // RKNN运行上下文,由rknn_init函数初始化后获得,是执行模型推理的核心句柄。 rknn_context rknn_ctx; // 指向最大内存块的指针,可根据需要动态调整以适应不同大小的输入或中间结果。 rknn_tensor_mem* max_mem; // 网络内存指针,通常用于存储模型运行过程中的临时数据或中间结果。 rknn_tensor_mem* net_mem; // 输入输出数量结构体,记录模型的输入和输出张量数量。 rknn_input_output_num io_num; // 指向输入张量属性数组的指针,存储每个输入张量的详细属性信息(如维度、数据类型等)。 rknn_tensor_attr* input_attrs; // 指向输出张量属性数组的指针,存储每个输出张量的详细属性信息。 rknn_tensor_attr* output_attrs; // 输入内存块指针数组,至少包含一个元素,用于存放模型推理的输入数据。 rknn_tensor_mem* input_mems[1]; // 输出内存块指针数组,这里假设模型有3个输出,用于接收模型推理的结果数据。 rknn_tensor_mem* output_mems[3]; // 模型输入通道数,对于图像处理任务,这通常指的是色彩通道数(如RGB图像的通道数为3)。 int model_channel; // 模型输入宽度,单位通常是像素。 int model_width; // 模型输入高度,单位通常是像素。 int model_height; // 表示模型是否进行了量化处理。量化模型通常用于加速推理并减少内存占用。 bool is_quant; } rknn_app_context_t; ``` ```c++ rknn_app_context_t rknn_app_ctx; // rknn结构体变量 int ret; const char *model_path = "./model/111.rknn"; //模型地址 memset(&rknn_app_ctx, 0, sizeof(rknn_app_context_t)); //初始化结构体 init_retinaface_model(model_path, &rknn_app_ctx); //模型初始化函数 printf("init rknn model success!\n"); ``` ```c++ /** * 初始化RKNN模型的函数 * * @param model_path 模型文件的路径 * @param app_ctx 应用上下文结构体指针,用于存储模型上下文及输入输出相关信息 * @return 成功返回0,失败返回-1 */ int init_retinaface_model(const char *model_path, rknn_app_context_t *app_ctx) { int ret; rknn_context ctx = 0; // RKNN模型运行上下文 // 使用rknn_init函数初始化模型,传入模型路径 ret = rknn_init(&ctx, (char *)model_path, 0, 0, NULL); if (ret < 0) { printf("rknn_init fail! ret=%d\n", ret); return -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) { printf("rknn_query fail! ret=%d\n", ret); return -1; } printf("model input num: %d, output num: %d\n", io_num.n_input, io_num.n_output); // 分别获取并打印输入和输出张量的详细信息 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.index = i; ret = rknn_query(ctx, RKNN_QUERY_NATIVE_INPUT_ATTR, &(input_attrs), sizeof(rknn_tensor_attr)); if (ret != RKNN_SUCC) return -1; dump_tensor_attr(&(input_attrs)); // 打印输入张量属性 } 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.index = i; ret = rknn_query(ctx, RKNN_QUERY_NATIVE_NHWC_OUTPUT_ATTR, &(output_attrs), sizeof(rknn_tensor_attr)); if (ret != RKNN_SUCC) return -1; dump_tensor_attr(&(output_attrs)); // 打印输出张量属性 } // 设置输入张量属性,并为输入分配内存 input_attrs[0].type = RKNN_TENSOR_UINT8; input_attrs[0].fmt = RKNN_TENSOR_NHWC; printf("input_attrs[0].size_with_stride=%d\n", input_attrs[0].size_with_stride); app_ctx->input_mems[0] = rknn_create_mem(ctx, input_attrs[0].size_with_stride); ret = rknn_set_io_mem(ctx, app_ctx->input_mems[0], &input_attrs[0]); // 为输出张量分配内存并设置内存属性 for (uint32_t i = 0; i < io_num.n_output; ++i) { app_ctx->output_mems = rknn_create_mem(ctx, output_attrs.size_with_stride); printf("output mem [%d] = %d \n",i ,output_attrs.size_with_stride); ret = rknn_set_io_mem(ctx, app_ctx->output_mems, &output_attrs); if (ret < 0) return -1; } // 将模型上下文和其他相关信息保存到app_ctx中 app_ctx->rknn_ctx = ctx; app_ctx->is_quant = (output_attrs[0].qnt_type == RKNN_TENSOR_QNT_AFFINE_ASYMMETRIC); app_ctx->io_num = io_num; // 分配内存并复制输入输出属性到app_ctx中 app_ctx->input_attrs = (rknn_tensor_attr *)malloc(io_num.n_input * sizeof(rknn_tensor_attr)); memcpy(app_ctx->input_attrs, input_attrs, io_num.n_input * sizeof(rknn_tensor_attr)); app_ctx->output_attrs = (rknn_tensor_attr *)malloc(io_num.n_output * sizeof(rknn_tensor_attr)); memcpy(app_ctx->output_attrs, output_attrs, io_num.n_output * sizeof(rknn_tensor_attr)); // 根据输入格式确定模型的尺寸信息 if (input_attrs[0].fmt == RKNN_TENSOR_NCHW) { printf("model is NCHW input fmt\n"); app_ctx->model_channel = input_attrs[0].dims[1]; app_ctx->model_height = input_attrs[0].dims[2]; app_ctx->model_width = input_attrs[0].dims[3]; } else { printf("model is NHWC input fmt\n"); app_ctx->model_height = input_attrs[0].dims[1]; app_ctx->model_width = input_attrs[0].dims[2]; app_ctx->model_channel = input_attrs[0].dims[3]; } printf("model input height=%d, width=%d, channel=%d\n", app_ctx->model_height, app_ctx->model_width, app_ctx->model_channel); return 0; // 成功返回 } ``` ### 模型推理函数 ```c++ /** * 执行模型推理并处理输出结果的函数 * * @param app_ctx 指向RKNN应用上下文的指针,包含了模型运行所需的全部资源和参数 * @param out_num 引用参数,推理后最可能的类别序号将被保存于此 * @return 成功返回0,失败返回-1 */ int inference_NUM_model(rknn_app_context_t *app_ctx, int &out_num) { int ret = 0; float probability = 0; // 使用rknn_run函数执行模型推理 ret = rknn_run(app_ctx->rknn_ctx, NULL); if (ret < 0) { printf("rknn run error %d\n", ret); return -1; } // 获取模型输出数据的指针 int8_t* output_data = (int8_t*)(app_ctx->output_mems[0]->virt_addr); // 创建一个数组用于存放反量化后的输出值 float dequantized_values[10]; // 对输出数据进行反量化处理 for (int i = 0; i < 10; ++i) { dequantized_values = (output_data - (float)(-4)) * 0.122913; } // 应用softmax函数使输出值转换为概率 applySoftmax(dequantized_values, 10); for (int i = 0; i < 10; ++i) { printf("Class %d: Probability = %.5f\n", i, dequantized_values); // 如果当前类别的概率大于0.5,则检查是否大于已找到的最大概率 if(dequantized_values > 0.5) { // 更新最大概率和对应的类别序号 if(probability < dequantized_values) { probability = dequantized_values; out_num = i; } } } return ret; } ``` ### 输出值转换为概率 ```c++ void applySoftmax(float* data, int size) { // 寻找数组中的最大值,用于数值稳定化softmax计算 float max_val = data[0]; for (int i = 1; i < size; ++i) { if (data > max_val) { max_val = data; } } // 计算每个元素exp(xi - max_val)的值并累加,用于归一化 float sum_exp = 0; for (int i = 0; i < size; ++i) { data = exp(data - max_val); // 对每个值减去最大值后进行指数运算,防止因指数运算导致的下溢出 sum_exp += data; // 累加所有指数值,用于后续的归一化 } // 归一化步骤,将所有exp值转换为概率分布(所有概率之和为1) for (int i = 0; i < size; ++i) { data /= sum_exp; // 每个元素除以总和,得到概率值 } } ``` ## 代码运行效果 [localvideo]68fd412d539aec90db007434d4d9208e[/localvideo] ## 结果分析 本模型只对数字6,0,7这种特征较为明显的数字有较好的识别率,分析应该是神经网络的框架太简单,训练时只使用了三层卷积(具体训练代码看前面的文章)。可以将神经网络框架换为MobileNetv3这种成熟结构(仅为猜想并未实践)。 ## 代码分享 链接:https://pan.baidu.com/s/1aSX1x_t9iWn5hv4Raj2_ow?pwd=ihrk 提取码:ihrk

  • 加入了学习《直播回放: 瑞萨新一代视觉 AI MPU 处理器 RZ/V2H: 高算力、低功耗、实时控制》,观看 瑞萨新一代视觉 AI MPU 处理器 RZ/V2H

  • 2024-05-08
  • 回复了主题帖: 入围名单公布:嵌入式工程师AI挑战营(初阶),获RV1106 Linux 板+摄像头的名单

    个人信息已确认,领取板卡,可继续完成&分享挑战营第二站和第三站任务。

  • 回复了主题帖: 入围名单公布:嵌入式工程师AI挑战营(初阶),获RV1106 Linux 板+摄像头的名单

    个人信息已确认,领取板卡,可继续完成&分享挑战营第二站和第三站任务。

  • 2024-04-30
  • 回复了主题帖: 【AI挑战营第二站】算法工程化部署打包成SDK

    1.ONNX是一种开放的神经网络交换格式,它的主要目标是提供一种通用的模型表示,使得深度学习模型可以在不同的框架之间无缝转换和使用。 RKNN是由瑞芯微电子公司推出的一款深度学习模型各阶段流程一体化开发和运行框架。 2.#AI挑战营第二站#Ubuntu下ONNX转RKNN - 嵌入式系统 - 电子工程世界-论坛 (eeworld.com.cn)

  • 发表了主题帖: #AI挑战营第二站#Ubuntu下ONNX转RKNN

    # 使用rknn-Toolkit2完成模型转换及其环境部署 ## rknn-Toolkit2介绍 RKNN-Toolkit2 是为用户提供在计算机上进行模型转换、推理和性能评估的开发套件 用户可以通过该工具轻松完成以下功能:模型转换、量化、推理、性能和内存评估、量化精度分析以及模型加密。 ## 环境部署 本帖使用Conda 创建 python 虚拟环境完成rknn-Toolkit2的安装 ### 安装Conda ``` #下载安装包 wget https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda/Miniconda3-4.6.14-Linux-x86_64.sh #设置安装包权限并安装 chmod 777 Miniconda3-4.6.14-Linux-x86_64.sh bash Miniconda3-4.6.14-Linux-x86_64.sh ``` ### 下载rknn-Toolkit2安装包,并在Conda环境中安装 ``` #下载rknn-Toolkit2安装包 git clone https://github.com/rockchip-linux/rknn-toolkit2 ``` #### 创建 RKNN-Toolkit2 Conda 环境 ``` #创建环境,设置python版本为3.8 conda create -n RKNN-Toolkit2 python=3.8 #进入环境 conda activate RKNN-Toolkit2 ``` 创建环境并成功进入后终端变化 #### 安装rknn-Toolkit2依赖 ``` #在Conda环境中进入文件夹 cd rknn-toolkit2 #安装依赖 pip install tf-estimator-nightly==2.8.0.dev2021122109 pip install -r rknn-toolkit2/packages/requirements_cp38-1.6.0.txt -i https://pypi.mirrors.ustc.edu.cn/simple/ #安装 RKNN-Toolkit2 pip install rknn-toolkit2/packages/rknn_toolkit2-1.6.0+81f21f4d-cp38-cp38-linux_x86_64.whl ``` #### 验证是否成功 输入图片中的命令没有报错则说明成功 ## 模型转换代码 ``` import cv2 import numpy as np from rknn.api import RKNN import os if __name__ == '__main__': # 模型部署平台 platform = 'rv1106' #训练模拟时输入图片大小 Width = 28 Height = 28 # 此处改为自己的模型地址 MODEL_PATH = '/home/tiankong/文档/RKNN_NUM/NUM_ONNX.onnx' # 导出模型地址 RKNN_MODEL_PATH = '/home/tiankong/文档/RKNN_NUM/RKNN_Num.rknn' # 创建RKNN对象并在屏幕打印详细的日志信息 rknn = RKNN(verbose=True) # 模型配置 # mean_values: 输入图像像素均值 # std_values: 输入图像像素标准差 # target_platform: 目标部署平台 # 本模型训练时输入图象为单通道 rknn.config(mean_values=[0], std_values=[255], target_platform=platform) # 模型加载 print('--> Loading model') ret = rknn.load_onnx(MODEL_PATH) if ret != 0: print('load model failed!') exit(ret) print('done') # 构建 RKNN 模型 print('--> Building model') #do_quantization:是否对模型进行量化。默认值为 True ret = rknn.build(do_quantization=True, dataset="./image_data.txt") if ret != 0: print('build model failed.') exit(ret) print('done') # 导出模型 ret = rknn.export_rknn(RKNN_MODEL_PATH) #释放RKNN模型 rknn.release() ``` ## RKNN模型

最近访客

< 1/1 >

统计信息

已有3人来访过

  • 芯积分:37
  • 好友:--
  • 主题:3
  • 回复:7

留言

你需要登录后才可以留言 登录 | 注册


现在还没有留言