2025寒假练 - 用EPS32实现数字识别
该项目使用了CrowPanel ESP32 Display 4.3英寸HMI开发板,实现了数字识别的设计,它的主要功能为:使用LVGL完成在触摸屏上书写数字完成数字识别。
标签
lvgl
数字识别
2025寒假练
CrowPanel ESP32
你豪吗
更新2025-03-17
121

2025 寒假在家一起练项目

一、项目介绍

本次有幸参与由电子森林精心举办的 2025 寒假在家一起练项目,在平台的选择中,我选择了平台5-CrowPanel ESP32 Display 4.3 英寸 HMI 开发板。这款开发板具备丰富的外设、强大的性能、足够多的内存和资源,并且配备了电阻触摸屏和矩阵LED灯,让我更好的完成本次活动的任务1 - 手写识别显示。

二、硬件介绍

项目采用 CrowPanel ESP32 Display 4.3 英寸 HMI 开发板,其主控为 ESP32-S3-WROOM-1-N4R2。该芯片基于先进的 Tensilica LX7 双核处理器,具备强大的运算能力,工作频率高达 240MHz ,支持 2.4GHz Wi-Fi 和蓝牙 5.0 双模通信,可轻松实现数据的无线传输与交互。

在硬件资源方面,这款开发板引出了丰富的外设。它配备了一块 4.3 英寸的 TFT LCD 触摸屏,分辨率为 480×272 像素,支持触摸操作,能够方便用户进行手写数字输入以及人机交互;同时,可以外接一个8*8的矩阵 LED 灯,可用于状态指示或简单的视觉反馈。开发板还引出了多个通用输入输出引脚(GPIO),方便连接各类传感器和执行器,如温度传感器、湿度传感器等,拓展系统功能。此外,它还具备 SPI、I2C 等通信接口,可与其他设备进行高速数据传输和通信。

三、方案框图和项目设计思路

(一)方案框图

整体系统主要由 CrowPanel ESP32 开发板、电阻触摸屏手写输入、数据处理模块(在开发板内完成)以及结果输出模块(在屏幕和矩阵LED上显示识别结果)构成。手写输入模块获取用户手写数字信息,传递给数据处理模块,处理后结果通过结果输出模块展示。

图片.png

(二)项目设计思路

  1. 对采集到的图像执行预处理操作,包括二值化处理、去除无效区域,并将图像缩放至 30×30 尺寸。
  2. 在完成图像预处理后,进行数字识别,通过计算汉明距离实现数据匹配,从而得出最佳匹配结果。
  3. 显示界面部分,借助定时器持续刷新 LED 矩阵,以实现实时动态展示。

四、软件流程图和关键代码介绍

在软件层面,本项目基于 Arduino 平台进行开发,运用 LVGL、LovyanGFX、Ticker 三个库辅助构建整体工程架构。在 UI 开发过程中,采用 EEZ studio 图形化工具对 LVGL 的 UI 进行配置。

(一)软件流程图

图片.png

(二)关键代码

//矩阵LED相关
#include <Ticker.h>
Ticker timer;

const int dataPin = 18; // DIN
const int clockPin = 17; // SRCLK
const int latchPin = 38; // RCLK

void shiftOutData(byte row, byte data) {
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, data);
shiftOut(dataPin, clockPin, MSBFIRST, row);
digitalWrite(latchPin, HIGH);
}

void onTimer(){
if(canvas_dig == -1)
return;

for(int i = 0; i < 8; i++){
shiftOutData((1<<i), dig_num[canvas_dig*8+i]);
}
}

void setup()
{
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
timer.attach(0.001, onTimer); // 0.1秒 = 100毫秒
}
// 手写板记录图像的三个过程 

// 写字板按下
void action_canvas_pressed(lv_event_t * e) {
lv_indev_t * indev = lv_indev_get_act();
lv_point_t p;
lv_indev_get_point(indev, &p);

lastPoint = p; // 记录按下时的点作为起点
lastPoint.x -= canvas_off_x;
lastPoint.y -= canvas_off_y;
}
// 写字板未松手
void action_canvas_pressing(lv_event_t * e) {
lv_indev_t * indev = lv_indev_get_act();
lv_point_t currentPoint;
lv_indev_get_point(indev, &currentPoint);

currentPoint.x -= canvas_off_x;
currentPoint.y -= canvas_off_y;

//实时更新边界
if(currentPoint.x < min_point.x)
min_point.x = currentPoint.x;

if(currentPoint.y < min_point.y)
min_point.y = currentPoint.y;

if(currentPoint.x > max_point.x)
max_point.x = currentPoint.x;

if(currentPoint.y > max_point.y)
max_point.y = currentPoint.y;

// 创建一个包含两个点的数组
lv_point_t points[2] = {lastPoint, currentPoint};

// Serial.print("Pressing at x: ");
// Serial.print(points[1].x);
// Serial.print(", y: ");
// Serial.println(points[1].y);

// Serial.print("LastPressing at x: ");
// Serial.print(points[0].x);
// Serial.print(", y: ");
// Serial.println(points[0].y);
// Serial.println("");


lv_canvas_draw_line(objects.dig_canvas, points, 2, &line_dsc); //画线
lastPoint = currentPoint; // 更新最后一点的位置
lv_obj_invalidate(objects.dig_canvas); // 刷新
}
// 写字板松手
void action_canvas_released(lv_event_t * e) {
min_point.x -= 5;
min_point.y -= 5;
max_point.x += 5;
max_point.y += 5;

// Serial.print("min_x = ");
// Serial.print(min_point.x);
// Serial.print(" min_y = ");
// Serial.println(min_point.y);

// Serial.print("max_x = ");
// Serial.print(max_point.x);
// Serial.print(" max_y = ");
// Serial.println(max_point.y);
}
// 数字识别和显示的代码 

//计算按键按下
void action_btn_cal_press(lv_event_t * e)
{
//Serial.println("action_btn22_pressed");

// 计算总像素数
size_t pixel_count = canvas_width * canvas_height;

//获取画布的图像描述符
const lv_img_dsc_t *img_dsc = (const lv_img_dsc_t *)lv_canvas_get_img(objects.dig_canvas);
if (!img_dsc) {
printf("无法获取画布图像描述符\n");
return;
}

// 获取图像头信息
lv_img_header_t header;
lv_res_t res = lv_img_decoder_get_info(img_dsc, &header);
if (res != LV_RES_OK) {
printf("无法获取图像头信息\n");
return;
}

const lv_color_t *buf = (const lv_color_t *)img_dsc->data;

// 分配内存用于存储二值化后的图像
uint8_t *binary_image = (uint8_t *)malloc(pixel_count * sizeof(uint8_t));
if (!binary_image) {
printf("内存分配失败\n");
return;
}

// 遍历每个像素进行二值化处理
for (size_t i = 0; i < pixel_count; ++i) {
if (buf[i].full == 0xFFFF) { // 如果是纯白(背景),注意这里是16位颜色格式
binary_image[i] = 0;
} else { // 不是白色即认为是前景(数字部分)
binary_image[i] = 1;
}
}

uint8_t writePic_width = max_point.x - min_point.x;
uint8_t writePic_height = max_point.y - min_point.y;

// 获取手写区域像素
uint8_t *vaildDig_image = (uint8_t *)malloc(writePic_width * writePic_height * sizeof(uint8_t));
for (int y = min_point.y; y < max_point.y; ++y) {
for (int x = min_point.x; x < max_point.x; ++x) {
vaildDig_image[(y-min_point.y) * writePic_width + (x-min_point.x)] = binary_image[y * canvas_height + x];
}
}
// for (int y = 0; y < writePic_height; ++y) {
// for (int x = 0; x < writePic_width; ++x) {
// printf("%d ", vaildDig_image[y * writePic_width + x]);
// }
// printf("\n");
// }
//printf("----------------------\n");

// 转化成30*30的大小
uint8_t *resized_image = (uint8_t *)malloc(TARGET_WIDTH * TARGET_HEIGHT * sizeof(uint8_t));
resize_image(vaildDig_image, resized_image, writePic_width, writePic_height, TARGET_WIDTH, TARGET_HEIGHT);
for (int y = 0; y < TARGET_HEIGHT; ++y) {
for (int x = 0; x < TARGET_WIDTH; ++x) {
printf("%d ", resized_image[y * TARGET_WIDTH + x]);
}
printf("\n");
}

// 识别图像
canvas_dig = findBestMatch(dig_templates, resized_image);
printf("dig = %d\n",canvas_dig);
printf("------------------------------\n");

String strCanvasDig = String(canvas_dig);
char text[10]; // 创建一个足够大的字符数组
strCanvasDig.toCharArray(text, sizeof(text));

lv_label_set_text(objects.lable_res, text);

// 清理资源
free(resized_image);
free(vaildDig_image);
free(binary_image);
//Serial.println("action_btn22_pressed end");
}

(三)EEZ studio界面

图片.png

在左上方可以看到有5个定义的触发事件,在左侧可以看到使用了哪几个控件

五、功能展示图及说明

(一)说明

在 CrowPanel ESP32 开发板的触摸屏指定区域,可通过手指或触摸笔进行手写数字输入。完成输入后,点击识别按键,系统将自动执行识别操作,并在屏幕右侧 “result” 区域显示识别出的数字,同时在矩阵 LED 上同步刷新该数字。例如,当手写数字 “5” 时,系统精准识别并在屏幕特定区域显示 “5”,这一过程表明系统的识别功能运行正常。

(二)功能展示图

图片.png

图片.png


六、项目中遇到的问题和解决方法

  1. 图像预处理效果不佳:写的算法有点问题,在将有效区域提取出来的时候,总是在越界操作,导致数据一直不太正确。操作vaildDig_image 的时候没有将min_point.y 和 min_point.x减去掉

修改后的代码

  // 获取手写区域像素
uint8_t *vaildDig_image = (uint8_t *)malloc(writePic_width * writePic_height * sizeof(uint8_t));
for (int y = min_point.y; y < max_point.y; ++y) {
for (int x = min_point.x; x < max_point.x; ++x) {
vaildDig_image[(y-min_point.y) * writePic_width + (x-min_point.x)] = binary_image[y * canvas_height + x];
}
}
  1. 数字识别:需先将数据导出,随后开展模型训练或模板匹配工作。因为训练数据获取难度较大,并且需要细致的参数调整与优化。所以拿到数据后用哪一种方式都可以。
  2. 在年前完成了 SquareLine Studio 的注册并搭建了基础开发环境,但该软件试用期限仅为一个月。当准备开展任务时,发现软件已过期无法使用。解决措施如下:1. 寻求特殊授权或解决方案(未找到)。2. 筛选其他替代软件,最终选用 EEZ Studio。

七、对本次活动的心得体会

  1. 技术能力提升:通过本次项目,深入学习了手写数字识别的原理和技术,掌握了 CrowPanel ESP32 开发板的使用,以及在 Arduino IDE 环境下进行代码编写和调试
  2. 掌握了几种常用的图像处理方式:二值化、提纯、缩放图片、汉明距离计算、模板匹配
  3. 问题解决能力:在项目中遇到各种难题,通过查阅资料、尝试不同方法,最终成功解决,锻炼了自己独立解决问题的能力。


附件下载
lvgl_project.zip
EEZ Studio
ESP32_project.zip
Arduino
团队介绍
个人玩家
评论
0 / 100
查看更多
猜你喜欢
2025寒假练 - 使用“CrowPanel ESP32 Display 4.3”实现手写数字识别显示该项目使用了CrowPanel ESP32 Display 4.3,实现了手写数字识别显示的设计,它的主要功能为:在LCD屏幕上设定一个正方形的写字区域,在写字区域里书写0-9的数字,esp32运行神经网络算法对数字进行识别。 该项目使用了74HC595驱动的led点阵模块,实现了数字显示的设计,它的主要功能为:使用esp32驱动74HC595芯片,在灯板上显示数字。。
lihuahua
331
2025寒假练 - 使用“CrowPanel ESP32 Display 4.3”实现手写数字识别显示本项目基于ESP32-S3微控制器实现嵌入式手写数字识别系统,通过触摸屏采集用户输入的手写数字图像,利用轻量化神经网络进行实时推理识别。使用LVGL编程在LCD屏幕上设定一个正方形的写字区域在写字区域里书写0-9的数字对书写的数广进行识别,
故海er
423
2025寒假练 - 基于CrowPanel ESP32的手写数字识别显示该项目使用了CrowPanel ESP32 Display 4.3英寸HMI开发板,实现了在触摸屏的设定画布区域手写数字0~9,经模型推理分析得出该数字,并将该数字显示在74HC595点阵屏上的设计,它的主要功能为:通过触摸笔在Elecrow ESP32屏上设定的画布区域内,手写数字0~9,按下“推算”按键,经大模型推算得出与之最相似的数字,并显示在触摸屏上的结果输出标签栏中,然后将该数字推送给8X8的74HC595点阵灯板显示。。
yinwuqing
364
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号