资讯动态

基于 ESP-DL 的触摸板手写数字识别:从数据采集、PyTorch 训练到 ESP32-S3 端侧量化部署

发布时间:2026/9/20 1:36:47 来源:尧图企业网站定制
物联网嵌入式驱动开发硬件开发【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址https://gitcode.com/GitHub_Trending/es/esp-iot-solution点击查看免费下载本指南以 esp-iot-solution 仓库中的 touchpad_digit_recognition 示例 为核心完整讲解如何在真实电容触摸板上实现 0~9 手写数字识别从触摸通道的坐标检测与图像重建到基于 PyTorch 的神经网络训练再到使用 ESP-PPQ 完成 INT8 量化并部署到 ESP32 平台的端侧推理全流程。读完本文你将掌握触摸屏数据的归一化与插值放大算法、模型量化导出的关键参数配置以及 ESP-DL 推理引擎的设备端集成方法可直接复用到其他触摸 AI 应用。示例整体架构该示例使用 ESP-Touch-Kit Touchpad 实物触摸板6×7 触摸通道阵列作为输入设备配合 ESP32-S3 主控、GPIO0 按键用于触发归一化校准以及 CH455 驱动的数码管用于显示识别结果。触摸板(6×7 通道) → 电容变化检测 → 归一化/插值放大(30×25 图像) → ESP-DL 模型推理 → 数码管显示预测数字整个处理链路分为两大流程触摸处理流程手指在触摸板上移动会改变触摸通道的电容值通过检测电容变化确定手指位置再经过软件插值算法将原始 6×7 的触摸数据扩展为 30×25 的像素图像用于模型训练与推理。AI 处理流程基于 PyTorch 构建卷积神经网络使用真实触摸板采集的数据集训练分类模型保存权重后用 ESP-PPQ 量化为 INT8 模型最后通过 ESP-DL 推理引擎在设备端运行。触摸数据采集与图像重建触摸检测原理触摸板由 6×7 阵列的触摸通道构成行 7 通道、列 6 通道。由于硬件差异每个通道的电容触发最大值与最小值各不相同因此必须先对原始电容值做归一化处理——记录手指划过触摸板时的最大值与最小值作为校准基准。示例中触摸底层使用 13 个触摸通道行、列通道的映射关系定义在 main/touch_digit.cpp 中#define CHANNEL_NUM 13 #define CHANNEL_LIST {1,2,3,4,5,6,7,8,9,10,11,12,13} #define ROW_CHANNEL_INDEX {4,6,8,7,10,9,12} #define COL_CHANNEL_INDEX {2,1,3,5,13,11}通道数据归一化每个通道的归一化逻辑封装在 main/include/touch_channel.h 的TouchChannel类中核心公式为normalized_data double((data - min) * 1.0f / (max - min));归一化结果被限制在 [0, 1] 区间超过 1.0 截断为 1.0低于 0.0 截断为 0.0。update_max_min()用于在校准阶段动态跟踪每个通道的最大、最小值reset()用于清空校准数据重新开始。归一化校准与持久化示例通过 GPIO0 按键配置见 main/main.cpp切换归一化校准状态按下开始校准调用touch_dight_begin_normalize()重置所有通道的 max/min 记录此后手指划过的所有采样数据都会更新各通道极值再次按下结束校准调用touch_dight_end_normalize()打印各通道的 max/min 值并通过set_normalization_data()将校准结果以 blob 形式写入 NVS。持久化实现位于 main/normalization_save.cpp使用nvs_set_blob(normalization, data, ...)保存整个touch_digit_data_t结构。设备重启后touch_digit_init()会先调用get_normalization_data()从 NVS 恢复校准数据避免重复校准。若 NVS 中不存在记录则自动写入一组空默认值并告警提示。坐标检测与插值放大touch_digit_detect()main/touch_digit.cpp实现了手指坐标检测算法分为三步寻找最大通道分别对行、列通道归一化后找出变化率最大的通道若最大值低于阈值 0.15 则判定为无触摸确定相邻通道在最大通道两侧选择归一化值更大的相邻通道边界通道直接取唯一邻居比例加权定位使用加权重心公式计算通道间的偏移量得到亚通道精度的坐标static double compute_position(int position_a, int position_b, double value_a, double value_b) { if (value_a value_b 0) { return 0; } return (position_a * value_a position_b * value_b) / (value_a value_b); }结合PRECISION 5定义于 main/include/touch_digit.h最终坐标x max_row[0] * 5 round(error * 4)将 6×7 的离散通道数据插值放大为(7-1)×5 30列、(6-1)×5 25行的像素图像即模型输入尺寸 30×25。图像数据由 main/include/touch_image.h 的TouchImage类管理按下写入像素值1print()可用*号可视化手写轨迹。书写状态机touch_digit_task()通过一个三态状态机完成一笔书写的采集WAIT_WRITE等待首次触摸if_touch true进入书写态BEGIN_WRITE持续将触摸坐标写入TouchImage当检测不到触摸时idle_cnt累加连续空闲超过IDEL_CNT_MAX90 次采样判定笔划结束END_WRITE将整幅 30×25 图像经队列xImageQueue投递到推理任务清空图像后回到等待态。触摸数据的采集由底层touch_sensor_lowlevel驱动完成13 通道配置见touch_digit_init()中断回调state_cb将 13 个通道的原始数据经队列送入处理任务。模型训练PyTorch 卷积网络为什么不用 MNIST触摸板上手写出的数字与 MNIST 数据集中的手写数字在视觉形态上差异很大直接使用 MNIST 训练的模型在真实触摸输入上表现不佳。因此示例在真实触摸板上分别采集了 0~9 十类数字作为训练集即上图所示的数据并按类别存入dataset/extra的子文件夹中ImageFolder 格式形成自定义数据集。网络结构基于 PyTorch 构建的网络结构如下输入尺寸 1×25×30输出 10 类对应数字 0~9class Net(torch.nn.Module): def __init__(self): super(Net, self).__init__() self.model torch.nn.Sequential( torch.nn.Conv2d(in_channels1, out_channels16, kernel_size3, stride1, padding1), torch.nn.ReLU(), torch.nn.MaxPool2d(kernel_size2, stride2), torch.nn.Conv2d(in_channels16, out_channels32, kernel_size3, stride1, padding1), torch.nn.ReLU(), torch.nn.MaxPool2d(kernel_size2, stride2), torch.nn.Conv2d(in_channels32, out_channels64, kernel_size3, stride1, padding1), torch.nn.ReLU(), torch.nn.Flatten(), torch.nn.Linear(in_features7 * 6 * 64, out_features256), torch.nn.ReLU(), torch.nn.Dropout(p0.5), torch.nn.Linear(in_features256, out_features10), torch.nn.Softmax(dim1) ) def forward(self, x): output self.model(x) return output网络由三层卷积16/32/64 通道3×3 卷积核、padding1与两个 2×2 最大池化层提取特征接全连接层256 维与 Dropout(0.5) 防止过拟合最终经 Softmax 输出 10 类概率分布。数据加载与增强数据集加载与增强流程如下注意增强项RandomAffine(degrees10, translate(0.1, 0.1))对笔迹做 ±10° 随机旋转与 ±10% 随机平移增强模型对书写变形的鲁棒性transform transforms.Compose([ transforms.Grayscale(num_output_channels1), transforms.RandomAffine(degrees10, translate(0.1, 0.1)), transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,)), ]) dataset datasets.ImageFolder(root./dataset/extra, transformtransform) train_size int(0.8 * len(dataset)) test_size len(dataset) - train_size train_dataset, test_dataset random_split(dataset, [train_size, test_size]) train_loader DataLoader(datasettrain_dataset, batch_size32, shuffleTrue) test_loader DataLoader(datasettest_dataset, batch_size32, shuffleFalse)数据集按 8:2 比例随机划分为训练集与测试集batch_size32训练集打乱顺序、测试集不打乱。训练参数与训练循环训练使用交叉熵损失函数与 Adam 优化器学习率 0.001共训练 100 个 epoch每个 epoch 结束后用测试集评估一次最终将模型权重保存为./models/final_model.pthdevice cuda:0 if torch.cuda.is_available() else cpu model Net().to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr0.001) num_epochs 100 train_acc_array [] test_acc_array [] for epoch in range(num_epochs): train_loss, train_acc train_epoch(model, train_loader, criterion, optimizer, device) test_loss, test_acc test_epoch(model, test_loader, criterion, device) print(fEpoch [{epoch 1}/{num_epochs}], fTrain Loss: {train_loss:.4f}, Train Acc: {train_acc:.2f}%, fTest Loss: {test_loss:.4f}, Test Acc: {test_acc:.2f}%) train_acc_array.append(train_acc) test_acc_array.append(test_acc) torch.save(model.state_dict(), ./models/final_model.pth)训练与测试集的准确率随 epoch 的变化曲线如下模型量化与部署ESP-PPQ安装 ESP-PPQESP-PPQ 是基于 PPQ 的量化工具。安装前需先卸载原有ppq再安装 Espressif 官方提供的esp-ppq完整安装命令见 ESP-PPQ 官方安装说明pip uninstall ppq pip install esp-ppq量化导出脚本训练完成后将权重文件作为 ESP-PPQ 量化与部署工具的输入。量化脚本的核心参数如下BATCH_SIZE 32 INPUT_SHAPE [1, 25, 30] TARGET esp32s3 NUM_OF_BITS 8 ESPDL_MODEL_PATH ./s3/touch_recognition.espdl quant_ppq_graph espdl_quantize_torch( modelmodel, espdl_export_fileESPDL_MODEL_PATH, calib_dataloadertestDataLoader, calib_steps8, input_shape[1] INPUT_SHAPE, inputs[input_tensor], targetTARGET, num_of_bitsNUM_OF_BITS, deviceDEVICE, error_reportTrue, skip_exportFalse, export_test_valuesTrue, verbose1, dispatching_overrideNone )关键参数说明TARGET目标芯片示例默认esp32s3如需导出 ESP32-P4 模型将其改为esp32p4仓库 models/esp32p4/touch_recognition.espdl 与 models/esp32s3/touch_recognition.espdl 均已内置量化好的模型文件NUM_OF_BITS量化位宽示例使用 8 位 INT8 量化calib_dataloader/calib_steps校准数据集与校准步数8 步export_test_valuesTrue在量化时附带测试数据便于在 PC 端验证推理结果。PC 端结果验证为了确认模型输出符合预期需要按推理流程在 PC 端预处理数据并解析推理结果。ESP-PPQ 会将测试数据的推理结果保存到*.info文件中例如test outputs value: %23, shape: [1, 10], exponents: [0], value: array([9.85415445e-34, 1.92874989e-22, 7.46892081e-43, 1.60381094e-28, 3.22134028e-27, 1.05306175e-20, 4.07960022e-41, 1.42516404e-21, 2.38026637e-26, 1.00000000e00, 0.00000000e00, 0.00000000e00], dtypefloat32)输出形状为[1, 10]对应 10 个类别的概率分布最大值所在的索引即为预测数字。量化与部署过程中请务必保证DataLoader的shuffle参数为False否则校准数据顺序变化会影响量化结果的可复现性。模型打包与分区量化导出的.espdl单模型文件可直接使用也可以借助 pack_model.py 将多个模型打包为一个二进制支持 EDL1/EDL2 两种格式EDL2 会对齐到 16 字节边界。打包格式为PDL1/PDL2魔数 模型数量 每个模型的 data_offset/name_offset/name_length 索引表 模型名 对齐填充 模型数据。模型存放在独立分区中分区表 partitions.csv 定义如下nvs, data, nvs, 0x9000, 0x6000, factory, app, factory, 0x010000, 2000K, model, data, spiffs, , 800K,其中model分区SPIFFS800KB用于存放模型文件应用固件位于factory分区2MB。设备端推理实现模型加载与内存策略推理封装在 main/touch_digit_recognition.cpp 的TouchDigitRecognition类中m_model new dl::Model(model_name, fbs::MODEL_LOCATION_IN_FLASH_PARTITION); m_model-minimize();模型从 Flash 的 model 分区加载调用minimize()缩减内存占用量化输入缓冲区使用heap_caps_calloc(..., MALLOC_CAP_SPIRAM)分配到 PSRAM避免挤占片内 SRAM。启动日志中的MemoryManagerGreedy: Maximum memory size: 15632即内存规划结果。数据预处理与推理触摸驱动上报的按压/未按压状态为 1/0而模型输入是归一化图像因此需要将 0/1 二值图像量化为模型输入所需的 INT8 格式与训练时的Normalize((0.5,), (0.5,))语义对应void DataPreprocessor::process(const uint8_t* input_data, int8_t* quant_buffer) { for (size_t i 0; i m_feature_size; i) { int8_t value (input_data[i] 0 ? -1 : 1); quant_buffer[i] dl::quantizeint8_t((float)value, m_input_scale); } }其中m_input_scale在构造时根据模型输入张量的 exponent 计算1.0f / DL_SCALE(input_tensor-exponent)。推理时构造输入 Tensor形状{1, 750}即 30×25750 个特征调用m_model-run(inputs)。后处理与结果显示后处理对输出做 argmax 求概率最大的类别索引float *output_ptr (float *)m_model_output-data; auto max_iter std::max_element(output_ptr, output_ptr size); int max_index std::distance(output_ptr, max_iter); ESP_LOGI(TAG, Predict result: %d, max_index);推理任务touch_digit_recognition_task从xImageQueue接收图像调用predict()后通过digital_tube_write_num()将识别数字显示在数码管上。构建与运行硬件要求ESP32-S3 开发板带 PSRAM示例配置为 2MB PSRAMESP-Touch-Kit Touchpad 触摸板CH455 数码管I2C 接口示例初始化于I2C_NUM_0SDAGPIO37、SCLGPIO38一个接 GPIO0 的按键用于归一化校准。注意示例基于 IDF v5.5触摸驱动使用driver/touch_sensor.h底层接口运行日志会提示 legacy touch driver 已废弃但示例通过CONFIG_TOUCH_SUPPRESS_DEPRECATE_WARNy抑制告警。构建配置main/CMakeLists.txt 依赖esp-dl、iot_button、esp_lcd_touch等组件见 main/idf_component.yml数码管组件位于示例自带的 components/digital_tube 目录。CMakeLists.txt 定义工程名touchpad_digit_recognition。ESP32-S3 构建配置 sdkconfig.defaults.esp32s3 关键项包括CONFIG_IDF_TARGETesp32s3 CONFIG_SPIRAMy CONFIG_SPIRAM_MODE_QUADy CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240y CONFIG_ESP32S3_INSTRUCTION_CACHE_32KBy CONFIG_ESP32S3_DATA_CACHE_64KBy CONFIG_PARTITION_TABLE_CUSTOMy CONFIG_PARTITION_TABLE_CUSTOM_FILENAMEpartitions.csv构建命令在示例目录下idf.py set-target esp32s3 idf.py build idf.py -p PORT flash monitor运行效果烧录并复位后串口输出示例摘自 README.md 中的运行日志I (710) button: IoT Button Version: 4.0.0 max:19678 min:8823 max:20068 min:8889 ... I (735) touch_lowlevel: Touch sensor lowlevel (v0.6.0) configured with 13 channels I (744) touch_lowlevel: Touch sensor lowlevel started I (749) FbsLoader: The storage free size is 29760 KB I (754) FbsLoader: The partition size is 800 KB I (759) dl::Model: model:main_graph, version:0 I (762) dl::Model: /model/model.0/Conv: Conv I (768) dl::Model: /model/model.2/MaxPool: MaxPool I (771) dl::Model: /model/model.3/Conv: Conv I (776) dl::Model: /model/model.5/MaxPool: MaxPool I (779) dl::Model: /model/model.6/Conv: Conv I (786) dl::Model: PPQ_Operation_0: Transpose I (787) dl::Model: /model/model.8/Flatten: Flatten I (792) dl::Model: /model/model.9/Gemm: Gemm I (994) dl::Model: /model/model.12/Gemm: Gemm I (995) dl::Model: /model/model.13/Softmax: Softmax I (998) MemoryManagerGreedy: Maximum memory size: 15632 I (1003) main_task: Returned from app_main() * * * * * * * * * * * * * ... I (6524) TouchDigitRecognition: Predict result: 2日志清晰展示了四个阶段触摸通道初始化13 通道→ 各通道校准 max/min 值 → Flash 中模型加载与算子图打印Conv/MaxPool/Gemm/Softmax→ 手写轨迹的*号可视化输出与最终识别结果Predict result: 2。小结该示例提供了一条完整的真实触摸数据 → 自定义训练 → INT8 量化 → 设备端推理落地路径其可复用的核心资产包括触摸坐标检测算法13 通道的归一化、重心插值与 6×7→30×25 图像重建main/touch_digit.cpp校准参数持久化基于 NVS 的 max/min 存储实现开机免校准main/normalization_save.cpp模型量化导出ESP-PPQ 的espdl_quantize_torch参数配置与 PC 端结果验证流程设备端推理封装从 Flash 分区加载模型、PSRAM 缓冲、INT8 预处理与 argmax 后处理的完整实现main/touch_digit_recognition.cpp。配套的完整教程含训练/测试代码与准确率曲线见 docs/en/ai/touch_digit_recognition.rst 与 docs/zh_CN/ai/touch_digit_recognition.rst示例中使用的触摸数据集下载入口也在其中标注。赞分享物联网嵌入式驱动开发硬件开发【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址https://gitcode.com/GitHub_Trending/es/esp-iot-solution点击查看免费下载相关推荐基于 ESP-DL 的触摸板手写数字识别从触摸感知、模型训练到 ESP32 设备端推理基于 ESP DL 的触摸板手写数字识别从触摸感知、模型训练到 ESP32 设备端推理 本篇技术指南以 esp iot solution 仓库中的 Touch物联网嵌入式驱动开发硬件开发ESP32 Touch 手写数字识别实战基于 esp-iot-solution 与 ESP-DL 的触控采集、模型训练到端侧部署全流程ESP32 Touch 手写数字识别实战基于 esp iot solution 与 ESP DL 的触控采集、模型训练到端侧部署全流程 导读 本文以 esp物联网嵌入式驱动开发硬件开发Caffe 实战在 MNIST 上训练 LeNet 手写数字识别模型从数据准备到训练部署全流程Caffe 实战在 MNIST 上训练 LeNet 手写数字识别模型从数据准备到训练部署全流程 本文以 Caffe 官方 MNIST 示例为线索完整讲解深度学习计算机视觉创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价