简介这份毕业设计资源围绕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) ... cant load SVM model这类报错。常见做法是创建一个 Python 3.6 的虚拟环境然后安装opencv-python3.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-python3.4.1.15 pip install numpy1.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-python3.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 到 124S 通道要足够高43V 通道也要够亮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, ksize3) # 二值化 _, 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的ksize3表示用 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, axis0) # 根据投影波谷切分字符 # 这里省略具体的波谷检测逻辑核心是找到连续的白色像素段 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, dataparams, headersheaders) 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: ... cant 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 rootlocalhost现象代码里存识别记录到数据库时连接被拒绝。原因数据库密码不对或者 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」这几组在低分辨率下很容易分错需要针对性补充样本。从那以后我每次重新训练模型都会强制走一遍「留出测试集 - 算准确率 - 看混淆矩阵」这个流程不看到具体数字不放心。希望这套流程能帮到你少踩几个我踩过的坑。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?