ARTICLE DETAIL

资讯详情

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

基于Python和OpenMV的车牌识别系统:从图像采集到字符识别全流程

基于Python和OpenMV的车牌识别系统:从图像采集到字符识别全流程 简介这份资源面向嵌入式视觉与智能小车方向的初学者及竞赛选手提供一套基于Python与OpenMV的车牌识别系统源码用于解决摄像头实时采集小车车牌、记录拍照时间与车辆图片等需求。压缩包共12个文件约316KB包含2个py脚本、1个cascade级联文件与配套xml、若干jpg与png示例图以及README说明和license其中Haar Cascades负责车牌检测脚本承担图像处理与数据存储逻辑。资源描述提到发挥部分可存储100个以上车牌进行比对适合作为课程设计、电子竞赛或毕业设计的起步参考。目前已有1958人学习下载读者可据此理解车牌定位与识别的基本流程参考级联模型与Python代码的配合方式并在此基础上扩展识别精度与存储管理功能。1. 从一块 OpenMV 摄像头说起车牌识别为什么值得自己搭一套很多人第一次接触车牌识别是从停车场闸机、小区门禁或者高速收费站开始的。商业方案一套动辄几千上万而实际上一块 OpenMV 摄像头加一块单片机再配上一台跑 Python 的电脑就能把「拍图—定位车牌—分割字符—识别字符」这条链路完整跑通。这正是「基于 Python 和 OpenMV 车牌识别系统」这个标题真正指向的东西OpenMV 负责在边缘端做图像采集和轻量预处理Python 负责在主机端做算法验证、字符识别和界面展示两者通过串口通信串起来。它适合三类人一是想入门嵌入式视觉的学生二是需要做课程设计或毕业设计的开发者三是想给现有硬件加一个低成本识别模块的工程师。整套方案的核心难点不在「识别」本身而在图像质量、车牌定位的鲁棒性以及 OpenMV 与主机之间的数据协议设计。下面按理论、实现、联调、优化的顺序把这条链路拆开讲清楚。2. OpenMV 端图像采集与车牌定位的 Python 实现2.1 OpenMV 固件与开发环境准备OpenMV 本质上是一块跑 MicroPython 的摄像头模块常见型号是 H7、H7 Plus、M7。它自带 IDE也可以配合 VSCode 使用。刷固件是第一步常见做法是通过 OpenMV IDE 的「工具—运行引导加载程序」完成选对应型号的固件文件即可。刷完后在 IDE 里连上设备能看到帧缓冲画面就说明环境正常。如果你习惯用 VSCode需要装 OpenMV 官方扩展然后在设置里指定 OpenMV IDE 的安装路径扩展会调用底层的调试工具。这一步容易踩的坑是串口被占用OpenMV IDE 和 VSCode 扩展不能同时连接同一块板子否则会报「设备忙」。另外OpenMV 的 MicroPython 和桌面 Python 语法接近但不完全一致cv2、numpy这类库在 OpenMV 上是没有的图像处理要用它自带的sensor、image模块。# OpenMV 端初始化摄像头并抓取一帧 import sensor, image, time sensor.reset() # 复位摄像头 sensor.set_pixformat(sensor.RGB565) # 彩色格式便于后续颜色阈值分割 sensor.set_framesize(sensor.QVGA) # 320x240兼顾速度和精度 sensor.skip_frames(time2000) # 等待感光元件稳定 clock time.clock() while True: clock.tick() img sensor.snapshot() # 抓取一帧 print(clock.fps()) # 打印帧率评估实时性这段代码是所有后续处理的基础。set_pixformat选 RGB565 是因为车牌定位常用颜色阈值灰度图会丢掉颜色信息set_framesize选 QVGA 是速度和分辨率的折中如果车牌在画面里占比很小可以升到 VGA但帧率会明显下降。skip_frames不能省否则前几帧白平衡没稳定颜色阈值会失效。2.2 用颜色阈值和矩形检测定位车牌区域车牌定位的常见做法有两类一类是基于颜色的阈值分割蓝色车牌找蓝色区域绿色新能源车牌找绿色区域另一类是基于边缘和形态学的矩形检测。OpenMV 上更实用的是颜色阈值加find_blobs因为它的算力有限跑复杂边缘检测会掉帧。# 蓝色车牌阈值(L_min, L_max, A_min, A_max, B_min, B_max) blue_threshold (0, 100, -20, 40, -60, -10) while True: img sensor.snapshot() blobs img.find_blobs([blue_threshold], pixels_threshold200, # 最小像素数过滤噪点 area_threshold200, # 最小面积 mergeTrue) # 合并相邻色块 for b in blobs: # 车牌长宽比大致在 2:1 到 5:1 之间 ratio b.w() / b.h() if 2.0 ratio 5.5 and b.w() 60: img.draw_rectangle(b.rect(), color(255, 0, 0)) img.draw_cross(b.cx(), b.cy())find_blobs返回的是色块对象pixels_threshold和area_threshold用来过滤小噪点mergeTrue会把挨得近的色块合并成一个这对车牌这种有字符间隔的区域很关键。长宽比过滤是核心车牌本身是扁矩形如果检测到的色块接近正方形多半是误检。实际调试时阈值不是拍脑袋定的要在 IDE 的「帧缓冲区」里用阈值编辑器实时拖滑块看着画面里车牌被框住为止。提示不同光照下蓝色阈值差别很大室内暖光和室外阴天的 A、B 分量能差 20 以上。稳妥做法是存两套阈值根据画面平均亮度切换。2.3 把车牌区域裁剪出来并通过串口发送定位到车牌后OpenMV 要做的是把这块区域裁剪出来压缩后通过串口发给主机。直接发原始图像会太慢常见做法是发灰度图或者二值化后的图分辨率压到 100x30 左右足够主机端做字符分割。import sensor, image, time from pyb import UART uart UART(3, 115200) # 使用 UART3波特率 115200 while True: img sensor.snapshot() blobs img.find_blobs([blue_threshold], pixels_threshold200, mergeTrue) for b in blobs: ratio b.w() / b.h() if 2.0 ratio 5.5 and b.w() 60: plate img.copy(roib.rect()) # 裁剪车牌区域 plate plate.resize(100, 30) # 统一尺寸 gray plate.to_grayscale() # 转灰度减少数据量 uart.write(gray.compress(quality70)) # 压缩后发送 breakroi参数是 region of interest即感兴趣区域copy会返回一个新的图像对象。resize到固定尺寸是为了让主机端不用再处理缩放。compress用的是 JPEG 压缩quality 越低数据越小但细节损失越大70 是个常用起点。串口波特率 115200 是 OpenMV 的常规上限再高容易丢包。发送前最好加一个帧头比如先发两个字节0xAA 0xBB主机端靠帧头对齐数据否则串口流里很难判断一帧从哪开始。3. 主机端 Python 车牌字符分割与识别3.1 串口接收与图像还原主机端用 Python 的pyserial接收数据用numpy和cv2还原图像。这里的关键是协议解析OpenMV 发的是压缩后的 JPEG 字节流主机端要先按帧头切分再解码。import serial import numpy as np import cv2 ser serial.Serial(COM3, 115200, timeout1) # Windows 下是 COMxLinux 下是 /dev/ttyUSBx buffer bytearray() def read_frame(): global buffer while True: data ser.read(1024) if data: buffer.extend(data) # 查找帧头 0xAA 0xBB idx buffer.find(b\xAA\xBB) if idx -1: continue # 帧尾用 0xCC 0xDD 标记 end buffer.find(b\xCC\xDD, idx) if end -1: continue payload buffer[idx2:end] buffer buffer[end2:] arr np.frombuffer(payload, dtypenp.uint8) img cv2.imdecode(arr, cv2.IMREAD_GRAYSCALE) return imgpyserial的read是阻塞读timeout1保证没数据时不会卡死。buffer累积数据是因为串口一次读到的可能只是半帧。帧头帧尾的设计让解析变得确定找到0xAA 0xBB就是帧开始找到0xCC 0xDD就是帧结束。cv2.imdecode直接把字节流解码成灰度图省去写临时文件。如果解码返回None说明这帧数据不完整直接丢弃重来。3.2 用 OpenCV 做字符分割拿到灰度车牌图后字符分割是识别的前提。常见流程是二值化、去噪、垂直投影、按投影谷值切分。def split_chars(gray): # 自适应二值化应对光照不均 binary cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 15, 8) # 开运算去噪 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2)) binary cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) # 垂直投影统计每列白色像素数 projection np.sum(binary, axis0) / 255 # 找字符边界 chars [] in_char False start 0 for i, v in enumerate(projection): if v 2 and not in_char: in_char True start i elif v 2 and in_char: in_char False if i - start 5: # 过滤太窄的噪声 chars.append(binary[:, start:i]) return charsadaptiveThreshold比全局阈值更适合车牌因为车牌常有反光和阴影。THRESH_BINARY_INV让字符变成白色前景方便投影统计。projection是每列白点数量字符所在列白点多字符间隙白点少谷值就是切分点。阈值v 2是经验值太小会把噪点当字符太大会把细笔画字符漏掉。切出来的字符宽度要过滤太窄的肯定是噪声。这套方法对标准蓝牌效果好对倾斜或污损车牌需要先做透视校正。3.3 字符识别模板匹配与轻量 CNN 的取舍字符识别有两条路模板匹配和深度学习。模板匹配实现简单把 0-9 和 A-Z 做成模板逐个算相似度适合字符规整、字体固定的场景。缺点是换字体或光照变化就崩。轻量 CNN 比如 LeNet 改版准确率高很多但需要训练数据和推理框架。方案准确率依赖适用场景模板匹配70%-85%无课程设计、固定字体轻量 CNN92%-98%训练数据实际部署、多字体第三方 OCR95%网络/大模型快速验证模板匹配的核心代码就是cv2.matchTemplate把每个字符图和模板库逐个比对取最高分。轻量 CNN 可以用 PyTorch 训练一个 3 层卷积的小网络输入 20x40 灰度图输出 34 类10 数字加 24 字母去掉 I 和 O 避免混淆。训练数据可以自己用 OpenMV 采集也可以找公开的车牌数据集。实际项目里如果只是演示模板匹配够用如果要落地CNN 是必须的。注意车牌里字母 I 和 O 通常不会出现因为容易和 1、0 混淆。做分类时把这两类去掉能减少误识别。4. OpenMV 与 STM32 通信及系统联调4.1 串口协议设计与 STM32 端接收OpenMV 和 STM32 通信是这套系统里最容易出问题的环节。常见做法是 OpenMV 做识别把结果字符串通过串口发给 STM32STM32 负责控制舵机抬杆或者驱动显示屏。协议要简单明确比如每帧固定 8 字节帧头 2 字节、车牌字符 6 字节、校验 1 字节。// STM32 端串口接收中断示例HAL 库 uint8_t rx_byte; uint8_t frame[9]; uint8_t idx 0; void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (idx 0 rx_byte ! 0xAA) return; // 等帧头 frame[idx] rx_byte; if (idx 9) { if (frame[1] 0xBB) { // 校验帧头 // frame[2..7] 是车牌字符frame[8] 是校验和 process_plate(frame 2); } idx 0; } HAL_UART_Receive_IT(huart, rx_byte, 1); // 重新开启接收 }STM32 端用中断接收每来一个字节进一次回调。idx是状态机的核心只有收到0xAA才开始记录收到完整 9 字节后校验帧头再处理。HAL_UART_Receive_IT必须在回调末尾重新调用否则只能收一次。校验和可以用简单的异或也可以加 CRC8看对可靠性要求。波特率两边必须一致OpenMV 设 115200STM32 也要设 115200差一点都收不到正确数据。4.2 联调时帧率与延迟的实测方法联调阶段最常遇到的是「识别到了但延迟大」或者「偶尔丢帧」。测延迟的方法是在 OpenMV 端翻转一个 GPIO主机端收到数据时也翻转一个 GPIO用示波器看两个上升沿的时间差。没有示波器的话可以在 OpenMV 发送前打印时间戳主机端收到后打印时间戳两者相减就是端到端延迟。帧率方面OpenMV 在 QVGA 下跑find_blobs大概能到 20-30 FPS加上串口发送会降到 10-15 FPS。如果帧率不够优先降分辨率到 QQVGA160x120或者缩小find_blobs的搜索区域只在画面下半部分找车牌。丢帧多半是串口缓冲区溢出主机端读取速度跟不上解决办法是提高波特率到 921600或者降低发送频率比如每 3 帧发一次。4.3 常见误检与漏检的排查清单误检和漏检是车牌识别绕不开的问题。排查时按这个顺序看先看 OpenMV 的帧缓冲画面确认车牌有没有被框住再看阈值是不是合适光照变了阈值要跟着调然后看长宽比过滤是不是太严把真车牌滤掉了最后看串口数据有没有完整到达主机。现象可能原因处理框住非车牌区域颜色阈值过宽收紧 A、B 分量范围车牌框不住阈值过窄或光照暗放宽阈值或补光字符切分错位二值化噪声多加开运算或调阈值串口收不到数据波特率不一致两端统一 115200识别结果乱码帧头对齐错检查帧头帧尾逻辑这张表基本覆盖了 80% 的现场问题。实际调试时建议在 OpenMV 端加一个 LED 指示检测到车牌就亮这样不用连电脑也能判断定位有没有成功。5. 提升识别率的几个实用技巧5.1 用透视校正解决倾斜车牌OpenMV 装在侧面时拍到的车牌是梯形的直接分割字符会错位。解决办法是找车牌的四个角点做透视变换。OpenMV 的find_blobs返回的min_corners()能给出色块的最小外接四边形角点用img.warp或者主机端cv2.getPerspectiveTransform校正。# 主机端透视校正 def correct_perspective(img, corners): # corners 是四个角点顺序左上、右上、右下、左下 src np.float32(corners) dst np.float32([[0, 0], [100, 0], [100, 30], [0, 30]]) M cv2.getPerspectiveTransform(src, dst) return cv2.warpPerspective(img, M, (100, 30))getPerspectiveTransform需要源点和目标点一一对应顺序错了图会翻转。目标尺寸设成 100x30 是为了和前面的分割流程对齐。校正后字符变成竖直的投影分割的准确率能提升 20% 以上。5.2 多帧投票降低偶发误识别单帧识别难免有偶发错误比如把 8 认成 B。多帧投票的思路是连续识别 5 帧取出现次数最多的结果作为最终输出。实现上用一个字典统计每个字符的出现频率超过 3 次才确认。from collections import Counter def vote(results): # results 是最近 N 帧的识别结果列表 counter Counter(results) char, count counter.most_common(1)[0] if count 3: return char return None # 票数不够继续等Counter的most_common(1)直接给出最高频项。阈值 3 是经验值帧率 10 FPS 时大概 0.3 秒出结果对闸机场景够用。如果要求更快可以降到 2但误识别率会上升。这个技巧对固定摄像头场景特别有效因为车牌在画面里的位置和角度基本不变多帧结果高度一致。5.3 把识别结果落到可视化界面最后一步是把结果展示出来。用 Python 的tkinter或者PyQt做一个简单界面左边显示 OpenMV 传来的车牌图右边显示识别结果和历史记录。tkinter自带不用装额外库适合快速验证。import tkinter as tk from PIL import Image, ImageTk root tk.Tk() root.title(车牌识别系统) label_img tk.Label(root) label_img.pack() label_text tk.Label(root, text等待识别..., font(Arial, 20)) label_text.pack() def update(img, text): im Image.fromarray(img) photo ImageTk.PhotoImage(im) label_img.configure(imagephoto) label_img.image photo # 必须保留引用否则图不显示 label_text.configure(texttext) root.mainloop()ImageTk.PhotoImage必须保留引用否则会被垃圾回收导致界面空白这是tkinter的经典坑。update函数在主循环里被调用传入最新的车牌图和识别文本。界面不用做太复杂能实时看到图和结果就够了。如果要加历史记录用一个Listbox追加就行。整套系统跑通后从 OpenMV 采集到界面显示端到端延迟能控制在 200 毫秒以内对大多数门禁场景足够。本文还有配套的精品资源点击获取
返回列表