基于炬芯ATS308X的DNN全连接神经网络手写数字识别系统设计

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,因此这是混合精度推理。运行流程如下:

  1. 第一层使用 784 个像素与 int8 权重做 int32 累加,再乘量化比例并加偏置。

  2. 经过 ReLU 后得到 48 个隐藏层数值。

  3. 第二层把隐藏层数值按固定比例转成整数,与 int8 权重相乘累加,得到 10 个输出分数(logit)。

  4. 分数最大的下标就是数字类别;不计算 softmax,因此输出分数不是概率。

现有 C 代码注释记载历史 MNIST 测试结果为 93.76%;这不是本次新增 Python 脚本复测的结果。重新训练的准确率应以脚本实际输出和开发板测试为准。当前补齐的训练脚本与固件模型格式兼容,不保证重新训练得到与现有权重逐字节相同的文件。开发板实际书写字体、笔画粗细、触摸采样方式与 MNIST 可能不同,应使用实板数据验证识别率。

2. 输入预处理

无论输入来自 U 盘图片还是触摸屏,最后都会统一成"黑底白字、28×28、8 位灰度"的输入,再送入 MLP。

图片输入

`mnist_digit_preprocess()` 的处理流程如下:

  1. 读取 PGM 或 BMP 解码得到的 8 位灰度像素。

  2. 统计图像四周边框的平均灰度,判断背景是黑色还是白色。

  3. 根据背景自动判断笔画极性:黑底亮字会保留亮笔画,白底黑字会自动反色。

  4. 用背景均值附近的阈值找出前景笔画,计算笔画包围框。

  5. 包围框四周留约 1 像素边界,保持宽高比,把最长边缩放到约 20 像素。

  6. 将缩放后的数字居中放到 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` 后恢复表盘运行。这是页面切换行为,不代表系统休眠。

页面没有笔迹时的检查

  1. 用手指直接按在黑色框内部,不要按在边框或按钮上。

  2. 发送 `app digit clear` 后重新按下并移动手指。

  3. 确认已经烧录包含手写页面修复的最新固件。

  4. 新版在第一次按下画布时打印 `digit: touch start x=... y=... canvas=224x224`。出现该日志表示触摸已进入画布回调;用 `app digit status` 可以查看采样点数 `points`。

  5. 如果页面完全没有显示,保存串口中的 `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,也未用这些小样本替换当前固件权重。触摸修复版已完成固件编译,实板手指绘制需要烧录后验证。

相关推荐
想七想八不如114081 小时前
TTA详细学习
深度学习
xiangzhihong81 小时前
番茄小说系统:从读者门户到作家与平台运营的多端实践
人工智能
古法搬砖工1 小时前
MCP:给 AI Agent 装一个“万能 USB 接口”
人工智能·skill·mcp·搬砖工
能源革命1 小时前
AI 日报|2026-10-23
人工智能
天空属于哈夫克31 小时前
企业微信 API 二次开发与 AI 结合:如何设计智能消息处理流程
人工智能·企业微信
龙粤GEO1 小时前
做GEO是不是只针对AI问答搜索
人工智能
stolentime1 小时前
SOS DP学习笔记
c++·笔记·学习·算法·贪心算法·动态规划·二进制
水母量化1 小时前
量化收益率计算别混用:算术均值、几何均值与对数收益
算法·均值算法·量化交易
frjc1 小时前
Spring WebFlux SSE 长连接,可以替换为 Java 21 虚拟线程吗?
java·人工智能·spring·ai·ai编程