最近不少做嵌入式视觉的朋友在问怎么把OpenMV跟STM32玩起来,正好我手上刚完成了一个车牌识别系统的小项目,整套链路是OpenMV采集图像、STM32做控制联动、PC端用YOLOv11检测车牌区域、PaddleOCR识别文字。很多初学者一听“车牌识别”就以为要上树莓派或者Jetson,其实用OpenMV+STM32这套轻量组合也能搭出能跑的原型,而且工程思路特别适合做毕业设计或者入门嵌入式AI。这篇我把从硬件接线、固件配置、模型训练到OCR部署的完整过程都写出来,包括我踩过的坑和换过三次方案的教训,希望能帮你少走弯路。
这个项目适合三类人:正在做嵌入式相关课设或毕设的学生、想熟悉OpenMV与STM32通信的开发爱好者、以及想把深度学习检测模型和单片机结合起来做落地demo的工程师。你不需要有很强的算法基础,但建议先会点MicroPython、熟悉STM32的基本外设编程,再来看这篇文章会顺畅很多。整套流程我尽量按“能跑起来”的标准写,每个环节都给到可直接用的配置和代码片段。
1. 车牌识别系统的整体设计与方案选型
1.1 为什么选OpenMV+STM32而不是单板电脑
先说个很多人纠结的问题:车牌识别这个任务,OpenMV的算力明显不够直接在板子上跑YOLOv11,STM32更跑不动深度学习推理,那为什么还要用这套组合?我的答案是——你做的不只是一个识别器,而是一个完整的嵌入式视觉系统。
OpenMV的优势在于它把摄像头、图像处理、IO控制集成在一块小板上,MicroPython环境上手快,可以快速验证图像采集、颜色识别、串口通信这些底层逻辑。STM32的优势在于外设丰富、实时性强,适合做道闸控制、LED提示、传感器联动,而且它是很多嵌入式岗位的基本功。把两者结合起来,正好发挥各自的长处,这也是工业上常见的主控+视觉模组架构。
如果你直接上树莓派或者RK3588这类板子,确实跑算法很爽,但成本和体积都上去了,而且失去了“自己做主控逻辑”的锻炼机会。从学习角度讲,先在小系统上把数据流跑通,再迁移到大算力平台,思路会清晰很多。
1.2 我的系统架构和它解决了什么问题
这套车牌识别系统整体分三层:
- 图像采集层:OpenMV负责实时拍摄画面,检测到运动或者触发信号后,把当前帧压缩成JPEG通过串口发给PC端,同时也通过IO口通知STM32“正在处理”。
- 控制决策层:STM32作为主控,接收OpenMV的状态信号,控制舵机(模拟道闸)、蜂鸣器和LED指示灯,并把状态信息显示在OLED屏上。
- 云端或PC识别层:PC端接收OpenMV传来的图像,先用YOLOv11做目标检测框出车牌区域,再用PaddleOCR对裁剪后的区域做文字识别,最后把结果通过串口回传给STM32显示。
这套架构的好处是各模块职责单一、容易调试。哪怕任何一个环节出问题,都能单独验证。比把所有逻辑塞进一块板子里要清晰得多,也符合真实产品的分层思路。
1.3 硬件清单和拓扑关系
我在实际项目中用到的硬件如下:
| 硬件 | 型号/规格 | 用途 |
|---|---|---|
| 视觉模组 | OpenMV Cam H7 Plus | 图像采集与预处理 |
| 主控 | STM32F407ZGT6开发板 | 控制逻辑与联动 |
| 摄像头 | MT9M114(OpenMV自带) | 拍摄车牌画面 |
| 舵机 | SG90 | 模拟道闸抬杆动作 |
| 显示器 | 0.96寸OLED I2C | 显示识别结果 |
| 报警 | 有源蜂鸣器 + 绿色/红色LED | 识别成功/失败提示 |
| USB转TTL | CH340模块 | OpenMV与STM32串口调试及PC通信 |
| 电源 | 5V 3A适配器 + AMS1117降压模块 | 供电 |
通信拓扑上,OpenMV的P1(TX)、P0(RX)分别接STM32的USART2_RX和USART2_TX,波特率设115200。PC端识别时,STM32的USART1再引出到CH340模块跟电脑连,形成OpenMV→STM32→PC这样一条数据链路。上一版方案我用过OpenMV直接连PC,少了主控中转,虽然也能跑,但就失去了单片机参与联动的意义,最后还是按现在的结构重新搭了。
2. OpenMV端:图像采集、检测触发与串口通信细节
2.1 OpenMV固件选择与初始配置
OpenMV官方固件通常够用,但如果你需要跑一些额外的神经网络模型,建议使用带IDE最新版固件,更新方法很简单:用USB连接OpenMV到电脑,打开OpenMV IDE,点击“工具→固件更新”,勾选“选择最新版本”即可。我建议定期更新,因为我曾经在旧固件上遇到串口DMA相关的奇怪Bug,升级之后就好了。
OpenMV上的存储空间不大,H7 Plus是16MB Flash,如果后续要存放神经网络模型或较多图片,可以在SD卡槽插一张32GB的TF卡,并创建一个images目录用于存放抓拍的车牌图片。下面是我在OpenMV端用的初始化代码片段,包含串口和摄像头基础配置:
import sensor, image, time from pyb import UART sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) # 320x240,兼顾清晰度与传输速度 sensor.skip_frames(time=2000) sensor.set_auto_whitebal(True) # 串口初始化:P1 TX, P0 RX,对应STM32的USART2 uart = UART(3, 115200, timeout_char=1000) uart.init(115200, bits=8, parity=None, stop=1) # 定义一个简单的帧协议:帧头+数据长度+数据 FRAME_HEADER = b'\xA5\x5A' def send_frame(img_bytes): length = len(img_bytes).to_bytes(2, 'big') uart.write(FRAME_HEADER + length + img_bytes) print("sent frame, size =", len(img_bytes))这里我特意把分辨率设在QVGA,因为再高的分辨率串口传输延迟会明显变大。实测在115200波特率下,一张QVGA的JPEG图大概15~25KB,传输时间约1.5秒,做原型完全够用。
2.2 触发抓拍策略:连续帧差异检测
车牌识别不需要每一帧都传,否则串口会被塞爆。我采用的是“画面差异检测”方式:OpenMV持续采集画面,当检测到画面中有明显变化(比如车辆驶入)才抓拍一帧并发送。这样既降低了通信压力,也能减少PC端无效识别。
差异检测的基础是计算前后两帧对应像素的平均绝对值差,超过阈值就认为画面有变化。OpenMV提供了image.get_similarity()方法,但更轻量的是用帧差法。下面是我的实现:
previous_frame = sensor.snapshot().copy() threshold = 25.0 while True: current_frame = sensor.snapshot() diff = current_frame.get_similarity(previous_frame) # get_similarity返回0~1,1表示完全不同 if diff > 0.35: print("motion detected, similarity diff =", diff) img_bytes = current_frame.compressed(quality=70) send_frame(img_bytes) time.sleep(1) # 避免连续抓拍导致串口阻塞 previous_frame = current_frame.copy()阈值0.35是我在室内灯光环境下调出来的,如果你在室外强光环境,可能需要适当提高到0.5左右。注意time.sleep(1)很关键——它防止同一辆车在画面里连续触发多次抓拍,虽然会漏掉一些帧,但对车牌识别场景来说足够了。
2.3 OpenMV串口通信协议设计要避开的坑
单片机之间通信最怕数据错位。因为图像数据是二进制流,如果接收端不知道一帧从哪里开始到哪里结束,很容易把上一帧尾部当成下一帧头部。
我采用的协议很简单:0xA5 0x5A作为两字节帧头,紧接着两个字节表示数据长度(大端序),后面跟的是JPEG图像字节流。STM32端解析时,先等帧头,然后读长度,再按长度收满整帧数据。这个协议没有加校验和,实际用下来误码率极低,如果你对稳定性要求更高,可以在帧尾加一个CRC16或简单的异或校验。
我踩过的一个坑是:OpenMV的uart.write()如果一次写入大量数据,偶尔会被系统中断切成两段,接收端如果不做缓冲处理,就会解析失败。解决办法是把发送函数改成循环发送并加小延时,或者在STM32端用DMA+空闲中断接收,细节见下一节。
3. STM32端:主控驱动、串口接收与OLED显示
3.1 STM32开发环境搭建要点(含C51兼容提示)
很多人在STM32开发环境上卡了很久,尤其是刚从51单片机转过来的同学,经常被Keil的芯片包和编译器版本搞得头大。
我用的是Keil MDK 5.37版本,安装STM32F4系列支持包时要注意,如果你以前装过C51版Keil,建议把两个版本分开装在不同目录,否则UV4.exe会冲突。打开Keil后,进入Pack Installer,搜索STM32F4xx_DFP,下载1.2.2或更高版本。如果下载速度慢,可以手动去Keil官网下载DFP包,然后双击安装。
STM32的USB无法识别问题,90%是驱动没装好。建议下载STM32CubeProgrammer或STM32 ST-LINK Utility附带驱动,安装后重新插线。注意有些开发板用的是一键下载电路(如正点原子、野火的板子),需要先装CH340或CP2102的USB转串口驱动。
工程创建方面,我建议直接用STM32CubeMX生成初始化代码,勾选USART1、USART2、I2C1、TIM3等外设,时钟配置为外部8MHz晶振倍频到168MHz,生成后再在Keil里添加自己的业务逻辑代码。这样的好处是避免手写寄存器配置出错,并且代码可读性高。
3.2 STM32串口中断接收与环形缓冲区
STM32这边最关键的是串口接收图像帧。由于图像数据比较大,用简单的阻塞式接收会浪费CPU,而且容易丢字节。我的做法是:用USART2接收OpenMV的数据,开启接收中断,每收到一个字节就放入环形缓冲区;在主循环里解析缓冲区,寻找帧头、读取长度、提取图像数据,再通过USART1转发给PC。
环形缓冲区的实现不复杂,但要注意读写指针的同步。我直接用了一个256字节的结构体:
#define RX_BUFFER_SIZE 2048 typedef struct { uint8_t buffer[RX_BUFFER_SIZE]; volatile uint16_t head; volatile uint16_t tail; } RingBuffer;中断服务函数里:
void USART2_IRQHandler(void) { if (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) { uint8_t data = USART_ReceiveData(USART2); uint16_t next_head = (ring.head + 1) % RX_BUFFER_SIZE; if (next_head != ring.tail) { ring.buffer[ring.head] = data; ring.head = next_head; } // 若缓冲区满,则丢弃当前字节,避免覆盖未读数据 } }主循环解析时,要先判断缓冲区里是否有完整的一帧数据,再处理。核心逻辑是状态机,分为“找帧头0xA5”、“验证0x5A”、“读长度高字节”、“读长度低字节”、“收数据体”五个状态。我用这个方式跑了半小时图像流,亲测不会丢帧。
3.3 OLED显示和舵机控制联动
识别结果通过OLED显示,我用的是0.96寸I2C接口SSD1306驱动。在STM32的I2C1上,SCL接PB6,SDA接PB7。实际使用中需要注意I2C上拉电阻,部分开发板没有板载上拉,需要外接4.7kΩ电阻到3.3V,否则OLED不显示。
显示代码就是标准SSD1306库,核心是封装一个OLED_ShowString(),在接收到PC端回传的识别字符串后,把内容打印到屏幕上。我在屏幕上分了三行:第一行显示当前状态“DETECTING”还是“RESULT”,第二行显示车牌号,第三行显示识别置信度或时间信息。
舵机控制我用TIM3输出PWM,频率50Hz,占空比5%~10%对应0°~180°。识别成功后,STM32把舵机转到90°模拟抬杆,蜂鸣器响一声;识别失败则保持0°不抬杆,红灯亮。这个联动逻辑很简单,但很直观,能让人一眼看懂系统的工作状态。
4. PC端识别:YOLOv11车牌检测与PaddleOCR文字识别完整配置
4.1 环境准备:Python、CUDA与依赖安装
PC端识别是整个系统的智能大脑。我的环境是Windows 11 + Python 3.10 + CUDA 12.1 + PyTorch 2.2。装好Python后,建议创建独立的虚拟环境:
conda create -n plate python=3.10 -y conda activate plate pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121为什么强调CUDA版本?因为PaddleOCR的GPU版也有对应的CUDA要求,如果混用版本,paddle的CUDA算子经常会报错。我用的PaddlePaddle GPU版安装命令如下:
python -m pip install paddlepaddle-gpu==2.6.1.post112 -f https://www.paddlepaddle.org.cn/whl/windows/mkl/avx/stable.html如果你电脑没有NVIDIA显卡,也可以直接装CPU版:
pip install paddlepaddle但CPU版跑PaddleOCR的识别速度会慢很多,每张图大概多花0.5~1秒,做实时性要求高的场景就很吃力。我强烈建议有显卡的同学直接上GPU版,速度差距是数量级的。
接下来安装YOLOv11依赖和PaddleOCR:
pip install ultralytics paddleocr paddlepaddle这里提醒一下,PaddleOCR 3.x版本的接口和2.x有一些变化,尤其是predict方法和结果数据结构。如果搜网上的旧教程,很多写法会报错,建议以官方文档为准。
4.2 YOLOv11环境配置与车牌数据集准备
YOLOv11是Ultralytics出的新模型,检测精度和速度都比较均衡。安装好ultralytics库后,你需要准备车牌检测数据集。我把自己标注的800张车牌图片整理成YOLO格式,目录结构如下:
plate_dataset/ images/ train/ val/ labels/ train/ val/每张图片对应一个同名txt标签文件,格式是class x_center y_center width height,坐标是归一化到0~1的。标注工具我用的是LabelImg,画框后自动生成YOLO格式。车牌区域通常是一个横着的矩形,标注时尽量贴合车牌边缘,不要留太多白边,否则会影响PaddleOCR的识别精度。
数据集准备好后,创建一个plate.yaml:
path: D:/projects/plate_dataset train: images/train val: images/val nc: 1 names: ['license_plate']注意path最好用绝对路径,有时候相对路径会因为工作目录变化而出错。接下来开始训练:
yolo detect train data=plate.yaml model=yolov11n.pt epochs=200 imgsz=640 batch=16 device=0我用的YOLOv11n是轻量版,训练速度快,在一张RTX 3060上200个epoch大概1小时左右。如果你追求更高精度可以换yolov11s或yolov11m,但推理速度会下降。
4.3 训练好的模型做预测并保存裁剪车牌图
YOLOv11预测有两种场景:单张图片处理和摄像头实时流。我这里因为OpenMV会不断传图过来,所以主要做单张图片处理,用predict接口拿到检测框,然后把框里的区域裁剪出来保存成单独图片,再传给PaddleOCR。以下是核心代码:
from ultralytics import YOLO model = YOLO('runs/detect/train/weights/best.pt') result = model.predict('captured.jpg', conf=0.35, imgsz=640, verbose=False) boxes = result[0].boxes.xyxy.cpu().numpy() if len(boxes) == 0: print("no plate detected") else: for idx, box in enumerate(boxes): x1, y1, x2, y2 = [int(v) for v in box] crop = result[0].orig_img[y1:y2, x1:x2] cv2.imwrite(f'plate_crop_{idx}.jpg', crop)实测中我发现修剪出的车牌区域如果倾斜角度太大,OCR识别率会明显下降。所以建议在裁剪前对检测框做一个轻微的角度矫正,最简单的方式是用OpenCV的minAreaRect加仿射变换,虽然会增加一些计算量,但收益很值。
4.4 PaddleOCR 3.x安装与文字识别乱码排查
PaddleOCR在3.x版本中把OCR能力整合为PaddleOCR类,初始化参数与2.x基本一致。我的OCR初始化代码如下:
from paddleocr import PaddleOCR ocr = PaddleOCR(use_angle_cls=True, lang='ch', show_log=False)识别函数:
result = ocr.predict('plate_crop_0.jpg') # 3.x的返回结果是列表,需要逐层取数据 for res in result: for line in res: text = line['rec_texts'][0] score = line['rec_scores'][0] print(text, score)这里有个3.x版本特有的坑:ocr.ocr()在2.x中返回的是嵌套列表,3.x改成了predict(),而且返回格式变了。如果你用了网上旧教程的result[0][0][1][0]那种索引方式,很可能会报KeyError。建议打印result的结构看看。
关于乱码问题,我遇到的情况是中文车牌字符显示成方块或问号,主要原因有三个:
- 中文字体缺失:PaddleOCR识别结果在终端打印没问题,但保存到文件后用Excel打开乱码,这是编码问题,保存时强制用
encoding='utf-8-sig'即可。 - 图片分辨率太低:车牌区域裁剪后太小,OCR识别字母数字可以,但中文字符会混淆。我的经验是裁剪后的车牌宽度最好大于150像素,如果小于100,先做双线性插值放大到2倍再识别。
- 识别模型把省份简称认错,这是模型本身精度问题,可以通过增大YOLO检测框的置信度阈值来过滤低质量检测,只对高置信度图做OCR。
我最终在测试集上跑了效果:YOLOv11检测mAP50约0.93,PaddleOCR文字识别准确率约0.88,整体端到端识别成功率约0.82。识别失败主要集中在夜间低照度和车牌倾斜角度过大的情况。
5. 端到端联调:OpenMV、STM32、PC三方协同处理
5.1 完整数据流向
把三端串起来后,数据流向是这样的:
- OpenMV上电初始化,持续拍摄并计算帧差异。
- 检测到画面变化后,OpenMV抓拍JPEG图片并通过串口发送给STM32。
- STM32收到完整帧后,通过另一路串口(连接CH340)把图片字节原样转发给PC端。
- PC端Python程序监听串口,收到图片后先调用YOLOv11检测车牌区域,再调用PaddleOCR识别文字。
- PC端把识别结果字符串通过串口回传给STM32。
- STM32解析结果后,控制OLED显示、舵机和蜂鸣器。
这套数据流看起来长,但因为每步都很清晰,调试时能快速定位是哪个环节出问题。我最开始尝试在STM32上直接跑图像算法,发现死路一条,后来把识别上移到PC端,整条链路才跑通。
5.2 PC端串口监听与图像接收Python代码
PC端用Python的pyserial库监听串口。波特率要和STM32转发端一致,我用的115200,建议加一个超时保护,避免串口卡死导致线程阻塞,以下是核心代码:
import serial import cv2 import numpy as np ser = serial.Serial('COM5', 115200, timeout=1) buffer = bytearray() def extract_frame(byte_stream): # 在字节流中寻找帧头0xA5 0x5A并提取完整帧 while True: idx = byte_stream.find(b'\xA5\x5A') if idx == -1: byte_stream.clear() return None if len(byte_stream) - idx < 4: break length = int.from_bytes(byte_stream[idx+2:idx+4], 'big') if len(byte_stream) - idx >= 4 + length: frame = bytes(byte_stream[idx+4:idx+4+length]) del byte_stream[:idx+4+length] return frame break if idx > 0: del byte_stream[:idx] return None while True: data = ser.read(256) if data: buffer.extend(data) frame_bytes = extract_frame(buffer) if frame_bytes: img = cv2.imdecode(np.frombuffer(frame_bytes, np.uint8), cv2.IMREAD_COLOR) if img is not None: cv2.imwrite('captured.jpg', img) # 调用YOLO和OCR,再把结果回传这段代码里最关键的是extract_frame函数。它不只是找一次帧头,而是要在字节流中循环查找,防止出现“帧头前面残留了上一个帧的尾部垃圾数据”的情况。这个逻辑我调试了整整一个下午才跑顺。
5.3 识别结果如何回传STM32并联动外设
PC端识别完成后,生成一个类似京A12345的字符串,通过串口发给STM32。为了保险起见,我发之前会做一个简单的加密处理(其实就是加一个头尾标记),让STM32能确认指令完整性。实际发送格式如下:
0xAA 0x55 <结果长度> <结果ASCII> 0x0D 0x0ASTM32端在收到完整指令后,从缓冲区解析出结果字符串,再调用OLED_ShowString()显示。同时判断结果是否包含中文省份缩写(如“京”“沪”等),如果包含,说明检测成功,转动舵机;否则说明只检测到了字母数字或者没有检测到结果,蜂鸣器长鸣两声提示失败。这里有一个细节:UTF-8和GBK编码问题,STM32串口收到的是UTF-8字节,OLED库本身只支持ASCII字符显示,中文字符会显示成乱码。我的解决方法是只在OLED上显示字母数字部分,中文省份用拼音首字母替代,比如“京A12345”显示成“JING A12345”,这样虽然不完美,但至少用户能看懂。
如果需要完整显示中文,就得在STM32里内置中文字库,用取模软件把需要用到的汉字转成点阵数据,再自己写一个OLED_ShowCHinese()函数。这个我后面加了几个常用省份字进去,效果还不错,但工程量上来了,非必要不建议一开始就做。
6. 常见问题与排查技巧实录
联调过程中我记录了不少典型问题,这里整理成表格,方便你对照排查。
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| OpenMV串口发送后STM32收不到 | 波特率不一致;TX/RX接反 | 检查两端波特率;交叉连接TX→RX |
| STM32收到乱码或残缺帧 | 串口接收未用中断;缓冲区太小 | 改DMA+空闲中断;环形缓冲区不少于2KB |
| OpenMV画面异常偏亮/偏暗 | 白平衡和曝光未固定 | 关闭自动增益,手动设置曝光值 |
| YOLOv11检测不到车牌 | 训练数据太少;置信度阈值过高 | 增加数据量;conf降到0.25~0.3 |
| PaddleOCR识别结果乱码 | 图片分辨率太低;编码问题 | 放大裁剪图;保存文件时用utf-8-sig |
| PC端串口读取阻塞卡死 | 没有超时设置;线程同步问题 | serial.Timeout设为1;用独立线程读取 |
| OLED不显示 | I2C地址错误;上拉电阻缺失 | 扫描地址是否0x3C;外接4.7kΩ上拉 |
| 舵机抖动或无法转到位 | PWM频率不对;电压不足 | 设置50Hz频率;单独给舵机供电 |
6.1 YOLOv11小目标优化的几个实测技巧
车牌在整张画面里通常只占很小比例,YOLOv11虽然比旧版对小目标更友好,但直接拿默认参数跑还是容易漏检。我做了几个优化,效果立竿见影:
- 把
imgsz从640提到960。虽然推理时间变长,但车牌区域在特征图上的像素点数变多,检测召回率明显提升。 - 数据增强时增加
mosaic=1.0和mixup=0.2。这样模型能学到更多背景变化,不会只认某种固定场景下的车牌。 - 后处理时把
conf设为0.3、iou设为0.45。如果设太高(比如0.5),低质量检框会被滤掉,漏检率上升。
另外如果你的车牌图片存在严重倾斜,可以在YOLO训练数据中额外加入一些旋转增强,或者在预测后对检测框区域单独做透视矫正再送OCR。倾斜角度超过15度时,检测框虽然能框住,但OCR识别率会断崖式下跌。
6.2 PaddleOCR识别速度与“乱码”问题深度剖析
PaddleOCR的乱码问题其实分成“显示乱码”和“识别结果错乱”两种。显示乱码基本都是编码问题,你只需要注意Python写入文件时用utf-8-sig,以及读取时统一编码格式即可。真正头疼的是识别结果错乱,比如“京”被识别成“示”或者“就”,这跟模型在中文车牌数据上的表现有关。
我试过几种优化:一是把裁剪出来的车牌图片做二值化,增强文字对比度;二是先做一个简单的HSV颜色分析,如果是蓝底白字车牌,可以用蓝色通道掩膜把底牌区域提出来再OCR;三是用use_angle_cls=True加方向分类器,对横竖车牌都能自动纠正方向。综合这三种处理后,我的识别准确率从0.82提升到了0.87左右。
6.3 关于STM32与OpenMV的供电和稳定性
这个坑很多人会忽略:OpenMV和STM32如果共用同一个USB口供电,电流时常不够,会导致OpenMV采集图像时突然重启,或者STM32的串口丢数据。我的做法是给OpenMV单独接5V供电,STM32单独用USB或者外部电源,共地后再通信。地线必须连在一起,否则串口的电平参考不一致,数据全是乱码。
如果你在工业现场使用,建议增加光耦隔离或者RS485收发器来延长通信距离,同时抑制干扰。家用实验环境直接用TTL串口就行,线材尽量短一些,不要超过20cm,否则高频图像数据很容易被干扰。
7. 一些来自实操的经验建议
整个项目从零开始到我跑通,前前后后花了两周多时间,踩的坑几乎比写的代码还多。最后分享几个我真实体会比较深的建议。
第一,先分别测试每个模块,再联调。OpenMV自己先能拍到照片、能发串口,STM32自己先能收固定数据、能驱动OLED和舵机,PC端自己先能用单张图片跑通YOLO和OCR,然后再把它们串起来。模块都没验证就联调,出了问题根本不知道从哪查起。
第二,做毕业设计的话,不用追求识别率特别高,重点是把整个流程跑通并表现出每个环节的技术理解。答辩时老师更关心你懂不懂为什么用YOLOv11、为什么用PaddleOCR、串口协议怎么设计的,而不是你刷到了多高的准确率。
第三,这套架构里所有组件都有替代方案。OpenMV可以换成K210或者ESP32-CAM,STM32可以换成GD32,PaddleOCR也可以换成Tesseract或自训练CRNN,YOLOv11可以换成YOLOv8或RT-DETR。理解了整个数据流的逻辑,换掉任何一个组件都只是重写对应模块而已。这才是做这个项目最有价值的收获。
最后再提一个扩展方向:下次如果要做实时识别,可以考虑把PC端改成局域网传输,OpenMV通过WiFi模块把图片发到服务器识别,STM32只做云端指令的下发执行,这样就能在硬件不变的前提下显著提升整体响应速度。这个思路我已经在规划了,跑通了再来更新。