

app digit recognize /NAND:/2.pgm



上位机的python训练,测试代码,怎么没有生成相应的模型文件?是如何与嵌入式端转换的,要有这些。
准确率也不够高,特别是手写的时候,页面会滑动。
数据集准备

Python下终端训练命令
D:\Master\1tools\python3.9\python.exe train_mnist_mlp.py --data-dir "D:\Master\6project\ATS3089C_to_ATS3089P\application\bt_watch\tools\mnist_digit\data\MNIST\raw" --epochs 8 --batch-size 128 --learning-rate 0.08 --output-dir "D:\Master\6project\ATS3089C_to_ATS3089P\application\bt_watch\tools\mnist_digit\generated"
产生模型文件:复制到\application\bt_watch\src\main\mnist_digit_model_data.c 替换即可





识别准确率95.32%,可以增加训练轮次,或者增加隐藏层个数以改进
ATS3089 开发板手写数字识别
本文说明 ATS3089P 固件中的手写数字识别功能。识别在开发板的 Cortex-M33 CPU 上完成,不需要电脑、手机或网络。当前功能只识别单个数字 **0~9**,属于数字分类器,不能直接当作通用文字 OCR 使用。
1. 算法是什么
当前算法是一个面向嵌入式的全连接多层感知器(MLP),不是 CNN,也没有调用 TFLite Micro(TFLM)运行库或 DSP 神经网络接口。模型结构如下:
```text
输入:28 × 28 = 784 个 8 位灰度像素
│
全连接层:784 → 48,权重为 int8
│
ReLU:max(0, x)
│
全连接输出层:48 → 10,分别对应数字 0~9
│
取最大 logit 作为识别结果
```
MLP 也是通过数据训练的神经网络。当前网络没有卷积层和池化层;它把图片展平成 784 个输入,再经过两层全连接计算。模型参数编译进 `mnist_digit_model_data.c/.h`,约占 38 KiB Flash。权重为 int8、乘加累积为 int32,偏置、缩放和隐藏层数值仍使用 float,因此这是混合精度推理。运行流程如下:
-
第一层使用 784 个像素与 int8 权重做 int32 累加,再乘量化比例并加偏置。
-
经过 ReLU 后得到 48 个隐藏层数值。
-
第二层把隐藏层数值按固定比例转成整数,与 int8 权重相乘累加,得到 10 个输出分数(logit)。
-
分数最大的下标就是数字类别;不计算 softmax,因此输出分数不是概率。
现有 C 代码注释记载历史 MNIST 测试结果为 93.76%;这不是本次新增 Python 脚本复测的结果。重新训练的准确率应以脚本实际输出和开发板测试为准。当前补齐的训练脚本与固件模型格式兼容,不保证重新训练得到与现有权重逐字节相同的文件。开发板实际书写字体、笔画粗细、触摸采样方式与 MNIST 可能不同,应使用实板数据验证识别率。
2. 输入预处理
无论输入来自 U 盘图片还是触摸屏,最后都会统一成"黑底白字、28×28、8 位灰度"的输入,再送入 MLP。
图片输入
`mnist_digit_preprocess()` 的处理流程如下:
-
读取 PGM 或 BMP 解码得到的 8 位灰度像素。
-
统计图像四周边框的平均灰度,判断背景是黑色还是白色。
-
根据背景自动判断笔画极性:黑底亮字会保留亮笔画,白底黑字会自动反色。
-
用背景均值附近的阈值找出前景笔画,计算笔画包围框。
-
包围框四周留约 1 像素边界,保持宽高比,把最长边缩放到约 20 像素。
-
将缩放后的数字居中放到 28×28 黑色背景中,未覆盖位置填 0。
图片建议只包含一个数字,背景干净,数字位于图像中央。过细、过小、贴边、带阴影或包含多个数字的图片都可能降低结果稳定性。
触摸输入
触摸页面的黑色画布当前为 `DIGIT_CANVAS_W × DIGIT_CANVAS_H`,源码默认是 **224×224 LVGL 像素**,适合手指书写。触摸坐标先映射到 28×28 模型坐标:
-
每个采样点使用约 3×3 像素笔刷;
-
相邻采样点用直线补点,避免快速划动时出现断线;
-
画布显示时把每个模型像素放大约 8×8,模型实际仍只接收 28×28;
-
页面最多保存 512 个触摸点,超过后请先点击"清除"。
不需要专用电子笔。请使用手指或电容触摸笔在黑色框内书写;普通圆珠笔、金属笔尖等不会被触摸屏识别,也可能划伤屏幕。手指按下黑框后连续移动即可看到白色笔迹,松开后可以继续写下一笔。
3. 页面操作
串口或 USB CDC Shell 均可使用以下命令:
```text
app digit status
app digit show
app digit clear
app digit recognize
app digit recognize /NAND:/2.pgm
app digit recognize /NAND:/2.bmp
app digit hide
```
-
`app digit show`:打开手写页面。页面包含黑色书写框、"清除""识别""返回"三个按钮。
-
`app digit clear`:清除当前笔迹和上一次识别结果。
-
`app digit recognize`:识别当前触摸笔迹;没有有效笔迹时返回错误。
-
`app digit recognize <路径>`:读取图片并立即识别,例如 `/NAND:/2.pgm`。
-
`app digit status`:打印当前图片、结果、置信度、识别次数、触摸点数量和最近错误码。
-
`app digit hide`:关闭手写页面并回到表盘。
页面上的"返回"按钮等价于 `app digit hide`。显示手写页面期间,表盘视图会暂停刷新;点击"返回"或执行 `app digit hide` 后恢复表盘运行。这是页面切换行为,不代表系统休眠。
页面没有笔迹时的检查
-
用手指直接按在黑色框内部,不要按在边框或按钮上。
-
发送 `app digit clear` 后重新按下并移动手指。
-
确认已经烧录包含手写页面修复的最新固件。
-
新版在第一次按下画布时打印 `digit: touch start x=... y=... canvas=224x224`。出现该日志表示触摸已进入画布回调;用 `app digit status` 可以查看采样点数 `points`。
-
如果页面完全没有显示,保存串口中的 `digit: push handwriting view ret=`、`digit: layout failed`、`digit: view unavailable` 或 `digit: view request timed out` 日志。
此次触摸修复在 `digit_view.c` 中显式为画布设置 `LV_OBJ_FLAG_CLICKABLE`。LVGL 的 canvas 继承 image,image 构造时默认清除此标志;仅添加触摸回调并不能让原画布接收按下事件。
4. 识别结果与置信度
成功日志示例:
```text
digit: result=2 confidence=47 source=/NAND:/2.pgm
digit: image=1 result=1 digit=2 confidence=47 runs=1 points=0 last=0
```
字段含义:
-
`digit=2`:识别出的数字是 2;
-
`result=1`:当前已有有效识别结果;
-
`last=0`:本次操作返回码为 0,执行成功;
-
`source`:输入来源,图片路径或 `touch`;
-
`points`:触摸点数量,图片识别时为 0;
-
`runs`:累计识别次数;
-
`confidence`:第一名 logit 与第二名 logit 的相对间隔,乘以 20 后限制在 0~100,仅用于界面提示,**不是 47% 的概率,也不是识别准确率**。置信度较低时应重新书写,或使用更清晰、居中的图片。
5. U 盘图片识别
设备进入 USB MSC/U 盘模式时,文件系统会被电脑占用,设备不能同时读取图片。推荐按下面的顺序操作:
```text
app udisk on
在 Windows 中把 2.pgm 或 2.bmp 复制到 U 盘根目录
在 Windows 中安全弹出 U 盘
app udisk off
app digit recognize /NAND:/2.pgm
app digit status
```
实际卷名以当前固件为准,可能是 `/SD:/`、`/USB:/`、`/NAND:/` 或 `/NOR:/`。路径必须使用绝对卷名和 ASCII 文件名,不能包含 `..`。U 盘仍挂载时识别通常返回 `-EBUSY`。
支持的 PGM
支持二进制 P5 和 ASCII P2,宽高不超过 128×128,文件不超过 64 KiB。推荐使用 28×28 的 P5:
```text
P5
28 28
255
<784 字节的 8 位灰度数据>
```
支持的 BMP
只支持无压缩的 24 位 BI_RGB BGR BMP:
-
文件头必须是 `BM`;
-
24 位、单帧、无压缩;
-
宽和高不超过 128;
-
支持标准正向 BMP 和负高度倒置 BMP。
PNG、JPEG、压缩 BMP、32 位 BMP、多帧图片均不支持。`-134` 表示当前图片格式不受支持,通常是图片不是上述格式或扩展名与实际格式不一致;`-27` 表示文件超过当前大小限制。
6. 源码位置
| 文件 | 作用 |
| --- | --- |
| `src/main/mnist_digit.h` | 识别 API、28×28 输入和触摸点结构 |
| `src/main/mnist_digit.c` | 图片预处理、触摸栅格化、int8 MLP 推理 |
| `src/main/mnist_digit_model_data.c/.h` | 784→48→10 的 int8 权重和量化参数 |
| `src/main/mnist_digit_service.c/.h` | PGM/BMP 解析、文件读取、识别状态和 Shell 服务 |
| `src/launcher/digit_view.c/.h` | 触摸画布、笔迹显示、清除/识别/返回按钮和页面注册 |
| `src/main/system_app_shell.c` | `app digit` 命令实现 |
| `tests/mnist_digit/test_core.c` | 预处理、模型推理和触摸栅格化回归测试 |
| `tools/mnist_digit/train_mnist_mlp.py` | NumPy 完整训练、量化、评估、C/H 导出源码 |
| `tools/mnist_digit/requirements.txt` | 训练工具依赖 |
| `tests/mnist_digit/test_training.py` | 训练工具的输入预处理、IDX 读取、学习和导出验证 |
查看推理实现时,重点阅读 `src/main/mnist_digit.c` 的三个函数:
-
`mnist_digit_preprocess()`:背景判断、前景裁剪、等比缩放和居中;
-
`mnist_digit_rasterize_touch()`:触摸点映射为 28×28 笔迹;
-
`mnist_digit_predict()`:第一层全连接、ReLU、第二层全连接、取最大分数及计算置信度。
`mnist_digit_model_data.c` 只保存权重、偏置和量化比例,实际推理循环在 `mnist_digit_predict()` 中。图片命令通过 `mnist_digit_service_recognize_file()` 进入推理;触摸界面通过 `mnist_digit_service_recognize_touch()` 进入推理。
新增或修改的关键 C 代码使用 `//zk+` 和 `//zk+-` 标记;Python 使用 `#zk+` 和 `#zk+-`。关键函数和计算步骤配有中文注释,包括输入输出、张量尺寸、归一化、反向传播梯度和量化比例,便于对照本文阅读。
7. 构建与后续升级
在 SDK 环境中执行:
```text
cd /zephyr
python build.py -n -b ats3089p_dev_watch -a bt_watch
```
`APP_MNIST_DIGIT` 默认在 `ats3089p_dev_watch` 中开启。当前实现选择轻量 MLP,是为了避免引入完整 TFLM C++ 运行库和较大的 tensor arena。若要提高真实触摸字体的识别率,可以采集开发板上的触摸样本,离线重新训练并量化 784→48→10 参数,然后替换 `mnist_digit_model_data.c/.h`;输入预处理和 UI 接口可以保持不变。
8. 重新训练和导出模型
工程提供了纯 NumPy 训练脚本:
```text
/application/bt_watch/tools/mnist_digit/train_mnist_mlp.py
```
脚本训练的结构固定为 `784→48 ReLU→10`。这是本次补充的可重新训练实现;原固件使用的 C 推理与已编译权重仍在上表文件中。电脑上需要 Python 3.8 及以上和 NumPy,训练数据集需要另外准备。以下命令均从工程的 `` 目录执行:
```text
python -m pip install -r .\application\bt_watch\tools\mnist_digit\requirements.txt
```
8.1 使用 MNIST IDX 文件
把下面四个文件放在同一个目录(可以是原始文件,也可以是 `.gz` 压缩文件):
```text
train-images-idx3-ubyte
train-labels-idx1-ubyte
t10k-images-idx3-ubyte
t10k-labels-idx1-ubyte
```
以下命令在工程的 `` 目录中执行;例如先切换到 `D:\Master\6project\ATS3089C_to_ATS3089P\`。
然后在 Windows PowerShell 中执行:
```powershell
python .\application\bt_watch\tools\mnist_digit\train_mnist_mlp.py `
--data-dir D:\dataset\MNIST `
--epochs 8 --batch-size 128 --learning-rate 0.08 `
--output-dir .\application\bt_watch\tools\mnist_digit\generated
```
也可以单独指定 IDX 文件:
```powershell
python .\application\bt_watch\tools\mnist_digit\train_mnist_mlp.py `
--train-images D:\dataset\train-images-idx3-ubyte `
--train-labels D:\dataset\train-labels-idx1-ubyte `
--test-images D:\dataset\t10k-images-idx3-ubyte `
--test-labels D:\dataset\t10k-labels-idx1-ubyte `
--output-dir .\application\bt_watch\tools\mnist_digit\generated
```
8.2 使用 NPZ 文件
NPZ 至少要包含 `x_train` 和 `y_train`,测试集可以额外包含 `x_test` 和 `y_test`。图像数组支持 `N×28×28` 或 `N×784`,像素支持 `uint8 0~255` 和 `float 0~1`:
```powershell
python .\application\bt_watch\tools\mnist_digit\train_mnist_mlp.py `
--npz D:\dataset\mnist.npz `
--epochs 8 --output-dir .\application\bt_watch\tools\mnist_digit\generated
```
原始 MNIST 已经是黑底白字,脚本默认不强制反色。默认训练和测试输入都会复现 C 端 `mnist_digit_preprocess()`:边界背景均值、±32 阈值、前景裁剪、1 像素边界、最长边约 20 像素的等比例最近邻缩放,然后居中到 28×28。预处理会自动适配白底黑字和黑底白字,通常不需要添加 `--invert`。
若自己的 NPZ 保存的是板端触摸最终栅格化后的 28×28 黑底白字输入,添加 `--preprocessed` 跳过上述图片裁剪缩放:
```powershell
python .\application\bt_watch\tools\mnist_digit\train_mnist_mlp.py `
--npz D:\dataset\touch_digits.npz --preprocessed
```
训练过程中会打印每轮损失和浮点模型准确率;有测试集时还会打印量化后准确率。`--max-train 10000 --max-test 1000` 可先用小数据验证脚本和导出流程,确认无误后再训练全部数据。
8.3 导出的量化格式
脚本默认生成到工具的 `generated` 目录:
```text
tools/mnist_digit/generated/mnist_digit_model_data.c
tools/mnist_digit/generated/mnist_digit_model_data.h
```
导出格式与当前 C 推理严格一致:
-
`w1` 形状为 `78448`,`w2` 形状为 `4810`,都是对称 int8 权重;
-
每层的 `scale` 为该层浮点权重最大绝对值除以 127;
-
第一层 C 代码计算 `int32(pixel × w1)`,再乘 `w1_scale / 255` 并加 `b1`;
-
ReLU 后隐藏层乘 256 截断为 int32,再计算第二层 `int32 × w2`,乘 `w2_scale / 256` 并加 `b2`;
-
`b1` 和 `b2` 保留为 float,避免额外的偏置量化误差;
-
数组排列使用 `w1i \* 48 + h`、`w2h \* 10 + d`,与 `mnist_digit.c` 的循环顺序相同。
先检查终端显示的量化后准确率,并用图片和触摸样本验证生成文件;确认后再手动备份并复制到固件目录:
```powershell
Copy-Item .\application\bt_watch\tools\mnist_digit\generated\mnist_digit_model_data.c .\application\bt_watch\src\main\mnist_digit_model_data.c
Copy-Item .\application\bt_watch\tools\mnist_digit\generated\mnist_digit_model_data.h .\application\bt_watch\src\main\mnist_digit_model_data.h
```
复制完成后重新构建并烧录:
```powershell
cd .\zephyr
python build.py -n -b ats3089p_dev_watch -a bt_watch
```
脚本生成的 C/H 文件包含 `//zk+` 注释。修改模型参数不会改变图片解析、触摸页面或 `app digit` 指令接口。
8.4 Python 源码阅读顺序和训练原理
| Python 函数 | 作用 |
| --- | --- |
| `_read_idx_images()` / `_read_idx_labels()` / `_load_npz()` | 读取图片及数字标签 |
| `_firmware_preprocess()` / `_to_unit_images()` | 复现板端图片预处理,并将像素归一化到 0~1 |
| `_forward()` | 浮点前向计算:`hidden = ReLU(x @ W1 + b1)`,`logits = hidden @ W2 + b2` |
| `_softmax()` | 训练时将 logits 转成类别概率,计算交叉熵及梯度 |
| `_train()` | He 初始化、小批量随机梯度下降、反向传播、逐轮输出损失与准确率 |
| `_quantize()` | 按每层最大绝对值计算比例,将浮点权重四舍五入成 int8 |
| `_quantized_accuracy()` | 使用与 C 代码相同的整数累加和缩放顺序评估量化模型 |
| `_write_model()` | 按固件需要的数组顺序输出 C/H 文件 |
| `main()` | 组织数据读取、训练、量化评估和导出 |
训练时,标签为正确数字 0~9。对一个小批次执行前向计算后,交叉熵衡量预测与标签的差异;反向传播求出每层权重和偏置的梯度,再执行 `参数 = 参数 - learning_rate × 梯度`。重复多个 epoch 后导出模型。softmax 只在训练求损失时使用,板端预测只取最大 logit 即可。
训练和部署流程为:
```text
MNIST IDX/NPZ 图片和标签
-> 与固件对齐的输入预处理
-> Python 浮点前向计算、交叉熵、反向传播和 SGD 更新
-> W1/W2 逐层对称 int8 量化(偏置保留 float)
-> 量化模型测试集评估
-> 生成 mnist_digit_model_data.c/.h
-> 替换固件参数文件,重新编译打包
-> ATS3089 CPU 上执行 mnist_digit_predict()
```
触摸实时识别时,开发板执行的是前向推理。学习和参数更新在电脑上的 Python 脚本中进行。
8.5 验证范围
从 `` 目录执行:
```powershell
python .\application\bt_watch\tests\mnist_digit\test_training.py
```
本次已验证 Python 训练脚本的黑白背景预处理、触摸栅格保留、IDX/GZIP 读取、简单分类学习、int8 量化和 C/H 导出。小样本测试只用于确认训练与导出流程可运行;未在本次流程中重新训练完整 MNIST,也未用这些小样本替换当前固件权重。触摸修复版已完成固件编译,实板手指绘制需要烧录后验证。