ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于OpenCV与SVM的车牌识别大作业实战:从环境配置到二次开发

基于OpenCV与SVM的车牌识别大作业实战:从环境配置到二次开发 简介这份资源是面向计算机视觉初学者与高校学生的车牌识别系统完整源码基于Python与OpenCV实现可作为期末大作业、课程设计或自学练手项目。项目已通过导师评审并获得96分以上成绩代码经过严格调试下载后可直接运行帮助读者省去从零搭建环境与调试的繁琐过程。压缩包共29个文件约13.87MB包含2个核心Python脚本predict.py、surface.py负责识别主流程配套svm.dat与svmchinese.dat两个训练模型文件以及15张jpg测试车牌图片、4个xml项目配置文件和2个7z字符训练集压缩包另含少量js、pyc等辅助文件目录结构清晰便于按模块阅读与二次开发。目前已有721人学习下载适合希望快速理解车牌定位、字符分割与SVM识别全流程的读者参考借鉴。1. 拆开这份 96 分车牌识别大作业它到底能跑出什么结果先说结论这不是一个教学 demo而是一份能直接交差、能二次开发的完整工程。压缩包里塞了predict.py、surface.py两个核心脚本外加svm.dat、svmchinese.dat两个训练好的模型文件还有chars2.7z、charsChinese.7z两套字符数据集。换句话说作者把训练和推理两条链路都留在了包里你拿到手不用从零攒数据。它解决的是最典型的场景给定一张含车牌的图片自动定位车牌区域、做字符分割、再逐字符识别最后拼出车牌号。技术栈就是 Python OpenCV识别环节用的是 SVM支持向量机不是现在流行的深度学习那一套。这一点很关键——意味着它对环境要求低、CPU 就能跑、依赖少特别适合课程设计、期末大作业、或者想快速理解传统车牌识别流程的人。如果你指望它做工业级高精度识别那得先看清它的边界后面章节会讲。适合谁正在做计算机视觉课程设计的学生、想拿一份能跑通的 OpenCV 实战代码练手的初学者、以及需要传统方案做对比基线的工程师。不适合谁想直接上生产、要求 99% 识别率、要处理复杂光照和倾斜车牌的团队。2. 环境与依赖把 Python、OpenCV 和模型文件摆到位2.1 为什么这份代码对版本敏感传统 OpenCV 项目最容易翻车的地方不是算法是版本。这份代码里出现了predict.cpython-37.pyc说明作者当年用的是 Python 3.7 编译的字节码。这不代表你非得用 3.7但它暗示了一个事实代码里用到的 OpenCV API 大概率是 3.x 到 4.x 早期那一批比如cv2.findContours的返回值个数、cv2.minAreaRect的角度定义在不同大版本之间是有差异的。我一般会建议用 Python 3.8 到 3.10 之间的版本配 OpenCV 4.5 左右的版本兼容性最稳。太新的 OpenCV4.8 以上有些老 API 虽然还在但行为细节变了容易出玄学问题。numpy 是 OpenCV 的硬依赖装 OpenCV 时会自动带上不用单独操心。2.2 依赖安装与目录结构确认先建虚拟环境别往系统 Python 里怼这是血泪经验——一旦版本冲突卸载都卸不干净。# 创建并激活虚拟环境Windows 用 venv\Scripts\activate python -m venv lpr_env source lpr_env/bin/activate # 安装核心依赖指定版本区间避免踩坑 pip install opencv-python4.5,4.8 numpy装完先验证 OpenCV 能不能正常导入这一步能提前暴露 90% 的环境问题import cv2 import numpy as np print(OpenCV 版本:, cv2.__version__) print(numpy 版本:, np.__version__) # 简单验证读一张包里的测试图确认路径和编码没问题 img cv2.imread(test/wA87271.jpg) if img is None: print(图片读取失败检查路径或文件是否损坏) else: print(图片尺寸:, img.shape) # 应为 (高, 宽, 3)逻辑说明cv2.imread返回None是最常见的翻车点原因通常是路径写错、中文路径、或者文件本身损坏。包里的test目录下有一批测试图wA87271.jpg、wATH859.jpg、lLD9016.jpg等拿它们做冒烟测试最合适。参数上img.shape返回三元组第三个维度是 3 表示彩色图如果是灰度图会是 2这能帮你判断后续处理流程对不对。2.3 模型文件与数据集的摆放包里的svm.dat和svmchinese.dat是训练好的模型前者大概率是英文字符数字的分类器后者是中文省份简称的分类器。这两个文件必须和predict.py在同一工作目录下或者代码里写死了相对路径。如果你移动了文件位置要么改代码里的路径要么保持原结构。chars2.7z和charsChinese.7z是字符训练集解压后是大量单字符小图。如果你只是想跑推理这两个压缩包可以先不解压但如果你想重新训练模型就必须解压并按代码预期的目录结构组织。常见做法是解压到train/目录下具体子目录名要看predict.py里读数据的部分怎么写的。提示先别急着改代码。把整个包解压到一个纯英文、无空格的路径下比如D:\lpr_project中文路径是 OpenCV 在 Windows 上的经典坑imread直接返回None排查半天以为是代码问题。3. 推理链路拆解从一张图到车牌号的完整流程3.1 车牌定位颜色分割 形态学传统车牌识别的第一步是定位。这份代码走的是颜色特征路线——中国车牌底色以蓝、黄为主蓝色车牌蓝底白字是最常见的。核心思路是把图像转到 HSV 空间用蓝色的 H 分量范围做掩膜再配合形态学操作把车牌区域连成一块。import cv2 import numpy as np def locate_plate(img): # 转 HSVHSV 比 RGB 更适合做颜色分割 hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 蓝色车牌的 HSV 范围这两个区间是经验值 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) # 车牌宽高比大致在 2.5 到 5 之间面积太小的直接丢 ratio w / float(h) if 2.0 ratio 5.5 and w 80: candidates.append((x, y, w, h)) return candidates逻辑说明inRange是颜色分割的核心lower_blue和upper_blue这两个数组决定了什么颜色被保留。H 分量在 OpenCV 里范围是 0-179不是 0-360蓝色大致落在 100-124。S 和 V 的下限设 80 是为了排除灰白和过暗区域。形态学闭运算的 kernel 尺寸(17, 5)是横向拉长的因为车牌是扁的这样能把字符之间的缝隙填上而不至于把相邻区域误连。参数怎么调如果你的测试图里车牌偏紫或偏青H 范围要相应移动如果图片整体偏暗V 的下限要降低。这一步没有万能参数得拿包里的测试图反复试。ratio的 2.0-5.5 是经验区间中国蓝牌标准比例约 3.14但拍摄角度会让它变形所以放宽。3.2 字符分割投影法切出每个字符定位到车牌后下一步是把车牌区域里的字符一个个切出来。传统做法是二值化 垂直投影——字符在垂直方向上的像素分布会有明显的波峰波谷波谷就是字符之间的间隙。def split_chars(plate_img): # 转灰度再二值化Otsu 自动找阈值 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) / 255 # 找波谷作为切分点这里用简单阈值判断 chars [] in_char False start 0 for i, val in enumerate(projection): if val 2 and not in_char: in_char True start i elif val 2 and in_char: in_char False # 宽度太窄的当噪声丢掉 if i - start 5: chars.append(binary[:, start:i]) return chars逻辑说明THRESH_OTSU会自动计算一个让前景背景分离最开的阈值比手动写死 127 靠谱得多。垂直投影np.sum(binary, axis0)把每一列的像素值加起来白色是 255除以 255 就得到该列的白色像素个数。阈值val 2表示这一列至少有 3 个白像素才算字符的一部分这个 2 是抗噪用的太小会把噪点当字符太大又会把细笔画切掉。常见问题是中文省份简称比如京沪笔画复杂投影波谷不明显容易和后面的字母数字粘连。这份代码用svmchinese.dat单独处理中文说明作者在分割阶段可能对第一个字符做了特殊处理或者用固定宽度切第一块。具体逻辑要看predict.py里的实现不同版本处理方式不一样。3.3 SVM 识别模型怎么加载、怎么预测切出字符后每个字符小图要缩放到固定尺寸SVM 要求输入维度一致然后展平成向量喂给模型。def recognize_char(char_img, model): # 统一缩放到 20x20这个尺寸要和训练时一致 char_img cv2.resize(char_img, (20, 20)) # 展平成一维向量SVM 的输入格式 feature char_img.flatten().astype(np.float32) # 预测返回标签 _, result model.predict(feature.reshape(1, -1)) return int(result[0])逻辑说明cv2.resize的尺寸必须和训练模型时用的尺寸完全一致否则特征分布对不上预测结果全是错的。这是新手最容易忽略的点——拿到模型直接用不查训练尺寸。flatten()把 20x20 的二维数组拉成 400 维向量reshape(1, -1)是因为 SVM 的predict要求输入是二维矩阵样本数 × 特征数哪怕只有一个样本也要包一层。model.predict返回两个值第一个是结果这里用_忽略第二个是预测标签。标签到字符的映射关系存在代码里某个字典中通常是{0: 0, 1: 1, ..., 10: A, ...}这种。如果你发现识别出来的字符全是乱的先检查这个映射字典和训练时的标签顺序是否一致。4. 避坑与排查跑不起来时先看这几条4.1 现象imread返回 None图片读不进来原因路径含中文或空格、文件实际不存在、或者 OpenCV 在 Windows 上对某些编码的路径支持有问题。这是最高频的翻车点。解决把项目移到纯英文路径下用绝对路径测试。如果必须用中文路径改用cv2.imdecode(np.fromfile(path, dtypenp.uint8), cv2.IMREAD_COLOR)绕过。4.2 现象findContours报错说返回值数量不对原因OpenCV 3.x 返回三个值image, contours, hierarchyOpenCV 4.x 返回两个值contours, hierarchy。代码是按某个版本写的你的版本不匹配。解决看报错信息如果是 too many values to unpack说明代码按 3.x 写的但你装了 4.x把_, contours, _ 改成contours, _ 。反过来就加一个下划线。4.3 现象模型加载失败或预测结果全是同一个字符原因svm.dat是用特定版本的 OpenCV 训练的版本差异可能导致加载后内部结构不兼容或者特征维度对不上。解决先确认cv2.ml.SVM_load能正常返回对象再检查输入特征的维度是否和训练时一致。如果模型实在加载不了用包里的chars2.7z数据集重新训练一个训练脚本通常在predict.py里有对应函数。4.4 现象车牌定位框歪了或者框到了非车牌区域原因HSV 颜色范围不匹配你的测试图或者形态学 kernel 尺寸不合适。解决把中间过程的 mask 图用cv2.imshow显示出来肉眼确认蓝色区域提取对不对。这一步别省调参全靠看中间结果。kernel 尺寸根据图片分辨率调整图大就加大。4.5 现象中文省份识别不出来只出字母数字原因中文和英文用了两个模型代码里可能只加载了svm.dat没加载svmchinese.dat或者中文分割逻辑没走通。解决检查代码里是否同时加载了两个模型文件以及第一个字符是否走了中文识别分支。中文模型的特征尺寸可能和英文不同别混用。5. 二次开发与验证怎么确认它真的靠谱5.1 用批量测试图做回归验证包里的test目录给了十几张测试图这是现成的验证集。别只跑一张就下结论写个循环全跑一遍统计识别正确的比例。import os import cv2 test_dir test correct 0 total 0 for fname in os.listdir(test_dir): if not fname.lower().endswith((.jpg, .png)): continue img cv2.imread(os.path.join(test_dir, fname)) if img is None: continue # 这里调用你的完整识别流程 plate full_pipeline(img) print(f{fname} - {plate}) total 1 print(f共处理 {total} 张)逻辑说明这个脚本本身不判断对错因为文件名不一定是真实车牌号。它的价值在于批量跑通暴露那些单张测试时发现不了的崩溃点——比如某张图尺寸特别大导致内存爆、某张图颜色异常导致定位失败。先把不崩做到再谈准不准。5.2 换数据集重新训练的思路如果你想提升识别率最直接的办法是拿chars2.7z和charsChinese.7z重新训练。流程是解压数据集 → 读入每张字符图 → 缩放到统一尺寸 → 提取特征通常是像素展平或 HOG→ 打标签 → 喂给cv2.ml.SVM_create()训练 → 保存模型。关键参数是 SVM 的核函数和惩罚系数 C。字符识别这种线性可分性较好的任务用线性核cv2.ml.SVM_LINEAR通常就够C 取 1 到 10 之间试。核函数选 RBF 会慢很多收益不一定明显。5.3 一个容易被忽略的验证技巧把定位、分割、识别三个阶段的中间结果都存成图片人工过一遍。定位阶段看 mask 和候选框分割阶段看切出来的字符小图识别阶段看每个字符的预测标签。哪一步错了问题就锁定在哪一步不用瞎猜。我自己的习惯是每次改完参数先跑三张最有代表性的图一张标准蓝牌、一张偏暗的、一张有倾斜的对比中间结果有没有变好。三张都过了再跑全量。从那以后我每次调传统 CV 项目的参数都强制走一遍中间结果可视化省下的排查时间比写代码还多。希望这份拆解能帮你把这份大作业真正跑起来、改起来而不是解压完就吃灰。本文还有配套的精品资源点击获取
返回列表