qiao---

  • 2024-07-12
  • 回复了主题帖: 颁奖:嵌入式工程师AI挑战营(初阶),致敬敢于将边缘AI收入统治领域的你们

    个人信息已确认,请安排邮寄。优秀奖选择均码防晒衣。感谢luckfox和EEworld对本次活动的大力支持

  • 回复了主题帖: 【STM32MP135F-DK】7-部署QT开发环境+运行第一个QT程序

    zhgongzuoshi 发表于 2024-7-11 16:03 楼主,“勾选上后我们从新编译一下我们的内核 make uImage LOADADDR=0xC2000040 ”时,会报错, ... 重新拉代码编译

  • 2024-07-01
  • 回复了主题帖: 【正点原子i.MX93开发板】4-LVGL的GUI制作(显示更新系统信息)

    wangerxian 发表于 2024-6-30 11:45 GUI_Guider免费还是收费的呀? 完全免费的

  • 2024-06-30
  • 发表了主题帖: 【正点原子i.MX93开发板】4-LVGL的GUI制作(显示更新系统信息)

    本帖最后由 qiao--- 于 2024-6-30 02:14 编辑 前言:在我的第二期测评我们成功的移植了LVGL框架,本期测评就可以利用这个框架开发出一些好看的UI。本人的审美能力欠缺,UI制作的可能并不是很好看(这是UI工程师干的活),帖友们不要介意,重要的是开发过程。本期测评将分为以下三个部分的: 用GUI_Guider开发一个简易界面并移植运行 写一个C语言程序读出系统信息(ip地址,cpu占用率,cpu温度等) 将以上两者综合移植 1.用GUI_Guider开发一个简易界面并移植运行 经常看我的测评的贴友应该知道,GUI_Guider是我的老朋友了,我经常用这个软件开发我的lvgl应用。里面自带模拟器开发起来方便,就是里面的控件有点少,不过也够用了。有对GUI_Guider不熟悉的贴友可以看我之前的文章: 【ACM32G103RCT6】8-基于RTC和LVGL的手机时间界面 https://bbs.eeworld.com.cn/thread-1270419-1-1.html 【Sipeed 博流BL808全能板】5-手表界面开发 https://bbs.eeworld.com.cn/thread-1273077-1-1.html 这两篇文章讲了GUI_Guider工程的创建和移植,但是不是Linux下的移植,本期 测评将移植于Linux系统。 在GUI_Guider创建一个工程后简单添加一些控件,并运行模拟器,我的效果如下图所示:   成功运行,接下来就是把我们这个工程移植到我们之前的lvgl框架中。移植步骤还是移植下面两个文件夹,由于我的custom文件夹下没有内容,我就不移植了,我就只移植generated这一个文件夹。   将这个文件夹复制到我们第二期测评拉下来代码的lvgl目录中,目录结构如下所示:   此时这个项目还不知道我们移植这个文件的.c和.h文件的位置,因此还要修改Makefile文件 修改lvgl目录下的lvgl.mk文件,添加以下内容:   include $(LVGL_DIR)/$(LVGL_DIR_NAME)/generated/generated.mk 在generated目录下的generated.mk文件添加以下内容   PRJ_DIR ?= $(LVGL_DIR)/$(LVGL_DIR_NAME) 修改完成后我们还需要修改我们的main.c中的代码,调用我们UI代码。代码主要是添加一个头文件,然后调用系统生成的UI创建代码。     然后进行编译make -j4  ,就可以编译成功。我们运行这个程序,效果如下所示:   但是这个UI里面的数据不是动态显示的,我们还要添加一些代码。   2.写一个C语言程序读出系统信息(ip地址,cpu占用率,cpu温度等) 由于上面的代码不能动态显示,我们还需要写一个demo,读出系统的信息,最后将他移植到我们项目中。Linux 的一些系统信息都是通过文件的形式来提供接口的,我们只需要通过文件IO来访问这些接口就行了。我的示例代码如下所示: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <net/if.h> #include <sys/ioctl.h> #include <time.h> #define MAX_BUFFER_SIZE 256 #define MAX_IP_LENGTH 16 // 定义IPv4地址的最大长度为15个字符,加上字符串终止符'\0' // 实现获取本地IP地址的函数 int get_local_ip(char *ip_buffer) { int fd; // 套接字描述符 struct ifreq ifr; // 网络接口请求结构体 // 创建一个套接字 fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { perror("socket"); // 输出错误信息并返回 return -1; } // 指定要查询的网络接口(这里使用eth0,可根据实际情况修改) strncpy(ifr.ifr_name, "eth0", IFNAMSIZ - 1); // 拷贝接口名到请求结构体 // 获取接口的IP地址 if (ioctl(fd, SIOCGIFADDR, &ifr) < 0) { perror("ioctl"); // 输出错误信息并返回 close(fd); // 关闭套接字 return -1; } // 将获取到的IP地址转换成字符串形式并存储在ip_buffer中 inet_ntop(AF_INET, &((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr, ip_buffer, INET_ADDRSTRLEN); close(fd); // 关闭套接字 return 0; // 返回成功 } // 获取 CPU 温度 float get_cpu_temperature() { FILE *fp; float temp = 0.0; fp = fopen("/sys/class/thermal/thermal_zone0/temp", "r"); if (fp == NULL) { perror("Error opening temperature file"); return -1.0; } fscanf(fp, "%f", &temp); fclose(fp); return temp / 1000.0; // 转换为摄氏度 } // 获取 CPU 占用率 float get_cpu_usage() { FILE *fp; char buffer[MAX_BUFFER_SIZE]; long double a[4], b[4]; float usage; fp = fopen("/proc/stat", "r"); if (fp == NULL) { perror("Error opening CPU stat file"); return -1.0; } fgets(buffer, sizeof(buffer), fp); sscanf(buffer, "cpu %Lf %Lf %Lf %Lf", &a[0], &a[1], &a[2], &a[3]); fclose(fp); sleep(1); // 等待一段时间再次获取数据 fp = fopen("/proc/stat", "r"); if (fp == NULL) { perror("Error opening CPU stat file"); return -1.0; } fgets(buffer, sizeof(buffer), fp); sscanf(buffer, "cpu %Lf %Lf %Lf %Lf", &b[0], &b[1], &b[2], &b[3]); fclose(fp); usage = ((b[0] + b[1] + b[2]) - (a[0] + a[1] + a[2])) / ((b[0] + b[1] + b[2] + b[3]) - (a[0] + a[1] + a[2] + a[3])) * 100.0; return usage; } int main() { float cpu_temp, cpu_usage; cpu_temp = get_cpu_temperature(); time_t rawtime; struct tm *timeinfo; char timeBuffer[80]; char ip_address[MAX_IP_LENGTH]; // 用于存储IP地址的字符数组 time(&rawtime); // 获取当前时间 timeinfo = localtime(&rawtime); // 将时间转换为本地时间结构体 strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %H:%M:%S", timeinfo); // 格式化时间为字符串 printf("当前时间是: %s\n", timeBuffer); // 调用获取本地IP地址的函数 if (get_local_ip(ip_address) == 0) { printf("Local IP address: %s\n", ip_address); // 打印获取到的IP地址 } else { printf("Failed to get local IP address.\n"); // 获取失败时的错误提示 } if (cpu_temp != -1.0) { printf("CPU Temperature: %.2f°C\n", cpu_temp); } else { printf("Failed to get CPU Temperature.\n"); } cpu_usage = get_cpu_usage(); if (cpu_usage != -1.0) { printf("CPU Usage: %.2f%%\n", cpu_usage); } else { printf("Failed to get CPU Usage.\n"); } return 0; } 在系统上编译并运行就可以成功的打印出系统信息,运行结果如下图所示:     3.将以上两步综合移植 现在我们就需要将上面两步综合让我们的UI能够动态显示。 我的想法是创建一个子线程来更新UI标签,这样就可以动态显示了。我的线程执行代码如下所示: // 线程函数,用于更新标签文本 void *update_label_thread(void *arg) { float cpu_temp, cpu_usage; char cpu_temp_s[10],cpu_usage_s[10]; char ip_address[MAX_IP_LENGTH]; // 用于存储IP地址的字符数组 char timeBuffer[80]; while (1) { time_t rawtime; struct tm *timeinfo; time(&rawtime); timeinfo = localtime(&rawtime); strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d\r\n%H:%M:%S", timeinfo); cpu_temp = get_cpu_temperature(); if (cpu_temp != -1.0) { printf("CPU Temperature: %.2f°C\n", cpu_temp); } else { printf("Failed to get CPU Temperature.\n"); } // 调用获取本地IP地址的函数 if (get_local_ip(ip_address) == 0) { printf("Local IP address: %s\n", ip_address); // 打印获取到的IP地址 } else { printf("Failed to get local IP address.\n"); // 获取失败时的错误提示 } cpu_usage = get_cpu_usage(); if (cpu_usage != -1.0) { printf("CPU Usage: %.2f%%\n", cpu_usage); } else { printf("Failed to get CPU Usage.\n"); } // 更新 lvgl 标签的文本 lv_label_set_text(guider_ui.screen_timeInfo, timeBuffer); lv_label_set_text(guider_ui.screen_ipInfo, ip_address); sprintf(cpu_usage_s, "%.2f%%", cpu_usage); lv_label_set_text(guider_ui.screen_CPULoad, cpu_usage_s); sprintf(cpu_temp_s, "%.2f *C", cpu_temp); lv_label_set_text(guider_ui.screen_CPUTemp, cpu_temp_s); // 等待一段时间,这里简单地使用 usleep 模拟 usleep(10000); // 等待 10毫秒 } return NULL; }   然后再在main函数里创建线程调用上面那个线程执行函数。   最后进行编译就OK了。动态显示效果如下的视频所示: [localvideo]e52272a624dabf6edfbdec7037b14ae8[/localvideo] 我在这里解压了一个程序,可以看到CPU占用率立即就上去了,而且时间也一直再变,可见我们的程序没有出错。 我改变一下我的ip地址试试。 [localvideo]306d5817bc86cf55c7d17062ee1a693e[/localvideo] IP地址也变了,移植成功!   总结:通过本期测评我们成功用LVGL框架开发出了一个UI,并且能够显示系统信息到UI上,后面我们将移植MQTT程序将这些数据上传到云平台上(这个板子没有传感器,只能采取读系统信息这种方式)。

  • 2024-06-27
  • 回复了主题帖: 【正点原子i.MX93开发板】3-人工智能的测评(AI识别)

    hellokitty_bean 发表于 2024-6-26 09:29 NPU厉害呀。。。。准确率、算力和10ms出结果,已算上乘了 是的,常见的一些应用已经足够了  

  • 回复了主题帖: 【正点原子i.MX93开发板】3-人工智能的测评(AI识别)

    wangerxian 发表于 2024-6-25 19:17 有NPU的手势识别例程嘛?话说NPU的AI性能比CPU强这么多呀。 无,npu并行计算的,肯定要快的多

  • 2024-06-25
  • 发表了主题帖: 【正点原子i.MX93开发板】3-人工智能的测评(AI识别)

    本帖最后由 qiao--- 于 2024-6-25 00:30 编辑 正点原子i.MX93开发板具有0.5TOPS算力,对于一般的一些AI识别应用来说已经足够了。接下来我将测评这块开发板这方面的性能。这里我将分别用CPU跑AI和用NPU跑AI来看看效果。   首先来测评一下正点原子这块开发板的AI的相关资料。直接说结论,目前这块开发板由于最近才上市,所以这块资料不是很完善,只能保证满足官方的历程能跑通。下面是他们文档中的声明,但是我相信后面正点原子会很快把这方面的资料补起来。     1.CPU进行AI识别 我这里跑的都是官方的例子。用的摄像头是USB摄像头。 首先将正点原子的模型文件拷贝到板子里,他们家的模型相关资料在01、程序源码\01、程序源码\06、AI例程源码\01、例程源码中,模型文件如下所示:   将他们拷贝到板子的/usr/bin/eiq-examples-git/modes下面即可。 这里我用CPU跑一个手势识别的例子,看看帧率如何,精度如何。 在跑这些例子的时候首先要做一下准备工作,如下: 将开发板的初始UI关掉,方便我们显示结果到显示屏上。 开启Weston 的支持,因为正点原子提供的程序需要使用 OpenCV 来管理窗口。 命令分别如下: systemctl stop systemui systemctl start weston 然后进入到手势识别的项目目录 cd /usr/bin/eiq-examples-git/gesture_detection/ 执行下面命令进行识别 python3 main.py -i /dev/video0 效果视频如下: [localvideo]21d5fd7215eadb3f7761f344c72e152c[/localvideo] 由上视频可以看出。用CPU进行推理的的帧率肉眼可见的低,几乎是1s推理一帧图片,识别率还可以,手掌可以使识别出来,经过我的测试发现,手掌做一些复杂的动作时候就推理不出来了。不过这块板子用CPU推理的速度相比其他市场上的一些开发板用CPU推理已经很高了。   2.GPU进行AI识别 我这里GPU进行AI识别的例子是使用的目标检测识别。 在上面拷贝了模型文件的基础上需要通过 vela 工具将模型编译成可以使 NPU 进行推理的 vela 模型。 进入到/usr/bin/eiq-examples-git 目录,使用 vela 工具编译 models 目录下 mobilenet_v1_1.0_224_quant.tflite模型,并使用--output-dir 参数指定 vela_models 目录,执行以下命令 这样NPU可推理的模型就在vela_models/目录下了。 cd /usr/bin/eiq-examples-git vela models/ssd_mobilenet_v1_quant.tflite --output-dir vela_models/ 出现下面的结果代表转化成功   在测试之前需要将 main.py 推理脚本里面的模型路径和模型名称进行修改 cd /usr/bin/eiq-examples-git/object_detection vi main.py 我的修改如下所示:   最后就可以进行推理了,使用下面的命令,其中/usr/lib/libethosu_delegate.so是指定NPU推理的动态库。 python3 main.py -i cars0.bmp -d /usr/lib/libethosu_delegate.so 推理效果如下所示: [localvideo]952d71b04109542941520db5d754a560[/localvideo] 从视频中可以看出,推理速度肉眼可见的上升,大约10ms可以推理出来一帧数据,检测的准确率还可以。   总结:通过本期测评我们测评了正点原子i.MX93开发板AI识别方面的性能以及资料情况,正点原子AI方面的教程有待完善,开发板的NPU推理的性能不错,有0.5TOPS算力,在推理目标检测模型的时候每10ms可以推出1帧数据,识别准确率较好。

  • 2024-06-24
  • 回复了主题帖: 【正点原子i.MX93开发板】2-LCD显示测评和移植LVGL显示框架

    本帖最后由 qiao--- 于 2024-6-25 00:57 编辑 lugl4313820 发表于 2024-6-22 22:33 大佬,你一出手,果然是惊艳呀,他应该支持HDMI吧,那样显示器是不是便宜一点? 是的,不支持HDMI,应该是接口PCB没画

  • 2024-06-22
  • 回复了主题帖: 【Luckfox幸狐 RV1106 Linux 开发板】5-给系统移植QT环境__显示电子时钟界面

    jafatek 发表于 2024-6-21 09:57 感谢大佬,根据您的步骤一点点操作完美运行。膜拜,辛苦了 感谢支持

  • 2024-06-21
  • 发表了主题帖: 【正点原子i.MX93开发板】2-LCD显示测评和移植LVGL显示框架

    本帖最后由 qiao--- 于 2024-6-22 00:20 编辑 前言: 为了给贴友们展示最好的测评结果,我持巨资买了一块mipiLCD的屏幕,这对还是学生的我还是有点压力的。本期测评我将测评开发板的LCD显示效果的清晰度、屏幕刷新率,其次我将移植LVGL,使开发板拥有开发精美UI的功能,然后我借着出厂UI测试一下开发板的音频质量以及网络稳定性等功能。为什么是移植lvgl?我测评计划准备是移植QT的,但是我想着之前我测评的每一款Linux开发板都是移植的QT,这次干脆就移植一次LVGL,这样两种UI框架的移植都有了岂不美哉。LVGL相对QT来说更加轻量,适用于那些资源较少、性能较弱的开发板。     1.LCD显示测评 上期测评说到正点原子家的开发板都有一个开机启动界面,这次给大家补上效果。系统开机后显示屏上显示的是一个手机,里面有备忘录、音乐、相机、照片、天气等等功能。 先来看看音乐播放的功能,看看音质怎么样,请看VCR。 [localvideo]29e6b3c09eb01b2e3644cc1dc69aea87[/localvideo] 可以听得出来音质还是相当不错的,同时开发板的触摸显示的也相对来说比较灵敏。 接下来我们来看看LCD显示效果的清晰度和色彩还原度,下面我打开相册里面的两张图片来测试,如下所示。     可以看到LCD显示的两张图片色彩基本没有偏差,都非常的清晰。 2.开发板网络测评 这里我为啥要来测评网络,因为开发板出厂UI里面有一个天气预报,他要获取云上的数据,就必须要联网,所以这里就一起测评了。 首先给开发板联网,我使用的是网线,让电脑共享网络给开发板。步骤如下。 (1)连接网线; (2)打开网络编辑器,共享给你开发板与电脑网线连接的口,我的是下面这个。   (3)给以太网配置IP sudo ifconfig eth0 192.168.137.29 netmask 255.255.255.0 broadcast 192.168.137.255 (4)测试 ping baidu.com,出现下面的结果,网络就没问题了。   然后我们来看看开发板的天气预报是否准确,开发板显示的天气如下所示:   然后给大家看一下此时我手机里面的天气预报做一下对比,我手机的天气预报如下所示:   很明显,网络传输的很稳定,天气预报无误。   3.移植LVGL显示框架 接下来就是我们的重头戏,给开发板移植移植LVGL显示框架,我下面的步骤都是在开发板上进行。 如果大家的开发板是使用的出厂系统,那么只需要按着我这个步骤来就行,如果不是,就要首先适配一下开发板的LCD驱动才能使用。 首先从网上下载lvgl的源码,网址分别如下: lvgl源码:https://github.com/lvgl/lvgl/releases/tag/v8.3.0 lv_drivers源码:https://github.com/lvgl/lv_drivers/releases/tag/v8.3.0 lv_port_linux_frame_buffer源码:https://github.com/lvgl/lv_port_linux_frame_buffer/tree/release/v8.2 可能大家都对第一个很熟悉,二三两个不是很熟悉,其中lv_port_linux_frame_buffer 负责在 Linux 系统上实现 LVGL 的帧缓冲设备端口,使得 LVGL 可以在 Linux 中正常渲染图形界面。而lv_drivers 则提供了对外部设备的支持,包括显示器、触摸屏等,这些驱动模块可以让 LVGL 与不同的硬件设备进行通信,从而实现全面的图形界面交互。 下载完了拷贝到我们的开发板上:   将他们解压重命名一下,方便我们辨认:   然后将lv_drivers和lvgl两个文件夹复制到lv_port_linux下面 目录结构如下所示:   因为我们是在开发板上直接编译,所以就省略了交叉编译这一步,不然在电脑上我们好需要配置编译器。 接下来看一下我们开发板的触摸是哪个,配置屏幕的触摸,用evtest命令。我的是event1:   然后在lv_drv_conf.h里面修改如下,改成我们开发板对应的触摸事件接口:  接着修改屏幕的分辨率在main.c里面修改,我的是720*1280如下所示:   最后就是编译啦,make -j4 编译。 编译成功后会有一个demo可执行文件,如下所示:   这样我们就可以运行测试一下啦,效果如下: [localvideo]28305f7a973a7c0d8fa6583b11e54fd5[/localvideo]   总结:通过本期我们测评了开发板LCD的显示相关的功能,通过测评发现LCD显示的色彩清晰度和色彩还原度都不错;与此同时我们还测评了开发板网络和音质,音质播放杂音较少,网络很稳定,能稳定获取到云天气数据。最后我们成功移植了LVGL显示框架,使我们能够在LINUX 开发板上设计精美的UI啦。

  • 2024-06-15
  • 回复了主题帖: 【正点原子i.MX93开发板】1-开箱与开机测评

    chejm 发表于 2024-6-11 11:41 首先祝贺楼主获得的测评机会,希望楼主能分享自己的测试心得 好的,后面会注重心得和技术分享。

  • 2024-06-09
  • 发表了主题帖: 【正点原子i.MX93开发板】1-开箱与开机测评

    本帖最后由 qiao--- 于 2024-6-9 20:35 编辑 前言:         感谢eeworld与正点原子给我这个机会测评i.MX93开发板。i.MX93开发板采用NXP i.MX9352处理器,i.MX935属于中高端嵌入式Linux开发处理器,双核CortexA55@1.7GHz+M33@250MHz+0.5TNPU,支持Linux多任务、RTOS实时控制、轻量级AI应用场景。现在很多多核的芯片都可以异核通信,现在刚好借这个机会来学习一下多核通信。接下来有请我们的主角登场。   1.开箱测评 正点原子的包装依旧采用黑盒子包装,很有高级感,在保证美感的同时保护内部的开发板。   黑盒子主要两层,下面一层装着我们的开发板,上面一层装着板子的相关配件。   板子的相关配件主要以下,俩根数据传输线,一个信号接收天线,一个板子的供电器还有两根细长的杜邦线。如下所示:   作为正点原子的老粉,看到这些东西其实都知道,这些都是正点原子的基本操作。下面来给板子来个特写: 正面:   反面:   接下来来介绍一下板子的相关的资源,这款开发板在具备性能的同时接口也非常丰富,所以想学习Linux驱动开发和应用开发的小伙伴这块板子也是个不错的选择。具体板子的接口介绍如下图:   同时板子的资料也非常丰富,从开发软件,到板子硬件,再到教程文档和资料源码全部都有,可以说就我用过的开发板,正点原子的资料是做的最完整的。资料目录如下所示,光我下载这些资料都花费了好几个小时。           2.开机测评 正点原子的Linux开发板出厂都烧写了出厂系统,一般是他们自己适配的QT程序,但是我这里没有mipi接口的显示屏,就给大家演示不了了。只能能通过命令行的方式给大家演示系统。 用正点原子的电源连接开发板的右上角,同时用数据线连接USB_TTL口,如下所示:   用串口工具查看会有两个串口,其中较小的那个就可以登录到我们的系统终端。   系统的启动界面如下所示。   查看我们的Linux版本为6.1.55版本。   开机测评就只能给大家展示这么多了,接下来就是我们漫长的学习和开发过程。  

  • 2024-06-04
  • 回复了主题帖: 【Luckfox幸狐 RV1106 Linux 开发板】7-基于ffmpeg的视频播放测试

    八月长安ii 发表于 2024-6-4 10:47 编译完FFmpeg出现了一个warning, 然后后面make就失败了, 请问这是什么原因呢? WARNING: /root/pico_sdk/ ... 没有配置sdk,把编译环境配置好

  • 回复了主题帖: 测评入围名单: 正点原子i.MX93开发板

    个人信息无误,确认可以完成测评分享计划

  • 2024-05-26
  • 回复了主题帖: 有没有大佬能帮忙内推实习

    吾妻思萌 发表于 2024-5-23 11:39 别去boss,建议去对应官网找邮箱,自投。可以写写东西,show你的优势。建议应届生去考公务员,选调生,事业 ... 中肯的建议

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

    本帖最后由 qiao--- 于 2024-5-25 14:50 编辑 前言: 经过前面两位贴友铺路,我的模型也是成功的部署。与前面两位贴友不同的是,我是将显示结果显示到屏幕上,所以多了一步驱动适配的过程。 因为我前面已经发布了st7735屏幕的适配过程,我这里就不在赘述了。有兴趣的贴友可以去看看我的这一篇帖子:【Luckfox幸狐 RV1106 Linux 开发板】4-SPI测试__驱动RGB_TFT正常显示 https://bbs.eeworld.com.cn/thread-1271812-1-1.html 然后模型的训练可以去看我之前的两个帖子: 模型训练:#AI挑战营第一站#MNIST手写数字识别模型训练 https://bbs.eeworld.com.cn/thread-1277782-1-1.html 模型转换:#AI挑战营第二站#Ubuntu22上将ONNX模型转换成RKNN模型 https://bbs.eeworld.com.cn/thread-1280156-1-1.html 如果有贴友对烧录过程和使用过程不熟悉的也可去看我之前的测评,这里我来个汇总: 【Luckfox幸狐 RV1106 Linux 开发板】1-开箱及上电测评 【Luckfox幸狐 RV1106 Linux 开发板】2-搭建开发环境和镜像烧录 【Luckfox幸狐 RV1106 Linux 开发板】3-PWM测试__控制舵机任意旋转 【Luckfox幸狐 RV1106 Linux 开发板】4-SPI测试__驱动RGB_TFT正常显示 【Luckfox幸狐 RV1106 Linux 开发板】5-给系统移植QT环境__显示电子时钟界面 【Luckfox幸狐 RV1106 Linux 开发板】6-ADC测试 【Luckfox幸狐 RV1106 Linux 开发板】7-基于ffmpeg的视频播放测试 【Luckfox幸狐 RV1106 Linux 开发板】8-opencv人脸识别测评 本次工程的代码我上传到了我的仓库,有需要可自取:tang/RV1106模型部署_yolo和mnist - 码云 - 开源中国 (gitee.com) 下面开始正题,如何将自己训练的手写模型部署到开发板上呢。 1.熟悉API 参考内容:RKNN 推理测试 | LUCKFOX WIKI 下面是模型部署的API使用流程图,我们可以参考这个来使用官方的API   2.实战操作 定义模型上下文 rknn_app_context_t rknn_app_ctx; memset(&rknn_app_ctx, 0, sizeof(rknn_app_context_t)); init_mnist_model(model_path, &rknn_app_ctx); 其中rknn_app_context_t结构体如下,存储rknn的上下文 typedef struct { 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[3]; rknn_tensor_mem* output_mems[3]; int model_channel; int model_width; int model_height; bool is_quant; } rknn_app_context_t; 因为我们要把结果显示到屏幕上,所以我们还要初始化屏幕,代码如下 //Init fb int fb = open("/dev/fb0", O_RDWR); if(fb == -1) { close(fb); return -1; } size_t screensize = FB_WIDTH * FB_HEIGHT * 2; uint16_t* framebuffer = (uint16_t*)mmap(NULL, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fb, 0); 这里我们使用mmap函数将帧缓冲设备文件/dev/fb0映射到内存中的一段区域,这里我们就可以操作这块内存来进行显示了。 就下来我们要初始化摄像头,和帧数据,其中bgr565用于显示到我们屏幕上的帧数据。 //Init Opencv-mobile cv::VideoCapture cap; cv::Mat bgr(disp_height, disp_width, CV_8UC3); cv::Mat bgr565(disp_height, disp_width, CV_16UC1); cv::Mat bgr640(model_height, model_width, CV_8UC3, rknn_app_ctx.input_mems[0]->virt_addr); cap.set(cv::CAP_PROP_FRAME_WIDTH, disp_width); cap.set(cv::CAP_PROP_FRAME_HEIGHT, disp_height); cap.open(0); 下面为输入图像的操作,我们直接可以用>>进行输入图像,因为opencv-mobile库对这个操作符进行重载了。 start_time = clock(); cap >> bgr; cv::resize(bgr, bgr640, cv::Size(model_width, model_height), 0, 0, cv::INTER_LINEAR); 下面进行对输入的图片帧进行预处理,因为我们需要的灰度图像(训练模型是灰度),下面这部分参考的luyism兄弟的。这里代码写的一次只能对最大的轮廓的数字进行识别,因为返回了最大轮廓的区域。 // 在图像中找到数字的轮廓,同时减小找到轮廓时的抖动 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(); } } // 对图像边框每个方向扩大15个像素 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; } 这里对所选轮廓进行预处理,让这个帧变成28*28*1的图像 // 预处理数字区域 cv::Mat preprocess_digit_region(const cv::Mat region) { // 将图像转换为灰度图像,然后调整大小为28x28,最后将像素值归一化为0到1之间的浮点数 cv::Mat gray, resized, bitwized, normalized; cv::cvtColor(region, gray, cv::COLOR_BGR2GRAY); // 扩大图像中的数字轮廓,使其更容易识别 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); // 对图像黑白进行反转,黑色变成白色,白色变成黑色 cv::bitwise_not(gray, bitwized); // 手动实现黑白反转 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,图片形状不发生畸变,过短的部分使用黑色填充 cv::resize(bitwized, resized, cv::Size(28, 28), 0, 0, cv::INTER_AREA); //定义一个局部静态变量,让其只初始化一次,让其在等于200时将图片保存到本目录下 static int count = 0; if (count == 5) { cv::imwrite("pre.jpg", resized); } count++; printf("count=%d\n", count); return resized; } 紧接着开始对这个区域进行识别 if (digit_rect.area() > 0) { cv::Mat digit_region = bgr(digit_rect); cv::Mat preprocessed = preprocess_digit_region(digit_region); // 运行推理 run_inference(&rknn_app_ctx, preprocessed); // 从predictions_queue中获取预测到的数字和其对应的概率 if (!predictions_queue.empty()) { Prediction prediction = predictions_queue.back(); cv::rectangle(bgr, digit_rect, cv::Scalar(0, 255, 0), 2); // 在图像上显示预测结果,显示字号为1,颜色为红色,粗细为2 cv::putText(bgr, 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(bgr, 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); // 打印预测到的数字和其对应的概率 // printf("****** Predicted digit: %d, Probability: %.2f ******\n", prediction.digit, prediction.probability); // 从predictions_queue中删除最旧的元素 predictions_queue.pop_back(); } } 打上帧率 sprintf(text,"fps=%.1f",fps); cv::putText(bgr,text,cv::Point(0, 20),cv::FONT_HERSHEY_SIMPLEX,0.5, cv::Scalar(0,255,0),1); 最后就是将我们结果帧显示到fb设备映射的内存区域就行了,如下: //LCD Show cv::cvtColor(bgr, bgr565, cv::COLOR_BGR2BGR565); memcpy(framebuffer, bgr565.data, disp_width * disp_height * 2); #if USE_DMA dma_sync_cpu_to_device(framebuffer_fd); #endif //Update Fps end_time = clock(); fps= (float) (CLOCKS_PER_SEC / (end_time - start_time)) ; memset(text,0,16); 在程序结尾记得释放资源,代码如下 deinit_post_process(); #if USE_DMA dma_buf_free(disp_width*disp_height*2, &framebuffer_fd, bgr.data); #endif ret = release_yolov5_model(&rknn_app_ctx); if (ret != 0) { printf("release_yolov5_model fail! ret=%d\n", ret); } 这样我们的主要代码部分就编写完了。 3.编译 工程目录是使用cmake进行管理的。 我们进入cpp目录,创建build目录存放编译结果。然后运行下面步骤进行编译。 cd build export LUCKFOX_SDK_PATH=<Your Luckfox-pico Sdk Path> cmake .. make && make install 最后就可以看到我们可执行和库文件就出现在下面这个目录   我们将上面这个目录打包scp到我们的板子上就OK了   4.运行与运行结果 装上摄像头,系统是有一个默认的rkipc进行占用了摄像头,我们可以运行关闭脚本,也可以暴力杀死这个进程。 我使用的是暴力法,先看看进程的 PID   然后用kill 终止这个进程就OK了   运行输入./luckfox_yolov5_demo_test model/mnist.rknn即可运行,如下   运行效果如下,我照了几张识别图片,然后拍了一个视频效果,如下所示。             视频效果如下所示 [localvideo]747c7ba4680ed44ead001a98911df515[/localvideo]   总结:从上面的效果来看准确率还是可以的,能达到70左右的准确率。代码在:tang/RV1106模型部署_yolo和mnist - 码云 - 开源中国 (gitee.com),其中也包含了我跑的yolo代码。  

  • 回复了主题帖: 有没有大佬能帮忙内推实习

    jinchy 发表于 2024-5-25 10:09 建议去对应官网投简历,找一个自己喜欢的专业工作,很多公司都比较喜欢实习生的。 确实是这样,建议很中肯。boss上必须对方回复了才能投,官网就直接可以投简历了。

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

    本帖最后由 qiao--- 于 2024-5-25 14:48 编辑 完成打卡:#AI挑战营终点站#数字识别模型部署 https://bbs.eeworld.com.cn/thread-1282906-1-1.html

  • 2024-05-22
  • 回复了主题帖: 【Luckfox幸狐 RV1106 Linux 开发板】5-给系统移植QT环境__显示电子时钟界面

    splendor 发表于 2024-5-22 13:56 你好,按照你的配置,我的应用到板子上什么字也不能显示。英文,汉字都不行。 你配置应该是对的,字库没有可用内容,搞一个字体文件到字库目录下就行了

  • 2024-05-21
  • 回复了主题帖: 有没有大佬能帮忙内推实习

    y909334873 发表于 2024-5-20 10:14 你这个项目经验最好着重写一下你在里面负责什么,侧重点在什么上,你简历上描述好多都太笼统,就是这些技能 ... 好的,谢谢建议

最近访客

< 1/6 >

统计信息

已有443人来访过

  • 芯积分:763
  • 好友:4
  • 主题:57
  • 回复:161

留言

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


现在还没有留言