Python+OpenCV车牌识别源码实战:从跑通到优化
2026/9/23 5:35:01 网站建设 项目流程

简介:这是一套面向计算机视觉初学者与课程设计/毕业设计需求者的车牌识别实战源码包,基于Python与OpenCV实现,覆盖车牌定位、字符分割与识别等核心流程,适合想动手理解传统图像处理方案、或需要可运行项目参考的开发者。压缩包共30个文件、约14.02MB,包含2个Python主脚本(predict.py、surface.py)负责推理与界面交互,配套svm.dat、svmchinese.dat等训练模型数据,以及chars2.7z、charsChinese.7z字符集压缩包;另有16张jpg测试图片、4个xml配置文件和pyc、js等辅助文件,目录结构清晰,便于直接运行与二次调试。目前已有399人学习下载。读者可据此获得一套完整的车牌识别实现思路,包括SVM分类模型调用、字符样本组织方式与多张实拍车牌图的测试用例,既能用于快速验证效果,也方便在此基础上替换数据集、调整参数或迁移到其他OCR场景,是入门OpenCV项目实践的实用参考。

1. 车牌识别这套 Python+OpenCV 源码,为什么值得先跑通再谈优化

很多人第一次接触车牌识别,是从一段几十行的 OpenCV 灰度加边缘检测代码开始的,跑完发现框都框不准,更别说识别字符。这套基于 Python+OpenCV 的车牌识别系统源码加数据,价值不在于算法多先进,而在于它把「定位 → 分割 → 字符识别」整条链路和训练数据一起给全了:predict.py负责推理,surface.py负责界面,svm.datsvmchinese.dat是训练好的 SVM 模型,chars2.7zcharsChinese.7z是字符训练集,train目录里还有一批真实车牌图。它适合两类人:一类是想搞懂车牌识别完整流程、拿现成数据练手的 Python 和 OpenCV 学习者;另一类是课程设计、嵌入式视觉项目里需要快速搭一个可演示原型的从业者。下面按「资源是什么 → 怎么跑起来 → 坑在哪 → 怎么调」的顺序拆开讲,每一步都尽量给到能直接抄的命令和参数。

2. 拆开压缩包先看结构:predict.py、surface.py 与两个 SVM 模型怎么配合

2.1 目录里每个文件到底干什么

拿到SYS.zip解压后,核心目录是LicensePlateRecognition-master。先别急着运行,把关键文件认全,后面排错会快很多。

文件/目录作用是否必须
predict.py单张图片推理入口,加载模型并输出车牌字符必须
surface.py图形界面,调用推理逻辑做交互演示可选
svm.dat英文+数字字符的 SVM 训练模型必须
svmchinese.dat中文省份简称的 SVM 训练模型必须
chars2.7z英文数字字符训练集训练时需要
charsChinese.7z中文省份字符训练集训练时需要
train/一批真实车牌测试图,如wA87271.jpgcar3.jpg测试用
config.js界面相关配置界面用
.idea/PyCharm 工程配置,可忽略

predict.pysurface.py是两条入口:前者适合命令行批量验证,后者适合演示。两个.dat是已经训练好的模型,意味着你不训练也能直接推理,这是这套资源最省事的地方。

2.2 环境依赖与 OpenCV 安装的常见坑

这套代码基于 Python 3.7 左右的环境(目录里有predict.cpython-37.pyc),依赖主要是 OpenCV 和 NumPy。安装时最容易翻车的是cv2装不上或版本不匹配。

# 建议用虚拟环境隔离,避免污染全局 python -m venv lp_env # Windows 激活 lp_env\Scripts\activate # Linux / macOS 激活 source lp_env/bin/activate # 安装核心依赖,opencv-python 不要装太新的版本 pip install opencv-python==4.5.5.64 pip install numpy==1.21.6

逻辑说明:opencv-python提供cv2模块,车牌识别里的灰度化、高斯模糊、Sobel 边缘、形态学闭运算全靠它;numpy负责矩阵运算。参数上,4.5.5.64是较稳的版本,新版 OpenCV 某些 API 行为有变化,容易让老代码报错。如果pip install opencv-pythonimport cv2ModuleNotFoundError: No module named 'opencv',八成是装到了别的解释器里,用python -c "import sys; print(sys.executable)"确认当前解释器路径,再pip install一次。

提示:Anaconda 用户如果conda环境里没有 cv2,优先用pip install opencv-python而不是conda install opencv,后者包名和版本经常对不上。

2.3 用 predict.py 跑通第一张图

环境好了,先拿train目录里的图做单张推理,确认模型能加载。

# 进入项目根目录 cd LicensePlateRecognition-master # 直接运行推理脚本,按脚本内约定传入图片路径 python predict.py

如果predict.py里图片路径是写死的,打开文件找到类似cv2.imread('...')的行,改成你要测的图:

import cv2 from predict import predict # 假设推理函数名为 predict # 读取一张测试图,路径按实际调整 img = cv2.imread('train/wA87271.jpg') # 调用推理,返回识别出的车牌字符串 result = predict(img) print('识别结果:', result)

逻辑说明:cv2.imread读入 BGR 图像,predict内部一般会做灰度化、边缘检测、轮廓筛选、透视校正、字符分割,再分别用svm.datsvmchinese.dat分类。参数上,图片路径别带中文和空格,OpenCV 在部分平台对中文路径支持不好,读进来是None,后面全崩。跑通这一步,说明模型和依赖都没问题,再去看界面。

3. 车牌定位与字符分割:OpenCV 形态学参数怎么调才不飘

3.1 定位流程背后的原理

车牌识别的第一步是从整张图里把车牌区域抠出来。常见做法是:灰度化 → 高斯模糊去噪 → Sobel 或 Canny 提取垂直边缘(车牌字符垂直边缘密集)→ 形态学闭运算把字符连成块 → 按长宽比和面积筛选轮廓 → 透视变换校正。这套源码的定位逻辑基本沿这条线走,理解它才能知道参数该动哪里。

import cv2 import numpy as np # 读图并转灰度 img = cv2.imread('train/car3.jpg') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 高斯模糊,核大小取奇数,5x5 适合多数车牌图 blur = cv2.GaussianBlur(gray, (5, 5), 0) # Sobel 提取垂直边缘,ksize=3 是常用值 sobel = cv2.Sobel(blur, cv2.CV_8U, 1, 0, ksize=3) # 二值化,阈值 0 配合 OTSU 自动选取 _, binary = cv2.threshold(sobel, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # 形态学闭运算,把断裂的字符边缘连成矩形块 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (17, 5)) closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)

逻辑说明:GaussianBlur的核越大越平滑,但太大会把字符边缘也糊掉,5x5 是平衡点。Sobelksize=3对细边缘敏感,车牌字符间距小,用 3 合适。morphologyEx的闭运算核(17, 5)是关键参数:宽度 17 是为了把同一行字符横向连起来,高度 5 控制纵向连接,核太宽会把相邻车牌或背景连进来,太窄则字符连不成块。这一步调不好,后面轮廓筛选全是噪声。

3.2 轮廓筛选与透视校正

闭运算后找轮廓,按车牌的长宽比(国内蓝牌约 440:140,接近 3:1)和面积过滤。

# 找外轮廓 contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: x, y, w, h = cv2.boundingRect(cnt) ratio = w / float(h) # 长宽比在 2.5 到 4 之间,且面积够大,才当候选车牌 if 2.5 < ratio < 4 and w > 100: plate = img[y:y+h, x:x+w] cv2.imwrite('plate_crop.jpg', plate)

逻辑说明:RETR_EXTERNAL只取最外层轮廓,减少内部字符轮廓干扰。ratio范围 2.5 到 4 覆盖了蓝牌和部分新能源牌的比例,w > 100过滤掉小噪点。实际图里如果车牌倾斜,boundingRect得到的是外接正矩形,会带进背景,这时需要cv2.minAreaRect加透视变换校正。参数上,长宽比范围要根据你的图源调整,卡太死会漏检,放太宽会误检。

3.3 字符分割的投影法

定位到车牌后,把字符一个个切开。常用垂直投影:对二值车牌图按列求和,字符处投影值高,字符间隙投影值低,据此切分。

# 车牌区域转灰度并二值化 plate_gray = cv2.cvtColor(plate, cv2.COLOR_BGR2GRAY) _, plate_bin = cv2.threshold(plate_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # 垂直投影:按列统计白色像素 projection = np.sum(plate_bin, axis=0) / 255 # 找字符起止列,阈值取投影最大值的 0.1 倍左右 threshold = projection.max() * 0.1 in_char = False for i, v in enumerate(projection): if v > threshold and not in_char: start = i in_char = True elif v <= threshold and in_char: cv2.imwrite(f'char_{start}.jpg', plate_bin[:, start:i]) in_char = False

逻辑说明:np.sum(axis=0)把每列白色像素数累加,字符笔画多的列值大。阈值取最大值的 0.1 倍是经验值,太低会把噪点当字符,太高会漏掉笔画细的字符。分割出的每个char_*.jpg再送进 SVM 分类。中文省份字符和英文数字用不同模型,别搞混。

4. 避坑与排查:跑这套源码最容易翻车的五个地方

4.1 现象:cv2.imread返回 None,后面全报错

原因:图片路径含中文、空格,或路径根本不对。OpenCV 在 Windows 上对非 ASCII 路径支持差。 解决:把图片放到纯英文路径下,或改用cv2.imdecode(np.fromfile(path, dtype=np.uint8), cv2.IMREAD_COLOR)读中文路径。

4.2 现象:模型加载报UnpicklingError或维度不匹配

原因:svm.dat是用特定版本 sklearn/OpenCV 训练的,你环境里的库版本差异导致反序列化失败。 解决:确认svm.dat的加载方式,如果是 sklearn 的 SVM,用对应版本的joblib.load;版本对不上就重训,用chars2.7z解压后的数据跑一遍训练脚本。

4.3 现象:定位框把整个背景都框进来

原因:形态学闭运算核太大,或轮廓筛选的长宽比范围太宽。 解决:把getStructuringElement的核从(17, 5)调小到(13, 5)试,长宽比范围收紧到 2.8 到 3.5。

4.4 现象:字符分割多切或少切

原因:垂直投影阈值不合适,或车牌二值化时反了(白底黑字 vs 黑底白字)。 解决:先cv2.imshow看一眼plate_bin,确认字符是白色;阈值从 0.1 倍调到 0.15 倍试,多切就调高,少切就调低。

4.5 现象:中文省份识别成英文或乱码

原因:中文和英文用了同一个模型,或svmchinese.dat没加载。 解决:确认代码里第一个字符走svmchinese.dat,其余走svm.dat;中文训练集charsChinese.7z解压后类别标签要和模型输出索引对齐。

注意:.idea/__pycache__/是工程和缓存文件,换机器跑之前删掉__pycache__,避免旧字节码干扰。

5. 从能跑到好用:用 train 目录做批量验证与模型微调

5.1 批量跑 train 目录,统计识别率

单张跑通不代表稳定,用train里那批图做批量验证,才能看出真实水平。

import os import cv2 from predict import predict correct = 0 total = 0 # 遍历 train 目录下所有 jpg for fname in os.listdir('train'): if not fname.lower().endswith('.jpg'): continue img = cv2.imread(os.path.join('train', fname)) if img is None: continue result = predict(img) total += 1 # 文件名里若含真实车牌,可对比;这里只打印 print(fname, '->', result) print('共处理', total, '张')

逻辑说明:os.listdir遍历目录,endswith('.jpg')过滤非图片。真实项目里文件名往往不含真值,要人工核对或另建标注表。跑完看哪些图识别错,错的那几张就是调参和补数据的依据。参数上,批量跑时注意内存,图多就分批。

5.2 用 chars2.7z 和 charsChinese.7z 重训 SVM

如果现成模型在你的场景下识别率低,用自带训练集重训是最直接的提升手段。

# 解压训练集 7z x chars2.7z -o chars2 7z x charsChinese.7z -o charsChinese

解压后每个字符一个子目录,目录名就是标签。训练时读取所有图,统一缩放到固定尺寸(如 20x20),展平成特征向量,喂给sklearn.svm.SVC

import os import cv2 import numpy as np from sklearn.svm import SVC import joblib X, y = [], [] for label in os.listdir('chars2'): d = os.path.join('chars2', label) if not os.path.isdir(d): continue for f in os.listdir(d): img = cv2.imread(os.path.join(d, f), cv2.IMREAD_GRAYSCALE) if img is None: continue img = cv2.resize(img, (20, 20)) X.append(img.flatten()) y.append(label) # 训练 SVM,核函数用线性或 RBF clf = SVC(kernel='linear') clf.fit(np.array(X), np.array(y)) joblib.dump(clf, 'svm_retrained.dat')

逻辑说明:resize到 20x20 是为了统一特征维度,flatten把二维图变一维向量。SVC(kernel='linear')对字符这种小图够用,样本多可换 RBF。训练完joblib.dump保存,替换原来的svm.dat。参数上,C值默认 1.0,识别率低可调大,但太大容易过拟合。

5.3 一个具体技巧:先做透视校正再分割

很多识别错误根源在车牌倾斜。定位到候选区域后,用cv2.minAreaRect拿到最小外接旋转矩形,再做透视变换拉正,字符分割会稳很多。

rect = cv2.minAreaRect(cnt) box = cv2.boxPoints(rect) box = np.int0(box) # 取宽高,构造目标矩形 w, h = int(rect[1][0]), int(rect[1][1]) dst = np.array([[0, 0], [w-1, 0], [w-1, h-1], [0, h-1]], dtype='float32') M = cv2.getPerspectiveTransform(box.astype('float32'), dst) warped = cv2.warpPerspective(img, M, (w, h))

逻辑说明:minAreaRect返回带角度的矩形,boxPoints拿到四个角点,getPerspectiveTransform算变换矩阵,warpPerspective拉正。参数上,目标宽高直接用旋转矩形的宽高,别硬编码。拉正后再走投影分割,字符切分准确率明显提升。

我自己的习惯是,每次拿到这类车牌识别源码,先不碰算法,先把train目录批量跑一遍,把错例截图存下来,再决定是调形态学参数还是重训模型。这套资源的好处是数据和模型都齐,省了找训练集的功夫,但 OpenCV 版本和路径这两个坑几乎每次都要踩一遍,提前按上面的排查清单过一遍能少走很多弯路。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询