简介基于Python与OpenCV开发的车牌识别系统面向毕业设计、课程设计及项目开发场景提供完整源码与配套文档。系统涵盖图像预处理、车牌定位、字符分割与识别等数字图像处理核心模块代码注释详细新手也能读懂部署简单可快速使用。资源共18个文件压缩包约4.73MB包含5个py源文件分别承担主程序、图像处理、识别与数学计算等任务5个png和3个jpg图片为测试样本与中间处理效果2个dat文件为训练好的SVM模型数据另有pptx演示文稿与md说明文档便于答辩汇报与项目复现。已有249人学习浏览属于导师认可的高分项目适合作为课程设计或毕业设计的起点也可在现有框架上扩展功能。整体来看这份资源为读者提供了从算法原理到工程实现的完整范例实用性强。1. 车牌识别系统数字图像处理在 Python OpenCV 中的一次完整落地停车场出口的摄像头时常要面对逆光和对面来车的近光灯车牌区域的对比度可能比环境还低。把这种场景里拍到的图像直接交给字符匹配算法结果往往是一串毫无规律的乱码问题首先不在识别而在前面的数字图像处理环节没有把车牌从复杂背景里剥离出来。用 Python 和 OpenCV 开发车牌识别系统正是理解整条处理管线的最佳路径灰度化、降噪、二值化、定位、分割、识别每一步都有可观察的中间结果和可调的参数。这里按这套思路给出一个能在个人电脑上跑通的实现并围绕源码结构与文档组织说明如何调整适合课程设计、图像处理入门以及需要快速验证方案的工程师参考。2. 用 Python 与 OpenCV 搭建车牌识别预处理流程在正式开始定位车牌之前先把数字图像处理中最基础的几件事做对图像怎么读、怎么转灰度、怎么降噪、怎么得到干净的字符区域。这一层做得越稳后面的轮廓筛选和字符分割出现异常的概率就越低。2.1 OpenCV 安装与 Python 环境准备安装 OpenCV 最省事的方式是创建虚拟环境后用 pip 安装避免把系统 Python 环境弄乱。在命令行里依次执行python -m venv .venv source .venv/bin/activate # Windows 下改为 .venv\Scripts\activate python -m pip install --upgrade pip pip install opencv-python numpy这段命令先创建并激活虚拟环境然后升级 pip最后安装 OpenCV 和 NumPy。opencv-python是官方预编译包自带常用的图像处理和dnn模块如果后面要用到 SIFT 等非自由算法才需要换装opencv-contrib-python。对车牌识别而言opencv-python足够。新手最容易遇到的是ModuleNotFoundError: No module named cv2。这不是 OpenCV 没装好而是当前终端激活的 Python 解释器和装包的 pip 不在同一个环境里。验证办法是在已激活的终端里执行python -c import cv2; print(cv2.__version__)能输出版本号就说明环境没问题。提示代码放在含中文的路径下时直接cv2.imread经常返回 None。建议先用np.fromfile把文件读成字节数组再用cv2.imdecode解码这样能绕开系统编码问题。2.2 车牌图像的灰度化、降噪与二值化车牌的字符和底色对比度较高但摄像头抓拍时往往带有夜间噪点、运动模糊或反光。常见的做法是先灰度化再用高斯滤波和中值滤波去噪最后用二值化把字符与背景拉开。import cv2 import numpy as np def load_image(path): data np.fromfile(path, dtypenp.uint8) img cv2.imdecode(data, cv2.IMREAD_COLOR) if img is None: raise ValueError(读取图片失败检查路径或文件格式) return img def preprocess(img): img cv2.resize(img, None, fx0.8, fy0.8, interpolationcv2.INTER_AREA) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) blur cv2.GaussianBlur(gray, (5, 5), 0) _, binary cv2.threshold( blur, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) return img, gray, binary这里fx和fy都设为 0.8意思是把图像长宽各缩放到原来的 80%。对像素很多的抓拍大图先缩小能显著减少后续轮廓计算时间。cv2.threshold使用 Otsu 算法时阈值参数填 0 即可函数会根据直方图自动算出一个让类间方差最大的阈值这对光照变化不剧烈的图像很管用。如果车牌区域一半亮一半暗固定阈值或 Otsu 会把亮部字符吞掉。这时候可以改用cv2.adaptiveThreshold它针对每个像素邻域计算局部阈值能保留更多笔画细节。代价是处理速度变慢而且背景噪声更容易残留。2.3 形态学处理及相关参数选择二值化后经常出现两类问题字符内部出现空洞或者字符断裂。车位识别对这些情况都很敏感定位阶段需要的是成块的候选区域而不是一个个碎点。形态学闭运算可以填补字符内部小孔开运算可以去掉孤立噪点。处理环节常用函数关键参数推荐取值与说明灰度化cv2.cvtColorCOLOR_BGR2GRAY把 BGR 三通道降到单通道字符轮廓更清晰降噪cv2.GaussianBlurksize(5,5)核越大越模糊超过 7 会削弱笔画边缘二值化cv2.thresholdTHRESH_OTSU自动阈值适合整体光照稳定的画面闭运算cv2.morphologyExMORPH_CLOSE核 3×3 起手能补字符断裂和内部空洞开运算cv2.morphologyExMORPH_OPEN核 3×3 起手清除孤立噪点配合形态学操作的代码一般是kernel cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) close cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel, iterations2) opened cv2.morphologyEx(close, cv2.MORPH_OPEN, kernel, iterations1)MORPH_CLOSE先膨胀再腐蚀可以让断开的笔画重新连成一片MORPH_OPEN是先腐蚀再膨胀用来消掉比核小的噪点。iterations不建议超过 3否则相邻字符可能因为膨胀过度而粘在一起后续分割十分被动。形态学处理本身不会让图像变得更清楚它是在利用结构元素改变连通区域的形状。实际项目中预处理阶段的输出不要直接拿去识别而是同时保留灰度图和二值图两条支路灰度图提供给车牌定位二值图提供给字符分割。这样能让每种算法各取所需不必为了迁就模板匹配强行要求整条管线使用同一张图。3. 基于 OpenCV 的车牌定位颜色阈值与轮廓筛选定位是整个系统中出错率最高的环节。多数识别失败不是字符识别模块不行而是车牌区域没有被找出来或者找出来的框把背景和文字混在一起。所以本章把定位拆成三个可调试的步骤先确定颜色阈值再筛选轮廓最后做透视校正。3.1 车牌先验特征与定位思路中国常见的蓝底白字车牌有两条重要先验底色是蓝色宽高比接近 440 比 140也就是大约 3.14。但在图像中的实际宽高比受拍摄角度影响会落在 2 到 4.5 之间因此不要把这个比例收得太紧。定位的思路是把颜色、边缘、几何特征三者交叉验证先用 HSV 颜色空间找出蓝色区域再用边缘检测补出被高光破坏的车牌边框最后用轮廓的面积、长宽比、矩形度过滤。定位线索实现函数适合场景主要风险颜色cv2.inRange蓝底、黄底车牌光照偏色H/S/V 漂移边缘cv2.Canny对比度明显的车牌背景纹理产生大量干扰框轮廓cv2.findContours与颜色或边缘结果结合断线导致一个车牌被拆成多块3.2 HSV 颜色空间划分车牌区域BGR 对光照敏感HSV 把颜色的色相 H 单独抽出来适合做固定颜色的阈值分割。把 BGR 图像转成 HSV 后用cv2.inRange生成二值掩膜。蓝色车牌大致落在 H 100 到 124、S 43 到 255、V 46 到 255 的范围实际需要根据素材微调。hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) mask_blue cv2.inRange(hsv, (100, 43, 46), (124, 255, 255)) mask_blue cv2.morphologyEx( mask_blue, cv2.MORPH_CLOSE, cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)))inRange的输出是单通道二值图值为 255 的像素表示颜色落在范围内。摄像头自动白平衡会导致同一块车牌在不同时间段的 H 值漂移所以颜色范围宁松勿紧先保住目标再用后面轮廓条件排除误检。注意HSV 低饱和时 H 分量没有意义。遇到白色车牌或反光严重的蓝色牌时需要把 S 和 V 下限适当调低。3.3 用 findContours 与 minAreaRect 筛选候选区域拿到蓝色掩膜后用cv2.findContours提取连通域。检索模式推荐RETR_EXTERNAL只拿最外层轮廓避免把车牌边框内部的字符轮廓也套进来。contours, _ cv2.findContours( mask_blue, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) candidates [] for cnt in contours: area cv2.contourArea(cnt) if area 1500 or area 20000: continue rect cv2.minAreaRect(cnt) w, h rect[1] if w 1 or h 1: continue ratio max(w, h) / min(w, h) if not (2.0 ratio 4.5): continue candidates.append(rect)cv2.minAreaRect返回的是旋转矩形包含中心点、宽高和旋转角度。这里用rect[1]取宽高并计算长宽比是因为车牌可能是歪的直接用cv2.boundingRect会算出一个包括了大量背景的水平外接框导致长宽比失真。很多资料会使用cv2.Canny先做边缘检测再找矩形框。边缘法的优势是能定位到浅色背景上的车牌颜色法会受到强阴影干扰。实际工程里可以把两条线并行跑最后取交集。如果只走单条线颜色法在蓝色车牌上的稳定性通常更好。3.4 透视变换与倾斜校正候选矩形是旋转框里面还带着倾斜角度。直接把旋转框裁出来送字符模块字符可能是斜的。常见做法是先按旋转矩形得到四个顶点再通过透视变换把矩形映射到固定尺寸的正面视图。def warp_plate(img, rect, dst_size(440, 140)): src cv2.boxPoints(rect) dst np.array( [[0, 0], [dst_size[0] - 1, 0], [dst_size[0] - 1, dst_size[1] - 1], [0, dst_size[1] - 1]], dtypenp.float32) matrix cv2.getPerspectiveTransform( src.astype(np.float32), dst) return cv2.warpPerspective(img, matrix, dst_size)cv2.boxPoints从旋转矩形得到四个点点的顺序未必是左上、右上、右下、左下。因此需要把src按点坐标排序后再传给getPerspectiveTransform。简单排序可以用左上角 xy 最小、右下角 xy 最大的规则否则变形后的图像可能左右颠倒。变形后的目标尺寸固定为 440×140正好接近实际车牌比例。后面的字符分割都基于这一尺寸模板匹配时也就少做一次归一化。4. 车牌字符分割与模板匹配识别字符分割和识别需要先确定字符排序的逻辑。正常蓝牌有 7 个字符第一位是汉字省份简称第二位是字母后面是字母和数字混合。模板匹配对排列顺序很敏感因此分割输出的字符顺序必须保持实际车牌从左到右的顺序这一步依赖垂直投影的稳定性。4.1 垂直投影法分割字符垂直投影是把二值图逐列统计白色像素数。字符所在列有明显峰值字符间隔处接近零。把投影曲线里上升沿和下降沿配对就得到每个字符的左右边界。def split_chars(binary, min_width10): h, w binary.shape col_sum np.sum(binary, axis0) // 255 # 每列中白色像素的个数 segments [] in_char False start 0 for x in range(w): if col_sum[x] 0 and not in_char: in_char True start x elif col_sum[x] 0 and in_char: in_char False end x if end - start min_width: segments.append((start, end)) return segmentsmin_width用来滤掉边框残影和铆钉这类窄干扰两张车牌上的小圆点直径远小于字符宽度按 10 像素过滤比较合适。分割后还要对每个片段做水平方向的裁剪把上下边缘去掉避免字符顶到图像边界。字符有断裂时投影会在一个字符内出现多个波峰。此时可以判断相邻片段之间的距离若间隙小于字符平均宽度的三分之一就合并成一个字符若大于这个阈值则按数字和字母的正常间距分析看是不是真的多了一个字符。4.2 基于模板匹配实现字符识别模板匹配的思路是准备一张字符模板库对分割出来的字符做同样的尺寸归一化计算输入和模板的相似度取最大值对应的类别。模板库可以用代码直接生成也可以用真实车牌剪辑拷贝生成。def match_char(char_img, templates): target cv2.resize(char_img, (20, 40)) target cv2.normalize(target, None, 0, 1, cv2.NORM_MINMAX) best_tag, best_score , -1.0 for tag, tpl in templates.items(): tpl cv2.normalize(tpl, None, 0, 1, cv2.NORM_MINMAX) score cv2.matchTemplate(target, tpl, cv2.TM_CCOEFF_NORMED)[0][0] if score best_score: best_score score best_tag tag return best_tag, best_scoreTM_CCOEFF_NORMED是归一化相关系数对模板与输入图片的整体亮度不敏感正好匹配同一个字符在夜间和白天亮度不同的场景。cv2.normalize把像素强度拉到 0 到 1让相似度不会因字符粗细差异而剧烈跳动。这里约定模板和输入字符都是黑底白字如果不是先用cv2.bitwise_not取反。模板匹配在字母和数字上识别率较高在汉字上表现一般。汉字结构差异大相近字形多比如“湘”和“浙”在低分辨率下容易混淆。若要提升汉字识别率可以把模板收集扩充到不同字体或者换成统计学习方法例如对模板图提取 HOG 特征后用 SVM 分类。开源的 HyperLPR 里就带了一套汉字识别器原理上也是特征加分类而不是逐像素匹配。4.3 自实现方案与开源 OCR 模型的取舍做工程交付时自写模板匹配未必是最佳选择。开源的 HyperLPR、PaddleOCR 的 PP-OCR 模型已经包含大量训练好的中文车牌字符权重不需要自己收集省份简称模板。下面是三个常见方向的对比方案优点局限推荐场景自建模板匹配轻量、无外部依赖汉字识别率低扩展性有限课程设计与图像处理学习HyperLPR车牌专用模型识别率稳定部署体积大依赖较重快速接入真实场景PaddleOCR通用模型鲁棒性高需要安装 Paddle 推理环境通用文字识别任务如果项目定位在数字图像处理课程或源码交付建议保留自建的模板匹配作为主逻辑同时预留一个 OCR 后处理接口。即分割出的字符先逐个送入模板匹配若置信度低于阈值再交给开源模型兜底。这样既能展示图像处理管线也能让最终输出在真实抓拍图上可用。5. 车牌识别源码的参数调优与端到端验证当识别流程能跑通之后真正的重头戏是源码文档化和参数调优。我一般会把系统拆成四到五个文件把阈值参数集中放到config.py中避免在算法代码里到处散落魔法数字。这样遇到新的摄像头素材时只需要改配置不必重写算法逻辑。5.1 可运行的源码骨架与文档入口plate_recognition/ ├── config.py # 颜色阈值、轮廓面积范围、模板尺寸 ├── preprocessing.py # 读图、灰度、滤波、二值化 ├── localization.py # HSV 定位、轮廓筛选、透视变换 ├── character_split.py # 垂直投影分割 ├── recognize.py # 模板匹配识别 ├── templates/ # 字符模板图片目录 └── README.md # 环境安装、运行命令、参数说明README 里至少要写清楚每个模块的输入输出以及一条能完整跑通的命令。在main.py中使用 argparse 接收命令行参数可以让源码演示更稳定import argparse from recognize import recognize_plate def main(): parser argparse.ArgumentParser(description车牌识别系统) parser.add_argument(image, help输入图片的路径) parser.add_argument(--show, actionstore_true, help展示中间结果) parser.add_argument(--templates, defaulttemplates) args parser.parse_args() plate, debug_info recognize_plate(args.image, args.templates) print(识别结果:, plate) if args.show: print(定位置信度:, debug_info.get(confidence))argparse 让使用者在验证时能快速指定图片和模板目录也方便外部脚本集成。--show的典型用途是输出定位框图片辅助观察车牌有没有被准确框出。5.2 测试集与调优技巧调优不能靠肉眼单张试至少要准备几十张不同角度和光照的车牌图片。常用指标是整牌准确率和字符准确率前者对字符顺序敏感后者允许单个字符错误。验证时优先记录定位失败的情况因为定位失败会连带字符分割失败。调优顺序建议从前往后排先检查透视校正后的车牌是否准确再检查字符分割是否每个字符都独立最后调模板匹配。如果 HSV 阈值把绿色植物或蓝色围挡也框进来优先降低 S 下限如果车牌被高光吃掉则放宽 V 上限。形态学核的迭代次数从 1 开始逐步加看到字符断裂被补上就停多一次可能导致两个字粘连。我自己的经验是把所有参数写进config.py之后用一批样本图像跑回归测试每次改动只调一个参数记录它对整牌准确率的影响。遇到位置对了但识别错的情况再去扩充模板库而不是继续扩大 HSV 范围。这种小步调整的做法能让一个 80% 准确率的原型在半天内稳定到 90% 以上。本文还有配套的精品资源点击获取