
简介本资源是一套基于Python的车辆牌照识别系统设计源码面向计算机视觉入门者、课程设计或毕业设计开发者用于解决车牌自动检测与识别问题。项目包含完整的检测与识别流程涉及图像预处理、车牌定位、字符分割及SVM模型训练等环节适合具备Python基础、希望实践车牌识别算法的学习者。压缩包共48个文件约30.78MB其中Python源文件7个承担核心检测与训练逻辑PNG与JPG图片共25个用于测试样本与界面素材另有UI界面文件、数据文件、图标及说明文档便于直接运行与二次开发。已有461人学习下载说明该方案具备一定参考价值。读者可获得可运行的源码工程、训练数据集与模型文件、界面设计资源及依赖清单既能快速复现车牌识别效果也能在此基础上调整算法或扩展功能适合作为课程设计、项目练手或算法入门的完整参考。1. 从一张糊到看不清的夜间车牌图说起这套 Python 车牌识别系统到底能干什么地下车库出口逆光车牌被远光灯糊成一团白人眼都得凑近看两秒。这种图丢给传统模板匹配基本就是玄学——阈值调半天白天能用晚上全崩。基于 Python 的车辆牌照识别系统设计源码要解决的就是这类真实场景把一张含车的图片或一段视频流自动定位车牌、切出字符、识别成文本最后输出「京A·12345」这样的结构化结果。它适合三类人做课程设计需要一套能跑通、能讲清原理的完整代码做停车场、门禁、卡口类小项目想快速搭原型的工程师以及刚学完 OpenCV 和深度学习、想找一个端到端项目练手的 Python 入门者。整套链路通常拆成四步——车牌检测、车牌矫正、字符分割、字符识别每一步都有成熟但各有脾气的方案。下面不讲空话直接按「怎么选、怎么装、怎么跑、哪里翻车」的顺序拆开。2. 车牌识别的四段流水线为什么这么拆每段该选什么方案2.1 检测、矫正、分割、识别四段各自的职责边界很多人一上来就想用一个端到端模型把整张图直接吐成字符串结果训练数据不够、标注成本爆炸最后卡在收敛上。工程上更稳的做法是拆成四段流水线每段只解决一个明确问题出错时也能定位到底是哪一段崩了。第一段是车牌检测输入整张图输出车牌的边界框坐标。第二段是矫正因为拍摄角度会让车牌产生透视变形蓝牌变梯形字符被压扁直接送识别准确率会掉一大截。第三段是字符分割把矫正后的车牌切成单个字符的小图中文、字母、数字分开处理。第四段是字符识别对每个小图做分类输出对应字符。这四段里检测和识别是准确率的大头矫正和分割是稳定性的关键。常见做法是检测用 YOLO 系列或 OpenCV 的级联分类器识别用 CNN 或 CRNN。如果你的场景是固定机位、光照稳定传统方法就够如果是移动拍摄、光照多变检测和识别都建议上深度学习。提示四段流水线的中间结果一定要存盘。调试时你会发现90% 的识别错误其实发生在检测框偏了或矫正没做好而不是识别模型本身不行。2.2 传统 OpenCV 方案和深度学习方案的选型对比选型这件事没有绝对答案取决于你的数据量和部署环境。下面这张表是我在实际项目里反复权衡后总结的对比参数都是常见取值区间不是拍脑袋。维度传统 OpenCV 方案深度学习方案检测方法颜色分割 形态学 轮廓筛选YOLOv5/v8 训练车牌检测识别方法模板匹配 / SVMCNN 单字符分类或 CRNN 序列识别训练数据需求几乎不需要调参即可检测至少 500 张识别每类 200 张起白天准确率85%92%95%98%夜间/逆光准确率50%70%极不稳定88%94%明显更稳推理速度CPU单帧 3080ms单帧 80200ms部署依赖只需 opencv-python需要 PyTorch/ONNX Runtime适合场景课程设计、固定机位、光照可控真实项目、移动拍摄、光照复杂我的建议很直接课程设计或演示传统方案足够代码短、依赖少、好讲真要落地到停车场或卡口检测和识别都上深度学习别在传统方法上耗时间夜间场景会让你怀疑人生。2.3 环境搭建Python 安装、vscode 配置和依赖清单环境这块是新手最容易卡住的地方血泪经验是版本别追新稳定优先。Python 用 3.83.10 都行3.11 以上有些库的 wheel 还没跟上。安装时记得勾选 Add Python to PATH否则后面命令行找不到 python。vscode 配置 Python 环境的步骤装 Python 扩展CtrlShiftP 输入 Python: Select Interpreter选中你安装的解释器。如果用的是虚拟环境先激活再选。依赖清单如下建议用 requirements.txt 管理# 创建虚拟环境避免污染全局 python -m venv venv # Windows 激活 venv\Scripts\activate # Linux/Mac 激活 source venv/bin/activate # 安装核心依赖 pip install opencv-python4.8.1.78 pip install numpy1.24.3 pip install pillow10.0.1 pip install torch2.0.1 torchvision0.15.2 pip install onnxruntime1.16.0opencv-python 负责图像读写、颜色空间转换、形态学操作numpy 处理矩阵运算pillow 用于字符小图的保存和展示torch 和 torchvision 是深度学习方案的训练和推理基础onnxruntime 用于把模型导出后做轻量部署。版本号不是死的但建议锁在相近区间避免 API 变动导致代码报错。注意如果你只用传统方案torch 和 onnxruntime 可以不装依赖体积能小一半以上。先想清楚走哪条路线再装。3. 从零跑通检测到识别可抄作业的代码与参数3.1 车牌检测颜色分割加轮廓筛选的最小实现传统方案里中国蓝牌的颜色特征很明显HSV 空间下蓝色区域的 H 通道集中在 100124。先转 HSV再做颜色掩膜然后形态学闭运算把车牌区域连成一块最后找轮廓、按宽高比和面积筛选。import cv2 import numpy as np def detect_plate_by_color(image_path): # 读取图片 img cv2.imread(image_path) if img is None: raise FileNotFoundError(f图片读取失败: {image_path}) # 转 HSVHSV 对光照变化比 RGB 更鲁棒 hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 蓝色车牌的颜色范围H 在 100-124S 和 V 给足余量 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) aspect_ratio w / float(h) area w * h # 中国蓝牌宽高比约 3.14面积过滤掉小噪点 if 2.5 aspect_ratio 4.0 and area 2000: candidates.append((x, y, w, h)) return img, candidates img, plates detect_plate_by_color(car.jpg) for (x, y, w, h) in plates: cv2.rectangle(img, (x, y), (x w, y h), (0, 255, 0), 2) cv2.imwrite(detected.jpg, img)逻辑说明颜色掩膜负责粗筛形态学闭运算负责把车牌上的字符缝隙填上轮廓筛选负责去掉不符合宽高比的干扰。参数说明lower_blue 和 upper_blue 的 H 范围对应蓝色S 下限 80 是为了排除灰白色区域V 下限 80 是为了排除过暗区域。kernel 的 (17, 5) 是横向长条因为车牌是横向的闭运算要横向连接。aspect_ratio 的 2.54.0 覆盖了蓝牌和部分新能源绿牌的变形范围。area 阈值 2000 是经验值图片分辨率高时要相应调大。这套方法白天效果不错但夜间或强逆光时蓝色会被冲淡掩膜几乎全空。这时候要么换深度学习检测要么加一个自适应白平衡预处理。3.2 透视矫正把斜拍的车牌拉正检测到的车牌如果是斜的直接切出来送识别字符会变形。矫正的思路是找到车牌的四个角点做透视变换。传统方法用最小外接矩形加旋转但遇到梯形变形就不行了。更稳的是用 cv2.minAreaRect 拿到旋转矩形再算变换矩阵。def correct_plate(img, rect): x, y, w, h rect plate img[y:yh, x:xw] # 灰度化后做边缘检测找车牌的精确角点 gray cv2.cvtColor(plate, cv2.COLOR_BGR2GRAY) edges cv2.Canny(gray, 50, 150) # 找最大轮廓作为车牌边界 contours, _ cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return cv2.resize(plate, (440, 140)) cnt max(contours, keycv2.contourArea) rect_rot cv2.minAreaRect(cnt) box cv2.boxPoints(rect_rot) box np.int0(box) # 按左上、右上、右下、左下排序 box order_points(box) dst np.array([[0, 0], [439, 0], [439, 139], [0, 139]], dtypenp.float32) M cv2.getPerspectiveTransform(box.astype(np.float32), dst) warped cv2.warpPerspective(plate, M, (440, 140)) return warped def order_points(pts): # 按 xy 和 x-y 排序确定四个角的顺序 rect np.zeros((4, 2), dtypenp.float32) s pts.sum(axis1) rect[0] pts[np.argmin(s)] # 左上 rect[2] pts[np.argmax(s)] # 右下 diff np.diff(pts, axis1) rect[1] pts[np.argmin(diff)] # 右上 rect[3] pts[np.argmax(diff)] # 左下 return rect逻辑说明先切出车牌区域再用 Canny 找边缘最大轮廓就是车牌边界。minAreaRect 给出带旋转角度的矩形boxPoints 拿到四个角点。order_points 是关键角点顺序错了透视变换会把图翻过来。dst 的 440×140 是中国蓝牌的标准比例统一到这个尺寸后后面的字符分割才好做。参数说明Canny 的 50 和 150 是低高阈值光照均匀时可以收紧到 80/200边缘更干净。如果车牌本身很清晰可以跳过 Canny直接用 minAreaRect 在灰度图上做。3.3 字符分割垂直投影法的参数怎么调矫正后的车牌是 440×140接下来要切成 7 个字符蓝牌。最常用的是垂直投影法把车牌二值化然后按列统计白色像素个数字符之间的间隙处投影值会掉到接近零据此切分。def segment_characters(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 255, axis0) # 找字符的起止列阈值取投影最大值的 1/10 threshold max(projection) * 0.1 chars [] in_char False start 0 for i, val in enumerate(projection): if val threshold and not in_char: in_char True start i elif val threshold and in_char: in_char False if i - start 5: # 宽度太小的当噪点丢掉 chars.append((start, i)) # 按宽度过滤正常字符宽度在 30-60 像素之间 chars [(s, e) for (s, e) in chars if 20 (e - s) 70] # 切出每个字符 result [] for (s, e) in chars: char_img binary[:, s:e] char_img cv2.resize(char_img, (20, 40)) result.append(char_img) return result逻辑说明Otsu 二值化能自适应不同光照比固定阈值稳。垂直投影把二维问题降成一维字符间隙处投影值骤降据此切分。threshold 取最大投影值的 10%这个比例很关键太高会漏切太低会把字符内部的笔画间隙也当成分割点。宽度过滤 2070 像素是经验值对应 440 宽度下车牌字符的典型尺寸。最后统一 resize 到 20×40是为了送进 CNN 时输入尺寸一致。注意中文和字母数字的分割策略不同。中文笔画复杂投影法容易在「京」「沪」这种字上切碎常见做法是先用固定位置切出第一个字符中文剩下的按投影切。4. 避坑与排查车牌识别里最容易翻车的五个地方4.1 现象白天识别正常晚上全乱原因颜色分割依赖 HSV 的蓝色范围夜间路灯偏黄或偏白蓝色被冲淡掩膜几乎为空。解决加自适应白平衡预处理或者直接换深度学习检测。如果坚持传统方案把 V 的下限从 80 降到 50同时把 S 的下限降到 60牺牲一些误检换召回。4.2 现象检测框总是偏大或偏小原因形态学 kernel 尺寸不合适。kernel 太大车牌区域膨胀过头框偏大太小字符缝隙填不上框偏小。解决kernel 宽度按图片宽度的 1/40 左右估算高度取宽度的 1/3。比如 1920 宽的图kernel 用 (48, 16) 起步再微调。4.3 现象字符分割把「1」和「I」切没了原因垂直投影的宽度过滤把窄字符当噪点丢了。数字 1 和字母 I 的宽度可能只有 1015 像素。解决把宽度下限从 20 降到 8同时加一个位置约束——车牌第 2 位通常是字母第 3 位起是数字按位置先验做二次校验。4.4 现象透视矫正后车牌是倒的原因order_points 的角点排序逻辑在极端角度下会错乱左上和左下判断反了。解决排序后加一个校验如果变换后的图上半部分比下半部分暗车牌顶部通常有螺丝孔或阴影就旋转 180 度。更稳的做法是用车牌的长宽比判断方向宽大于高才是正的。4.5 现象CNN 识别模型训练准确率 99%实测只有 70%原因训练集和实测数据的分布不一致。训练集多是清晰、正面的车牌实测有模糊、倾斜、光照不均。解决训练时加数据增强随机旋转 ±15 度、随机亮度调整 ±30%、随机高斯模糊。另外把矫正和分割的中间结果也存下来人工检查是不是这两步引入了噪声。5. 把识别率从 85% 推到 95% 的两个进阶技巧第一个技巧是检测和识别的级联校验。单靠检测框切出来的车牌有时候会包含边框或螺丝字符分割时多出干扰。我的做法是检测框先向内收缩 5% 再切切完做一次字符分割如果分割出的字符数不是 7 或 8就回退到检测框重新调整。这个回退机制能把误检率压下去不少。第二个技巧是识别结果的后处理。中国车牌的格式是固定的第一位是省份简称第二位是字母后面五位是字母数字混合。识别完拿到 7 个字符后用正则做一次格式校验不符合的按混淆矩阵纠正。比如「0」和「O」、「1」和「I」、「8」和「B」是高频混淆对按位置先验强制替换——第二位如果是数字 0大概率是字母 O。import re def post_process(chars): # chars 是识别出的 7 个字符列表 province 京津冀晋蒙辽吉黑沪苏浙皖闽赣鲁豫鄂湘粤桂琼渝川贵云藏陕甘青宁新 confusion {0: O, O: 0, 1: I, I: 1, 8: B, B: 8} # 第一位必须是省份简称 if chars[0] not in province: # 尝试从混淆对里找最接近的 for p in province: if p in confusion and confusion[p] chars[0]: chars[0] p break # 第二位必须是字母 if not chars[1].isalpha(): chars[1] confusion.get(chars[1], chars[1]) # 后五位按位置做数字/字母校验 result .join(chars) pattern r^[京津冀晋蒙辽吉黑沪苏浙皖闽赣鲁豫鄂湘粤桂琼渝川贵云藏陕甘青宁新][A-Z][A-Z0-9]{5}$ if re.match(pattern, result): return result return result # 校验不过也返回但记录日志人工复核逻辑说明省份简称列表是硬编码的第一位不在列表里就尝试用混淆对纠正。第二位强制字母后五位用正则校验整体格式。参数说明confusion 字典只列了高频混淆对实际项目里可以根据混淆矩阵动态生成。这个后处理能把识别准确率再提 35 个百分点尤其是数字和字母混排的场景。我自己的习惯是每次调完参数一定拿 20 张不同场景的图跑一遍把检测框、矫正图、分割字符、识别结果四张中间图拼成一张对比图存下来。翻车的时候这张图就是后悔药一眼能看出是哪一段出了问题。这套方案值不值得做如果你要的是能讲清原理、能跑通、能改的完整链路值得如果你指望一套代码通吃所有场景那任何方案都会让你失望。希望帮到你。本文还有配套的精品资源点击获取