尧图精选

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

🕒 发布时间:2026/9/17 3:41:34 📁 来源:尧图网络
简介这是一份基于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 PlusCPU 是 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 端可以临时不装任何额外库。注意区分两套 PythonMicroPython运行在 openmv 内部import 的是image、sensor、time等内置模块CPython运行在你的电脑上用来写验证代码importcv2、numpy、matplotlib两套环境的语法 90% 相同但库不同。写代码时不要混用比如在板端千万别写 import cv2。2.2 PC 端最小环境配置命令Windows 用户建议安装 Anaconda它自带 numpy、matplotlib省去手动装包。在 Anaconda Prompt 里执行conda create -n plate python3.8 conda activate plate pip install opencv-python4.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 线连接 boardIDE 右下角会显示连接的串口号比如 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(time2000) clock time.clock() while(True): clock.tick() img sensor.snapshot() print(clock.fps())代码逻辑很直白reset重置感光元件set_pixformat设置像素格式为 RGB565set_framesize把分辨率设为 320x240snapshot抓一帧画面。把这段代码烧录到板子上之后如果 IDE 右下角的串口终端不停输出帧率数字比如 30 到 50就说明摄像头、处理器、烧录链路全部正常。clock.tick()和clock.fps()是用于测帧率的内置功能不是图像处理必需的但调试时建议保留因为一旦预处理算法写重了帧率会掉到个位数此时就要考虑减小分辨率或简化算法。3. 车牌定位用色块与几何特征筛出车牌区域3.1 车牌区域的特征假设在实际场景里车牌不一定在画面正中角度也可能有倾斜因此定位算法必须结合颜色特征和几何特征。中国蓝底白字车牌的主色调是蓝色HSV 空间的 H 值集中在 100 到 130 度左右。普通环境下的天空、蓝色衣物也会落入该区间所以光靠颜色不够还需要叠加面积、纵横比、矩形度三个条件。openmv 的image.find_blobs()函数就是干这个的它会扫描图像把满足颜色阈值范围的像素连通成色块blob然后返回每个色块的外接矩形、面积、质心等属性。我们不需要自己实现连通域标记但需要理解它的参数含义尤其是pixels_threshold和area_threshold这两个值它们用来滤除噪声点。3.2 单个色块的阈值调试脚本先写一个只输出色块信息的脚本把板子对着车牌通过 IDE 帧缓冲窗口观察实时画面和色块框import sensor, image, time sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) sensor.skip_frames(time2000) # 蓝色阈值格式为 (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_threshold200, area_threshold200, mergeTrue) 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_threshold200色块包含的像素数小于 200 时不返回这个值滤掉细小噪点area_threshold200色块面积阈值单位是像素面积如果画面噪声多就调大到 500mergeTrue把相邻且颜色相近的色块合并成一个整体车牌被反光分割成两块时需要打开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(time2000) 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_threshold100, area_threshold100, mergeTrue) for b in blobs: if is_plate(b): img.draw_rectangle(b.rect(), color(255, 0, 0), thickness2) 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, threshold0.6) if r best_score: best_score r best_char name return best_char这里的threshold0.6是相似度阈值从 0 到 1高于它就认为匹配成功。实际调试时如果经常把 8 认成 B把 0 认成 O要先把模板图片的字体和实际拍摄字体统一。不少车牌字体是特殊的防伪字体与电脑里的系统字体差异很大模板最好用真实车牌字符截图生成。4.4 中文省份简称识别的一个替代方案中文汉字京、沪、粤、苏等笔画复杂openmv 的模板匹配效果不稳定尤其是 20x32 的小图。常见做法是不在板端识别汉字而是把汉字区域图片直接通过串口发送给上位机由电脑端用 OpenCV 做模板匹配或直接让用户人工确认。这属于合理的工程取舍因为毕设核心是车牌检测整体流程不是丝滑的全自动识别。方案设计时可以在 openmv 端只输出 PLATE: 苏A12345 里的后面的字母数字汉字对应输出一个问号或直接跳过。5. 部署到板端帧率优化与串口输出验证5.1 帧率优化核心参数板端内存只有 1MB如果把分辨率设在 VGA640x480一帧原始图像就占 614400 x 2 字节直接报内存错误。所以部署阶段必须坚持 QVGA 甚至 QQVGA160x120。降低分辨率是帧率提升的第一手段。常用优化方案如下sensor.set_framesize(sensor.QQVGA) # 160x120 sensor.set_pixformat(sensor.GRAYSCALE) # 灰度图占用减半 sensor.skip_frames(time3000)灰度图牺牲了颜色信息但如果你确定车牌在画面中的位置固定可以先用 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, timeout1) 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(time2000) uart UART(3, 115200) def plate_detect(img): threshold_blue (20, 80, 20, 70, -40, -10) blobs img.find_blobs([threshold_blue], pixels_threshold100, area_threshold100, mergeTrue) 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 帧才执行一次识别频率降低不影响演示效果但能明显提高响应速度。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →