☰
Python+OpenCV+SVM+百度AI:车牌识别毕业设计实战
2026/10/1 4:18:54 网站建设 项目流程

简介:本资源是一套面向计算机相关专业学生与初学者的车牌识别毕业设计项目,基于Python3.6与OpenCV实现,融合SVM机器学习算法与百度AI平台接口,可用于毕设、课程设计或项目立项演示。项目采用图像边缘与车牌颜色双重定位车牌,字符识别核心为OpenCV自带的SVM类与StatModel类,配套svm.dat与svmchinese.dat训练数据文件;当两种方法均无法识别时,可调用百度API并保留手动触发按钮,形成完整的识别兜底方案。压缩包共171个文件,约94.55MB,包含11个py源码、31个pyc编译文件、大量jpg与png测试图片、xml配置、dat模型、sql数据库脚本及mp4演示视频,另附README说明文档,便于快速理解项目结构与运行流程。目前已有77人学习下载,代码经测试可正常运行,适合在此基础上修改扩展功能,也可作为机器学习与图像处理入门实践参考。

1. 从一张歪斜的车牌说起:Python+OpenCV+SVM+百度AI这套组合到底解决什么问题

地下车库出口,摄像头拍到的车牌往往是歪的、暗的、带反光的,甚至只露出半张。纯靠模板匹配或者纯靠云端 OCR,前者一遇到角度变化就崩,后者一断网就抓瞎。这个毕业设计标题里塞了四个东西——Python、OpenCV、SVM、百度AI平台,看起来像堆料,实际上是一条很务实的流水线:OpenCV 负责把车牌从整张图里抠出来并摆正,SVM 负责判断「这块区域到底是不是车牌」,百度AI 负责最后把字符认出来。三者各干各的活,谁也别越界。

这套方案适合谁?适合正在做计算机视觉方向毕业设计、想找一个「能跑通、能讲清原理、还能写进论文」的本科生;也适合刚入门机器学习、想搞明白传统算法和深度学习平台怎么配合的工程师。它不追求 SOTA 精度,追求的是每一环都能解释清楚、每一环都能单独调试。你不需要 GPU,一台装了 Python 的普通笔记本就能跑完整条链路。接下来我会按「环境怎么搭 → 车牌怎么定位 → SVM 怎么训练 → 百度AI 怎么接 → 坑在哪」的顺序,把这条流水线拆开讲透。

2. 环境搭建与工程骨架:把 Python、OpenCV、sklearn 一次装对

2.1 版本选择与依赖清单

这套方案对版本不算挑剔,但有几个组合是血泪经验验证过能跑通的。Python 建议 3.8~3.10,太新的 3.12 在部分 sklearn 和 opencv-python 的预编译轮子上会翻车。核心依赖就四个:opencv-python 负责图像处理,scikit-learn 提供 SVM,numpy 做矩阵运算,requests 用来调百度AI 的 HTTP 接口。

依赖包建议版本作用
Python3.8~3.10运行时
opencv-python4.5.x~4.8.x图像读取、灰度、边缘、透视变换
scikit-learn1.0.x~1.3.xSVM 训练与预测
numpy1.21.x~1.24.x特征向量运算
requests2.28+调用百度AI 车牌识别接口

安装命令一行搞定,但要注意国内网络环境下 pip 源的问题:

# 建议用国内镜像源,避免下载超时 pip install opencv-python==4.8.0.74 scikit-learn==1.3.0 numpy==1.24.3 requests==2.31.0 -i https://pypi.tuna.tsinghua.edu.cn/simple

装完之后立刻验证,别等到写代码时才发现ModuleNotFoundError: No module named 'opencv':

import cv2 import sklearn import numpy as np print("OpenCV:", cv2.__version__) print("sklearn:", sklearn.__version__) print("numpy:", np.__version__)

如果cv2导入报错,九成是装成了opencv-contrib-python和opencv-python冲突,先pip uninstall opencv-python opencv-contrib-python再重装一个即可。VSCode 里记得选对解释器,Ctrl+Shift+P→ Python: Select Interpreter,选到你 pip 安装的那个环境。

2.2 工程目录结构

毕业设计最怕代码一锅粥,答辩时老师问「你车牌定位的代码在哪」你翻半天。建议一开始就分好目录:

plate_recognition/ ├── data/ │ ├── raw/ # 原始车辆图片 │ ├── train/ # SVM 训练集(正样本/负样本) │ └── output/ # 识别结果输出 ├── models/ │ └── svm_model.pkl # 训练好的 SVM 模型 ├── src/ │ ├── preprocess.py # 图像预处理 │ ├── locate.py # 车牌定位 │ ├── train_svm.py # SVM 训练脚本 │ ├── predict.py # 主流程 │ └── baidu_ocr.py # 百度AI 接口封装 ├── config.py # 路径、API Key 等配置 └── requirements.txt

config.py里把百度AI 的 API Key 和 Secret Key 单独放,别硬编码在业务代码里,答辩演示时也不至于把密钥投到大屏幕上。

# config.py import os BASE_DIR = os.path.dirname(os.path.abspath(__file__)) DATA_DIR = os.path.join(BASE_DIR, "data") MODEL_PATH = os.path.join(BASE_DIR, "models", "svm_model.pkl") # 百度AI 开放平台申请后填入 BAIDU_API_KEY = "你的API_KEY" BAIDU_SECRET_KEY = "你的SECRET_KEY"

提示:百度AI 的 API Key 和 Secret Key 在控制台创建应用后生成,access_token 有 30 天有效期,代码里要做缓存,别每次请求都重新获取。

3. 车牌定位:用 OpenCV 把车牌从整张图里抠出来

3.1 颜色分割 + 边缘检测的定位思路

车牌定位是整个流程里最玄学的一步。常见做法有两条路:一是基于颜色的 HSV 分割,二是基于边缘和形态学的轮廓筛选。我一般两个都用,先颜色粗筛,再边缘精修。

中国车牌底色主要是蓝底白字(小型车)和绿底黑字(新能源),蓝色在 HSV 空间里特别好分离。先把图转 HSV,用inRange卡出蓝色区域,再做形态学闭运算把车牌上的字符孔洞填上,最后找轮廓、按长宽比筛选。

import cv2 import numpy as np def locate_plate_by_color(image): """基于 HSV 颜色分割定位蓝色车牌""" # 转 HSV,蓝色 H 通道大致在 100~124 hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) lower_blue = np.array([100, 80, 80]) 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_CLOSE, kernel) # 找轮廓 contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) candidates = [] for cnt in contours: x, y, w, h = cv2.boundingRect(cnt) ratio = w / float(h) area = w * h # 中国车牌长宽比约 3.14:1,放宽到 2.5~4.5 if 2.5 < ratio < 4.5 and area > 2000: candidates.append((x, y, w, h)) return candidates

逻辑说明:inRange把蓝色像素变成白色、其余变黑,得到二值掩膜;闭运算的核宽度 17 是为了横向连接被字符切断的蓝色区域,高度 5 是经验值,太大容易把相邻车身也连进来。长宽比 2.5~4.5 是给拍摄角度留的余量,严格卡 3.14 会漏掉很多斜拍样本。面积阈值 2000 是过滤小噪点,图片分辨率高的话要相应调大。

3.2 边缘检测兜底与透视校正

颜色分割对蓝牌好使,但遇到绿牌、黄牌或者光照偏色就歇菜。这时候用 Sobel 边缘检测兜底:车牌区域字符密集,垂直边缘特别丰富。

def locate_plate_by_edge(image): """基于 Sobel 垂直边缘 + 形态学定位""" gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 高斯模糊去噪,核大小必须是奇数 blur = cv2.GaussianBlur(gray, (5, 5), 0) # 只取 x 方向梯度,车牌字符垂直边缘强 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) contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) candidates = [] for cnt in contours: x, y, w, h = cv2.boundingRect(cnt) if 2.5 < w / float(h) < 4.5 and w * h > 2000: candidates.append((x, y, w, h)) return candidates

定位到候选框之后,斜拍的车牌需要做透视校正,否则字符会变形影响识别。用cv2.getPerspectiveTransform把车牌四角映射到标准矩形:

def perspective_correct(image, box): """把倾斜车牌校正为正视图""" x, y, w, h = box roi = image[y:y+h, x:x+w] # 标准车牌尺寸比例,宽 440 高 140 dst_w, dst_h = 440, 140 src_pts = np.float32([[0, 0], [w, 0], [w, h], [0, h]]) dst_pts = np.float32([[0, 0], [dst_w, 0], [dst_w, dst_h], [0, dst_h]]) M = cv2.getPerspectiveTransform(src_pts, dst_pts) return cv2.warpPerspective(roi, M, (dst_w, dst_h))

参数说明:dst_w=440, dst_h=140是常见的车牌归一化尺寸,你也可以用 136×36 这种小尺寸加快后续处理。getPerspectiveTransform需要四个点一一对应,这里简化用了矩形四角,实际斜拍场景应该先用cv2.minAreaRect拿到最小外接旋转矩形,再取它的四个顶点,校正效果才准。

注意:颜色分割和边缘检测的候选框可能重叠,实际代码里要做 NMS(非极大值抑制)去重,否则同一块车牌会送进去好几次。

4. SVM 训练:判断「这块区域是不是车牌」

4.1 为什么用 SVM 而不是直接上 CNN

标题里明确写了 SVM,这不是随便选的。SVM 在这个任务里的定位是二分类器:输入一个候选区域的特征向量,输出「是车牌」或「不是车牌」。相比 CNN,SVM 的优势是训练样本需求小、训练速度快、可解释性强——你能清楚说出用了哪些特征、权重怎么分布,答辩时好讲。缺点是特征得手工设计,泛化能力不如深度学习。

常见做法是提取 HOG(方向梯度直方图)特征喂给 SVM。HOG 能描述图像的边缘方向分布,对车牌这种字符密集、边缘丰富的目标很合适。sklearn 里没有直接的 HOG,但 OpenCV 有cv2.HOGDescriptor,或者用skimage.feature.hog。

from skimage.feature import hog import cv2 import numpy as np def extract_hog(roi): """提取 HOG 特征,输入统一 resize 到 64x64""" gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) gray = cv2.resize(gray, (64, 64)) features = hog( gray, orientations=9, # 9 个方向 bin pixels_per_cell=(8, 8),# 每个 cell 8x8 像素 cells_per_block=(2, 2),# 每 2x2 个 cell 组成一个 block block_norm='L2-Hys', # 归一化方式 visualize=False ) return features

参数说明:orientations=9是 HOG 的经典配置,覆盖 0~180 度;pixels_per_cell=(8,8)决定特征粒度,越小越细但维度越高;cells_per_block=(2,2)做局部对比度归一化,能抵抗光照变化。最终特征维度是 9×2×2×7×7=1764 维,对 SVM 来说完全能扛。

4.2 训练脚本与样本准备

训练集需要正样本(车牌区域)和负样本(非车牌区域)。正样本可以从公开车牌数据集里裁,负样本从车辆图片的非车牌区域随机裁。每类准备 500~1000 张就够 SVM 用了。

import os import cv2 import numpy as np import joblib from sklearn.svm import SVC from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report from skimage.feature import hog def load_dataset(pos_dir, neg_dir): """加载正负样本,提取 HOG 特征""" X, y = [], [] for label, folder in [(1, pos_dir), (0, neg_dir)]: for fname in os.listdir(folder): path = os.path.join(folder, fname) img = cv2.imread(path) if img is None: continue feat = hog( cv2.resize(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), (64, 64)), orientations=9, pixels_per_cell=(8, 8), cells_per_block=(2, 2), block_norm='L2-Hys' ) X.append(feat) y.append(label) return np.array(X), np.array(y) # 加载数据 X, y = load_dataset("data/train/pos", "data/train/neg") X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # 训练 SVM,RBF 核 clf = SVC(kernel='rbf', C=10, gamma='scale', probability=True) clf.fit(X_train, y_train) # 评估 y_pred = clf.predict(X_test) print(classification_report(y_test, y_pred)) # 保存模型 joblib.dump(clf, "models/svm_model.pkl")

逻辑说明:SVC用 RBF 核是因为车牌/非车牌在 HOG 特征空间里不是线性可分的,RBF 能把样本映射到高维找超平面。C=10是惩罚系数,越大越不容忍误分类,容易过拟合;gamma='scale'让 sklearn 自动按特征方差定核宽度,比手调省事。probability=True会启用概率估计,预测时能拿到置信度,方便后续做阈值过滤,但会拖慢训练速度。

训练完看classification_report,重点看正类的 recall。车牌定位阶段已经筛掉大部分负样本了,SVM 这关主要是把「长得像车牌的广告牌、蓝色贴纸」这类硬负样本干掉。如果 recall 低于 0.9,要么加正样本,要么调C和gamma。

提示:SVM 训练前一定要做特征归一化吗?HOG 自带 block 归一化,通常不需要再 StandardScaler,但如果你混入了颜色直方图等其他特征,就得统一归一化。

5. 接入百度AI 平台:字符识别的最后一公里

5.1 access_token 获取与缓存

百度AI 的车牌识别接口不是直接拿 API Key 调的,得先用 API Key 和 Secret Key 换 access_token。token 有效期 30 天,必须缓存,否则每次请求都换 token 会被限流。

import requests import time import base64 from config import BAIDU_API_KEY, BAIDU_SECRET_KEY _token_cache = {"token": None, "expire_at": 0} def get_access_token(): """获取并缓存百度AI access_token""" now = time.time() if _token_cache["token"] and now < _token_cache["expire_at"]: return _token_cache["token"] url = "https://aip.baidubce.com/oauth/2.0/token" params = { "grant_type": "client_credentials", "client_id": BAIDU_API_KEY, "client_secret": BAIDU_SECRET_KEY } resp = requests.post(url, params=params, timeout=10) data = resp.json() if "access_token" not in data: raise RuntimeError(f"获取 token 失败: {data}") _token_cache["token"] = data["access_token"] # 提前 5 分钟过期,避免边界情况 _token_cache["expire_at"] = now + data.get("expires_in", 2592000) - 300 return _token_cache["token"]

逻辑说明:expires_in默认 2592000 秒(30 天),缓存时减 300 秒留缓冲。timeout=10防止网络卡死拖垮主流程。如果返回里没有access_token,多半是 Key 填错了或者应用没开通车牌识别权限。

5.2 车牌识别接口调用与结果解析

百度AI 的车牌识别接口支持传图片 base64 或 URL。本地图片转 base64 最方便:

def recognize_plate(image_path): """调用百度AI 车牌识别接口""" token = get_access_token() url = f"https://aip.baidubce.com/rest/2.0/ocr/v1/license_plate?access_token={token}" with open(image_path, "rb") as f: img_base64 = base64.b64encode(f.read()).decode("utf-8") headers = {"Content-Type": "application/x-www-form-urlencoded"} data = {"image": img_base64} resp = requests.post(url, headers=headers, data=data, timeout=10) result = resp.json() if "words_result" in result: return result["words_result"].get("number", "") else: print("识别失败:", result) return None

参数说明:接口返回的words_result里number字段就是车牌号,color是车牌颜色。如果返回error_code,常见的有 282810(图片格式不对)、282003(缺少参数)、17(每日配额超限)。免费额度一般每天几百次,毕业设计演示够用,但批量测试时要留意配额。

把整条链路串起来的主流程:

def main(image_path): img = cv2.imread(image_path) # 1. 定位候选框 candidates = locate_plate_by_color(img) or locate_plate_by_edge(img) if not candidates: print("未检测到车牌区域") return # 2. 取面积最大的候选框 x, y, w, h = max(candidates, key=lambda b: b[2] * b[3]) roi = img[y:y+h, x:x+w] # 3. SVM 二次确认 import joblib from skimage.feature import hog clf = joblib.load("models/svm_model.pkl") feat = hog(cv2.resize(cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY), (64, 64)), orientations=9, pixels_per_cell=(8, 8), cells_per_block=(2, 2), block_norm='L2-Hys') if clf.predict([feat])[0] != 1: print("SVM 判定为非车牌区域") return # 4. 保存 ROI 并调百度AI cv2.imwrite("data/output/roi.jpg", roi) plate_no = recognize_plate("data/output/roi.jpg") print("车牌号:", plate_no)

这条链路里,OpenCV 负责「找」,SVM 负责「验」,百度AI 负责「认」。三者职责清晰,任何一环出问题都能单独替换或调试。

6. 避坑与排查:那些让毕业设计卡三天的真实问题

6.1 现象:cv2.imread 读进来是 None,后面全崩

原因:路径里有中文,或者路径写错。OpenCV 的imread在 Windows 上对中文路径支持很差,读不到不报错,直接返回 None,然后cvtColor就抛异常。

解决:用cv2.imdecode配合np.fromfile读中文路径,或者干脆把图片路径改成全英文。我一般直接改路径,省事。

def imread_chinese(path): """支持中文路径的图片读取""" return cv2.imdecode(np.fromfile(path, dtype=np.uint8), cv2.IMREAD_COLOR)

6.2 现象:SVM 训练准确率 99%,实际用起来全是误检

原因:训练集正负样本分布和实际场景不匹配。正样本都是从标准车牌数据集裁的,干干净净;负样本随机裁的非车牌区域太「简单」,SVM 学到的决策边界在实际复杂背景上不成立。

解决:负样本要「难」——从车牌定位阶段产生的误检框里裁,这些才是真正难分的样本。另外正负样本比例控制在 1:1 到 1:2 之间,别一边倒。

6.3 现象:百度AI 返回 error_code 17,配额超限

原因:免费额度用完了,或者代码里没缓存 token 导致频繁换 token 触发限流。

解决:先确认 token 缓存逻辑生效,再检查当天调用次数。批量测试时加个 sleep,别一秒发几十个请求。实在不够用就申请多个应用轮换,但毕业设计演示一般用不到。

6.4 现象:车牌定位框偏大,把车身也框进去了

原因:形态学闭运算的核太大,把车牌和车身连成一片;或者长宽比阈值放太宽。

解决:闭运算核宽度从 17 降到 13 试试,长宽比上限从 4.5 收到 4.0。另外可以在颜色分割后先做一次开运算去掉小噪点,再做闭运算。

6.5 现象:透视校正后字符被拉变形,识别率下降

原因:getPerspectiveTransform用的四个角点是矩形四角,不是车牌实际四角。斜拍时车牌在图像里是平行四边形,用矩形四角映射必然变形。

解决:先用cv2.minAreaRect拿到最小外接旋转矩形,取它的四个顶点做透视变换的源点。这一步多写十行代码,识别率能提一大截。

7. 把识别率再往上推一截:几个我踩过坑才总结出的技巧

第一个技巧是多帧投票。视频流里同一辆车会连续出现几十帧,单帧识别可能因为反光、模糊出错,但把连续 10 帧的识别结果做多数投票,准确率能明显提升。实现很简单,维护一个长度为 10 的队列,每次取众数。

from collections import deque, Counter class PlateVoter: def __init__(self, window=10): self.buffer = deque(maxlen=window) def add(self, plate_no): if plate_no: self.buffer.append(plate_no) def vote(self): if not self.buffer: return None return Counter(self.buffer).most_common(1)[0][0]

第二个技巧是SVM 置信度阈值过滤。SVC(probability=True)之后可以用predict_proba拿到概率,低于 0.7 的候选框直接丢掉,宁可漏检也别误检。误检送进百度AI 不仅浪费配额,还会返回乱七八糟的结果干扰投票。

第三个技巧是ROI 边缘留白。裁车牌区域时往外扩 5~10 个像素,百度AI 对边缘完整的车牌识别更稳。裁太紧把字符切掉一半,识别必错。

最后一个习惯:每次调完参数,固定用同一组测试图片跑一遍,记录识别率。别凭感觉说「好像变好了」,毕业设计答辩时老师问「你优化前后差多少」,你得拿得出数字。我一般建个test_results.csv,每次跑完追加一行,参数、准确率、耗时都记上,回头写论文直接引用。

这套 Python+OpenCV+SVM+百度AI 的方案,单看每一环都不新鲜,但把它们串成一条能跑通、能解释、能调优的流水线,才是毕业设计真正要交付的东西。别追求一步到位,先把最小链路跑通,再逐个环节优化。希望帮到你。

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

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

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

立即咨询