简介:面向计算机视觉课程设计与期末大作业场景,基于OpenCV的车牌号码识别项目提供了可直接运行的Python源码,适合需要快速搭建完整识别流程的本科学生,也适合作为项目实战练习的入门范例。代码覆盖车牌定位、字符分割、SVM分类识别等关键环节,并附带训练好的svm.dat与svmchinese.dat模型文件,使用者无需自行准备训练数据集,解压后即可直接跑通推理过程。压缩包共25个文件,以py代码为核心,配有jpg/png测试图片、7z数据集分包、dat模型文件、js配置及txt文档说明,整体大小14.56MB,目录结构清晰、模块划分明确。目前已有192人下载学习,predict.py负责推理、surface.py提供可视化界面,test目录下多张真实车牌照片可直接验证效果,便于快速生成课程报告或答辩演示结果。资源内置的chars7z与charsChinese数据集也可供有进阶需求的读者重新训练或优化模型。
1. 为什么“可直接运行”的车牌号码识别Python代码,关键在定位
搜“车牌号码识别”能找到大量教程,但下载后能立刻跑起来的很少。多数人拿到的代码缺图片样张、缺依赖版本说明,或者定位算法本身就没调过参数,一运行就是 ModuleNotFoundError,或者弹个黑窗口什么也不输出。真正能直接运行的 OpenCV 车牌识别 Python 代码,不是把函数抄齐,而是把环境假设和图片假设都缩到最小:只依赖 opencv-python 和 numpy,自带模拟测试图,复制保存后就能看到“定位 → 分割 → 识别”的完整链路。下面这份实现适合第一次接触 OpenCV 图像处理、需要快速验证方案的读者;有五年以上经验的人,也能从 HSV 阈值和形态学核参数里看出真实场景的可调空间。
2. 车牌号码识别流程拆解:HSV 颜色空间与形态学定位原理
2.1 为什么车牌定位优先用 HSV 颜色阈值,而不是 Sobel 边缘
网上最常见的传统方案是灰度化后提取 Sobel 纵向边缘,再做闭运算和轮廓筛选。它依赖边缘密度,可车头的散热格栅、车灯、玻璃倒影都有密集竖边,候选框很容易漂到进气口或前挡风玻璃上。更稳定的先验是颜色——国内蓝底白字车牌有固定色调,HSV 空间下色相不会随光照强度剧烈变化,比 BGR 更接近人对“这是蓝色”的认知。HSV 把颜色拆成 H(色调)、S(饱和度)、V(明度)三个通道,BGR 下蓝值是(255, 0, 0),一旦光线变暗就变成(100, 0, 0),而 HSV 里的 H 基本还在 100 到 130 之间。这是定位环节最值得信耐的一条路径。
2.2 HSV 阈值参数表与不同光照下的调整
OpenCV 的 HSV 取值范围和用户习惯不同,H 是 0 到 179,S 和 V 是 0 到 255。蓝牌对应的常用区间如下。
| 车牌类型 | H 范围 | S 范围 | V 范围 | 识别场景 |
|---|---|---|---|---|
| 蓝色车牌 | 100–130 | 80–255 | 60–255 | 白天正常光照 |
| 黄色车牌 | 15–35 | 80–255 | 80–255 | 驾校、教练车 |
| 新能源渐变绿 | 40–80 | 80–255 | 80–255 | 绿牌车 |
提示:调参时先动 H,再动 S。画面偏暗就降低 V 的下界;S 下界设太低会把白色车身反光也放进来。实际项目中我一般先把 mask 用
cv2.imshow单独显示出来,确认阈值能看到完整车牌再继续。
2.3 形态学闭运算与开运算的组合:把字符区焊成一块
颜色阈值得到的 mask 里,字符笔画之间是断开的,需要形态学处理把它连成完整矩形。闭运算用cv2.MORPH_CLOSE先膨胀后腐蚀,能把字符间隙填平;开运算是先腐蚀后膨胀,负责去掉面积小的噪点。核的选择很关键:宽 15、高 5 的矩形核,宽度略大于单个字符笔画间距,高度接近车牌字符高度,才能既填住缝隙又不把周围区域涂黑。对小尺寸车牌,核太宽会把旁边色块也并进来;核太窄则缝隙合不上。先闭后开是车牌定位最常用的组合,顺序不要调反。
2.4 候选框筛选:面积与宽高比双条件
形态学处理后的轮廓还包含干扰色块,必须用车牌几何先验过滤。车牌长宽比通常在 1.8 到 6.0 之间,小于 1.8 的是方块色块,大于 6.0 的可能是路牌或横幅。简单做法是遍历所有轮廓,先用cv2.boundingRect拿到外接矩形,再对面积和比例打分:满足比例区间的候选给高权重,最终取分数最高的那个,而不是contours[0],因为轮廓列表的先后顺序和面积无关。
for cnt in contours: x, y, w, h = cv2.boundingRect(cnt) if w < 60 or h < 20: continue area = w * h ratio = w / h score = area * (1.0 if 2.0 <= ratio <= 5.5 else 0.2)3. 基于 OpenCV 的车牌号码识别 Python 代码:定位、分割与模板匹配
3.1 完整可运行脚本
下面是整份代码,保存为plate_recognition.py,运行python plate_recognition.py即可。没有测试图时会自动生成一张模拟场景图,保证首次运行也能看到完整流程。
import cv2 import numpy as np import string TEMPLATE_CHARS = string.digits + string.ascii_uppercase def make_demo_image(): """生成一张包含蓝色车牌的模拟场景图,用于在没有真实照片时跑通流程""" bg = np.random.randint(80, 120, size=(360, 640, 3), dtype=np.uint8) plate = np.zeros((80, 260, 3), dtype=np.uint8) plate[:] = (255, 0, 0) # BGR 蓝色 cv2.putText(plate, "SU 12345", (14, 52), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 255, 255), 2) bg[120:200, 190:450] = plate noise = np.random.normal(0, 8, bg.shape) bg = np.clip(bg.astype(np.int16) + noise, 0, 255).astype(np.uint8) return bg def locate_plate(img): """通过 HSV 颜色阈值 + 形态学处理 + 轮廓筛选定位车牌区域""" hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) mask = cv2.inRange(hsv, (100, 80, 60), (130, 255, 255)) kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 5)) mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) best, best_score = None, 0 for cnt in contours: x, y, w, h = cv2.boundingRect(cnt) if w < 60 or h < 20: continue area = w * h ratio = w / h score = area * (1.0 if 2.0 <= ratio <= 5.5 else 0.2) if score > best_score: best_score = score best = (x, y, w, h) if best is None: return None, None x, y, w, h = best return img[y:y + h, x:x + w], (x, y, w, h) def split_chars(plate): """对定位到的车牌区域做二值化,用连通域切出单个字符""" gray = cv2.cvtColor(plate, cv2.COLOR_BGR2GRAY) blur = cv2.GaussianBlur(gray, (3, 3), 0) _, thresh = cv2.threshold(blur, 127, 255, cv2.THRESH_BINARY_INV) contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) chars = [] for cnt in contours: x, y, w, h = cv2.boundingRect(cnt) if h > plate.shape[0] * 0.3 and w > 2 and w / h < 0.8: chars.append((x, y, w, h, thresh[y:y + h, x:x + w])) chars.sort(key=lambda c: c[0]) return chars def generate_templates(): """用 OpenCV 自带字体生成数字与字母模板""" templates = {} for ch in TEMPLATE_CHARS: img = np.zeros((60, 40), dtype=np.uint8) cv2.putText(img, ch, (5, 44), cv2.FONT_HERSHEY_SIMPLEX, 1.2, 255, 2) templates[ch] = img return templates def match_char(cell, templates): """将待识别字符与每个模板求绝对差,取均值最小的作为结果""" cell = cv2.resize(cell, (40, 60)) best_char, best_score = '?', float('inf') for ch, tpl in templates.items(): tpl = cv2.resize(tpl, (40, 60)) diff = cv2.absdiff(cell, tpl).mean() if diff < best_score: best_score = diff best_char = ch return best_char if __name__ == "__main__": img = cv2.imread("car.jpg") if img is None: print("[提示] 未找到 car.jpg,使用内置模拟图跑通流程") img = make_demo_image() plate, rect = locate_plate(img) if plate is None: print("未定位到车牌,请检查 HSV 阈值") else: templates = generate_templates() chars = split_chars(plate) plate_no = "".join(match_char(cell, templates) for _, _, _, _, cell in chars) print(f"定位坐标: {rect}") print(f"分割字符数: {len(chars)}") print(f"识别结果: {plate_no}") cv2.rectangle(img, (rect[0], rect[1]), (rect[0] + rect[2], rect[1] + rect[3]), (0, 0, 255), 2) cv2.imshow("result", img) cv2.imshow("plate", cv2.resize(plate, (plate.shape[1] * 2, plate.shape[0] * 2))) cv2.waitKey(0) cv2.destroyAllWindows()3.2 定位函数的筛选参数说明
| 参数 | 当前取值 | 作用 | 调参建议 |
|---|---|---|---|
w < 60 | 60 像素 | 过滤宽度太小的噪点 | 图像小就降到 40 |
h < 20 | 20 像素 | 过滤高度太小的色块 | 车牌在画面中占比较小时重点调这里 |
| 宽高比区间 | 2.0–5.5 | 匹配车牌长宽比 | 新能源车牌比例更大,可放宽到 6.0 |
| 形态学核 | (15, 5) | 横向填平字符缝隙 | 核宽约为图像中车牌宽度的 1/20 |
3.3 模板匹配识别的工作原理与局限
代码先把分割出的字符统一resize成 40×60,再用cv2.putText生成 0-9、A-Z 的模板,逐对计算cv2.absdiff的均值,差值最小就是识别结果。这个方法不依赖第三方 OCR 库,所以能“直接运行”。但要认清边界:模拟图的字符和模板都出自同一个FONT_HERSHEY_SIMPLEX,识别率自然高;真实车牌使用专用印刷字体,笔画粗细和模板差异明显,模板匹配的准确率会明显下降。所以这份代码的意义在于把定位和分割的关键步骤跑通,字符识别部分到真实场景再替换为 OCR 或深度模型。
4. 运行这份车牌识别 Python 代码:环境准备、测试图片与常见报错排查
4.1 最小环境安装命令
需要 Python 3.7 及以上版本,然后安装两个依赖包。opencv-python提供cv2模块,numpy负责数组操作,模板匹配里大量用到np.zeros和np.absdiff的底层计算,这两个缺一不可。
pip install opencv-python numpy安装速度慢时,可以临时指定国内镜像源。装完用一条命令确认版本,能打印出 4.x 就说明环境正常。
python -c "import cv2; print(cv2.__version__)"4.2 首次运行的三种场景
把代码保存为plate_recognition.py,当前目录下放一张清晰的车辆前脸照片并命名为car.jpg,直接运行python plate_recognition.py。如果目录里没有这张图,程序会自动生成模拟场景图继续执行,这是这份代码能做到“直接运行”的关键设计。运行后终端会依次输出定位坐标、分割字符数和识别结果,屏幕上会弹出两个窗口:一个画了红色矩形框的整图,一个放大的车牌区域。按任意键关闭窗口,程序正常退出。
4.3 常见报错与排查思路
| 现象 | 原因 | 处理方式 |
|---|---|---|
ModuleNotFoundError: No module named 'cv2' | 依赖没装,或 pip 装到了另一个 Python 环境 | 重新执行pip install opencv-python,用pip -V检查当前环境路径 |
| 输出“未定位到车牌” | HSV 阈值不匹配,或被测图不是蓝牌 | 单独显示mask中间结果,观察蓝色区域是否完整 |
| 分割字符数超过 8 个 | 铆钉、边框、车牌汉字被切碎 | 收紧split_chars中h > plate.shape[0] * 0.3的高度条件 |
| 窗口弹出但按键没反应 | 终端焦点不在图像窗口上 | 先点击图像窗口再按键;或改用cv2.imwrite保存结果代替窗口显示 |
注意:
cv2.waitKey(0)会无限等待按键,这是 OpenCV 程序“看起来卡住”的最常见原因。调试阶段建议改为cv2.waitKey(2000),让窗口自动关闭。
5. 从模拟图到真实车牌:三个立马上手的扩展方向
5.1 老牌的扩展:黄色车牌与新能源绿牌
蓝色阈值只能覆盖蓝牌,真实停车场里还有黄色和新能源绿色车牌。做法是把cv2.inRange执行两次,分别用黄色区间(15, 80, 80)-(35, 255, 255)和绿色区间(40, 80, 80)-(80, 255, 255),最后把两个 mask 用cv2.bitwise_or合并。注意黄牌是黑字黄底,二值化时不能直接套THRESH_BINARY_INV,需要先判断车牌主色,黑字场景改用THRESH_BINARY,否则字符区域会反白,模板匹配全盘失效。
5.2 中文省份字的处理:模板匹配的边界
国内车牌第一位是省份简称汉字,第二位是发牌机关字母,后面五位是数字和字母。模板匹配不擅长处理汉字,因为字模库要覆盖几十个省份简称,工作量成倍增长。常见做法是做一套省份汉字模板,利用“第一位必是省份”的位置先验,把匹配范围限制在 31 个汉字里,而不是全部字符集。更省力的方案是只保留 OpenCV 定位与分割,识别部分换成 PaddleOCR 或 Tesseract,由 OCR 直接吃字符序列,这一步替换后准确率会有一个明显跃升。
5.3 实时视频流:先缩图再跳帧
要处理摄像头视频,把主流程包进VideoCapture循环即可,但要注意性能。常见做法是先把每一帧缩到 640 宽度再送入定位函数,这会显著减少 HSV 转换和形态学计算量;定位到车牌后再把裁剪区域放大做字符分割,等于把算力集中在 ROI 上。识别帧率不需要拉满,每 5 帧做一次完整识别已经足够覆盖大多数车辆通行场景。这样组合下来,普通笔记本 CPU 也能跑到 10 帧以上的处理速度,并且省下的算力还能留出一部分给后续的追踪模块。
本文还有配套的精品资源,点击获取