xianhangCheng

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

    打卡完成 #AI挑战营终点站#基于RV1106手写数字识别部署 - 嵌入式系统 - 电子工程世界-论坛 (eeworld.com.cn)

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

    本帖最后由 xianhangCheng 于 2024-5-30 15:57 编辑 实物图片 上图包括RV1106开发板+SC3336摄像头,这板子挺精致,自带的 SPI NAND FLASH,并在出厂时搭载了测试系统,收到板子后需要自行烧录网盘系统。 这个价格与性能来说,性价比很高,如此小巧的板子,板载256MB DDR3L,还可以直接运行yolov5。唯一的缺点就是在运行推理时发热挺严重的。   准备 参考手册:Luckfox官网教程SPI NAND Flash 镜像烧录 | LUCKFOX WIKI 提前准备工具:         1.下载RK驱动助手 DriverAssitant(戳我下载)。         2.下载和解压烧录工具(戳我下载)。         3.下载镜像文件镜像百度网盘链接。         4.下载VLC media player官方 VLC 媒體播放器下載,最好的開放原始碼播放器 - VideoLAN 注意:在下载RK驱动助手和烧录工具时最好关闭防火墙和病毒与威胁防护,否则可能导致下述状况: 驱动程序无法使用; 无法识别出板子。 如下图: 开启板子和摄像头 烧录镜像: 参考:SPI NAND Flash 镜像烧录 | LUCKFOX WIKI 首先我们要对板子进行烧录,官方提供了linux的镜像。提供的有ubuntu的镜像和buildroot镜像。这里我们选择官方推荐的buildroot镜像,活动的板子是Max系列,我们选择luckfox_pico_pro_max_image镜像。 登录: 参考文件:SSH/Telnet 登录 | LUCKFOX WIKI Luckfox Pico 系列的最新固件默认启用了 SSH。这里我们通过 USB 连接使用静态 IP 进行登录。 然后我们关闭防火墙,配置电脑 rndis网口的ip地址 为172.32.0.100 子网掩码 255.255.0.0,此时可以通过ssh 访问 172.32.0.93。用自带的 Powershell 终端输入口令进行直接登录。格式是 ssh 客户端用户名@服务器ip地址 ssh root@172.32.0.93 登录账号:root 登录密码:luckfox 静态IP地址:172.32.0.93 通过ls命令查看都有哪些文件。   系统将自动识别摄像头,生成 rkipc.ini 文件。 下载并安装 VLC media player 。 打开 VLC media player 软件,在媒体—>打开网络串流,输入默认的 IP 地址:rtsp://172.32.0.93/live/0 这样子就可以看到摄像头拍摄的画面了。 注意:为了后续的部署,我们需要关闭系统默认 rkipc 程序,执行 RkLunch-stop.sh 命令。 配置开发环境: 参考文件:SDK 环境部署(PC端) | LUCKFOX WIKI 这里较为复杂,我也不懂,跟着教程照搬就可以了。 编译与运行: export LUCKFOX_SDK_PATH=<Your Luckfox-pico Sdk Path> mkdir build cd build cmake .. make && make install 将编译生成的luckfox_rtsp_opencv_demo/luckfox_rtsp_opencv 和、lib目录以及模型权重model.rknn都上传到 luckfox-pico 上,进入文件夹运行. 文件传输命令: # 传输文件 scp model.rknn root@172.32.0.93:/root # 传输文件夹 scp -r luckfox_rtsp_opencv_demo root@172.32.0.93:/root 注意:首次连接需要输入 yes 确认,然后输入密码 luckfox 开始传输 运行目录下应有 luckfox_rtsp_opencv lib model.rknn 三个文件. 赋予权限并运行。 chmod 755 luckfox_rtsp_opencv ./luckfox_rtsp_opencv ./model.rknn 代码实现细节: 定义结构体存储预测的数字及其概率 // 定义一个结构体储存预测到的数字和其对应的概率 struct Prediction { int digit; float probability; }; // 定义全局变量简单队列用于存储预测到的数字和其对应的概率 std::vector<Prediction> predictions_queue; 定义了加载模型的函数,用于从指定的二进制文件中加载模型数据,并确保在操作完成后释放资源。 // 函数名称: load_model // 输入参数: filename - 要加载的模型文件名,model_size - 用于存储模型大小的指针 // 返回值: 加载成功则返回模型数据指针,失败则返回NULL static unsigned char *load_model(const char *filename, int *model_size) { // 以二进制模式打开文件 FILE *fp = fopen(filename, "rb"); if (fp == nullptr) { // 打开文件失败,输出错误信息并返回NULL printf("fopen %s fail!\\n", filename); return NULL; } // 获取文件长度 fseek(fp, 0, SEEK_END); int model_len = ftell(fp); // 分配model_len字节大小的内存,类型为unsigned char 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_size = model_len; // 关闭文件 if (fp) { fclose(fp); } // 返回模型数据的内存地址 return model; } 定义函数find_digit_contour,在输入图像中查找数字的轮廓,并进行抖动减小和形状过滤。 // 函数名称:find_digit_contour // 功能:在输入图像中查找数字的轮廓,并进行抖动减小和形状过滤 // 输入参数:image - 输入图像 // 返回值:bounding_box - 数字的边界框 cv::Rect find_digit_contour(const cv::Mat &image) { // 预处理图像 cv::Mat gray, blurred, edged; cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY); // 转换为灰度图像 cv::GaussianBlur(gray, blurred, cv::Size(5, 5), 0); // 高斯模糊去噪 cv::Canny(blurred, edged, 30, 150); // 边缘检测 // 应用形态学操作 cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5)); cv::dilate(edged, edged, kernel); // 膨胀操作 cv::erode(edged, edged, kernel); // 腐蚀操作 // 查找轮廓 std::vector<std::vector<cv::Point>> contours; cv::findContours(edged, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE); if (contours.empty()) { return cv::Rect(); } // 找到最大的轮廓 auto largest_contour = std::max_element(contours.begin(), contours.end(), [](const std::vector<cv::Point>& a, const std::vector<cv::Point>& b) { return cv::contourArea(a) < cv::contourArea(b); }); // 轮廓面积过滤 if (cv::contourArea(*largest_contour) < 10) { return cv::Rect(); } // 轮廓形状过滤 cv::Rect bounding_box = cv::boundingRect(*largest_contour); float aspect_ratio = static_cast<float>(bounding_box.width) / bounding_box.height; if (aspect_ratio < 0.2 || aspect_ratio > 3) { return cv::Rect(); } // 轮廓稳定性检测 static std::vector<cv::Rect> prev_bounding_boxes; if (prev_bounding_boxes.size() > 5) { prev_bounding_boxes.erase(prev_bounding_boxes.begin()); } prev_bounding_boxes.push_back(bounding_box); if (prev_bounding_boxes.size() == 5) { float avg_width = 0.0; float avg_height = 0.0; for (const auto& box : prev_bounding_boxes) { avg_width += box.width; avg_height += box.height; } avg_width /= prev_bounding_boxes.size(); avg_height /= prev_bounding_boxes.size(); float width_diff = std::abs(bounding_box.width - avg_width) / avg_width; float height_diff = std::abs(bounding_box.height - avg_height) / avg_height; if (width_diff > 0.1 || height_diff > 0.1) { return cv::Rect(); } } // 扩大边界框,以包含更多相关像素 bounding_box.x = std::max(0, bounding_box.x - 15); bounding_box.y = std::max(0, bounding_box.y - 15); bounding_box.width = std::min(image.cols - bounding_box.x, bounding_box.width + 30); bounding_box.height = std::min(image.rows - bounding_box.y, bounding_box.height + 30); // 返回最大轮廓的边界框 return bounding_box; } 这段代码的主要功能是在输入图像中查找数字的轮廓,并进行抖动减小和形状过滤。主要包括以下步骤: 图像被预处理,包括灰度化、高斯模糊和边缘检测。 形态学操作(膨胀和腐蚀)用于去除噪声和连接轮廓。 查找所有外部轮廓,并选择最大的轮廓。对于最大的轮廓,进行面积和形状过滤,以减少不必要的小轮廓。 对图像边框每个方向扩大15个像素,以包含更多相关像素。 返回最终的边界框。 定义图像预处理函数,其目的是对输入的数字区域图像进行预处理,以便于后续的数字识别。 // 函数名称:preprocess_digit_region // 功能:对输入的数字区域图像进行预处理,包括灰度转换、二值化、颜色反转和大小调整 // 输入参数:region - 输入的数字区域图像 // 返回值:resized - 预处理后的图像,大小为28x28,像素值归一化为0到1之间的浮点数 cv::Mat preprocess_digit_region(const cv::Mat &region) { // 将输入图像转换为灰度图像 cv::Mat gray; cv::cvtColor(region, gray, cv::COLOR_BGR2GRAY); // 使用OTSU方法自动确定阈值,将灰度图像二值化 cv::threshold(gray, gray, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU); // 再次应用阈值处理,将灰度值低于127的像素设置为0,高于200的像素设置为255 cv::threshold(gray, gray, 127, 255, cv::THRESH_BINARY_INV); // 创建一个与gray相同大小的Mat对象,用于存储颜色反转后的图像 cv::Mat bitwized = cv::Mat::zeros(gray.size(), gray.type()); // 对图像进行颜色反转,将黑色变成白色,白色变成黑色 cv::bitwise_not(gray, bitwized); // 手动实现颜色反转,以验证bitwise_not函数的效果 for (int i = 0; i < bitwized.rows; i++) { for (int j = 0; j < bitwized.cols; j++) { bitwized.at<uchar>(i, j) = 255 - bitwized.at<uchar>(i, j); } } // 将处理后的图像大小调整为28x28,使用INTER_AREA插值方法以保持图像细节 cv::Mat resized; cv::resize(bitwized, resized, cv::Size(28, 28), 0, 0, cv::INTER_AREA); // 返回调整大小后的图像 return resized; } 这段代码的主要步骤包括: 将输入的彩色图像转换为灰度图像。 使用OTSU方法自动确定阈值,将灰度图像二值化。 再次应用阈值处理,调整图像的颜色分布。 对二值化后的图像进行颜色反转。 手动验证颜色反转的效果。 将图像大小调整为28x28,使用INTER_AREA插值方法以保持图像细节。 返回预处理后的图像 后处理阶段: deqnt_affine_to_f32函数:将量化后的INT8数据转化为浮点数。 output_normalization函数:将模型的输出进行归一化,并计算输出的概率分布。 // 将量化的INT8数据转换为浮点数 // Parameters: // qnt: 量化后的整数数据 // zp: 零点(zero point)值,用于零点偏移(zero-point offset) // scale: 缩放因子,用于缩放量化后的整数数据到浮点数范围 // Returns: // 浮点数,表示经过反量化(dequantization)后的数据 static float deqnt_affine_to_f32(int8_t qnt, int32_t zp, float scale) { // 返回((float)qnt - (float)zp) * scale,即量化值减去零点值后乘以缩放因子 return ((float)qnt - (float)zp) * scale; } // 将模型输出进行归一化,并计算输出的概率分布 // Parameters: // output_attrs: 输出张量属性,包含了零点(zero point)值和缩放因子等信息 // output: 模型输出的数据,以INT8格式存储 // out_fp32: 存储归一化后的浮点数输出数据 static void output_normalization(rknn_tensor_attr* output_attrs, uint8_t *output, float *out_fp32) { int32_t zp = output_attrs->zp; // 从输出张量属性中获取零点值 float scale = output_attrs->scale; // 从输出张量属性中获取缩放因子 // 将INT8格式的输出数据进行反量化为浮点数,并进行存储 for(int i = 0; i < 10; i ++) out_fp32[i] = deqnt_affine_to_f32(output[i],zp,scale); // 计算输出数据的L2范数 float sum = 0; for(int i = 0; i < 10; i++) sum += out_fp32[i] * out_fp32[i]; // 对归一化后的浮点数输出进行归一化处理,确保输出数据的范围在[0,1]之间 float norm = sqrt(sum); for(int i = 0; i < 10; i++) out_fp32[i] /= norm; // 打印输出数据的值 printf("\\n===================Output data values:===================\\n"); for (int i = 0; i < 10; ++i) { printf("%f ", out_fp32[i]); } printf("\\n"); // 找出最大概率对应的数字,并记录最大概率及其对应的数字 float max_prob = -1.0; int predicted_digit = -1; // 计算最大值的索引 for (int i = 0; i < 10; ++i) { if (out_fp32[i] > max_prob) { max_prob = out_fp32[i]; predicted_digit = i; } } // 将预测的数字及其对应的概率记录到队列中 predictions_queue.push_back({predicted_digit, max_prob}); // 打印预测的数字与其对应的概率 printf("========Predicted digit: %d, Probability: %.2f========\\n\\n", predicted_digit, max_prob); } 使用deqnt_affine_to_f32函数将INT8格式的数据转换为浮点数。 在output_normalization函数中,对转换后的浮点数数据进行归一化处理,使其概率分布的总和为1。计算并打印每个数字的概率值,找出最大概率对应的数字,并将其预测结果和概率记录到队列中。 定义推理函数,主要用于处理输入的图像数据,将其转换为模型所需的格式,并进行推理以获取输出结果。包括以下功能: 获取模型的输入输出属性。 处理输入图像,将其转换为模型所需的格式。 创建并设置输入输出张量的内存。 运行模型推理。 处理输出结果,获取预测的数字。 释放分配的内存。 // 定义函数run_inference,接收一个cv::Mat类型的图像帧作为输入 int run_inference(cv::Mat &frame) { int ret = 0; // 初始化返回值为0 rknn_input_output_num io_num; // 定义结构体用于存储输入输出通道数 // 获取模型的输入输出通道数 rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num)); // 初始化输入属性数组 rknn_tensor_attr input_attrs[io_num.n_input]; memset(input_attrs, 0, io_num.n_input * sizeof(rknn_tensor_attr)); for (uint32_t 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; // 如果查询失败,返回错误 } dump_tensor_attr(&input_attrs[i]); // 打印输入属性信息 } printf("output tensors:\\n"); // 初始化输出属性数组 rknn_tensor_attr output_attrs[io_num.n_output]; memset(output_attrs, 0, io_num.n_output * sizeof(rknn_tensor_attr)); for (uint32_t i = 0; i < io_num.n_output; i++) { output_attrs[i].index = i; // 设置输出属性的索引 // 查询输出属性的详细信息 ret = rknn_query(ctx, RKNN_QUERY_NATIVE_OUTPUT_ATTR, &(output_attrs[i]), sizeof(rknn_tensor_attr)); if (ret != RKNN_SUCC) { printf("rknn_query fail! ret=%d\\n", ret); return -1; // 如果查询失败,返回错误 } dump_tensor_attr(&output_attrs[i]); // 打印输出属性信息 } printf("Gray image size: %dx%d\\n", frame.rows, frame.cols); printf("Gray image type: %d\\n", frame.type()); // 计算并分配用于存储调整大小后图像的内存 int mem_size = MODEL_WIDTH * MODEL_HEIGHT * CHANNEL_NUM; unsigned char *resize_buf = (unsigned char *)malloc(mem_size); memset(resize_buf, 0, mem_size); // 创建输入张量内存 rknn_tensor_mem *input_mems[1]; input_attrs[0].type = input_type; // 设置输入类型 input_attrs[0].fmt = input_layout; // 设置输入格式 input_mems[0] = rknn_create_mem(ctx, input_attrs[0].size_with_stride); // 将输入数据复制到输入张量内存 int width = input_attrs[0].dims[2]; int stride = input_attrs[0].w_stride; if (width == stride) { memcpy(input_mems[0]->virt_addr, frame.data, width * input_attrs[0].dims[1] * input_attrs[0].dims[3]); } else { int height = input_attrs[0].dims[1]; int channel = input_attrs[0].dims[3]; uint8_t *src_ptr = frame.data; uint8_t *dst_ptr = (uint8_t *)input_mems[0]->virt_addr; int src_wc_elems = width * channel; int dst_wc_elems = stride * channel; for (int h = 0; h < height; ++h) { memcpy(dst_ptr, src_ptr, src_wc_elems); src_ptr += src_wc_elems; dst_ptr += dst_wc_elems; } } // 创建输出张量内存 rknn_tensor_mem *output_mems[io_num.n_output]; for (uint32_t i = 0; i < io_num.n_output; ++i) { output_mems[i] = rknn_create_mem(ctx, output_attrs[i].size_with_stride); } // 设置输入张量内存 ret = rknn_set_io_mem(ctx, input_mems[0], &input_attrs[0]); if (ret < 0) { printf("rknn_set_io_mem fail! ret=%d\\n", ret); return -1; } // 设置输出张量内存 for (uint32_t i = 0; i < io_num.n_output; ++i) { ret = rknn_set_io_mem(ctx, output_mems[i], &output_attrs[i]); if (ret < 0) { printf("rknn_set_io_mem fail! ret=%d\\n", ret); return -1; } } // 运行推理 ret = rknn_run(ctx, nullptr); if (ret < 0) { printf("rknn_run failed! %s\\n", ret); return -1; } uint8_t *output= (uint8_t*)malloc(sizeof(uint8_t) * 10); float *out_fp32 = (float*)malloc(sizeof(float) * 10); output = (uint8_t *)output_mems[0]->virt_addr; // 获取预测的数字 output_normalization(&output_attrs[0], output, out_fp32); // 释放内存 rknn_destroy_mem(ctx, input_mems[0]); for (uint32_t i = 0; i < io_num.n_output; ++i) { rknn_destroy_mem(ctx, output_mems[i]); } } 主函数主要目的是在实时视频流中检测并显示数字,同时将视频编码后发送到rtsp服务器。包括以下步骤:  初始化必要的模块,如rkaiq、rkmpi、rtsp等。 通过rkmpi绑定Vi(Video Interface)和VPSS(Video Processing System)模块,用于视频处理和编码。 初始化VENC(Video Encoder)模块,用于视频编码。 加载并初始化rknn模型,进行手写数字识别。 在一个循环中,从VPSS获取帧,进行物体识别和数字预测,然后编码成H264并发送到rtsp流。 更新帧率(fps)并显示在图像上。 在每次循环结束时,释放帧资源和内存。 最后,释放所有模块的资源,销毁rknn模型,并退出程序。 首先我们需要,定义图像分辨率 int width = 640; int height = 480; 在这里,我们主要介绍while循环里的逻辑代码 1.opencv获取摄像头帧,并调用find_digit_contour函数在其中查找数字的轮廓,并进行抖动减小和形状过滤。 void *data = RK_MPI_MB_Handle2VirAddr(stVpssFrame.stVFrame.pMbBlk); cv::Mat frame(height, width, CV_8UC3, data); cv::Rect digit_rect = find_digit_contour(frame); 2.if (digit_rect.area() > 0),opencv截取数字的区域并进行预处理,然后将预处理后的数据送入run_inference函数进行数据推理。 cv::Mat digit_region = frame(digit_rect); cv::Mat preprocessed = preprocess_digit_region(digit_region); int prediction = run_inference(preprocessed); 3.对当前帧上显示识别结果。 // 从predictions_queue中获取预测到的数字和其对应的概率 //检查predictions_queue是否为空,如果不为空,则取出最后一个元素作为当前帧的识别结果。 if (!predictions_queue.empty()) { Prediction prediction = predictions_queue.back(); cv::rectangle(frame, digit_rect, cv::Scalar(0, 255, 0), 2); // 在图像上显示预测结果,显示字号为1,颜色为红色,粗细为2 cv::putText(frame, std::to_string(prediction.digit), cv::Point(digit_rect.x, digit_rect.y - 10), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(255, 0, 0), 2); // 在图像上显示预测概率 cv::putText(frame, std::to_string(prediction.probability), cv::Point(digit_rect.x+ 30, digit_rect.y - 10), cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(230, 0, 0), 2); // 从predictions_queue中删除最旧的元素,以便下一次迭代时可以取出新的识别结果 predictions_queue.pop_back(); } 4.最后当前帧图像数据复制到rtsp帧中 memcpy(data, frame.data, width * height * 3);   实际演示效果 没有设备固定摄像头,一边录屏一边手拿摄像头,视频有点晃动。 但是数字识别的效果还是不错的,也没有数字识别错误的情况。数字0~9全部识别正确。 [localvideo]499910638be8d84fc57eadd6fd509e5e[/localvideo] 附参考代码:GitHub - knva/luckfox_pico_rtsp_opencv                                    

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

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

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

    1、跟帖回复:什么是ONNX模型、RKNN模型 OXXN模型:ONNX是一种用于表示深度学习模型的开放源代码格式,旨在促进不同深度学习框架(如pytorch、mxnet等)之间的模型互操作性和转换。通过ONNX,可以在一个深度学习框架中训练模型,然后将其转换为ONNX格式,最后在另一个支持ONNX的框架中加载和使用该模型,从而实现跨框架的模型迁移和共享。 RKNN模型:由瑞芯微(Rockchip)推出的一种神经网络模型转换工具。RKNN模型是为了在Rockchip的芯片上进行深度学习模型的高效部署而设计的。RKNN模型的主要功能包括将深度学习模型转换成Rockchip芯片上的可执行文件,以便在嵌入式设备、移动设备或其他Rockchip芯片支持的平台上进行推理。通过RKNN模型,用户可以将经过训练的神经网络模型(如TensorFlow、Caffe等)转换成Rockchip芯片所支持的格式,以实现在硬件上高效地运行神经网络模型。 2、以#AI挑战营第二站#为标题前缀,发帖分享ONNX模型转换成RKNN模型过程,包含环境部署、转换代码、解读代码,并附上RKNN模型。 #AI挑战营第二站# windows下基于RV1106开发板的ONNX转RKNN模型 - 嵌入式系统 - 电子工程世界-论坛 (eeworld.com.cn)  

  • 发表了主题帖: #AI挑战营第二站# windows下基于RV1106开发板的ONNX转RKNN模型

    本帖最后由 xianhangCheng 于 2024-5-8 11:54 编辑 介绍 OXXN模型:ONNX是一种用于表示深度学习模型的开放源代码格式,旨在促进不同深度学习框架(如pytorch、mxnet等)之间的模型互操作性和转换。通过ONNX,可以在一个深度学习框架中训练模型,然后将其转换为ONNX格式,最后在另一个支持ONNX的框架中加载和使用该模型,从而实现跨框架的模型迁移和共享。   RKNN模型:由瑞芯微(Rockchip)推出的一种神经网络模型转换工具。RKNN模型是为了在Rockchip的芯片上进行深度学习模型的高效部署而设计的。RKNN模型的主要功能包括将深度学习模型转换成Rockchip芯片上的可执行文件,以便在嵌入式设备、移动设备或其他Rockchip芯片支持的平台上进行推理。通过RKNN模型,用户可以将经过训练的神经网络模型(如TensorFlow、Caffe等)转换成Rockchip芯片所支持的格式,以实现在硬件上高效地运行神经网络模型。   模型转换:将ONNX模型转换为RKNN模型是通常需要搭建好python环境,并使用ONNX-RKNN转换工具,例如 rknn-toolkit,这是由RKNN官方提供的转换工具。可以在RKNN的GitHub上找到它瑞芯微-linux/rknn-工具包 --- rockchip-linux/rknn-toolkit (github.com)。其转换过程如下: 加载ONNX模型:使用Python的ONNX库(onnx)加载你的ONNX模型。 检查兼容性:确保ONNX模型的结构和层与RKNN支持的模型结构兼容。某些复杂架构可能需要调整。 转换模型:使用rknn-toolkit的convert命令将ONNX模型转换为RKNN模型。这通常涉及指定输入和输出格式,以及可能的模型优化参数。 配置RKNN:可能需要根据目标设备(如Raspberry Pi)的性能和内存限制进行模型配置。 编译和优化:生成RKNN模型文件,这个文件可以在嵌入式设备上运行。 环境配置 我们可以看到,根据介绍我们使用的是基于RV1106开发板,所以这里我们选择下载rknn-toolkit2. 我们根据提示从https://github.com/rockchip-linux/rknn-toolkit/releases下载 rknn-toolkit-v1.7.5-packages.tar.gz或者rknn-toolkit-v1.7.5-packages.zip。 下载完,解压后能看见多个平台的whl文件,这里因为用的是win所以选择rknn_toolkit-1.7.5-cp36-cp36m-win_amd64.whl这个文件,并选择requirements_cpu.txt作为我们的安装包。并且我们观察到这个whl文件是cp36,即win环境下支持python3.6。所以接下来我们开始配置环境。   使用 Conda 创建 Python 环境。 #创建conda虚拟环境 conda create --name=RKNN python=3.6 #激活环境 conda activate RKNN 进入解压的rknn-toolkit-v1.7.5-packages目录里,安装packages/requirements_cpu.txt文件里的依赖包,path_txt是自己解压的rknn-toolkit-v1.7.5-packages/packages目录下的requirements_cpu.txt文件在电脑硬盘里的路径。 pip install -r path_txt 最后安装rknn_toolkit-1.7.5-cp36-cp36m-win_amd64.whl文件,path_whl是自己解压的自己解压的rknn-toolkit-v1.7.5-packages/packages目录下的rknn_toolkit-1.7.5-cp36-cp36m-win_amd64.whl文件在电脑硬盘里的路径。 pip install path_whl 踩坑在安装whl文件时,可能会遇到错误,比如我遇到AssertionError: Could not find “cmake“ executable!导致的ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full。(已解决,忘记截图了),这时候网上部分教程是更新pip、setuptools包;或者pip install xxx改为pip3 install xxx。但这些都不能解决!!!应该根据提示的具体错误加以解决。比如我遇到的AssertionError: Could not find “cmake“ executable!错误解决,需要安装cmake才能解决。安装命令:pip install cmake   ONNX模型转换为RKNN模型 from rknn.api import RKNN # 创建对象 rknn = RKNN() # 加载ONNX模型 print('--> Loading model') ret = rknn.load_onnx(model="./minst_model.onnx") if ret != 0: print('Load model failed!') exit(ret) print('done') # 配置平台 print('--> config model') rknn.config(mean_values=[[128]], std_values=[[128]]) print('done') # 建立模型 print('--> Building model') ret = rknn.build(do_quantization=False) if ret != 0: print('Build model failed!') exit(ret) print('done') # 保存RKNN模型 print('--> Export rknn model') ret = rknn.export_rknn("best_model.rknn") if ret != 0: print('Export rknn model failed!') exit(ret) print('done') 运行代码,成功完成oxxn模型到rknn模型的转换。(红色的警告不必理会)                              

  • 回复了主题帖: #AI挑战营第二站# 基于RV1106芯片的RKNN环境部署及模型转换过程

    xianhangCheng 发表于 2024-5-8 11:25 rknn.config加上target_platform='rv1106'参数,就会报错,PermissionError: [WinError 32] 另一个程序正在 ... 发现有一个这个错误, AssertionError: target_platform set as ['RV1106'] is not support

  • 回复了主题帖: #AI挑战营第二站# Win10 ONNX转RKNN踩坑

    求,rknn.config这种问题怎么解决呢,PermissionError: [WinError 32] 另一个程序正在使用此文件,进程无法访问。: 'C:\\Users\\24423\\AppData\\Local\\Temp\\tmpx00mgkkk\\log_feedback_to_the_rknn_toolkit_dev_team.log'

  • 回复了主题帖: #AI挑战营第二站# Windows下ONNX转RKNN模型过程

    求,rknn.config这种问题怎么解决呢,PermissionError: [WinError 32] 另一个程序正在使用此文件,进程无法访问。: 'C:\\Users\\24423\\AppData\\Local\\Temp\\tmpx00mgkkk\\log_feedback_to_the_rknn_toolkit_dev_team.log'

  • 2024-04-16
  • 回复了主题帖: 免费申请:幸狐 RV1106 Linux 开发板(带摄像头),助力AI挑战营应用落地

    本帖最后由 xianhangCheng 于 2024-4-16 13:58 编辑 #AI挑战营第一站# pytorch实现基于卷积神经网络的MNIST手写数.... - 嵌入式系统 - 电子工程世界-论坛 (eeworld.com.cn) 预期应用:部署智能家居应用的人脸识别解锁功能

  • 2024-04-12
  • 回复了主题帖: 【AI挑战营第一站】模型训练:在PC上完成手写数字模型训练,免费申请RV1106开发板

    本帖最后由 xianhangCheng 于 2024-4-13 01:14 编辑 1.用自己的语言描述,模型训练的本质是什么,训练最终结果是什么 模型训练的本质是通过给定的数据集,利用机器学习算法来调整模型的参数,使其能够从数据中学习规律和模式。在训练过程中,模型会根据输入的数据进行计算,然后根据计算结果和实际标签之间的差异(损失),通过反向传播算法来调整参数,以最小化损失函数。这个过程不断迭代进行,直到模型收敛到一个满足要求的状态。训练的最终结果是一个经过优化的模型,可以用来对新的未见过的数据进行预测或分类。 2.PyTorch是什么?目前都支持哪些系统和计算平台? PyTorch是一个开源的机器学习框架,提供了丰富的工具和库,用于构建深度学习模型、进行模型训练和部署。 PyTorch目前支持的系统和计算平台包括: 系统:Linux、Windows、macOS 计算平台:CPU、GPU(NVIDIA CUDA)、TPU(Google的Tensor Processing Unit) 3.动手实践链接: #AI挑战营第一站# pytorch实现基于卷积神经网络的MNIST手写数.... - 嵌入式系统 - 电子工程世界-论坛 (eeworld.com.cn)

  • 发表了主题帖: #AI挑战营第一站# pytorch实现基于卷积神经网络的MNIST手写数....

    本帖最后由 xianhangCheng 于 2024-4-13 01:00 编辑 1.安装包 import torch import torch.nn as nn import torch.optim as optim import torchvision import torchvision.transforms as transforms from torch.utils.data import DataLoader import matplotlib.pyplot as plt 2.加载数据以及定义一些超参数(训练轮数/种类/学习率) 利用torchvision.datasets.MNIST函数下载数据集,其中root表示保存目录,train=True表示使用训练集,反之表示测试集。download表示如果本地没有MNIST就从服务器下载,transform是对图片的预处理工作(如尺寸resize、rgb转灰度图等) # 加载MNIST数据集 transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))]) train_dataset = torchvision.datasets.MNIST(root='./data', train=True, transform=transform, download=True) test_dataset = torchvision.datasets.MNIST(root='./data', train=False, transform=transform, download=True) #shuffle 参数设置为 True,在装载的过程会将数据随机打乱顺序并进行打包 train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True) test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False) #定义超参数 num_classes = 10 # 0-9数字分类 num_epochs = 20 learning_rate = 0.001 3.定义模型 我们定义了一个卷积神经网络,包含两个卷积层,2个全连接层,num_classes=10,代表数字0~9共10个总类。 # 定义模型 class model(nn.Module): def __init__(self): super(model, self).__init__() self.layer1 = nn.Sequential( nn.Conv2d(1, 32, kernel_size=5, stride=1, padding=2), nn.ReLU(), nn.MaxPool2d(kernel_size=2, stride=2)) self.layer2 = nn.Sequential( nn.Conv2d(32, 64, kernel_size=5, stride=1, padding=2), nn.ReLU(), nn.MaxPool2d(kernel_size=2, stride=2)) self.drop_out = nn.Dropout() self.fc1 = nn.Linear(7 * 7 * 64, 1000) self.fc2 = nn.Linear(1000, num_classes) def forward(self, x): out = self.layer1(x) out = self.layer2(out) out = out.reshape(out.size(0), -1) out = self.drop_out(out) out = self.fc1(out) out = self.fc2(out) return out 4.初始化模型,定义损失函数/优化器 # 初始化模型、损失函数和优化器 model = model() criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=learning_rate) 5.训练网络 指定训练次数epoch,对每一轮训练,都会进行:前向传播计算损失、反向传播梯度下降更新权重。在每一轮训练好之后,再计算 测试集的准确率,当指准确最大时保存该轮的模型权重。losses、accuracies、best_accuracy分别表示记录每一轮训练的loss值和准确率以及最高的准确率。 # 训练模型 # 在每一轮训练中记录损失和准确率 losses = [] accuracies = [] best_accuracy = 0.0 for epoch in range(num_epochs): # 假设训练20个epoch model.train() for images, labels in train_loader: optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() # 测试模型 model.eval() total = 0 correct = 0 with torch.no_grad(): for images, labels in test_loader: outputs = model(images) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() accuracy = correct / total print(f'Epoch {epoch+1}, Loss: {loss.item():.4f}, Accuracy: {100 * accuracy:.2f}%') losses.append(loss) accuracies.append(accuracy) # 保存准确率最高的模型权重 if accuracy > best_accuracy: torch.save(model.state_dict(), 'best_model.pth') best_accuracy = accuracy 6.结果及其可视化   准确率最高是第18轮训练,99.34%(红色框) 测试集准确率可视化:   训练集Loss可视化:   数据可视化代码如下: # 绘制损失图 plt.figure() plt.plot(range(1, num_epochs+1), losses, label='Loss') plt.xlabel('Epoch') plt.ylabel('Loss') plt.title('Training Loss') plt.legend() plt.savefig('loss_plot.png') plt.show() # 绘制准确率图 plt.figure() plt.plot(range(1, num_epochs+1), accuracies, label='Accuracy') plt.xlabel('Epoch') plt.ylabel('Accuracy') plt.title('Training Accuracy') plt.legend() plt.savefig('accuracy_plot.png') plt.show()   7.pth转换xoon # 定义输入数据大小 dummy_input = torch.randn(1, 1, 28, 28) # 导出为ONNX格式及其路径 onnx_path = "best_model.onnx" # 导出模型为ONNX格式 torch.onnx.export(model, dummy_input, onnx_path, input_names=['input'], output_names=['output'], # 输入和输出节点名称 do_constant_folding=True, # 是否执行常量折叠优化 export_params=True, # 是否包含权重 verbose=False, # 是否打印转换细节 dynamic_axes={'input': {0: 'batch_size'}, 'output': {0: 'batch_size'}}) # 设置动态轴,允许在推理时调整batch_size  

最近访客

< 1/1 >

统计信息

已有8人来访过

  • 芯积分:52
  • 好友:--
  • 主题:3
  • 回复:8

留言

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


现在还没有留言