简介:这份毕业设计资源围绕Python、OpenCV与SVM机器学习算法构建车牌识别系统,并接入百度AI平台作为兜底识别方案,适合计算机、人工智能、通信工程、自动化等专业的在校学生、教师及企业员工学习参考,也可作为毕设、课程设计或项目立项的演示素材。项目采用Pycharm与Python3.6开发,数据库为MySQL,核心识别流程先用图像边缘与车牌颜色完成定位,再通过OpenCV自带SVM类与StatModel类训练svm.dat、svmchinese.dat模型识别字符,两种方法均失败时可调用百度API并保留手动触发按钮。压缩包共171个文件,约94.55MB,包含11个py源码、31个pyc编译文件、大量jpg与png测试图片、xml配置、dat模型、sql建库脚本及mp4演示视频,另附README与文档说明。目前已有77人学习,下载后可参照说明运行,遇到问题可私聊咨询并支持远程教学,便于快速理解车牌识别完整链路与SVM训练细节。
1. 从一张歪斜的车牌说起:这套 SVM 车牌识别系统到底能跑出什么结果
去年帮一个学弟看毕设,他发来一张地下车库拍的照片,车牌倾斜大概十五度,光线偏暗,蓝底白字糊成一团。他用的就是这套 Python + OpenCV + SVM 的车牌识别方案,跑完给我看结果,车牌号识别对了。我当时的第一反应是「这也能行」,因为纯靠边缘检测和颜色定位,在这种画质下通常是要翻车的。后来把源码拆开看了一遍,才明白它的设计思路:不追求单一路径的极致,而是用「颜色 + 边缘」双通道定位,再用 SVM 做字符分类,两条路都走不通的时候留了一个百度 AI 平台的接口兜底。
这套资源适合谁?计算机相关专业的在校生做毕设或课程设计,尤其是计科、人工智能、通信工程、自动化这几个方向,需要一份能跑通、有文档、有演示视频的完整项目。它不是一个工业级的车牌识别系统,而是一个教学向的、结构清晰的机器学习落地案例。你能从里面看到传统图像处理(边缘检测、颜色空间转换、形态学操作)和经典机器学习(SVM 多分类)是怎么串起来的,也能看到当传统方法失效时,怎么用第三方 API 做补充。源码里带了svm.dat和svmchinese.dat两个训练好的模型文件,省去了你自己收集字符样本、训练模型的步骤,下载完配好环境就能跑。
但我要先把话说在前面:这套代码用的是 Python 3.6 + OpenCV 的 SVM 实现,代码风格偏老,部分 API 在新版本 OpenCV 里已经变了。如果你直接pip install opencv-python装最新版,大概率会报错。下面我会把环境配置、核心流程、参数调整和踩坑记录拆开讲,让你少走弯路。
2. 环境配置与依赖安装:为什么 Python 3.6 和特定版本 OpenCV 是硬约束
2.1 版本锁定的原因:SVM 类 API 的兼容性边界
这套代码里的 SVM 实现来自 OpenCV 自带的 sample,用的是cv2.ml.SVM_create()这套接口。OpenCV 在 3.x 到 4.x 的过渡中,cv2.ml模块的命名空间和部分方法签名有过调整。代码里svm.dat和svmchinese.dat是用特定版本的 OpenCV 训练出来的,模型文件的内部格式和加载时的解析逻辑跟版本绑定。如果你用 OpenCV 4.5 以上去加载,可能会遇到cv2.error: OpenCV(4.x) ... can't load SVM model这类报错。
常见做法是:创建一个 Python 3.6 的虚拟环境,然后安装opencv-python==3.4.1.15。这个版本是很多老项目的标配,SVM 接口稳定,cv2.ml.SVM_load()能正常读取那两个 dat 文件。Python 3.6 本身在 Windows 上安装时注意勾选「Add to PATH」,不然后面在 PyCharm 里配解释器会找不到。
# 创建 Python 3.6 虚拟环境(假设你本地有 3.6 的解释器) python -m venv venv36 # Windows 激活 venv36\Scripts\activate # macOS/Linux 激活 source venv36/bin/activate # 安装锁定版本的 OpenCV 和依赖 pip install opencv-python==3.4.1.15 pip install numpy==1.16.6 pip install requests pip install mysql-connector-python这里numpy也要锁版本。OpenCV 3.4.1.15 编译时依赖的 numpy ABI 跟 numpy 1.20 以上不兼容,装最新版 numpy 会在import cv2时直接报ImportError: numpy.core.multiarray failed to import。requests是用来调百度 AI 接口的,mysql-connector-python是数据库部分用的,如果你不需要把识别记录存库,这个可以不装。
提示:如果你在 Ubuntu 上配置,
opencv-python==3.4.1.15的 wheel 包可能没有对应你系统架构的预编译版本,需要从源码编译。编译时记得装libsm6、libxext6、libxrender-dev这几个系统依赖,否则import cv2会报libSM.so.6: cannot open shared object file。
2.2 PyCharm 项目导入与解释器配置
下载完源码后,用 PyCharm 打开项目根目录。第一步是配解释器:File -> Settings -> Project -> Python Interpreter,点齿轮选Add,然后指向你刚才创建的venv36目录下的python.exe。配好之后,PyCharm 会自动索引依赖,你可以在底部 Terminal 里跑pip list确认opencv-python的版本是 3.4.1.15。
项目结构里通常会有main.py或者car_plate.py作为入口,svm.dat和svmchinese.dat放在根目录或者model/文件夹下。代码里加载模型的路径一般是相对路径,如果你移动了文件位置,记得改代码里的路径字符串。我一般会先把路径改成绝对路径跑一遍,确认模型能加载成功,再改回相对路径。
import cv2 import numpy as np # 加载 SVM 模型,注意路径要跟实际文件位置一致 svm = cv2.ml.SVM_load('svm.dat') svm_chinese = cv2.ml.SVM_load('svmchinese.dat') # 验证模型是否加载成功 print('SVM 模型加载状态:', svm.isTrained()) print('中文模型加载状态:', svm_chinese.isTrained())SVM_load()返回的是一个cv2.ml_SVM对象,isTrained()返回True说明模型文件解析正常。如果返回False或者直接抛异常,大概率是版本不匹配或者文件路径写错了。这一步验证通过之后,再往下跑识别流程,不然会在字符分类阶段报一堆莫名其妙的错误。
3. 车牌定位的双通道设计:颜色分割与边缘检测怎么配合
3.1 颜色定位:HSV 空间下的蓝色区域提取
国内车牌以蓝底白字为主,这套代码的颜色定位就是基于这个特征。它先把图像从 BGR 转到 HSV 空间,然后用cv2.inRange()做阈值分割,把蓝色区域抠出来。HSV 里蓝色的 H 通道范围大概是 100 到 124,S 通道要足够高(>43),V 通道也要够亮(>46)。这三个阈值不是拍脑袋定的,是代码里根据样本统计出来的经验值。
def color_locate(img): # 转 HSV 空间 hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 蓝色阈值范围 lower_blue = np.array([100, 43, 46]) upper_blue = np.array([124, 255, 255]) mask = cv2.inRange(hsv, lower_blue, upper_blue) # 形态学操作:先开运算去噪,再闭运算填洞 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (17, 5)) mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) return maskgetStructuringElement里的(17, 5)这个核尺寸是有讲究的。车牌是扁长形状,宽高比大概 3:1 到 4:1,所以核的宽度要明显大于高度,这样开运算才能把细碎的噪点去掉,同时保留车牌的矩形结构。如果你换成(5, 17),车牌会被腐蚀成一条竖线,后面轮廓检测就找不到正确的矩形了。
inRange出来的 mask 是二值图,白色是候选区域,黑色是背景。开运算MORPH_OPEN先腐蚀后膨胀,去掉孤立的小白点;闭运算MORPH_CLOSE先膨胀后腐蚀,把车牌内部因为字符造成的空洞填上。这两步做完,mask 上应该能看到几个比较完整的矩形块。
3.2 边缘检测定位:Sobel 算子与形态学闭操作
颜色定位在光线偏暗或者车牌褪色的情况下会失效,所以代码里还有一条边缘检测的路径。它用 Sobel 算子对灰度图做水平方向的梯度计算,因为车牌字符的垂直边缘很密集,水平梯度会形成明显的条带。
def edge_locate(img): gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 高斯模糊去噪 blur = cv2.GaussianBlur(gray, (5, 5), 0) # Sobel 水平方向梯度 sobel = cv2.Sobel(blur, cv2.CV_8U, 1, 0, ksize=3) # 二值化 _, 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) return closedcv2.Sobel的ksize=3表示用 3x3 的卷积核,1, 0表示只算 x 方向梯度。THRESH_OTSU是自动阈值,不需要手动指定,适合光照不均匀的场景。闭运算的核尺寸跟颜色定位用的一样,目的是把字符之间的间隙连起来,形成一个完整的矩形区域。
两条路径各自跑完之后,代码会对候选区域做筛选:宽高比在 2.5 到 5.5 之间、面积在合理范围内的矩形才被认为是车牌。如果两条路径都找到了候选区域,会优先选颜色定位的结果,因为颜色定位的准确率通常更高。如果颜色定位没找到,就用边缘检测的结果。如果两个都没找到,就进入百度 AI 兜底流程。
注意:边缘检测对背景复杂的图像很敏感,比如车身有大量横向条纹或者背景有类似车牌的矩形物体时,会误检。代码里没有做太复杂的误检过滤,实际跑的时候如果发现误检率高,可以调高宽高比的筛选阈值,或者加一个颜色验证步骤,对边缘检测出的候选区域再跑一次颜色判断。
4. 字符分割与 SVM 分类:从车牌区域到单个字符的识别链路
4.1 字符分割:垂直投影法与连通域分析
定位到车牌区域后,下一步是把车牌里的字符一个个切出来。代码用的是垂直投影法:对车牌二值图做垂直方向的像素统计,字符所在列的白色像素多,字符间隙的白色像素少,根据这个波谷来切分。
def segment_chars(plate_img): gray = cv2.cvtColor(plate_img, cv2.COLOR_BGR2GRAY) _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # 垂直投影 projection = np.sum(binary, axis=0) # 根据投影波谷切分字符 # 这里省略具体的波谷检测逻辑,核心是找到连续的白色像素段 chars = [] # ... 切分逻辑 ... return chars实际代码里还会先做一个「去边框」操作,把车牌的外框去掉,不然边框会被当成字符切出来。去边框的方法通常是扫描上下左右边界,找到第一个白色像素密集的行或列,然后裁剪掉。这一步如果没做好,切出来的第一个和最后一个「字符」会是边框的竖线,SVM 分类时会被识别成乱码。
切分出来的字符需要统一尺寸,一般是 20x20 或者 28x28,然后展平成一维向量,作为 SVM 的输入。代码里用的是 20x20,展平后是 400 维的特征向量。
4.2 SVM 分类:HOG 特征与多分类策略
SVM 本身是二分类器,要做多分类(数字 0-9、字母 A-Z、省份简称汉字)需要一对多或者一对一策略。OpenCV 的 SVM 实现用的是 one-vs-one,训练时每两个类别之间训练一个分类器,预测时投票决定最终类别。svm.dat里存的是数字和英文字母的分类器,svmchinese.dat里存的是省份简称汉字的分类器。
def svm_predict(svm_model, char_img): # 统一尺寸 char_resized = cv2.resize(char_img, (20, 20)) # 展平 feature = char_resized.reshape(1, -1).astype(np.float32) # 预测 _, result = svm_model.predict(feature) return int(result[0][0])predict返回的result是一个 numpy 数组,result[0][0]是预测的类别标签。这个标签跟训练时的标签编码对应,代码里会有一个映射表把标签转成实际字符。如果你自己重新训练模型,标签编码要跟映射表保持一致,不然识别结果会错位。
提示:SVM 对输入特征的尺度很敏感。代码里在展平之前没有做归一化,因为训练数据本身就是二值图(0 或 255),尺度一致。如果你换成灰度图做输入,记得先做
feature / 255.0归一化,不然预测结果会偏。
4.3 百度 AI 兜底:什么时候触发、怎么调接口
当颜色定位和边缘检测都找不到车牌,或者 SVM 分类的置信度低于阈值时,代码会触发百度 AI 的接口。触发逻辑一般是:如果定位阶段返回空,或者字符分割后字符数量不在 7 到 8 之间(普通车牌 7 位,新能源 8 位),就调用百度 API。
import requests import base64 def baidu_ocr(image_path, access_token): url = 'https://aip.baidubce.com/rest/2.0/ocr/v1/license_plate' with open(image_path, 'rb') as f: img_base64 = base64.b64encode(f.read()).decode() params = {'image': img_base64} headers = {'Content-Type': 'application/x-www-form-urlencoded'} response = requests.post(url + '?access_token=' + access_token, data=params, headers=headers) return response.json()access_token需要你自己去百度 AI 开放平台申请,代码里一般会留一个占位符或者配置文件。接口返回的 JSON 里words_result字段就是识别出的车牌号。代码里还做了一个「手动按钮」,当自动识别失败时,用户可以手动点击按钮触发百度 API 调用,这个设计在演示的时候很实用,避免现场翻车。
注意:百度 AI 的免费额度有限,车牌识别接口每天有调用次数限制。如果你在演示时频繁触发兜底流程,可能会把额度用完。建议在代码里加一个本地缓存,同一张图片第二次识别时直接读缓存结果,不要重复调接口。
5. 避坑与排查:版本、路径、编码和模型加载的五个血泪教训
5.1 坑一:ModuleNotFoundError: No module named 'cv2'
现象:在 PyCharm 里跑代码,第一行import cv2就报错,提示找不到模块。
原因:PyCharm 当前使用的解释器不是你装了 opencv-python 的那个虚拟环境。或者你在系统 Python 里装了,但 PyCharm 用的是项目自带的 venv。
解决:打开File -> Settings -> Project -> Python Interpreter,确认列表里有opencv-python。如果没有,点+号搜索安装,注意版本选 3.4.1.15。装完后重启 PyCharm 的 Terminal,再跑python -c "import cv2; print(cv2.__version__)"验证。
5.2 坑二:cv2.error: ... can't load SVM model
现象:SVM_load('svm.dat')抛异常,提示无法加载模型文件。
原因:OpenCV 版本不匹配。svm.dat是用 OpenCV 3.x 训练的,你用 OpenCV 4.x 去加载,内部格式解析失败。
解决:降级 OpenCV 到 3.4.1.15。如果已经装了 4.x,先pip uninstall opencv-python,再装指定版本。如果降级后还报错,检查svm.dat文件是否完整,有时候下载过程中文件损坏,重新下载一次。
5.3 坑三:识别结果全是乱码或者固定输出同一个字符
现象:车牌定位对了,字符也切出来了,但 SVM 分类结果全是「A」或者全是「0」。
原因:字符分割后的图像没有做二值化,或者二值化阈值不对,导致输入 SVM 的特征向量跟训练数据分布不一致。另外,如果字符图像没有统一尺寸,reshape之后的维度对不上,SVM 会输出默认类别。
解决:在segment_chars里加一步cv2.threshold,确保输出是二值图。然后在svm_predict里打印feature.shape,确认是(1, 400)。如果维度不对,检查cv2.resize的目标尺寸是不是(20, 20)。
5.4 坑四:百度 API 返回{"error_code": 110, "error_msg": "Access token invalid or no longer valid"}
现象:兜底流程调百度接口,返回 token 无效。
原因:access_token过期了。百度 AI 的 token 有效期是 30 天,过期需要重新获取。代码里如果写死了 token,过一个月就跑不通了。
解决:把 token 获取逻辑写成自动刷新。用client_id和client_secret调https://aip.baidubce.com/oauth/2.0/token接口,每次运行前先获取新 token。或者把 token 存在配置文件里,过期了手动更新。
5.5 坑五:MySQL 连接报Access denied for user 'root'@'localhost'
现象:代码里存识别记录到数据库时,连接被拒绝。
原因:数据库密码不对,或者 MySQL 服务没启动,或者用户权限不够。
解决:先确认 MySQL 服务在运行(Windows 服务管理器里看,或者net start mysql)。然后检查代码里的连接字符串,用户名、密码、数据库名是否跟本地一致。如果用的是mysql-connector-python,连接参数是host、user、password、database,不要写成db。如果还是不行,用mysql -u root -p命令行登录试试,确认密码正确。
6. 进阶技巧:用自定义样本重新训练 SVM 模型
6.1 为什么要重新训练
自带的svm.dat和svmchinese.dat是在特定样本集上训练的,对某些字体、某些光照条件下的车牌可能识别率不高。如果你要做毕设答辩,评委很可能拿一张自己拍的车牌让你现场跑,这时候自带模型的泛化能力就受考验了。重新训练一个针对你实际场景的模型,是提升识别率最直接的办法。
6.2 样本采集与标注
你需要收集至少 200 张包含车牌的图片,然后手动切出每个字符,保存成 20x20 的二值图。每个字符类别(0-9、A-Z、省份简称)至少要有 20 个样本。样本越多,模型越稳。标注的方式是:按类别建文件夹,比如train/0/、train/1/、train/A/,每个文件夹里放对应字符的图片。
import os import cv2 import numpy as np def load_train_data(root_dir): samples = [] labels = [] label_map = {} current_label = 0 for char_dir in sorted(os.listdir(root_dir)): dir_path = os.path.join(root_dir, char_dir) if not os.path.isdir(dir_path): continue label_map[current_label] = char_dir for img_name in os.listdir(dir_path): img_path = os.path.join(dir_path, img_name) img = cv2.imread(img_path, 0) if img is None: continue img = cv2.resize(img, (20, 20)) samples.append(img.reshape(1, -1).astype(np.float32)) labels.append(current_label) current_label += 1 return np.array(samples), np.array(labels), label_mapload_train_data返回三个东西:样本矩阵、标签数组、标签到字符的映射表。label_map要保存下来,预测的时候用它把 SVM 输出的数字标签转回字符。
6.3 训练参数设置与模型保存
OpenCV 的 SVM 训练有几个关键参数:kernel_type、C、gamma。对于字符识别这种非线性可分的问题,kernel_type选cv2.ml.SVM_RBF(径向基核)。C是惩罚系数,越大越容易过拟合,一般从 1 开始试。gamma是核函数的宽度参数,默认是1/特征维度,也就是1/400 = 0.0025。
def train_svm(samples, labels): svm = cv2.ml.SVM_create() svm.setType(cv2.ml.SVM_C_SVC) svm.setKernel(cv2.ml.SVM_RBF) svm.setC(1.0) svm.setGamma(0.0025) svm.setTermCriteria((cv2.TERM_CRITERIA_MAX_ITER, 1000, 1e-6)) svm.train(samples, cv2.ml.ROW_SAMPLE, labels) svm.save('svm_custom.dat') return svmsetTermCriteria里的1000是最大迭代次数,1e-6是收敛阈值。训练完成后svm.save()保存模型文件。下次跑识别时,把SVM_load的路径改成svm_custom.dat就行。
6.4 验证方法与效果对比
训练完之后,不要直接上真实场景,先用留出的测试集跑一遍准确率。把样本按 8:2 分成训练集和测试集,训练完在测试集上算准确率。如果准确率低于 90%,要么加样本,要么调C和gamma。我一般会写一个简单的验证脚本,打印混淆矩阵,看看哪些字符容易被混淆。比如「0」和「O」、「1」和「I」、「2」和「Z」这几组,在低分辨率下很容易分错,需要针对性补充样本。
从那以后我每次重新训练模型,都会强制走一遍「留出测试集 -> 算准确率 -> 看混淆矩阵」这个流程,不看到具体数字不放心。希望这套流程能帮到你,少踩几个我踩过的坑。
本文还有配套的精品资源,点击获取