2025寒假练 - 用 CrowPanel ESP32 Display 4.3英寸HMI开发板实现手写识别显示
该项目使用了CrowPanel ESP32 Display 4.3英寸HMI开发板,实现了手写识别显示的设计,它的主要功能为:对手写的数字进行识别,将结果显示在4.3寸的屏幕和一个外接的LED点阵屏上。
标签
嵌入式系统
显示
开发板
Rabbid
更新2025-03-17
25

一、 项目介绍

 

本项目通过CrowPanel ESP32 Display 4.3英寸HMI开发板实现了手写识别显示。该项目在vscodeplatformio平台使用C++进行开发,通过LVGL编程实现ui界面,在LCD屏幕上设定了一个正方形的写字区域,可以在写字区域内书写0-9的数字,并对书写的数字进行识别,将结果显示在4.3寸的屏幕和一个外接的LED点阵屏上。手写数字识别模型使用torch进行训练,数据集采用MNIST手写数字数据集,该模型在仿真的过程中识别准确率可以到达97%左右。esp32上总的资源占用情况为RAM使用36.2%Flash资源使用21.5%

 

二、 硬件介绍

 

本项目的硬件分为两部分,首先是CrowPanel ESP32 Display 4.3英寸HMI开发板,Elecrow ESP32 4.3英寸LCD显示屏是一款功能强大的HMI触摸屏,具有480*272分辨率。它使用ESP32-S3-WROOM-1-N4R2模组作为主控处理器,适用于物联网应用设备等场景。

开发板的实物图和原理图如下

然后是用于数字显示的led点阵屏,此点阵屏8*864颗单色LED灯,同时搭配了两颗串并变换、SOCIC-16封装的74HC595D芯片,并且引出了需要输入信号的引脚。

灯板的实物图和原理图如下

对于灯板和开发板的连接,选择开发板的IO171837分别和灯板的->dSRCLKRCLK引脚进行连接即可。

 

三、 方案框图和设计思路

image.png

上图为方案的框图,从图中可以看出,我们先使用torch训练出手写数字识别的模型,然后将其转换成相同结构的keras模型,并将用torch训练好的权重赋值给keras模型。完成模型转化后,使用Tinymlgen库把keras模型转换成C++能调用的头文件。对于开发板的UI设计使用LVGL库来完成,使用LVGL库的画布控件配合触摸屏函数完成手写数字的输入,并使用按钮控件完成清空和识别功能。识别完成后将结果显示在显示屏上,并用相应的io控制将数字显示在点阵屏上。

 

四、 软件流程图


image.png

上图为软件流程图,从图中可以看出,程序开始运行先进行初始化,包括画布控件的位置、大小、不透明度、画笔的粗细、按钮的大小、功能等。然后程序就进入loop,开始循环处理一些操作。首先是判断用于输入手写字体的画布区域是否被按下,如果被按下了,就要将每个触摸点之间用线段连起来,绘制出需要的线条,否则直接判断清除按钮是否被按下,如果被按下了就清除画布上已经画的内容,同时将点阵屏用于显示的数组清零;否则直接进入下一步,判断预测按钮有没有被按下,如果被按下了,则调用模型进行预测,将预测结果显示在屏幕上,同时更新用于点阵屏显示的数组;否则直接完成一次点阵屏显示。

 

五、关键代码介绍

 

首先是python的训练模型

class MnistModel(nn.Module):
def __init__(self):
super(MnistModel, self).__init__()
self.conv1 = Conv2d(in_channels=1, out_channels=5, kernel_size=5, stride=1, padding=0)
self.maxpool1 = MaxPool2d(2)
self.conv2 = Conv2d(in_channels=5, out_channels=5, kernel_size=5, stride=1, padding=0)
self.maxpool2 = MaxPool2d(4)
self.linear1 = Linear(20, 10)
self.relu = ReLU()

def forward(self, x):
x = self.relu(self.maxpool1(self.conv1(x)))
# conv1_output = x
x = self.relu(self.maxpool2(self.conv2(x)))
# conv2_output = x
x = x.view(x.size(0), -1)
x = self.linear1(x)

return x

这是用torch定义的模型,模型的结构第一层为输入通道为1,输出通道为5,卷积核为5,步长为1,不填充的卷积层,然后经过池化和激活函数,第二层为输入通道为5,输出通道为5,卷积核为5*5,步长为1不填充的卷积层,然后经过池化和激活函数;将得到的数据进行铺平,最后经过一个全连接得到每个分类的预测值,最后再通过排序得到最有可能的预测结果。

然后是esp32的核心代码框图如下:

从图中可以看出,在main文件中定义的setup函数复制完成各部分的初始化,包括串口、引脚、lvgllcd显示等功能的初始化,在loop函数中处理lvgl的定时器处理器,手写数字数据处理与预测,led点阵屏显示的功能。在ui_screen1文件中完成ui界面的控件配置,包括按钮、画布控件的位置大小等配置;在ui文件中完成按钮事件的定义,这部分主要用于将外部变量进行修改,然后交给loop函数进行相关处理。

void WriteByte(unsigned char Byte_arr[])
{
  unsigned char col_num = 0x80;
  for(int j=0;j<8;j++)
  {
    unsigned char Byte = Byte_arr[j];
    for(int i=0;i<8;i++)
    {
      if((col_num&(0x80>>i))!= 0x00 )
      {
        digitalWrite(17,HIGH);
      }else{
        digitalWrite(17,LOW);
      }//向右移位 8个数据排好队依次移动位置
      digitalWrite(18,HIGH);
      digitalWrite(18,LOW);
    }
    col_num = col_num>>1;
    for(int i=0;i<8;i++)
    {
      if((Byte&(0x80>>i))!= 0x00 )
      {
        digitalWrite(17,HIGH);
      }else{
        digitalWrite(17,LOW);
      }//向右移位 8个数据排好队依次移动位置
      digitalWrite(18,HIGH);
      digitalWrite(18,LOW);
    }
    digitalWrite(37,HIGH);
    digitalWrite(37,LOW);
    delay(1);
  }
}

上述代码为led点阵屏的显示驱动代码,主要通过两个串并转换移位寄存器和人眼的视觉暂留效应实现led点阵屏的图像显示

void loop()
{
 
  lv_timer_handler();
   
  // if(led == 1)
  // {  
  //   //printf("LED_ON\r\n");
  // }
  if(led == 0)
  {
    predict_led_num = led_Byte[10];
  }
  if(led == 1 && process_flag == 0)
  {
    lv_img_dsc_t * img_dsc = lv_canvas_get_img(canvas);
        float pic_data[784];
        int pic_pos = 0;
        for (int y = 0; y < 224; y=y+8) {
            // printf("[");
            for (int x = 0; x < 224; x=x+8) {
                int index = y * 224 + x;
                uint8_t byte = img_dsc->data[index / 4];
                uint8_t alpha = (3-(byte >> ((index % 4) * 2)) & 0x03);
                // printf("%d",alpha);
                pic_data[pic_pos] = (alpha/3.0);
                pic_pos++;
                // if(x!=216)
                // {
                //     printf(",");
                // }
            }
            // printf("],");
            // printf("\r\n");
        }
    float predicted[10];
    float mnlss=ml.predict(pic_data,predicted);
    // for(int t=0;t<10;t++)
    // {
    //   printf("%f\r\n",predicted[t]);
    // }    
    // printf("%d\r\n",predicted);
    int max_num = 0;
    float max_value = predicted[0];
    for(int t=1;t<10;t++)
    {
      if(max_value < predicted[t])
      {
        max_num = t;
        max_value = predicted[t];
      }
    }    
    lv_label_set_text_fmt(num_label,"num: %d",max_num);
    predict_led_num = led_Byte[max_num];
    process_flag = 1;
    //printf("LED_OFF\r\n");
  }
    WriteByte(predict_led_num); 
}

上述代码为loop中一直循环的操作,通过按键事件触发修改ledprocess_flag这个两个外部变量,然后在循环中判断变量的值,完成预测和清除的操作。同时在预测操作被按下时,会对手写图片进行下采样,变成28*28,此处的处理操作比较简单,后续可以继续改进。

 

六、功能展示及说明

功能展示如下图所示,在中间黑色的区域内写下数字,按下预测按钮后显示预测的结果,并在led点阵屏上显示


七、遇到的困难和解决方法


在本项目中,遇到的困难主要有两个,首先,如何选择合适的模型结构应用在esp32上,因为esp32的资源有限,不可能去运行网上准确率非常高,参数非常多的模型,因此需要对别人的网络结构进行删减训练,到达合适的程度;第二个困难是如何把python训练好的模型移植到esp32上,这里主要通过pythontinymlgen库进行转化成可以调用的头文件。


八、心得体会


经过这次寒假在家一起练,让我了解到了一些深度学习的知识、esp32lvgl开发,收获很大!

附件下载
code.zip
包含了python训练代码和esp32的代码,esp32代码由于文件大小限制原因,删除了.pio和.vscode文件夹
团队介绍
Rabbid疯狂的兔子
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号