GLM-OCR实战:集成STM32F103C8T6实现嵌入式端文字识别
GLM-OCR实战:集成STM32F103C8T6实现嵌入式端文字识别
最近在做一个工业设备巡检的项目,遇到一个挺有意思的挑战:需要在现场离线识别设备铭牌上的文字。铭牌位置固定,但环境光线复杂,而且设备本身没有网络。用手机拍照再传回服务器识别?太麻烦,而且实时性差。用树莓派加个摄像头?成本又上去了。
后来我们把目光投向了手头那些“小钢炮”——STM32F103C8T6最小系统板。这块板子大家应该不陌生,价格便宜,资源也够用,但要在上面跑OCR(光学字符识别),听起来是不是有点天方夜谭?毕竟它只有64KB的RAM和20KB的RAM。
但实际跑下来,我们发现只要选对模型、做好优化,这事儿还真能成。这篇文章,我就来聊聊我们是怎么把GLM-OCR这个轻量级文字识别模型,塞进STM32F103C8T6里,并成功用在工业场景中的。整个过程,就像给一辆小轿车装上赛车引擎,虽然空间有限,但跑起来一样带劲。
1. 为什么要在STM32上跑OCR?
你可能要问,现在云端OCR服务那么多,为什么非要折腾这个“小不点”?这得从实际需求说起。
我们面对的典型场景有两个:一个是工厂里各种电机、泵机上的金属铭牌识别,上面有型号、功率、出厂编号等信息;另一个是老旧配电柜里那些指针式或数字式仪表的读数识别。这些场景有几个共同特点:
- 网络缺失或信号差:工厂车间、配电房内部,Wi-Fi覆盖不全,4G/5G信号也可能很弱,依赖云服务不现实。
- 实时性要求高:巡检人员拿着设备扫一下,最好立刻出结果,而不是等图片上传、服务器处理、结果返回。
- 成本敏感:一个巡检点可能就需要一个识别终端,如果每个终端都用高性能计算板,整体成本会非常高。
- 数据安全与隐私:有些设备信息可能涉及生产细节,企业不希望图片数据流出本地。
STM32F103C8T6这类MCU,单价仅十元左右,功耗极低,正好能匹配这些需求。它就像一个高度集成的“单片计算机”,虽然算力有限,但处理特定、优化过的任务足够了。我们的目标,就是让它在不联网的情况下,独立完成从“看到”图像到“认出”文字的全过程。
2. 挑战与核心思路:给模型“瘦身”
在STM32F103C8T6上直接运行原始的GLM-OCR模型是不可能的。它的Flash(程序存储器)只有64KB,RAM(运行内存)只有20KB。而一个未经处理的神经网络模型,动辄几MB甚至几十MB。
所以,核心思路就一个字:“瘦”。我们要对模型进行极致的压缩和优化,让它能住进STM32这个“小房子”里。这主要分三步走:
2.1 模型量化:从浮点到定点
神经网络模型里的权重(参数)通常是32位浮点数(float32),精度高但占用空间大。模型量化的核心,就是把float32转换成更低精度的格式,比如8位整数(int8)。
你可以把它想象成存储照片。原来用RAW格式(float32),细节丰富但文件巨大;现在转成高质量的JPEG(int8),肉眼几乎看不出区别,但体积小了好几倍。对于很多OCR任务,int8精度带来的微小精度损失,在实际应用中是完全可接受的。
量化后,模型大小通常能减少为原来的1/4,同时,在支持整数运算的硬件上(经过特定优化后,MCU也能受益),推理速度还能提升。
2.2 模型裁剪:去掉“冗余”神经元
神经网络模型往往存在冗余,有些神经元对最终输出贡献很小。模型裁剪就是识别并移除这些不重要的连接或整个神经元。
这有点像给树修剪枝叶。剪掉那些不结果、不向阳的枝条(冗余参数),主干(核心特征提取能力)反而能获得更多养分,模型变得更紧凑、更高效。裁剪可以显著减少模型的参数量和计算量。
2.3 针对STM32的再优化
经过量化和裁剪的模型,还需要转换成STM32的Cube.AI工具链能识别的格式。Cube.AI是ST官方提供的AI模型部署工具,它能把通用的神经网络模型(如ONNX、TensorFlow Lite)转换成高度优化的C代码,充分利用STM32的硬件资源,比如CMSIS-NN库,来加速计算。
我们的技术路线图大致是这样的:在PC上训练和优化GLM-OCR模型 -> 进行量化和裁剪 -> 导出为适合嵌入式部署的格式(如TFLite Micro) -> 使用STM32Cube.AI工具导入并生成C代码 -> 集成到STM32工程中。
3. 系统设计与工作流程
光有“瘦身”模型还不够,我们还得设计一套适合STM32的工作流程。整个系统可以分成三大部分:图像获取、MCU处理、结果输出。
[图像传感器或SD卡] --> [STM32F103C8T6] --> [串口/显示屏/IO]
| |
(图像数据) (预处理 -> 推理 -> 后处理 -> 文本结果)
3.1 图像输入:两种务实的选择
对于STM32F103C8T6,直接处理高清摄像头数据流压力太大。我们采用了两种更实际的方式:
- 串口传输:这是最灵活的方式。可以由上位机(如工控机、带摄像头的另一块主板)拍摄图片,完成初步压缩(如裁剪、降分辨率到模型输入尺寸如32x128),然后通过串口发送给STM32。STM32端只需要一个串口接收中断服务程序,把数据包拼装成完整的图像数组即可。
- SD卡读取:对于非实时性场景,比如批量处理预先拍摄好的铭牌图片,可以将图片存入SD卡。STM32通过SPI接口读取SD卡中的图片文件(存储为BMP等简单格式),再加载到内存处理。这种方式对STM32的实时性要求更低。
在我们的项目中,由于是手持设备巡检,采用了第一种方式,通过蓝牙串口模块与手机APP通讯,由手机负责拍照和初步处理。
3.2 STM32端的处理流水线
图像数据进入STM32后,会经历一个标准的处理流水线:
- 预处理:这步在MCU上做非常高效。主要是将接收到的RGB图像转换为灰度图,然后进行二值化(让文字更突出),最后进行归一化(将像素值缩放到模型要求的范围,如[0, 1]或[-1, 1])。这些操作都可以用简单的整数运算完成。
- 模型推理:这是核心步骤。调用由Cube.AI生成的模型推理C函数,将预处理后的图像数据(现在是一个一维数组)输入进去。模型会在内部进行一系列卷积、池化等操作,最终输出一个概率矩阵。这个矩阵的每一行代表图像中一个位置,每一列代表一个字符(如0-9, A-Z)的概率。
- 后处理:模型输出的不是直接的文字。后处理的任务就是“解码”。最常见的是使用CTC(连接主义时间分类)解码算法。我们需要从这个概率矩阵中,找到概率最高的字符序列,并合并重复的字符、去除空白符,最终得到识别出的字符串。这部分逻辑需要自己用C语言实现,虽然有一定复杂度,但代码量是固定且可控的。
3.3 结果输出与应用
识别出的文本字符串,可以通过串口发送给上位机,也可以显示在简单的OLED屏幕上,或者直接通过GPIO控制其他设备。在我们的仪表读数场景中,识别出的电流、电压值会与预设阈值比较,如果超限,则通过STM32的IO口点亮一个报警LED。
4. 关键代码与实现片段
理论说了不少,来看看实际代码长什么样。这里我摘取几个最关键的片段。
首先,是STM32Cube.AI生成的模型相关接口。它会提供两个关键函数:
// 模型初始化函数,主要分配中间缓冲区(Tensor arena)
int ai_glm_ocr_init(void);
// 模型推理函数,输入图像数据,输出结果到output结构体
int ai_glm_ocr_run(const ai_i8* input_data, ai_glm_ocr_output* output);
我们的主处理逻辑,可能会放在一个单独的任务或主循环中:
// 假设我们通过串口接收到了预处理好的图像数据,存放在g_image_buffer中
// 图像尺寸为32x128,已转为灰度并归一化到int8数组
// 1. 执行推理
ai_glm_ocr_output model_output;
if (ai_glm_ocr_run(g_image_buffer, &model_output) != AI_STATUS_OK) {
printf("Model inference failed!\r\n");
return;
}
// 2. 后处理:CTC解码 (这里是一个极度简化的示意)
char recognized_text[64] = {0};
int text_index = 0;
int last_char_index = -1;
// model_output.data 是一个指向概率矩阵的指针,假设其布局已知
for (int t = 0; t < TIME_STEPS; t++) { // 遍历时间步(图像宽度方向)
int max_index = 0;
ai_i8 max_prob = -128; // int8最小值
// 找到当前时间步概率最高的字符类别
for (int c = 0; c < NUM_CHAR_CLASSES; c++) {
ai_i8 prob = model_output.data[t * NUM_CHAR_CLASSES + c];
if (prob > max_prob) {
max_prob = prob;
max_index = c;
}
}
// CTC解码规则:忽略空白符(假设为第0类),合并连续相同字符
if (max_index != BLANK_INDEX) {
if (last_char_index != max_index) {
recognized_text[text_index++] = index_to_char(max_index); // 索引转字符
last_char_index = max_index;
}
}
}
recognized_text[text_index] = '\0'; // 字符串结尾
// 3. 输出结果
printf("Recognized: %s\r\n", recognized_text);
// 或者通过串口发送出去
UART_SendString(recognized_text);
内存管理提示:g_image_buffer和模型内部缓冲区是内存消耗的大头。务必在CubeMX配置中精确计算和分配这些数组所需的内存,并确保它们被放置在正确的内存区域(如CCM RAM如果可用,速度更快)。
5. 实际效果与优化建议
我们最终在STM32F103C8T6上跑起来的模型,识别一张32x128像素(单行文字)的图像,整个流程(包含预处理和后处理)大约需要300-500毫秒。对于工业巡检这种“拍一下,等半秒看结果”的场景,完全够用。识别准确率在简单的白底黑字、字体清晰的铭牌上能达到95%以上。
当然,这个过程也踩了不少坑,这里分享几点优化建议:
- 输入尺寸是黄金参数:模型输入宽度(TIME_STEPS)直接影响推理时间和内存。在满足最小可识别精度的前提下,尽量缩小输入图像宽度。我们是从128减到96,最后稳定在80,速度和精度找到了平衡。
- 预处理放在上位机:如果可能,把灰度化、二值化、缩放这些操作放在性能更强的上位机(如手机)来做,可以极大减轻STM32的负担,让它专注推理。
- 关注RAM峰值使用:使用STM32Cube.AI的分析工具,仔细查看模型运行时的内存消耗峰值。确保它不会超过你的20KB RAM,并留出足够空间给系统栈、串口缓冲区等。
- 字符集裁剪:如果你的应用只识别数字(0-9)和少量字母(如型号代码),一定要在模型训练阶段就裁剪输出类别,这能直接减少模型大小和后处理复杂度。
6. 总结
回过头看,在STM32F103C8T6这样的资源受限设备上部署GLM-OCR,更像是一次精致的“嵌入式系统微雕”。它考验的不仅仅是AI模型的知识,更是对嵌入式硬件资源(内存、算力、外设)的精准把握和权衡能力。
这套方案的价值在于它提供了一种极低成本的离线文字识别终端解决方案。对于那些需要大量部署、环境恶劣、且对实时性和数据隐私有要求的工业场景,比如设备资产管理、仪表自动化抄表、简易产品分拣等,它展示了一条可行的技术路径。
实现过程中,最大的成就感来自于看到经过深度“瘦身”的模型,在小小的STM32上稳定输出识别结果的那一刻。它证明了,即使是在传统的微控制器领域,经过精心优化的人工智能模型,也能找到用武之地,为古老的设备赋予新的“视力”。如果你正在为类似的离线、低成本识别需求寻找方案,不妨从一块STM32F103C8T6最小系统板开始尝试,这个过程本身,就是一次充满乐趣的技术探险。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
更多推荐


所有评论(0)