news 2026/9/17 3:41:31

基于OpenMV与Python的车牌识别系统实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于OpenMV与Python的车牌识别系统实现

简介:这是一份基于Python与OpenMV的车牌检测毕业设计资源包,面向计算机视觉、嵌入式方向的本科毕业生及入门开发者。项目围绕小车摄像头实时识别车牌展开,覆盖车牌定位与内容识别两条核心链路,并选用Haar级联作为检测方案。资源共11个文件、约314KB,包含Python主控脚本、Haar级联模型文件(cascade/xml)、图片样本与说明文档等,其中py脚本承担图像处理和检测流程,图像文件用于测试与效果展示,级联与XML提供训练好的检测模型,README可辅助快速上手。目前已有316人学习下载。借助这套资源,读者可以掌握OpenMV与Python配合的车牌检测基本思路,获得可运行的实验代码和模型文件;结合题目要求中存储拍照时间、小车图片以及100+车牌比对等发挥项,也便于在原始基础上扩展存储比对、性能优化等模块,适合作为毕业设计起步模板或课程项目参考。

1. 为什么选 Python + openmv 做车牌检测,而不是直接用电脑

车牌检测在毕业设计里属于常青树题目,但大多数同学一上来就想跑 YOLO、OpenCV + SVM,结果卡在环境配置和硬件选型上。openmv 是一块带摄像头、能跑 MicroPython 的嵌入式视觉板,型号常见的有 OpenMV4 H7 Plus,CPU 是 Cortex-M7,主频 480MHz,内存只有 1MB 左右,不能装 Linux,也不能直接 import cv2、torch。它的卖点是固件里内置了 image sensor、image processing、模板匹配等底层图像库,用 Python 脚本就能调,适合做规则明确的视觉任务。车牌识别恰好符合这个场景:车牌位置固定、背景相对简单、字符排布规律,不需要深度学习也能给出可用结果。

选 Python + openmv,本质上是把"图像采集 + 前端处理 + 算法验证"放在板端,把"数据标注 +参数调试"放在电脑端。你可以在电脑上用标准 Python 写算法原型,验证思路后再改写成 MicroPython 语法烧进板子。这样做的直接好处是:毕设答辩时能当场演示实时画面,老师看到的是"硬件在跑",而不只是 PPT 里的仿真图。另一个实际好处是成本低,openmv 板子几百块,加上舵机、显示屏、电源,整套硬件预算能控制在千元以内,适合学生课题。

这篇博文按"环境搭建 → 车牌定位 → 字符分割 → 识别与部署"的顺序展开,每步给可直接抄的代码,并标注参数含义和踩坑点。你不需要额外购买树莓派或 GPU 服务器。

2. 搭好开发链路:openmv IDE 与 PC 端 Python 环境

2.1 软件准备清单

去 openmv 官网下载 OpenMV IDE,这是官方集成开发环境,内置固件烧录、帧缓冲查看器、串行终端,Windows / macOS / Linux 都有对应安装包。IDE 自带 MicroPython 解释器,但你写的脚本最终是烧录到板子上的,不是在本机运行,所以不需要在宿主机单独装 MicroPython。PC 端 Python 环境主要用于做图像算法验证、生成阈值文件、批量处理数据集;如果只想跑通 openmv 端识别,PC 端可以临时不装任何额外库。

注意区分两套 Python:

  • MicroPython:运行在 openmv 内部,import 的是imagesensortime等内置模块
  • CPython:运行在你的电脑上,用来写验证代码,importcv2numpymatplotlib

两套环境的语法 90% 相同,但库不同。写代码时不要混用,比如在板端千万别写 import cv2。

2.2 PC 端最小环境配置命令

Windows 用户建议安装 Anaconda,它自带 numpy、matplotlib,省去手动装包。在 Anaconda Prompt 里执行:

conda create -n plate python=3.8 conda activate plate pip install opencv-python==4.5.5.64 pip install numpy matplotlib

提示:openv-python 版本不需要追新,4.5.5 足够稳定,很多教程的 API 示例基于这个版本。如果联网安装失败,换用pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple走国内镜像。

安装完成后,在 Python 交互环境里验证:

import cv2 import numpy as np print(cv2.__version__) print(np.__version__)

能打印版本号就说明图像处理环境就绪。这里再确认一下各参数含义:opencv-python是 OpenCV 的 Python 绑定包,包含核心算法和 I/O 模块;numpy是 OpenCV 底层数据容器ndarray的提供者,图像在 Python 里就是一个三维数组,后续所有操作都是改数组数值。

2.3 烧录固件与连接串口

打开 OpenMV IDE,用 USB 线连接 board,IDE 右下角会显示连接的串口号,比如 COM3 或 /dev/ttyACM0。首次连接建议先刷固件:菜单栏 Tools → Run Bootloader,选择最新固件 .dfu 文件,等待进度条完成。之后 IDE 会自动重启板子。

写一段最简测试脚本,验证链路通:

import sensor, image, time sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) sensor.skip_frames(time=2000) clock = time.clock() while(True): clock.tick() img = sensor.snapshot() print(clock.fps())

代码逻辑很直白,reset重置感光元件,set_pixformat设置像素格式为 RGB565,set_framesize把分辨率设为 320x240,snapshot抓一帧画面。把这段代码烧录到板子上之后,如果 IDE 右下角的串口终端不停输出帧率数字,比如 30 到 50,就说明摄像头、处理器、烧录链路全部正常。

clock.tick()clock.fps()是用于测帧率的内置功能,不是图像处理必需的,但调试时建议保留,因为一旦预处理算法写重了,帧率会掉到个位数,此时就要考虑减小分辨率或简化算法。

3. 车牌定位:用色块与几何特征筛出车牌区域

3.1 车牌区域的特征假设

在实际场景里,车牌不一定在画面正中,角度也可能有倾斜,因此定位算法必须结合颜色特征和几何特征。中国蓝底白字车牌的主色调是蓝色,HSV 空间的 H 值集中在 100 到 130 度左右。普通环境下的天空、蓝色衣物也会落入该区间,所以光靠颜色不够,还需要叠加面积、纵横比、矩形度三个条件。

openmv 的image.find_blobs()函数就是干这个的,它会扫描图像,把满足颜色阈值范围的像素连通成色块(blob),然后返回每个色块的外接矩形、面积、质心等属性。我们不需要自己实现连通域标记,但需要理解它的参数含义,尤其是pixels_thresholdarea_threshold这两个值,它们用来滤除噪声点。

3.2 单个色块的阈值调试脚本

先写一个只输出色块信息的脚本,把板子对着车牌,通过 IDE 帧缓冲窗口观察实时画面和色块框:

import sensor, image, time sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) sensor.skip_frames(time=2000) # 蓝色阈值,格式为 (L_MIN, L_MAX, A_MIN, A_MAX, B_MIN, B_MAX) # 这里用的是 LAB 色彩空间 threshold_blue = (20, 80, 20, 70, -40, -10) while(True): img = sensor.snapshot() blobs = img.find_blobs([threshold_blue], pixels_threshold=200, area_threshold=200, merge=True) for b in blobs: img.draw_rectangle(b.rect(), color=(255, 0, 0)) img.draw_cross(b.cx(), b.cy(), color=(0, 255, 0)) print("area=%d, w=%d, h=%d, ratio=%.2f" % (b.area(), b.w(), b.h(), b.w()/b.h()))

关键参数说明:

  • pixels_threshold=200:色块包含的像素数小于 200 时不返回,这个值滤掉细小噪点
  • area_threshold=200:色块面积阈值,单位是像素面积,如果画面噪声多就调大到 500
  • merge=True:把相邻且颜色相近的色块合并成一个整体,车牌被反光分割成两块时需要打开

b.rect()返回(x, y, w, h)元组,draw_rectangle会在帧缓冲上画红色框。当画面里出现蓝色框正好套住车牌时,记录此时打印的 w/h 比值,正常车牌宽高比在 2.5 到 3.5 之间。

3.3 加入几何校验的完整定位代码

只画框不算完成,要让板子自动确认"这是车牌",还得加判断条件。下面这版代码把面积、宽高比、矩形度三个条件同时放入:

import sensor, image, time sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) sensor.skip_frames(time=2000) threshold_blue = (20, 80, 20, 70, -40, -10) def is_plate(blob): # 车牌宽高比通常在 2.5 ~ 3.5 ratio = blob.w() / blob.h() if ratio < 2.0 or ratio > 4.0: return False # 车牌面积占画面比例,假设画面分辨率 320x240 img_area = 320 * 240 if blob.area() < img_area * 0.05: return False # 矩形度:色块面积占外接矩形面积的比例 rect_area = blob.w() * blob.h() if rect_area == 0: return False solidity = blob.area() / rect_area if solidity < 0.4: return False return True while(True): img = sensor.snapshot() blobs = img.find_blobs([threshold_blue], pixels_threshold=100, area_threshold=100, merge=True) for b in blobs: if is_plate(b): img.draw_rectangle(b.rect(), color=(255, 0, 0), thickness=2) img.draw_string(b.x(), b.y()-10, "PLATE", color=(255, 0, 0))

is_plate返回 False 的几种常见情况值得注意:ratio 小于 2.0 说明这是个竖条形色块,可能是蓝色垃圾桶;solidity 低说明色块内部有大量空洞或不规则,比如蓝色雨伞边缘;面积占比低则说明目标太远。这三个参数是定位的核心,调整它们比调颜色阈值更影响最终效果。

3.4 常见定位失败定位

  • 蓝色阈值把你衣服也框进去了:把条件调严,增加面积过滤,只保留画面下三分之一区域
  • 车牌反光导致中间断裂:把merge打开,再加大色块合并距离,find_blobs会合并边缘距离小于 10 像素的同色块
  • 误检率仍然高:把宽高比压缩到 3.0 到 3.3,很多 logo 和告示牌会因此被排除

定位完成之后,下一章做字符分割才有意义。如果定位不稳,不要急着往下走,先确认画面里红色框稳定跟随车牌。

4. 字符分割与识别:从外接矩形到逐字输出

4.1 分割前先做预处理

拿到车牌外接矩形后,要做三步预处理:旋转校正、裁剪、灰度化。openmv 的img.rotation_corr()可以修正倾斜,传入x_rotation参数,单位是度,正数逆时针旋转。但旋转会消耗大量 CPU 时间,实际毕设中如果拍摄距离固定、角度比较正,可以跳过旋转,只做裁剪。

裁剪代码模板:

# 假设上一步已经得到 b 色块对象 # 扩展 10% 边缘,避免字符贴边 x = max(0, b.x() - int(b.w()*0.1)) y = max(0, b.y() - int(b.h()*0.1)) w = min(320, b.w() + int(b.w()*0.2)) h = min(240, b.h() + int(b.h()*0.2)) # openmv 的 crop 方法返回新图像 plate_img = img.copy(roi=(x, y, w, h)) plate_img.to_grayscale() plate_img.threshold(0, 80) # 灰度值小于 80 的置为黑色,背景置为白色

crop不开新画布,返回原图的引用,所以后面如果需要独立处理,必须调用copy()threshold是把灰度图二值化,这里的 0 到 80 是像素灰度范围,低于 80 的变黑,其余变白。这是字符分割的前提,二值化好坏直接决定投影分割是否干净。

4.2 投影法分割字符

二值化之后,遍历每一列统计黑色像素数量,得到一个数组。车牌字符之间有明显的间隔,黑色像素数会先高后低,形成连续峰和谷。从谷底切分就是字符分割的经典方法。

openmv 不直接提供投影函数,但用get_pixel()遍历即可,320 列逐列扫描,计算量可以接受:

def split_chars(img): chars = [] width = img.width() height = img.height() # 垂直投影 proj = [] for x in range(width): count = 0 for y in range(height): if img.get_pixel(x, y) == 0: # 黑色像素 count += 1 proj.append(count) # 找出黑色像素数大于阈值的列区域,切分 threshold_v = 5 in_char = False start = 0 for x in range(width): if proj[x] > threshold_v and not in_char: in_char = True start = x elif proj[x] <= threshold_v and in_char: if x - start > 5: # 字符最小宽度,过滤噪点 chars.append((start, x)) in_char = False return chars

逻辑说明:proj[x]就是第 x 列的黑色像素总数,in_char用来标记是否处于字符区间;当连续多列的黑色像素数高于阈值,认为是一个字符的起点,低于时则切分。x - start > 5这一步很重要,如果不加,车牌上的一颗污点就会把字符区域切成几段,导致识别结果多出好几个"字符"。

4.3 模板匹配识别

字符分割完成后,把每个子区域归一化到固定大小,比如 20x32,然后和预先存在 SD 卡里的模板图片做像素级比对。openmv 的template.match()返回相似度分数,值越高越相似。

先做模板:

# 模板文件放在 SD 卡 /templates/0.pgm, 1.pgm ... 9.pgm # 以及 A.pgm, B.pgm ... Z.pgm templates = {} for name in "0123456789ABCDEFGHJKLMNPQRSTUVWXYZ": templates[name] = image.Image("/templates/%s.pgm" % name)

识别函数:

def recognize_char(img): best_score = 0 best_char = None for name, template in templates.items(): r = img.match(template, threshold=0.6) if r > best_score: best_score = r best_char = name return best_char

这里的threshold=0.6是相似度阈值,从 0 到 1,高于它就认为匹配成功。实际调试时,如果经常把 8 认成 B,把 0 认成 O,要先把模板图片的字体和实际拍摄字体统一。不少车牌字体是特殊的防伪字体,与电脑里的系统字体差异很大,模板最好用真实车牌字符截图生成。

4.4 中文省份简称识别的一个替代方案

中文汉字"京、沪、粤、苏"等笔画复杂,openmv 的模板匹配效果不稳定,尤其是 20x32 的小图。常见做法是不在板端识别汉字,而是把汉字区域图片直接通过串口发送给上位机,由电脑端用 OpenCV 做模板匹配或直接让用户人工确认。

这属于合理的工程取舍,因为毕设核心是车牌检测整体流程,不是丝滑的全自动识别。方案设计时可以在 openmv 端只输出 "PLATE: 苏A12345" 里的后面的字母数字,汉字对应输出一个问号或直接跳过。

5. 部署到板端:帧率优化与串口输出验证

5.1 帧率优化核心参数

板端内存只有 1MB,如果把分辨率设在 VGA(640x480),一帧原始图像就占 614400 x 2 字节,直接报内存错误。所以部署阶段必须坚持 QVGA 甚至 QQVGA(160x120)。降低分辨率是帧率提升的第一手段。

常用优化方案如下:

sensor.set_framesize(sensor.QQVGA) # 160x120 sensor.set_pixformat(sensor.GRAYSCALE) # 灰度图,占用减半 sensor.skip_frames(time=3000)

灰度图牺牲了颜色信息,但如果你确定车牌在画面中的位置固定,可以先用 RGB565 定位一次,再把整个画面切成车牌区域,后续帧只处理该区域,这样既保留了颜色定位能力,又减少扫描范围。

5.2 通过串口输出识别结果

识别结果可以直接打印到 IDE 终端,也可以发给串口。openmv 与 STM32、Arduino 通信是毕设里常见需求,使用uart模块:

from pyb import UART uart = UART(3, 115200) # 使用 UART3,波特率 115200 while(True): img = sensor.snapshot() plate_str = process_plate(img) # 假设这是定位+识别主函数 if plate_str: uart.write(plate_str.encode() + b'\r\n') print("Send:", plate_str)

UART(3, 115200)第一个参数是串口编号,具体看板子串口丝印位置;波特率要与接收端一致,否则乱码。这里encode()把字符串转成字节流,\r\n是行结束符,方便接收端逐行解析。

如果要发给电脑端 Python 程序,电脑端用serial库接收:

import serial ser = serial.Serial('COM3', 115200, timeout=1) while True: line = ser.readline() if line: print(line.decode().strip())

两边波特率必须一致,COM 口要换成实际识别出来的串口号。串口通信加上 CRC 校验更好,但简单毕设场景下,行协议已足够。

5.3 一个可用的验证脚本

把上面的逻辑合在一起,形成完整代码框架,方便你对照修改:

import sensor, image, time from pyb import UART, LED sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) sensor.skip_frames(time=2000) uart = UART(3, 115200) def plate_detect(img): threshold_blue = (20, 80, 20, 70, -40, -10) blobs = img.find_blobs([threshold_blue], pixels_threshold=100, area_threshold=100, merge=True) for b in blobs: if 2.5 < b.w() / b.h() < 4.0 and b.area() > 1500: img.draw_rectangle(b.rect(), color=(255, 0, 0)) return "SUCCESS:%d,%d" % (b.cx(), b.cy()) return None while True: img = sensor.snapshot() result = plate_detect(img) if result: uart.write((result + "\r\n").encode())

这个脚本只做定位和框选,字符分割识别需要自行嵌入第四章内容。实际调试时你会发现,b.area() > 1500这个值在 QVGA 画面里对应的是车牌占画面的合理比例,内置到is_plate会让代码更清晰。

5.4 部署后必须验证的三件事

  • 帧率不低于 15fps,否则画面卡顿严重,串口输出延迟高
  • 距离 0.5m 到 1.5m 内能稳定框出车牌,超出该范围自动不识别,不能误框
  • 连续运行 30 分钟不跑飞,内存不增长,这需要观察 IDE 的实时内存占用

如果帧率不达标,优先把分辨率改为 QQVGA,其次关闭 Ide 窗口的 JPEG 压缩预览、减少膨胀腐蚀操作次数。字符识别可以放到车定位成功后,每隔 5 帧才执行一次,识别频率降低不影响演示效果,但能明显提高响应速度。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 3:40:32

STM32实现MODBUS RTU通信的硬件时序与DMA接收关键技术

简介&#xff1a;本资源是一套基于STM32平台实现Modbus-RTU通信的完整工程实践代码&#xff0c;面向嵌入式初学者与工业通信开发人员&#xff0c;解决RS485多节点主从通信系统的设计与调试难题。工程包含主机与从机双模式功能&#xff1a;默认为主机轮询地址01从机&#xff0c;…

作者头像 李华
网站建设 2026/9/17 3:38:36

锂电鼓包的力学-电化学耦合机制与相场法建模

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 3:36:43

大模型训练环境可复用:从驱动容器到依赖锁定与自检

第一次把大模型训练环境搭起来的时候&#xff0c;几乎所有人心态都一样&#xff1a;只要python train.py能打印出第一个 loss&#xff0c;就觉得这件事已经完成了。过两周换一台机器、或者让同事接手、或者自己想复现三个月前的实验结果&#xff0c;才发现当初那套环境已经彻底…

作者头像 李华
网站建设 2026/9/17 3:31:34

抚养权协议公证书怎么写?从材料到出证,手把手教证天下操作步骤

不少夫妻解除婚姻关系之后&#xff0c;会就孩子抚养权、抚养费、探视等事项重新协商&#xff0c;签订抚养权协议。很多人不清楚抚养权协议公证书怎么写&#xff0c;办理需要准备哪些材料、完整流程是什么。本文就从概念、适用场景、所需资料&#xff0c;对比线下办理方式&#…

作者头像 李华