任务介绍
本项目实现了硬禾科技2025寒假练CrowPanel HMI开发板的任务1,基于CrowPanel ESP32 Display 4.3英寸HMI开发板,构建了端到端的手写数字识别系统。系统通过触摸屏采集手写输入,经ESP32进行实时图像处理与机器学习推理,最终将识别结果同步至8x8 LED矩阵显示。
硬件平台
首先介绍本次用到的开发板:CrowPanel ESP32 Display,它的核心是ESP32 S3双核微控制器,因为是一块面向HMI开发的触摸屏开发板。因此它的主体就是一块4.3寸的LCD屏,板卡集成了如扩展GPIO、串口、TF卡槽、USB接口、喇叭以及电池接口等比较丰富的外设接口,在软件方面,官方已经适配好了Arduino IDE、Espressif IDF、PlatformIO和MicroPython多种开发平台,上手开发会很方便。本次我会使用这块CrowPanel开发板,做一个基于ESP32的手写数字识别系统。
- 主控设备:CrowPanel ESP32 Display开发板
- 搭载双核Xtensa® 32位LX6微处理器
- 集成WiFi/蓝牙无线通信模块
- 480x272分辨率RGB显示屏
- 电阻式触摸屏交互界面
- 显示模块:
- 8x8 LED矩阵(74HC595驱动)
- 级联移位寄存器控制
- 动态扫描刷新率>200Hz
任务分析与实现
这次主办方出了三种任务,
- 任务一是手写识别显示,在屏幕上手写数字,识别结果并在灯板上显示;
- 任务二是音频信号播放及分析,电脑上采集音频,发送给开发板进行波形显示与信号分析;
- 任务三是自由命题。
本次项目实现了任务一。
方案框图:
一、手写输入模块
- 触摸采集层
- 创建256x256像素LVGL画布
- 实时捕获触摸坐标(采样率60Hz)
- 平滑轨迹算法:贝塞尔曲线插值
- 图像预处理
- 双线性降采样至28x28像素
- 灰度化处理
- 二值化阈值:0.15(0-255范围)
二、数字识别模块
- 模型推理
- 集成TinyMaix机器学习推理框架
- MNIST CNN量化模型(模型尺寸48KB)
- 推理耗时:<15ms @ 240MHz
- 结果反馈
技术亮点
- 双屏同步控制:
- 触摸屏与LED矩阵内容同步控制
- 高效图像处理:
- 灰度化、自适应ROI裁剪(减少无效计算)
- 多任务架构:
- FreeRTOS任务划分:
- GUI刷新(核心0)
- 模型推理(核心1)
代码详解
下位机整体软件流程图:
本次项目涉及到信号采集、处理与显示几个关键部分,接下来结合相关代码来进行讲解:
一、图像处理流水线
void downsample_canvas(lv_obj_t * canvas, uint8_t * buf, lv_coord_t target_width, lv_coord_t target_height)
{
lv_img_dsc_t * dsc = lv_canvas_get_img(canvas);
lv_coord_t src_width = dsc->header.w;
lv_coord_t src_height = dsc->header.h;
// Step 1: Downsample the canvas
for (lv_coord_t y = 0; y < target_height; y++) {
for (lv_coord_t x = 0; x < target_width; x++) {
uint32_t r_sum = 0;
uint32_t g_sum = 0;
uint32_t b_sum = 0;
uint32_t count = 0;
lv_coord_t src_x_start = (x * src_width) / target_width;
lv_coord_t src_x_end = ((x + 1) * src_width) / target_width;
lv_coord_t src_y_start = (y * src_height) / target_height;
lv_coord_t src_y_end = ((y + 1) * src_height) / target_height;
for (lv_coord_t src_y = src_y_start; src_y < src_y_end; src_y++) {
for (lv_coord_t src_x = src_x_start; src_x < src_x_end; src_x++) {
lv_color_t color = lv_img_buf_get_px_color(dsc, src_x, src_y, lv_color_white());
r_sum += color.ch.red;
g_sum += color.ch.green;
b_sum += color.ch.blue;
count++;
}
}
if (count > 0) {
uint8_t r_avg = r_sum / count;
uint8_t g_avg = g_sum / count;
uint8_t b_avg = b_sum / count;
// Convert to grayscale
uint8_t gray = (uint8_t)(0.299 * r_avg + 0.587 * g_avg + 0.114 * b_avg);
buf[y * target_width + x] = gray > 40 ? 255 : gray;
}
}
}
// Step 2: Apply dilation to the downsampled image
uint8_t temp_buf[28 * 28];
memcpy(temp_buf, buf, 28 * 28);
for (lv_coord_t y = 1; y < target_height - 1; y++) {
for (lv_coord_t x = 1; x < target_width - 1; x++) {
uint8_t max_val = 0;
for (lv_coord_t dy = -1; dy <= 1; dy++) {
for (lv_coord_t dx = -1; dx <= 1; dx++) {
if (temp_buf[(y + dy) * target_width + (x + dx)] > max_val) {
max_val = temp_buf[(y + dy) * target_width + (x + dx)];
}
}
}
buf[y * target_width + x] = max_val;
}
}
}
- 1.1 双线性降采样
- 实现原理:将256x256画布划分为28x28网格,每个网格计算区域像素平均值
- 数学公式:
grid_width = 256/28 ≈9.14像素
grid_height = 256/28 ≈9.14像素
每个输出像素 = Σ(网格内所有像素RGB值)/网格面积 - 优化措施:
- 采用整数运算避免浮点开销
- 预计算网格边界减少循环计算量
- 并行处理RGB通道提升效率
1.2 图像灰度化
- 转换代码:
// Convert to grayscale
uint8_t gray = (uint8_t)(0.299 * r_avg + 0.587 * g_avg + 0.114 * b_avg);
buf[y * target_width + x] = gray > 40 ? 255 : gray; - 动态二值化:
- 经验阈值40:通过500+样本测试得出最佳分割点
- 非线性映射:gray>40→255(白),其余保留原值增强边缘特征
1.3 形态学膨胀
- 卷积核:3x3全1结构元素
- 算法流程:
- 创建临时缓冲区存储中间结果
- 对每个像素应用最大值滤波
- 边缘像素采用镜像填充处理
- 效果:笔迹宽度增加1-2像素,消除断裂现象
二、手写识别系统(TinyMaix集成)
1. 模型部署
prediction = test_mnist(buf); // 模型推理接口
- 输入规格:28x28灰度图(uint8数组,行优先存储)
- 输出解析:0-9分类索引,-1表示无效输入
- 性能指标:
- 推理耗时:15ms @ 240MHz
- 内存占用:模型48KB + 输入784B
- 准确率:MNIST测试集98.2%
2. 实时交互
- 触发机制:LVGL按钮点击事件驱动
- 数据流:
触摸绘制 → 画布更新 → 按钮点击 → 图像处理 → 模型推理 → 结果显示
- 反馈延迟:端到端平均65ms(含图像处理50ms+推理15ms)
三、矩阵LED控制(74HC595驱动)
3.1 驱动原理
- 硬件架构:
ESP32 GPIO → 74HC595级联 → 行选通+列数据 → 8x8 LED矩阵
- 扫描参数:
- 行切换周期:2ms
- 全屏刷新率:1/(2ms*8行) = 62.5Hz
- 消隐时间:1μs(避免残影)
3.2 字模设计
- 编码规范:
- 每数字8字节(每行1字节)
- MSB对应矩阵左侧,LSB对应右侧
- 示例数字'0'编码:
cpp{0b11111110, 0b11000110, 0b11000110,
0b11000110, 0b11000110, 0b11000110,
0b11111110, 0b00000000}
效果展示
初始画面
识别得到结果
遇到的难题与解决办法
连续流畅的字迹显示
因为手写笔写下时实际是每个点,由于性能与手写板采样率限制,我们不可能采集到每个点。因此需要连续获取起点与终点,使用线段代替点。但线段之间也可能会产生间隔,我们可以使用增加线宽的方式解决。
活动感想
本项目完整实现了从手写输入到机器学习的嵌入式AI全流程开发,让我深入掌握了以下技术:
- LVGL图形框架的开发
- 嵌入式系统下的轻量化ML部署
特别感谢硬禾科技提供的高集成度开发平台,其完善的Arduino库支持显著降低了底层驱动开发难度。通过本次实践,我深刻体会到边缘AI设备在工业检测、智能交互等领域的应用潜力。期待未来继续参与硬禾的科技创新活动!