十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32F407嵌入式AI实战:手写数字识别从PyTorch到C代码部署

STM32F407嵌入式AI实战:手写数字识别从PyTorch到C代码部署 简介本资源是面向嵌入式初学者与STM32进阶开发者的手写数字识别实战项目聚焦于在资源受限的STM32F407系列MCU上部署轻量级图像识别功能适用于智能终端、人机交互界面及IoT边缘识别等场景。压缩包共635个文件含279个C源码实现ADC采样、触摸坐标处理、特征提取与KNN分类核心逻辑、275个头文件封装驱动接口与算法参数、44张PNG图像含训练样本示意图与界面效果参考以及lib库文件、Keil工程文件uvprojx/uvoptx和可执行hex镜像整体大小为5.61MB。已有183人学习下载。项目提供完整可编译运行的HAL库工程涵盖从触摸数据采集、笔迹归一化预处理、Zernike矩特征提取到嵌入式端KNN分类器部署的全流程代码所有驱动已适配STM32F40X全系列无需额外移植目录结构按硬件抽象层、算法模块、应用逻辑分层组织便于理解嵌入式AI落地的关键约束与优化思路。 手写数字识别这个题目在PC端早就是烂大街的东西了PyTorch里随便搭个两层全连接网络MNIST测试集上拿个98%以上的准确率轻轻松松。但放到STM32F407这颗Cortex-M4单片机上事情就没那么顺理成章了——没有操作系统内存只有192KBFlash也就512KB到1MB你要在这么点资源里塞进一个神经网络还得保证前向推理速度能用、识别结果靠谱中间涉及的模型选型、权重导出、C代码移植和预处理细节每一步都有坑。我最近完整走了一遍这个流程从PyTorch离线训练、模型裁剪、权重导出到STM32F407上的工程搭建、前向推理实现最后在正点原子探索者开发板上通过触摸屏手写输入把数字识别跑通了单次推理耗时在毫秒级交互体验基本跟手。这篇文章就把整个链路掰开揉碎讲一遍包括我当时踩过的坑和最后采用的优化方案。无论你是正在做毕业设计的学生还是想入门嵌入式AI的工程师这篇内容都可以直接作为参考至少能帮你少走两周弯路。1. 项目整体设计与思路拆解1.1 为什么选STM32F407而不是性能更强的平台很多人看到“单片机跑AI”第一反应是“为什么不用树莓派或手机”第二反应是“为什么不用带NPU的芯片”。这两个问题我在做项目前也反复权衡过最后仍然选了STM32F407核心原因是它刚好卡在“资源够用”和“上手门槛低”的平衡点上。先说性能。STM32F407主频168MHzCortex-M4F内核带单精度硬件FPU192KB SRAM512KB到1MB Flash。这个配置跑一个中等规模的全连接网络完全没问题784-64-10这种结构单次前向推理的乘加运算量大约5万次F407实测只需要几个毫秒。对比一下F103那种72MHz无FPU的M3内核同样网络能慢出好几倍交互起来就有明显卡顿感。再说外设。F407的FSMC接口可以很方便地驱动TFT-LCDDCMI接口能接摄像头SPI、UART、USB也齐全这意味着手写数字的“输入方式”可以灵活选择——触摸屏、摄像头、串口上位机都可以。而F103虽然也能做但FSMC带宽、主频和外设丰富度都差一档。最后是生态。正点原子、野火这些开发板资料极其丰富STM32CubeMX生成工程很成熟HAL库和标准库都能用遇到问题几乎都能搜到答案。对一个要快速出结果的项目来说资料多本身就是最大的优势。从应用场景看这个项目的价值也不只是“跑个demo”。脱机手写数字识别在智能仪表、电子签名板、工业字符识别这些场景里都有实际需求单片机能做到低功耗、低成本、离线运行刚好补上树莓派和手机方案覆盖不了的盲区。1.2 模型选型MLP还是CNN确定平台之后下一个关键决策就是模型结构。手写数字识别最经典的模型有两类全连接网络MLP和卷积网络CNN。我最终主线用的MLP但CNN也做了验证这里把取舍讲清楚。MLP的方案是输入784个像素28x28灰度图经过一个64节点的隐藏层ReLU激活再经过10节点输出层Softmax分类。这个结构参数量大概5万个float约200KBFlash放得下计算量5万次乘加对F407来说压力不大。最关键的是MLP的每一层就是一次矩阵乘法加一个激活函数用C语言实现极其直观调试也方便非常适合做MCU端移植的第一版。CNN这边LeNet-5是手写数字识别的经典结构参数量约6万比MLP多不了太多但计算量完全不是一个量级。卷积层逐窗口滑动的计算方式在168MHz的M4上跑一次完整推理要几秒到几十秒取决于图像尺寸和层数交互体验非常差。除非做极致的层融合和int8量化否则在F407上跑CNN的性价比并不高。我的建议是毕设或第一个版本老老实实用MLP把全链路跑通之后再扩展CNN。MLP能让你把注意力集中在“单片机端如何部署”这个核心问题上而不是被卷积层的移植细节拖住。1.3 两种部署路线手工移植还是STM32Cube.AI模型有了怎么把它弄到单片机上去有两条路线。路线一是手工移植在PC端训练好模型后提取权重和偏置生成C语言数组然后在STM32工程里手写前向推理代码。这条路线的优势是透明可控每一层在干什么你都清清楚楚答辩时能讲明白原理出问题时也能一步步排查。劣势是改模型结构后要重新导出权重步骤相对繁琐。路线二是用官方工具STM32Cube.AI也就是X-CUBE-AI把训练好的Keras或TFLite模型直接转换成C代码。优势是省事工具会自动做层融合和优化劣势是版本兼容问题多生成的代码可读性差出问题不好定位而且对模型层的支持也有限制比如某些自定义层直接不支持。两条路线我都试过最终项目采用的是路线一。原因很简单对于MLP这种简单结构手写前向推理代码量并不大而且能精确控制每一步的实现细节——比如矩阵乘法用CMSIS-DSP库加速ReLU和Softmax按自己的方式优化。这些控制力是Cube.AI给不了的。当然如果你的模型是复杂CNN且不介意黑盒路线二也值得尝试。2. 数据准备与离线训练2.1 MNIST数据集与预处理要点训练数据我用的是MNIST60000张训练图、10000张测试图28x28灰度数字0-9这是手写数字识别领域的标准数据集。PyTorch里用torchvision加载非常方便但有几个预处理细节需要注意直接影响后面部署到单片机上的效果。首先是归一化。MNIST原始像素值是0到255的整数训练时通常归一化到0到1或-1到1。这个归一化参数要记下来因为部署到单片机上时输入数据也要做同样的归一化操作否则模型输出分布会不对。我在C代码里就是简单地把输入像素值除以255.0f保证和训练时一致。其次是数据增强。刚开始我做第一版训练时没加任何增强MNIST测试集准确率97.5%看着还行但移植到触摸屏后识别率掉得惨不忍睹。原因很简单MNIST里是规规矩矩居中缩放的印刷体手写数字而触摸屏上写出来的数字歪歪扭扭、有大有小、还有断笔和噪声。后来我加了随机平移、随机旋转±10度、随机缩放这几类增强测试集准确率提升到98.6%关键是触摸屏场景下明显更稳了。如果想让模型更贴合触摸屏输入还有一个更狠的做法自己在开发板上采集一批真实触摸笔迹扩充训练集。这个属于进阶玩法后面讲预处理时再展开。2.2 网络结构与训练配置我最终采用的网络结构是输入层784个节点对应28x28灰度图的像素值展开隐藏层64个节点ReLU激活输出层10个节点对应数字0-9训练配置如下损失函数用CrossEntropyLoss优化器用Adam初始学习率0.001batch size 128训练20个epoch。PyTorch代码很短核心训练循环大概这样import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms transform transforms.Compose([ transforms.RandomAffine(degrees10, translate(0.1, 0.1), scale(0.9, 1.1)), transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) train_dataset datasets.MNIST(./data, trainTrue, downloadTrue, transformtransform) test_dataset datasets.MNIST(./data, trainFalse, transformtransforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ])) train_loader torch.utils.data.DataLoader(train_dataset, batch_size128, shuffleTrue) test_loader torch.utils.data.DataLoader(test_dataset, batch_size256) class MLP(nn.Module): def __init__(self): super().__init__() self.fc1 nn.Linear(784, 64) self.fc2 nn.Linear(64, 10) def forward(self, x): x x.view(-1, 784) x torch.relu(self.fc1(x)) x self.fc2(x) return x model MLP() criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr0.001) for epoch in range(20): for images, labels in train_loader: optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() torch.save(model.state_dict(), mnist_mlp.pth)这个配置在我本机CPU上训练一个epoch也就十几秒20个epoch几分钟就完成了不需要GPU。训练完成后在测试集上评估一下准确率应该在98%以上。如果用的是无增强版本97%左右也正常。这里有一个经验之谈隐藏层节点数不要贪多。有人觉得128、256个节点准确率更高就往上加但MCU端Flash和算力有限64节点在准确率和资源占用之间是比较平衡的选择。实测64节点和128节点在MNIST上的准确率差距不到0.3%但对F407来说Flash占用和推理时间却差了一倍。2.3 权重导出为C数组的完整脚本训练完模型最关键的一步是把权重导出来。PyTorch里Linear层的权重存在state_dict里key分别是fc1.weight、fc1.bias、fc2.weight、fc2.bias。有一点要特别小心PyTorch中Linear层权重的shape是(out_features, in_features)也就是W1的shape是(64, 784)W2的shape是(10, 64)。做前向推理时如果按标准做法y Wx b来算直接按这个行优先的顺序导出就能用。下面是我用的导出脚本把权重和偏置写成C语言的头文件数组import numpy as np import torch model MLP() model.load_state_dict(torch.load(mnist_mlp.pth, map_locationcpu)) model.eval() params model.state_dict() def dump_array(name, tensor): data tensor.numpy().flatten() print(fconst float {name}[{data.size}] {{) for i in range(0, data.size, 8): row , .join(f{v:.6f}f for v in data[i:i8]) print(f {row},) print(};) dump_array(W1, params[fc1.weight]) dump_array(b1, params[fc1.bias]) dump_array(W2, params[fc2.weight]) dump_array(b2, params[fc2.bias])注意我用了.6f格式化保留6位小数加上f后缀表示float常量。运行这个脚本把输出内容保存成mnist_weights.h后面在Keil工程里直接#include进来就能用。还有一个强烈建议导出时顺便做一次“PC端前向推理验证”。具体做法是把导出的数组在Python里重新做一次矩阵乘法对比PyTorch模型的输出确保导出没有错位。这个步骤看着多余但能省掉后面在单片机上查半天逻辑错误的时间。3. STM32F407工程搭建与前向推理实现3.1 CubeMX工程配置与CMSIS-DSP库接入STM32端我用的是STM32CubeMX生成工程框架具体芯片型号选STM32F407VET6如果你的是F407ZGT6也行配置基本一致。需要使能的外设有RCC时钟树配置为168MHz主频、FSMC接口驱动TFT-LCD、一个SPI接口接触摸屏、一个USART用于打印调试信息另外配几个GPIO作为按键等功能。时钟树配置是整个工程的基础F407最高支持168MHz用8MHz外部晶振PLL倍频到168MHzAPB1总线时钟42MHzAPB2总线时钟84MHz。这些时钟配置对了后面所有外设才能正常工作。CMSIS-DSP库的加入方式有两种。一是通过CubeMX的Software Packs组件管理器勾选CMSIS-DSP让工程自动包含库文件二是手动下载CMSIS-DSP源码包把arm_math.h和对应的库文件arm_cortexM4lf_math.lib拷到工程目录。我用的是第二种因为更可控版本不会跟CubeMX自动生成的工程冲突。直接在Keil工程里添加CMSIS-DSP要格外注意硬件浮点设置。Keil的Options for Target - Target - Floating Point Hardware必须选Single Precision。如果选错FPU不会被正确启用CMSIS-DSP的矩阵乘法虽然能跑但性能会大打折扣因为你可能在用软浮点模拟。检查方法很简单编译后看map文件里有没有__hardfp相关的符号或者直接看反汇编里有没有vadd.f32这类浮点指令。3.2 前向推理的C代码实现细节前向推理的逻辑就是上篇文章里MLP结构的直接翻译。输入是一个784长度的float数组代表28x28灰度图0到1区间输出是10个float值分别是数字0-9的分类得分取最大值对应的索引就是识别结果。我最初手写的矩阵乘法是三层嵌套循环代码很直观但F407实测一次推理大约花12毫秒。换成CMSIS-DSP库的arm_mat_mult_f32之后推理时间直接降到了4毫秒左右提速明显。所以强烈建议用DSP库不要自己写矩阵乘法。核心的predict函数长这样#include arm_math.h #include mnist_weights.h #define INPUT_SIZE 784 #define HIDDEN_SIZE 64 #define OUTPUT_SIZE 10 static float input_data[INPUT_SIZE]; static float hidden[HIDDEN_SIZE]; static float output[OUTPUT_SIZE]; void model_predict(float *input, uint8_t *result, float *confidence) { arm_matrix_instance_f32 W1 {HIDDEN_SIZE, INPUT_SIZE, (float32_t *)W1_data}; arm_matrix_instance_f32 x_in {INPUT_SIZE, 1, input}; arm_matrix_instance_f32 hidden_mat {HIDDEN_SIZE, 1, hidden}; arm_mat_mult_f32(W1, x_in, hidden_mat); for (int i 0; i HIDDEN_SIZE; i) { hidden[i] b1_data[i]; if (hidden[i] 0.0f) hidden[i] 0.0f; // ReLU } arm_matrix_instance_f32 W2 {OUTPUT_SIZE, HIDDEN_SIZE, (float32_t *)W2_data}; arm_matrix_instance_f32 hidden_in {HIDDEN_SIZE, 1, hidden}; arm_matrix_instance_f32 output_mat {OUTPUT_SIZE, 1, output}; arm_mat_mult_f32(W2, hidden_in, output_mat); for (int i 0; i OUTPUT_SIZE; i) { output[i] b2_data[i]; } softmax(output, OUTPUT_SIZE); *result 0; float max_val output[0]; for (int i 1; i OUTPUT_SIZE; i) { if (output[i] max_val) { max_val output[i]; *result i; } } *confidence max_val; }有几个细节值得强调。第一权重数组W1_data、b1_data、W2_data、b2_data来自mnist_weights.h头文件在编译时存入Flash。用const float修饰确保不要把这个200多KB的数据放到SRAM里否则192KB的SRAM直接爆掉。第二arm_mat_mult_f32的输入矩阵和输出矩阵必须预先分配好它不会帮你动态分配内存。而且矩阵结构体里的指针需要指向内存对齐的数组一般4字节对齐就够了Keil默认的全局变量对齐没问题但如果用动态分配就要小心。第三softmax函数我做了数值稳定性处理。标准的Softmax是先对指数求和再相除但如果输入值稍大exp会溢出变成inf。简单的方法是对所有输出减去最大值再做expvoid softmax(float *x, int len) { float max_val x[0]; for (int i 1; i len; i) { if (x[i] max_val) max_val x[i]; } float sum 0.0f; for (int i 0; i len; i) { x[i] expf(x[i] - max_val); sum x[i]; } for (int i 0; i len; i) { x[i] / sum; } }其实对分类结果来说argmax不依赖分母Softmax最后一步归一化对判断类别没有影响但保留它能拿到置信度方便后面显示“这个数字的把握有多大”调试时很有用。3.3 触摸屏输入与图像预处理模型推理跑通之后真正的难点来了怎么让触摸屏上手写的数字变成模型能用的28x28灰度图。这一步处理得不好模型准确率再高也白搭。我用的是LCD电阻触摸屏方案LCD分辨率480x320。我在屏幕上画了一个240x240的白色书写区域用户在这个区域内用手指或笔书写系统通过触摸屏控制器采集笔画坐标点同时在LCD上实时画出来。书写结束后把笔画坐标转换成28x28的灰度图。具体做法是把240x240的书写区域划分成28x28个网格每个网格大约8.5x8.5像素。遍历笔画坐标点根据坐标落在哪个网格给对应网格的灰度值累加1。如果某格被笔迹覆盖了灰度值高反之则低。最后把所有网格值映射到0-255范围。这个过程中最关键的坑是居中预处理。MNIST的训练样本都是把数字缩放并居中到28x28的中心区域触摸屏直接按坐标划分的图完全不是这个分布直接喂给模型识别率会非常低。我一开始没做居中误以为“模型应该能识别任意位置的数字”结果数字稍微偏一点就识别错。正确的做法是计算笔迹的包围盒所有笔迹点的最小x、最大x、最小y、最大y然后把包围盒等比缩放到20x20大小最后把这个20x20的图平移到28x28画布的中心位置。这正好是MNIST数据集的官方预处理方式。我加上这一步之后触摸屏识别率提升非常明显。实际代码中这个缩放和平移要在灰度网格上做一次双线性插值否则缩放的锯齿会引入噪声。如果嫌插值麻烦简单地取最近邻也行但建议还是做双线性代码量不大效果更稳。另外触摸屏坐标方向是个容易踩的坑。很多开发板的触摸屏X方向和LCD显示的X方向是反的笔画会镜像显示。如果用户写的明明是“7”显示出来却是个镜像的“L”模型自然会识别错。调试时先画一个十字交叉线检查触摸画出的线和LCD坐标是否一致再往下做。4. 性能优化与内存管理4.1 推理耗时实测与优化方向先给一组实测数据我最初手写的三层循环矩阵乘法主频168MHz不开编译器优化时单次推理约20毫秒。开了Keil -O2优化后降到12毫秒左右。换成CMSIS-DSP的arm_mat_mult_f32后直接降到4毫秒左右。如果再把优化等级开到-O3可以压到3毫秒上下。这个速度对触摸屏手写场景完全够用。人的书写动作一般是几百毫秒松笔之后立刻开始推理3毫秒的耗时用户根本感知不到LCD上刚画完笔画识别结果马上就能弹出来。如果你对耗时敏感还有个简单的测量手段用DWT数据观察点计数器。F407内部有DWT模块可以用来做精确的周期计数精度比SysTick高得多。初始化代码CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk;然后在推理前清零推理后读DWT-CYCCNT除以168MHz就是秒数。这个方法不会打断程序执行非常适合做性能分析。4.2 内存占用分析与量化取舍这个MLP模型的内存账很好算。权重W1是64x78450176个float约200KBb1是64个float约256字节W2是10x64640个float约2.5KBb2是10个float40字节。全部加起来约203KB放到Flash里完全没问题F407VET6是512KB Flash。运行时SRAM方面输入数组784个float约3KB隐藏层64个float加输出层10个float加上矩阵乘法临时变量总共不超过10KB。对192KB的SRAM来说非常宽裕。但如果你想把模型换成更大的结构或者用CNN内存就要精打细算了。一个常用的优化手段是int8量化把float权重量化到int8Flash占用直接降到原来的四分之一200KB变50KB。代价是前向推理代码要额外处理scale和zero_point而且F407没有原生的int8矩阵加速指令计算速度不一定比float快。所以我的建议是在F407上做MLP推理float就够了不需要量化。量化更适合资源更紧张的场景比如F103或更高层的CNN。4.3 FPU与CMSIS-DSP的使用细节既然F407带FPU就要把它的性能吃满。有几个细节直接影响浮点性能。第一确认FPU被正确使能。Keil工程里Target选项页的Floating Point Hardware必须选Single Precision并且编译器的Define里最好加上__FPU_USED1和__FPU_PRESENT1。启动文件里也有一段FPU使能代码SCB-CPACR | ((3UL 10*2) | (3UL 11*2))标准启动文件默认包含但如果是自己精简过的启动文件容易漏掉后果是浮点指令进HardFault。第二CMSIS-DSP库的矩阵乘法函数对内存对齐有要求。arm_matrix_instance_f32结构体里的pData指针建议4字节对齐如果是从外部SRAM分配的内存尤其要注意。F407的外部SRAM基址通常是0x68000000或0x60000000对齐没问题但如果自己做了偏移就要小心。第三不要在中断服务函数里做推理。触摸屏的SPI中断、定时器中断会频繁打断推理过程导致推理时间不稳定甚至因为超时丢数据。正确的做法是触摸屏中断只负责记录坐标和置一个标志位主循环检测到“书写结束”标志后再执行推理。这样推理是一个原子操作耗时稳定也不会漏掉触摸事件。5. 常见问题与排查技巧实录5.1 编译链接失败或Flash不足这是我在项目初期遇到最多的编译问题。典型报错是Keil的L6220E: Region RX overflowed by xxx bytes意思是代码和常量数据超出了Flash容量。排查思路其实很清晰。首先确认权重数组是不是用const声明了如果忘了加const编译器会把这个200KB的数组放到可读写的SRAM区F407的192KB SRAM根本扛不住即使没报Flash溢出也会报内存不足。其次把编译优化等级从-O0调到-O2代码体积能减少不少。再者检查有没有不小心把printf的重定向、FPU库、DSP库整个连进来这些都会占Flash。最后如果实在放不下最粗暴的办法是缩小隐藏层从64降到48或32准确率损失很小但Flash占用直接砍掉三分之一以上。5.2 触摸屏识别率低不一定是模型的问题模型在MNIST测试集98%以上触摸屏上一测只有60%这个问题我调试了整整两天最后发现模型本身没问题是预处理环节的锅。最典型的三类问题一是没有居中缩放数字在28x28画布里的位置和MMNIST训练样本差异太大二是二值化阈值不合适触摸屏笔画颜色浅、压力轻灰度值没有正确映射到0-255三是触摸屏坐标镜像或旋转导致数字形状都变了。排查方法是分步做别一上来就猜模型。第一步把每个手写数字的28x28网格数据通过串口打印出来在PC端还原成ASCII图肉眼看这个图是不是一个清晰、居中、结构正常的数字。如果ASCII图看起来都别扭说明预处理有问题先调预处理。如果ASCII图正常但识别结果还是错再接一个调试手段把模型的10个输出概率通过串口打出来看模型是“错得犹豫”还是“错得自信”。错得犹豫说明特征不明确错得自信说明模型学到的东西跟输入的分布不一致通常还是预处理导致的分布偏移。5.3 推理偶尔卡顿或死机这个问题的别名叫“HardFault”。我在调试时遇到过一次现象是笔画一多就死机。排查后定位到两个原因一是在触摸屏中断里分配了较大的局部数组导致栈溢出二是DSP矩阵乘法访问了未对齐或者越界的指针。解决方案是把所有中间计算的大数组比如输入数组、隐藏层、输出层都定义为全局静态数组不要放在函数内部。这样它们分配在.bss段不占用栈空间。另外在HardFault_Handler里加一个调试断点挂上调试器后可以在中断里查看LR寄存器和栈回溯定位到具体是哪个函数触发的异常这个方法能救命的。5.4 CMSIS-DSP库和HAL库的版本冲突这个坑比较隐蔽。我在一个CubeMX生成的工程里手动加入了CMSIS-DSP库的源码编译时出现一堆重复定义的报错原因是CubeMX默认也会在工程里加入一部分CMSIS-Core的相关文件两边的头文件版本不一致导致宏重复。解决办法很简单要么只用CubeMX的Software Packs方式添加CMSIS-DSP不要手动再加要么手动添加时把CubeMX自动生成的CMSIS-DSP相关文件从工程里删掉只保留一套。另外CMSIS-DSP的版本要跟CMSIS-Core版本匹配如果工程用的CMSIS是5.xDSP库也尽量用5.x系列。6. 项目扩展方向与我的实际体会MLP全链路跑通之后这个项目还有很多可以玩的空间。我自己接着做了两个扩展都验证可行这里一并分享。第一个扩展是摄像头输入。F407的DCMI接口可以接OV7670或OV2640摄像头模块拍摄纸上手写的数字然后用像素值判断数字区域裁剪、缩放、灰度化后送入同一个模型。这个方案的预处理比触摸屏复杂得多因为要处理背景、光照、数字定位等问题但做出来之后效果很惊艳更像是“真实产品”的感觉。第二个扩展是增加识别类别。手写数字识别只有10类如果你把模型输出从10改成2610输入尺寸稍作调整就能识别手写字母。训练数据可以自己采集或者用EMNIST的字母数据集。STM32F407的资源跑一个字母识别MLP依然没问题Flash占用和推理时间都在可接受范围内。最后说一点个人经验。这个项目最核心的难点在我看来其实不是单片机端的代码而是“让预处理方式与训练数据分布保持一致”这件事。PC端训练你有一万种方法把数据洗干净但到了嵌入式设备上物理世界的噪声、触摸屏的飘移、光照变化都会让输入数据偏离训练分布。处理不好这一层模型再优秀也无法落地。踩过几次坑之后我现在做任何嵌入式AI项目都会先花时间把“采集输入到模型输入”这一步做成可视化——在PC端写个模拟器把同样的预处理代码跑一遍看看输出图像是否合理再决定要不要上板子。这比在单片机上一遍遍烧录调试要高效得多。希望这篇内容能帮你少走一些弯路早日跑通自己的手写数字识别项目。本文还有配套的精品资源点击获取
返回列表