简介:基于Python与OpenCV实现的车牌识别系统源码,面向计算机视觉课程期末大作业及相关技术学习者,可帮助快速掌握车牌定位、字符分割与识别的完整流程。压缩包共29个文件,大小仅约13.87MB,内部包含Python预测脚本与界面模块、SVM分类模型文件、中英文训练字符数据集(7z)、多张JPG测试图片以及XML、IML等工程配置,结构划分清晰,便于对照学习。该项目曾获导师评审96分以上,代码经严格调试,下载后即可运行,适合直接作为课程设计、毕业设计的参考实现。目前已有七百余人学习下载,覆盖了从刚接触OpenCV的学生到需要快速搭建原型的研究者。通过阅读源码与数据组织,不仅能理解图像预处理、特征提取等关键环节,还能学习如何将训练好的SVM模型应用到实际车牌识别任务中,实用性与参考价值兼备。
1. 把 OpenCV 车牌识别源码包拆开:传统视觉方案为何还能拿 96 分
车牌识别在 OpenCV 与 Python 的经典组合下,一直是期末大作业和毕设的高频选题。这份“基于 Python+OpenCV 的车牌识别系统源码”,走的不是深度学习路线,而是传统计算机视觉里的三段式流程——车牌定位、字符分割、SVM 识别。它能在不依赖 GPU、不用 TensorFlow 的环境里稳定跑到 96 分,靠的是两个训练好的 SVM 模型文件(svm.dat 与 svmchinese.dat)加上一套完整的 test 实拍图验证集。对刚接触 opencv python 车牌识别的人而言,这是理解图像处理流水线的最佳起点,也是课程设计里性价比极高的参考实现。它的定位很明确:解决“怎么从一个复杂背景的车辆照片里,把车牌区域找出来、把字符读出来”这件事。
2. 系统全貌与文件角色:谁管定位,谁管识别
2.1 解压后的文件清单与运行入口
拿到压缩包后,第一件事是先看清每个文件干什么,避免瞎跑。从项目正文里能看到的主要文件有这些:
| 文件 / 目录 | 作用 |
|---|---|
| predict.py | 主入口脚本,负责加载模型、处理图片、输出识别结果 |
| surface.py | 可视化或批处理接口,适合对 test 目录批量验证 |
| svm.dat | 已训练好的字符模型,覆盖字母与数字 |
| svmchinese.dat | 已训练好的汉字模型,覆盖省份简称与中文车牌字符 |
| config.js | 参数配置文件,包含颜色空间阈值、尺寸参数等 |
| train chars2.7z | 字母与数字训练样本压缩包 |
| charsChinese.7z | 汉字训练样本压缩包 |
| test / *.jpg | 测试图片,包含不同场景的实拍车牌 |
入口是 predict.py。打开它第一步应检查里面的图片路径参数,常见实现会写成sys.argv[1]从命令行接收图片文件名,或者在代码里硬编码成test/u=1691206349.jpg这种相对路径。我一般建议先跑自带的那张测试图,确认环境通,再换成自己的图片。
2.2 三段式识别流程:定位、分割、识别的配合逻辑
这套系统不依赖神经网络,整个预测过程可以拆成三个环节。
第一步是车牌定位。读入图片后先转成 HSV 色彩空间,因为蓝色车牌和黄色车牌的色调区间相对稳定,用颜色阈值做掩膜,比直接用灰度边缘检测更抗背景干扰。常见做法是用cv2.inRange提取蓝色区域,再做形态学闭运算,把零散的车牌字符连通成一个矩形块,最后通过轮廓的宽高比和面积过滤掉非车牌区域。
第二步是字符分割。拿到车牌矩形后,转灰度、二值化,用垂直投影统计每一列的黑白像素分布,字符之间的间隔会在投影图上形成低谷,据此切出单个字符区域。切完后统一缩放到固定尺寸,比如 20×20,保证 SVM 输入维度一致。
第三步是 SVM 分类。每个字符图生成特征向量,分别送进 svm.dat(字母数字)或 svmchinese.dat(汉字)做预测。这里有个值得注意的设计:字符识别分两套模型,是因为字母数字的类别空间与汉字差异较大,混在一起训容易互相干扰。predict.py 里通常能看到两次load调用,分别加载这两个模型文件。
2.3 双模型设计的工程考量:错一个汉字比错一个字母更伤
单独说下为什么汉字要单独占一个模型文件。车牌第一位是省份简称(京、沪、粤这类),如果这个字识别错,整个车牌就完全不对了。而后续的字母和数字,比如“A”和“4”在某些字体下本来就接近,错一个还能靠上下文猜测补救,但首位汉字没有这种容错空间。
所以工程上把汉字单独训练,样本集也单独存放一份 charsChinese.7z。这种做法在资源包里体现得很清楚。训练时汉字模型的类别数是 31 个左右(省份简称),而 svm.dat 覆盖 34 个字母数字类别(有些车牌字母与数字共用字符,比如 O 和 0 只保留一个)。两套特征提取参数可能也不一样,汉字笔画密集,特征维度或者核函数参数需要单独调。这就是为什么重训时不能只动 svm.dat,必须把 svmchinese.dat 一起考虑的底层原因。
3. 环境搭好与首次运行:一张图跑通需要几步
3.1 依赖安装与 Python 版本选择
这个项目从predict.cpython-37.pyc能看出来,原开发环境是 Python 3.7。OpenCV 的版本选择有讲究,推荐装 4.x 系列,因为接口和 3.x 基本兼容,但对新系统的支持更好。
pip install opencv-python==4.5.5.64 pip install numpy逻辑说明:只装 opencv-python 和 numpy 就够跑预测流程。opencv-python 这个包自带 OpenCV 运行库,不需要单独装原生 OpenCV。版本锁到 4.5.5 是为了避免过新版本在 Windows 上出现 DLL 加载问题,如果你用 Linux 或 macOS,直接装最新版也可以。
参数说明:numpy 是 OpenCV 的底层依赖,mask 操作和矩阵运算全依赖它。缺少 numpy 时,cv2.inRange这类函数会直接报module 'numpy' has no attribute 'ndarray'之类的错,所以它必须单独确认安装成功。
3.2 解压与目录结构还原
train chars2.7z 和 charsChinese.7z 需要用 7-Zip 或 WinRAR 解压。解压后目录结构建议保持成这样:
LicensePlateRecognition/ ├── predict.py ├── surface.py ├── svm.dat ├── svmchinese.dat ├── config.js ├── train_chars/ # 由 train chars2.7z 解压得到 └── charsChinese/ # 由 charsChinese.7z 解压得到逻辑说明:预测阶段其实不需要训练样本目录,但 svm.dat 和 svmchinese.dat 必须在 predict.py 的同一级目录下,否则代码里相对路径svm.dat会找不到文件。
参数说明:解压时注意路径不要带空格和中文。Windows 下解压到C:\Users\你的用户名\Desktop\xxx这种带中文的路径,OpenCV 的imread在部分版本下有历史遗留问题,读到中文路径会直接返回 None。后面避坑章节会再展开。
3.3 运行 predict.py 并理解输出
python predict.py test/wA87271.jpg逻辑说明:如果 predict.py 内部用sys.argv[1]接收图片路径,上面的命令就会对 test 目录下的测试图执行识别。如果脚本内部不是这种写法,而是一开始就硬编码了某张图,那就直接改成python predict.py,看看默认效果。
参数说明:test 目录里那些以字母开头的 jpg(如 wA87271.jpg、cAA662F.jpg)都是不同省份、不同采集角度的真实场景图。以 w 开头的一般是蓝色车牌样例,c 开头的可能包含黄色或特殊底色车牌,跑批时最好把它们全测一遍,别只盯着一张图看效果。
3.4 config.js 里的参数到底改了有没有用
config.js 是这套代码里容易被忽略、但改错影响很大的文件。它通常保存 HSV 阈值范围、形态学核大小、字符归一化尺寸。例如这样的结构:
{ "blue_lower": [100, 43, 46], "blue_upper": [124, 255, 255], "kernel_size": [17, 3], "char_width": 20, "char_height": 20 }逻辑说明:blue_lower 和 blue_upper 是 HSV 蓝色范围的上下限。OpenCV 的 HSV 中 H(色调)范围是 0 到 180,蓝色大约在 100 到 124 之间。如果你拍的图片偏色严重,定位不到蓝色车牌,先改这里,而不是改灰度阈值。
参数说明:kernel_size 是形态学闭运算的核大小。核太宽会把两个相邻字符粘成一个区域,导致分割失败;核太窄,车牌区域连不成一整块,轮廓筛选时会被面积条件过滤掉。这个值是按 136×36 这种典型车牌尺寸设计的,如果你的输入图分辨率特别大,按比例放大核大小会更稳。
提示:改完 config.js 后要重新启动 predict.py,这个文件只在程序启动时读一次,不是热加载。
4. 训练数据与模型重训:从 96 分往 98 分走的路径
4.1 样本集结构与数量估算
train chars2.7z 里是字母数字样本,charsChinese.7z 里是汉字样本。典型结构是每个字符一个文件夹,文件夹名就是标签,里面是该字符的各种字体、各种亮度样本。比如train_chars/0/放数字 0 的样本,train_chars/A/放字母 A 的样本。
这种结构对重训非常友好。用 Python 遍历文件夹就能得到带标签的数据集,不需要手动标注。样本数量上,字母数字每类几十到一两百张属于正常量级,汉字因为笔画复杂,样本量往往需要更多。如果你发现识别率卡在某个字上,比如“鄂”和“湘”经常互相认错,优先检查这个字的样本数是否太少。
4.2 特征提取与训练脚本:HOG 还是原始像素
经典做法有两种,一种是直接把字符图 resize 到 20×20,转灰度后拉成一维 400 维向量喂给 SVM;另一种是提取 HOG(方向梯度直方图)特征。这两种方案在这个项目里都有人用,但效果差异明显。原始像素对光照变化敏感,同一个字在不同亮度下像素分布差异大;HOG 特征关注梯度方向,对光照有一定的鲁棒性。
我一般建议用 HOG,因为车牌字符是印刷体,边缘清晰,梯度特征非常稳定。下面是一个基于 OpenCV 的 HOG 特征提取加 SVM 训练的补全思路:
import cv2 import numpy as np from sklearn.svm import LinearSVC import os def load_samples(base_dir): X, y = [], [] for label in os.listdir(base_dir): label_dir = os.path.join(base_dir, label) if not os.path.isdir(label_dir): continue for fname in os.listdir(label_dir): img = cv2.imread(os.path.join(label_dir, fname), cv2.IMREAD_GRAYSCALE) if img is None: continue img = cv2.resize(img, (20, 20)) hog = cv2.HOGDescriptor((20, 20), (10, 10), (5, 5), (5, 5), 9) feat = hog.compute(img).flatten() X.append(feat) y.append(label) return np.array(X), np.array(y) X, y = load_samples("train_chars") clf = LinearSVC() clf.fit(X, y)逻辑说明:load_samples函数按文件夹名读取标签,对每张图统一 resize 到 20×20,然后用 HOGDescriptor 计算特征。HOGDescriptor 的参数(20, 20)是窗口大小,(10, 10)是块大小,(5, 5)是块步长,(5, 5)是单元格大小,9 是梯度方向数。这组参数对印刷体字符是比较通用的设定。
参数说明:如果做汉字重训,建议把窗口调大到 24×24 或 32×32,因为汉字笔画比字母数字密集,20×20 的分辨率可能丢失关键笔画细节。LinearSVC 的 C 值默认是 1.0,如果训练集不大,把 C 调小到 0.5 有助于防止过拟合。
4.3 保存模型并替换 svm.dat
训练完成后,需要把模型保存成 OpenCV 能加载的格式。sklearn 的模型不能直接被 predict.py 里的cv2.ml.SVM_load使用,需要做一层转换。常见做法是用 OpenCV 自带的 SVM 再训一遍,或者把 sklearn 的权重导出后重新构造。更省事的方案是直接用 OpenCV 的 SVM 接口训练:
svm = cv2.ml.SVM_create() svm.setType(cv2.ml.SVM_C_SVC) svm.setKernel(cv2.ml.SVM_LINEAR) svm.setC(1.0) svm.train(feat.astype(np.float32), cv2.ml.ROW_SAMPLE, labels.astype(np.int32)) svm.save("svm.dat")逻辑说明:这段代码用 OpenCV 原生 SVM 训练线性分类器,setType指定 C-SVC 分类器,setKernel指定线性核,setC是惩罚系数。训练数据必须转成float32,标签必须转成int32,OpenCV 对数据类型的要求比 sklearn 严格得多。
参数说明:训练前要把标签映射成从 0 开始的连续整数。比如字母数字共 34 类,那就映射成 0 到 33,否则 OpenCV SVM 在训练时可能报train data must be a floating-point matrix或者类别数不连续的错误。保存成 svm.dat 后,要把原文件备份再替换,因为一旦新模型效果不理想,你还有后悔药可以吃。
提示:重训 svmchinese.dat 时,样本路径换成 charsChinese 解压后的目录,其他流程完全一样。但注意汉字类别多,训练时间会比字母数字长不少,如果卡住不动,先把图片缩到 24×24 再试。
5. 避坑指南:这套系统最容易翻车的五个现场
5.1 相对路径错误:svm.dat 明明存在却报文件不存在
现象:在 PyCharm 里点击运行 predict.py,报Unable to load SVM model from svm.dat,但文件管理器里看得到 svm.dat 就在项目根目录。
原因:PyCharm 的运行目录默认为项目根目录,但如果你把 test 目录设置为 working directory,或者代码里用了os.chdir切换了当前路径,相对路径就失效了。文件在磁盘上存在,但进程的工作目录不是文件所在目录。
解决:先把 predict.py 里的模型加载路径改成绝对路径,或者统一用os.path.join(os.path.dirname(__file__), "svm.dat")定位。这个写法是从脚本所在目录出发找文件,不受当前工作目录影响。从那以后我接手任何 Python 视觉项目,第一件事就是把所有静态资源路径改成基于__file__的形式。
5.2 OpenCV 版本不同导致接口报错
现象:运行到 SVM 加载时报AttributeError: module 'cv2.ml' has no attribute 'SVM_load',或者训练时报SVM_create is not defined。
原因:OpenCV 2.x 时代,SVM 接口是cv2.SVM_create();OpenCV 3.x 之后,SVM 被挪到cv2.ml子模块下。如果环境里装的是老版本或者自定义编译版本,接口路径对不上。
解决:统一用cv2.ml.SVM_create()和cv2.ml.SVM_load()。如果项目里既有老代码又有新代码,可以在文件头部加一段兼容层判断,例如if hasattr(cv2, 'ml'): SVM = cv2.ml.SVM_create。检查当前版本用print(cv2.__version__),4.x 版本直接支持新接口,不需要额外处理。
5.3 中文车牌显示乱码:识别对但输出全是问号
现象:识别结果在控制台打印时,汉字部分显示成??或者乱码,英文字母和数字正常。
原因:控制台默认编码与 Python 输出编码不一致。Windows 中文系统的 GBK 编码与 Python 默认的 UTF-8 有冲突,print 中文字符串时被解码错误。另外 OpenCV 的putText无法直接渲染中文,如果 surface.py 里把结果画到图上,中文也会变成问号。
解决:如果只是控制台输出,在文件开头加import io, sys; sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')。如果是画到图片上,必须用 PIL 加载中文字体渲染,不能依赖 cv2.putText。这个坑在期末答辩演示时很尴尬,识别结果图上全是问号,分数直接受影响。
5.4 蓝色阈值调了半天,黄色车牌全漏检
现象:对蓝色车牌识别正常,换成黄色车牌(比如驾校教练车、部分工程车)直接定位不到,输出为空。
原因:config.js 里的 inRange 阈值只覆盖蓝色区间。如果代码里没有根据车牌颜色动态切换上下限,黄色车牌的 HSV 分量会被蓝色掩膜直接过滤掉。还有一层可能是 HSV 的 H 分量在 OpenCV 里范围是 0 到 180,而有些教程写的 Hue 范围是 0 到 360,直接套用导致阈值永远不匹配。
解决:看 config.js 里是否定义了 yellow_lower 和 yellow_upper。没有的话,通过cv2.cvtColor(img, cv2.COLOR_BGR2HSV)手动取黄色像素的 HSV 值,一般黄色 H 在 20 到 35 之间。然后修改定位逻辑,分别计算蓝色和黄色掩膜,做按位或后合并,再走后面的形态学与轮廓筛选。
5.5 样本泄漏:训练时把测试图当样本,评估分数虚高
现象:自己重训模型后,test 目录里几张图识别率接近 100%,但拿一张没见过的实拍图,识别率骤降。
原因:训练样本集和验证图片来自同一个采集来源,甚至有的测试图就是从样本库里挑出来的。这类数据在课程作业里很常见,但真实场景中车牌角度、光照、遮挡千差万别,模型泛化能力弱。
解决:重训时把样本随机划分成训练集和验证集,比例 8:2,验证集不参与拟合。每次训练完先看验证集准确率,再看 test 目录实拍准确率。如果训练集 98 分但验证集只有 85 分,这就是过拟合信号,减小 SVM 的 C 值或者增加训练样本量再试。
6. 进阶用法:把单张图识别改造成批量验证脚本
当单张图跑通后,下一步值得做的不是调参数,而是写一个批量验证脚本,把 test 目录里的二十多张图全部跑一遍,统计定位成功率和字符识别准确率。这个习惯能让你在答辩时拿出表格数据,而不是只演示三张效果好的图。
import os import subprocess test_dir = "test" results = [] for fname in os.listdir(test_dir): if not fname.endswith(".jpg"): continue fpath = os.path.join(test_dir, fname) out = subprocess.check_output(["python", "predict.py", fpath], encoding="utf-8") results.append((fname, out.strip())) print("共测试", len(results), "张") for item in results: print(item)逻辑说明:这个脚本遍历 test 目录下所有 jpg,逐个调用 predict.py,收集识别结果。它不会修改原代码,只是把 predict.py 当作一个命令行工具来驱动,适合不熟悉内部接口的快速验证。输出格式是(文件名, 识别结果)的元组列表。
参数说明:如果你的 predict.py 不接受命令行参数,也可以改用surface.py的批处理入口,或者直接在脚本里import predict后调用其内部函数。判断依据是 predict.py 里的主逻辑是否包在if __name__ == "__main__":块里,包了就能安全导入。
做完批量验证后,把识别错的图片挑出来,单独看是定位问题还是字符分割问题:用调试模式保存中间结果图(掩膜图、二值化图、分割后的每个字符图),这一步能让你在报告里写清楚每个环节的错误来源。这类传统视觉项目的通病是定位错了后面全错,但答辩老师更关心你有没有排错能力。
我从那次以后,每次拿到新的车牌数据集,都强制自己走一遍「单张 → 批量 → 错误分类 → 针对性调参」这套流程,尤其是平时开发中,这算是这套源码带给我最大的价值了。如果你也准备把这份资源用在自己的课设或毕设里,建议先按第 3 章跑通一次,再按第 4 章重训一次,最后按第 6 章做批量验证,希望帮到你。
本文还有配套的精品资源,点击获取