knv

  • 2024-10-15
  • 加入了学习《【Follow me第二季第1期】任务提交-使用makecode开发(JavaScript)》,观看 【Follow me第二季第1期】任务提交-使用makecode开发(JavaScript)

  • 2024-08-12
  • 加入了学习《【Follow me第二季第1期】使用Makecode图形化完成任务》,观看 【Follow me第二季第1期】使用Makecode图形化完成任务

  • 加入了学习《FollowMe 第二季: 1 Adafruit Circuit Playground Express及任务讲解》,观看 Adafruit Circuit Playground Express 及任务讲解

  • 2024-08-06
  • 回复了主题帖: 【Sipeed MAix BiT AIoT 开发套件】4,自动瞄准跟拍的系统

    厉害

  • 2024-08-02
  • 加入了学习《【Follow me第二季第1期】全部任务演示》,观看 全部任务演示2.0

  • 2024-08-01
  • 回复了主题帖: 解锁功能强大且灵活的【Arduino UNO R4 WiFi】,快来报名得捷Follow me第二季第1期!

    板子有点意思

  • 2024-07-30
  • 回复了主题帖: 【Follow me第二季第1期】创意任务二:害怕声音的纸章鱼~

    非常巧妙

  • 2024-07-27
  • 上传了资料: 【Follow me第二季第1期】任务提交

  • 发表了主题帖: 【Follow me第二季第1期】任务提交-使用makecode开发(JavaScript)

    本帖最后由 knv 于 2024-7-27 12:55 编辑 【Follow me第二季第1期】作品提交 大家好,我是knv,很高兴参加这次follow me活动,这次的开发板很有意思,自带10个RGB灯,以及充足的传感器。这次活动我除了必做任务,选做了 创意任务二:章鱼哥——章鱼哥的触角根据环境声音的大小,章鱼哥的触角可舒展或者收缩。搭配器件: Adafruit Circuit Playground Express、舵机。 使用到的器件内容:   设计思路: 主控为Circuit Playground Express 其中选做任务使用了舵机。 主要使用Circuit Playground Express的传感器进行数据采集,然后通过灯光显示。   入门任务(必做):开发环境搭建,板载LED点亮   1:搭建开发环境,由于到手的版本是最新的express版本,所以我选择使用Code.org CSD来制作应用程序。   浏览网站发现还有版本区别,我们收到的应该都是express版本,资源充足,玩micropython都可以。   设备上电后的效果,默认就是七彩灯光,非常好看。   简单介绍一下刷入固件的方法:   首先PC通过USB线链接开发板,等待开机成功后,轻按RESET键并松开, 进入刷机模式,当所有RGB灯变绿后,电脑会弹出一个磁盘,     将固件拖入磁盘即可完成固件升级。     跑第一个工程,点亮所有RGB。只需要拖动一个show ring 手动选择灯光效果,就可以点亮灯光了。 编写代码点亮RGB灯       基础任务一(必做):控制板载炫彩LED,跑马灯点亮和颜色变换     流程图:     let r = 0 input.onGesture(Gesture.Shake, () => {     r = Math.randomRange(0, 4)     if (r == 0) {         light.showAnimation(light.runningLightsAnimation, 2000)     } else if (r == 1) {         light.showAnimation(light.rainbowAnimation, 2000)     } else if (r == 2) {         light.showAnimation(light.cometAnimation, 2000)     } else {         light.showAnimation(light.theaterChaseAnimation, 2000)     } }) 代码内容: 摇一摇触发亮灯,每次随机生成一个数字,然后触发不同的灯效2秒。           基础任务二(必做):监测环境温度和光线,通过板载LED展示舒适程度   任务预期,根据温度和光线亮度来生成舒适指数,使用光线传感器获取环境亮度,使用温度传感器获取温度。当温度舒适时 灯光 绿色,温度过高 灯光红色 温度过低 灯光蓝色,亮度过高 黄色灯光闪烁   代码如下: let currentBrightness = 0   // 持续更新 forever(function () {     // 获取光线和温度     let lightLevel = input.lightLevel()     let temperature = input.temperature(TemperatureUnit.Celsius)       // 根据光线条件决定是否显示黄色     let isYellow = lightLevel > 200     let primaryColor = isYellow ? 0xffff00 : getColorBasedOnTemperature(temperature)     let secondaryColor = isYellow ? getColorBasedOnTemperature(temperature) : 0xffff00       // 执行呼吸灯效果     if (isYellow) {         showBreathingColor(primaryColor, secondaryColor)     } else {         showStaticColor(primaryColor)     } })   // 根据温度条件返回对应的颜色 function getColorBasedOnTemperature(temperature: number): number {     if (temperature > 25) {         return 0xff0000 // 红色     } else if (temperature <= 25 && temperature > 16) {         return 0x00ff00 // 绿色     } else {         return 0x0000ff // 蓝色     } }   // 使用呼吸灯效果显示交替的颜色 function showBreathingColor(primaryColor: number, secondaryColor: number) {     let steps = 10     let stepSize = 255 / steps     let pauseTime = 20       // 从 primaryColor 渐变到 secondaryColor     for (let step = 0; step <= steps; step++) {         let brightness = Math.round(step * stepSize)         let color = interpolateColor(primaryColor, secondaryColor, step / steps)         light.setAll(color)         light.setBrightness(brightness)         pause(pauseTime)     }     // 从 secondaryColor 渐变回 primaryColor     for (let step = steps; step >= 0; step--) {         let brightness = Math.round(step * stepSize)         let color = interpolateColor(primaryColor, secondaryColor, step / steps)         light.setAll(color)         light.setBrightness(brightness)         pause(pauseTime)     } }   // 使用静态颜色显示LED function showStaticColor(color: number) {     light.setAll(color)     light.setBrightness(255) // 设置最大亮度 }   // 插值函数:计算两种颜色之间的渐变颜色 function interpolateColor(color1: number, color2: number, ratio: number): number {     let r1 = (color1 >> 16) & 0xff     let g1 = (color1 >> 8) & 0xff     let b1 = color1 & 0xff     let r2 = (color2 >> 16) & 0xff     let g2 = (color2 >> 8) & 0xff     let b2 = color2 & 0xff       let r = Math.round(r1 + (r2 - r1) * ratio)     let g = Math.round(g1 + (g2 - g1) * ratio)     let b = Math.round(b1 + (b2 - b1) * ratio)       return (r << 16) | (g << 8) | b } 效果图: 当光线大于200时:   当温度大于16,并小于25时(在冰箱测试的,感觉温度传感器并不是很灵敏)       基础任务三(必做):接近检测——设定安全距离并通过板载LED展示,检测到入侵时,发起声音报警 任务目标,当光线检测传感器检测到较黑(亮度传感器<50)后,触发入侵报警。 流程图:   代码如下:   // 持续更新 forever(function () {     // 获取光线值     let lightLevel = input.lightLevel()       if (lightLevel < 50) {         // 当光线小于50时,闪烁红灯并发出声音报警         flashRedLight()         soundAlarm()     } else {         // 光线大于或等于50时,关闭所有LED灯         light.clear()     } })   // 使用闪烁的红色灯光显示LED function flashRedLight() {     let flashCount = 5 // 闪烁次数     let flashDuration = 200 // 每次闪烁的时间,单位:毫秒       for (let i = 0; i < flashCount; i++) {         light.setAll(0xff0000) // 红色         pause(flashDuration)         light.clear()         pause(flashDuration)     } }   // 发起声音报警 function soundAlarm() {     // 通过扬声器发出声音     music.baDing.play()     pause(200) // 每200毫秒发出一次声音 }   遮挡时:   无遮挡时:     进阶任务(必做):制作不倒翁——展示不倒翁运动过程中的不同灯光效果   任务目标,将设备贴到不倒翁上,当X Y加速度增加,则计算灯光速度,加速度越快,灯光速度越快。   // 持续更新 forever(function () {     // 获取 X 和 Y 轴的加速度值     let accelX = input.acceleration(Dimension.X)     let accelY = input.acceleration(Dimension.Y)       // 计算平面上的综合加速度     let strength2D = Math.sqrt(Math.pow(accelX, 2) + Math.pow(accelY, 2))       // 计算跑马灯的速度     let normalizedStrength = Math.min(strength2D / 1023, 1) // 标准化到 [0, 1] 范围     let speed = 100 - (90 * normalizedStrength) // 速度范围从 10 毫秒到 100 毫秒       // 执行七彩跑马灯效果     runColorMarquee(speed) })   // 运行七彩跑马灯效果 function runColorMarquee(speed: number) {     const colors = [0xff0000, 0xffa500, 0xffff00, 0x008000, 0x0000ff, 0x4b0082, 0xee82ee] // 红、橙、黄、绿、蓝、靛、紫     const numLEDs = 10       // 清除所有 LED     light.clear()       // 循环显示 LED     for (let i = 0; i < numLEDs; i++) {         // 先关闭所有 LED         light.clear()           // 点亮当前 LED 和下一个 LED         light.setPixelColor(i, colors[i % colors.length])         light.setPixelColor((i + 1) % numLEDs, colors[(i + 1) % colors.length])                pause(speed) // 等待时间,根据速度调整     } } 当晃动时,跑马灯速度更快:       ■  (选做)创意任务二:章鱼哥——章鱼哥的触角根据环境声音的大小,章鱼哥的触角可舒展或者收缩   搭配器件: Adafruit Circuit Playground Express、舵机   舵机链接: 橙色  链接-->   A1 棕色  链接-->  GND 红色   链接-->   VOUT     任务目标,使用设备的麦克风,获取音量,音量越大,舵机转动越快。同时启动线程,读取音量,使开发板上的RGB灯光以跑马灯形式点亮。代码开发完成后,用A4纸剪出章鱼触角的样子,贴到舵机上,完成收缩或者舒展功能。 流程图:   代码如下: let servo = servos.A1; let lastLevel = 0;   // 设置音量的阈值 let threshold = 120; // 小于此音量时,停止伺服电机   // 伺服电机控制 forever(function () {     let level = input.soundLevel();     if (lastLevel != level) {         if (level < threshold) {             servo.run(0);         } else {             // 将音量映射到伺服电机速度             let speed = Math.map(level, threshold, 255, 0, 100);             // 确保映射后的值在有效范围内             speed = Math.max(0, Math.min(speed, 100));               // 随机决定正转或反转             let direction = Math.randomRange(0, 1) === 0 ? 1 : -1;             let finalSpeed = speed * direction;               console.log(finalSpeed);             servo.run(finalSpeed);             pause(100)         }         lastLevel = level;     } });   // 跑马灯效果 forever(function () {     let level = input.soundLevel();     if (level >= threshold) {         // 计算跑马灯的速度         let lightSpeed = Math.map(level, threshold, 255, 500, 50); // 数字越大速度越慢         runLights(lightSpeed);     } else {         clearLights();     } });   function runLights(delay: number) {     const colors = [         0xff0000, // Red         0xffa500, // Orange         0xffff00, // Yellow         0x008000, // Green         0x0000ff, // Blue         0x4b0082, // Indigo         0xee82ee  // Violet     ];     for (let i = 0; i < 10; i++) {         let level = input.soundLevel();         if (level < threshold) {             clearLights();             return;         }           for (let j = 0; j < 10; j++) {             light.setPixelColor((i + j) % 10, colors[j % colors.length]); // 设置当前灯光为多彩颜色         }         pause(delay); // 根据音量调整速度     } }   function clearLights() {     light.clear(); // 清除所有灯光 } 运行图片:       任务汇总视频: 链接:【Follow me第二季第1期】任务提交-使用makecode开发(JavaScript)-【Follow me第二季第1期】任务提交-使用makecode开发(JavaScript)-EEWORLD大学堂   源码下载: download.eeworld.com.cn/detail/knv/633848   心得体会: 本次Follow me 第二季第1期任务活动的目标是利用 Adafruit Circuit Playground Express 开发一些灯光效果的功能,该项目不仅增强了我对传感器数据的处理能力,还培养了我们在硬件编程中的综合应用能力。学习了如何使用 Circuit Playground Express 的 LED 环实现多彩的视觉效果,并结合传感器数据动态控制这些效果。感谢得捷电子,电子工程世界提供的活动。

  • 加入了学习《【得捷电子Follow me第2期】从显示中文到电子天气日历》,观看 【得捷电子Follow me第2期】从显示中文到电子天气日历

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

    本帖最后由 knv 于 2024-7-8 17:47 编辑 个人信息已确认,请安排邮寄。感谢luckfox和eeworld的活动

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

    本帖最后由 knv 于 2024-5-24 17:55 编辑 完成打卡:) 大家有问题可以群里讨论或者帖子留言 https://bbs.eeworld.com.cn/thread-1282455-1-1.html

  • 回复了主题帖: #AI挑战营终点站# rv1106数字识别模型部署与性能优化

    大佬牛皮

  • 2024-05-23
  • 回复了主题帖: #AI挑战营终点站#将模型部署到RV1106中通过摄像头采集进行手写数字识别

    aramy 发表于 2024-5-22 16:40 大佬:求助!按您这个流程跑下来。提示:E RKNN: failed to decode config data!   求指导!谢 ... 将编译后生成的 luckfox_rtsp_opencv_demo 目录下面的 lib也复制到开发板运行目录中,再尝试运行

  • 2024-05-21
  • 发表了主题帖: #AI挑战营终点站#将模型部署到RV1106中通过摄像头采集进行手写数字识别

    本帖最后由 knv 于 2024-5-24 10:16 编辑 感谢eeworld提供的开发板,,东西还挺精致的。检查一下直接插上USB。 开发板都自带的 SPI NAND FLASH,并在出厂时搭载了测试系统。然而,用户在收到板子后需要自行烧录网盘系统,以确保正常使用 PWM、I2C、UART 等功能。 1.下载和解压烧录工具(戳我下载)。 https://files.luckfox.com/wiki/Luckfox-Pico/Software/SocToolKit.zip 安装驱动 https://files.luckfox.com/wiki/Luckfox-Pico/Software/DriverAssitant_v5.12.zip 2.选择rv1106 3.按住boot键链接usb,设备管理器增加下面的设备 4:开始刷机 建议使用buildroot系统 因为https://forums.luckfox.com/viewtopic.php?t=22 刷机完毕后,重新链接开发板,电脑会多一个rndis网口,配置电脑 rndis网口的ip地址 为172.32.0.100  子网掩码 255.255.0.0 此时可以通过ssh 或者telnet 访问 172.32.0.93 登录账号:root 登录密码:luckfox 静态IP地址:172.32.0.93 测试摄像头 LuckFox Pico Pro/Max 开发板连接摄像头时,请确保摄像头排线的金属面朝向开发板芯片。 查看摄像头,若成功识别摄像头会生成 rkipc.ini 文件。 ``` ls /userdata/ ethaddr.txt lost+found image.bmp video0 video2 rkipc . ini video1 ``` 此时可以通过vlc 拉流看到摄像头内容 默认的 IP 地址:rtsp://172.32.0.93/live/0 接下来配置开发环境 参考 https://wiki.luckfox.com/zh/Luckfox-Pico/Luckfox-Pico-SDK 此处不再赘述 我这种小白肯定是不会使用太复杂的代码,所以直接照抄历程 在ubuntu中新建一个文件夹 运行 ``` git clone https://github.com/luckfox-eng29/luckfox_pico_rtsp_opencv export LUCKFOX_SDK_PATH= mkdir build cd build cmake .. make && make install ``` 将编译生成的luckfox_rtsp_opencv_demo/luckfox_rtsp_opencv 和 lib目录都上传到 luckfox-pico 上,进入文件夹运行 运行目录下应有 luckfox_rtsp_opencv lib model.rknn 三个文件 ``` RkLunch-stop.sh ./luckfox_rtsp_opencv ``` 使用 vlc 打开网络串流 rtsp://172.32.0.93/live/0(按实际情况修改IP地址拉取图像) 注意:运行前请关闭系统默认 rkipc 程序,执行 RkLunch-stop.sh 关闭 在 luckfox-pico / mini / plus 上尝试降低推流分辩率保存程序运行。 如何降低分辨率 设置main方法的width height  即可 配置rknpu库引用 在根目录的 CMakeLists.txt 加入这行 首先我的思路就是将摄像头采集到的帧,直接进行模型分析。 直接开始编写代码 ,因为 rv1106不支持get,set, 所以需要使用        rknn_query及其后续函数进行模型推理。 ~~此处大坑~~ 参考项目:https://github.com/MontaukLaw/rv1106_night_vision ``` int run_inference(cv::Mat &frame) {         int ret = 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.index = i;                 // query info                 ret = rknn_query(ctx, RKNN_QUERY_INPUT_ATTR, &(input_attrs), sizeof(rknn_tensor_attr));                 if (ret < 0)                 {                         printf("rknn_init error! ret=%d\n", ret);                         return -1;                 }                 dump_tensor_attr(&input_attrs);         }         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.index = i;                 // query info                 ret = rknn_query(ctx, RKNN_QUERY_NATIVE_OUTPUT_ATTR, &(output_attrs), sizeof(rknn_tensor_attr));                 if (ret != RKNN_SUCC)                 {                         printf("rknn_query fail! ret=%d\n", ret);                         return -1;                 }                 dump_tensor_attr(&output_attrs);         }         cv::Mat gray;         cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY);         cv::resize(gray, gray, cv::Size(28, 28), 0, 0, cv::INTER_LINEAR);         // printf("Gray image size: %dx%d\n", gray.rows, gray.cols);         // printf("Gray image type: %d\n", gray.type());         // 为resize后的图形申请内存         int mem_size = MODEL_WIDTH * MODEL_HEIGHT * CHANNEL_NUM;         unsigned char *resize_buf = (unsigned char *)malloc(mem_size);         memset(resize_buf, 0, mem_size);         // Create input tensor memory         rknn_tensor_mem *input_mems[1];         // default input type is int8 (normalize and quantize need compute in outside)         // if set uint8, will fuse normalize and quantize to npu         input_attrs[0].type = input_type;         // default fmt is NHWC, npu only support NHWC in zero copy mode         input_attrs[0].fmt = input_layout;         input_mems[0] = rknn_create_mem(ctx, input_attrs[0].size_with_stride);         // Copy input data to input tensor memory         int width = input_attrs[0].dims[2];         int stride = input_attrs[0].w_stride;         if (width == stride)         {                 memcpy(input_mems[0]->virt_addr, gray.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];                 // copy from src to dst with stride                 uint8_t *src_ptr = gray.data;                 uint8_t *dst_ptr = (uint8_t *)input_mems[0]->virt_addr;                 // width-channel elements                 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;                 }         }         // Create output tensor memory         rknn_tensor_mem *output_mems[io_num.n_output];         for (uint32_t i = 0; i < io_num.n_output; ++i)         {                 output_mems = rknn_create_mem(ctx, output_attrs.size_with_stride);         }         // Set input tensor memory         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;         }         // Set output tensor memory         for (uint32_t i = 0; i < io_num.n_output; ++i)         {                 // set output memory and attribute                 ret = rknn_set_io_mem(ctx, output_mems, &output_attrs);                 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;         }         printf("output origin tensors:\n");         rknn_tensor_attr orig_output_attrs[io_num.n_output];         memset(orig_output_attrs, 0, io_num.n_output * sizeof(rknn_tensor_attr));         for (uint32_t i = 0; i < io_num.n_output; i++)         {                 orig_output_attrs.index = i;                 // query info                 ret = rknn_query(ctx, RKNN_QUERY_OUTPUT_ATTR, &(orig_output_attrs), sizeof(rknn_tensor_attr));                 if (ret != RKNN_SUCC)                 {                         printf("rknn_query fail! ret=%d\n", ret);                         return -1;                 }                 dump_tensor_attr(&orig_output_attrs);         }         // 创建存储模型输出的NCHW格式数据的向量         std::vector output_mems_nchw;         for (uint32_t i = 0; i < io_num.n_output; ++i)         {                 int size = orig_output_attrs.size_with_stride;                 int8_t *output_mem = new int8_t[size];                 output_mems_nchw.push_back(output_mem);         }         // 进行NC1HWC2_int8_to_NCHW_int8转换         // for (uint32_t i = 0; i < io_num.n_output; i++) {         //     int channel = orig_output_attrs.dims[1];         //     int h = orig_output_attrs.n_dims > 2 ? orig_output_attrs.dims[2] : 1;         //     int w = orig_output_attrs.n_dims > 3 ? orig_output_attrs.dims[3] : 1;         //     int hw = h * w;         //     NC1HWC2_int8_to_NCHW_int8((int8_t*)output_mems->virt_addr, (int8_t*)output_mems_nchw,         //                               (int*)output_attrs.dims, channel, h, w);         // }         // 获取预测的数字         int predicted_digit = get_predicted_digit(output_mems_nchw);         // 释放内存         for (uint32_t i = 0; i < io_num.n_output; ++i)         {                 delete[] output_mems_nchw;         }         rknn_destroy_mem(ctx, input_mems[0]);         for (uint32_t i = 0; i < io_num.n_output; ++i)         {                 rknn_destroy_mem(ctx, output_mems);         }         return predicted_digit; } ``` 将编译生成的luckfox_rtsp_opencv_demo/luckfox_rtsp_opencv 和 lib目录都上传到 luckfox-pico 上,进入文件夹运行 运行目录下应有 luckfox_rtsp_opencv lib model.rknn 三个文件 ``` # chmod 777 luckfox rtsp opencv # ./luckfox rtsp opencv model.rknn ``` 运行效果 # 调优: 此时我们已经可以通过摄像头进行图片获取,并且识别了。 但是识别准确率极低,因为有太多的干扰项目了。我们调整一下代码,增加一个裁剪视频帧的功能, 下面是两个裁剪函数。 ``` 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, 50, 150);         std::vector 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 &a, const std::vector &b)                                                                                         {                                                                                                 return cv::contourArea(a) < cv::contourArea(b);                                                                                         });         return cv::boundingRect(*largest_contour); } cv::Mat preprocess_digit_region(const cv::Mat ®ion) {         cv::Mat gray, resized, normalized;         cv::cvtColor(region, gray, cv::COLOR_BGR2GRAY);         cv::resize(gray, resized, cv::Size(28, 28), 0, 0, cv::INTER_LINEAR);         resized.convertTo(normalized, CV_32F, 1.0 / 255.0);         return normalized; } ``` 接下来调整调用逻辑 ,有文本裁剪数据时才使用模型推理。 ```         cv::Mat frame(height, width, CV_8UC3, data);                         // 复制一个帧,然后使用模型推理,获取结果,传递到resdata中。                         cv::Rect digit_rect = find_digit_contour(frame);                         if (digit_rect.area() > 0)                         {                                 cv::Mat digit_region = frame(digit_rect);                                 cv::Mat preprocessed = preprocess_digit_region(digit_region);                                 int prediction = run_inference(preprocessed);                                 // 在图像上显示预测结果                                 cv::rectangle(frame, digit_rect, cv::Scalar(0, 255, 0), 2);                                 cv::putText(frame, std::to_string(prediction), cv::Point(digit_rect.x, digit_rect.y - 10),                                                         cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 2);                         } ``` [localvideo]87f05bc08ca9bbb4de2aa68d31b91b64[/localvideo] # 最后结果: 识别率还是很差,识别不是很稳定,该模型也没什么区别,等待大佬来带了。 ## 二次调整及优化 感谢luyism [luyism](https://gitee.com/luyism/luckfox_rtsp_mnist "luyism") 大佬做出的开源优化 通过阅读代码,了解到了之前的代码中的不足, 1:预处理问题 2:后处理问题 通过修正这部分代码,来重新编译程序,完成手写数字的识别。 增加预处理部分 ``` // 在图像中找到数字的轮廓,同时减小找到轮廓时的抖动 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 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& a, const std::vector& 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(bounding_box.width) / bounding_box.height;         if (aspect_ratio < 0.2 || aspect_ratio > 3) {                 return cv::Rect();         }         // **轮廓稳定性检测**:         // 通过比较当前帧和之前几帧的轮廓位置来判断轮廓的稳定性。         // 如果多帧之间的轮廓位置变化较小,则可以认为轮廓比较稳定,不需要进行过多的调整。         static std::vector 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; } // 预处理数字区域 cv::Mat preprocess_digit_region(const cv::Mat ®ion) {         // 将图像转换为灰度图像,然后调整大小为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(i, j) = 255 - bitwized.at(i, j);                 }         }         // 将图片大小调整为28x28,图片形状不发生畸变,过短的部分使用黑色填充         cv::resize(bitwized, resized, cv::Size(28, 28), 0, 0, cv::INTER_AREA);         return resized; } ``` 增加将量化的INT8数据转换为浮点数 ``` // 将量化的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) { 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 = deqnt_affine_to_f32(output,zp,scale);         // 计算输出数据的L2范数     float sum = 0;     for(int i = 0; i < 10; i++)         sum += out_fp32 * out_fp32;             // 对归一化后的浮点数输出进行归一化处理,确保输出数据的范围在[0,1]之间         float norm = sqrt(sum);     for(int i = 0; i < 10; i++)         out_fp32 /= norm;                 // 打印输出数据的值         printf("\n===================Output data values:===================\n");         for (int i = 0; i < 10; ++i)         {                 printf("%f ", out_fp32);         }         printf("\n");         // 找出最大概率对应的数字,并记录最大概率及其对应的数字         float max_prob = -1.0;         int predicted_digit = -1;         // 计算最大值的索引         for (int i = 0; i < 10; ++i)         {                 if (out_fp32 > max_prob)                 {                         max_prob = out_fp32;                         predicted_digit = i;                 }         }         // 将预测的数字及其对应的概率记录到队列中         predictions_queue.push_back({predicted_digit, max_prob});         // 打印预测的数字与其对应的概率         printf("========Predicted digit: %d, Probability: %.2f========\n\n", predicted_digit, max_prob); } ``` # 最终演示: 视频演示: 可以看到这个版本比上个版本稳定很多。 [localvideo]668efdd254b7a0c7f8735129b1c0a64c[/localvideo] 识别率测试: 将识别率获取增加到代码中,下面是测试结果,可以看到识别率大约60%左右。 # 代码提交 代码放到了github仓库中,有兴趣的朋友看看 https://github.com/knva/luckfox_pico_rtsp_opencv 单独识别图片的 https://github.com/knva/luckfox_pico_rknn_test  

  • 2024-05-08
  • 回复了主题帖: #AI挑战营第一站# PC基于PyTorch的MNIST模型训练过程与模型转换

    本帖最后由 knv 于 2024-5-8 18:10 编辑 补充准确率: 验证准确率 99.16%         新版本代码: import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader class OptimizedConvNet(nn.Module): def __init__(self,num_classes=10): super(OptimizedConvNet, 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 # 定义超参数 learning_rate = 0.001 batch_size = 64 num_epochs = 10 # 检查是否有可用的GPU device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') # 创建模型,并将模型移动到GPU上 model = OptimizedConvNet(num_classes=10).to(device) # 定义损失函数和优化器 criterion = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate) # 加载MNIST数据集 transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))]) train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform) train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True) # 加载验证集 valid_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform) valid_loader = DataLoader(valid_dataset, batch_size=64, shuffle=False) # 初始化最优验证损失为无穷大 best_valid_loss = float('inf') import matplotlib.pyplot as plt # 初始化准确率列表 train_accuracies = [] valid_accuracies = [] for epoch in range(num_epochs): # 在训练集上训练 model.train() total_train_loss = 0 correct_train_preds = 0 total_train_preds = 0 for i, (images, labels) in enumerate(train_loader): images = images.to(device) labels = labels.to(device) outputs = model(images) loss = criterion(outputs, labels) _, predicted = torch.max(outputs.data, 1) total_train_preds += labels.size(0) correct_train_preds += (predicted == labels).sum().item() optimizer.zero_grad() loss.backward() optimizer.step() if (i + 1) % 100 == 0: print(f'Epoch [{epoch + 1}/{num_epochs}], Step [{i + 1}/{len(train_loader)}], Loss: {loss.item()}') total_train_loss += loss.item() average_train_loss = total_train_loss / len(train_loader) train_accuracy = correct_train_preds / total_train_preds train_accuracies.append(train_accuracy) print(f'Epoch: {epoch + 1}, Training Loss: {average_train_loss:.4f}, Training Accuracy: {train_accuracy:.4f}') # 在验证集上验证 model.eval() valid_loss = 0.0 correct_valid_preds = 0 total_valid_preds = 0 with torch.no_grad(): for images, labels in valid_loader: images = images.to(device) labels = labels.to(device) outputs = model(images) loss = criterion(outputs, labels) _, predicted = torch.max(outputs.data, 1) total_valid_preds += labels.size(0) correct_valid_preds += (predicted == labels).sum().item() valid_loss += loss.item() valid_loss /= len(valid_loader) valid_accuracy = correct_valid_preds / total_valid_preds valid_accuracies.append(valid_accuracy) print(f'Epoch: {epoch+1}, Validation Loss: {valid_loss:.4f}, Validation Accuracy: {valid_accuracy:.4f}') # 如果验证损失有所下降,则保存模型 if valid_loss < best_valid_loss: best_valid_loss = valid_loss torch.save(model.state_dict(), 'best_model.pth') print('Model saved.') # 绘制训练和验证准确率 plt.plot(range(1, num_epochs + 1), train_accuracies, label='Train') plt.plot(range(1, num_epochs + 1), valid_accuracies, label='Valid') plt.title('Model Accuracy') plt.xlabel('Epochs') plt.ylabel('Accuracy') plt.legend() plt.show()  

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

    本帖最后由 knv 于 2024-5-9 09:12 编辑 个人信息已确认,领取板卡,可继续完成&分享挑战营第二站和第三站任务。   训练结果已补充到原帖回复中。

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

    xianhangCheng 发表于 2024-5-8 11:31 发现有一个这个错误, AssertionError: target_platform set as ['RV1106'] is not support 跟我的步骤走的肯定没问题,手动创建一下data.txt 内容是一张测试图片的路径 我的是28*28像素的png 比如: 6.png  

  • 2024-04-28
  • 回复了主题帖: (已颁奖)嵌入式工程师AI挑战营(初阶):基于RV1106,动手部署手写数字识别落地

    和和123 发表于 2024-4-26 11:32 什么时候公布获取幸狐RV1106开发板名单、开发板派发? 五月十号

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

      RKNN模型是RK公司设计的一套模型格式,用于在rknpu上加速运行 ONNX模型是通用的开放模型格式,可以用于直接使用或模型转换。在专用npu上可能无法加速运行 #AI挑战营第二站# 基于RV1106芯片的RKNN环境部署及模型转换过程 https://bbs.eeworld.com.cn/thread-1280038-1-1.html

统计信息

已有71人来访过

  • 芯积分:261
  • 好友:--
  • 主题:9
  • 回复:20

留言

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


现在还没有留言