
简介这是一套面向计算机视觉初学者与课程设计者的车牌检测识别完整项目源码基于Python实现覆盖图像预处理、车牌定位、字符分割与字符识别四大核心环节适合用于毕业设计、课程作业或算法练手。压缩包共48个文件约22.11MB包含7个py脚本承载检测与SVM训练逻辑17个png与8个jpg提供测试图片和界面素材另有dat模型数据、ui界面文件、ico图标及mp4演示视频等结构完整可直接运行。项目整合OpenCV图像处理、NumPy数据操作与深度学习字符识别思路并涉及灰度化、直方图均衡、边缘检测、连通组件分析等具体技术点还附带训练数据与配置文件便于读者理解从定位到识别的全流程。目前已有150人学习下载适合希望快速上手车牌识别实战、对照代码梳理算法脉络的开发者参考。1. 从一张违停抓拍图说起Python 车牌检测和识别到底在做什么路口相机拍下一张车尾图人工要做的第一件事是找到那块蓝底白字的矩形第二件事是把它上面的字符读出来。基于 Python 的车牌检测和识别系统本质就是把这两件事串成一条自动流水线检测负责回答“车牌在哪”识别负责回答“车牌是多少”。它适合三类人想给停车场、门禁、称重站做一套本地化识别的开发者手里有几千张过车图、想批量结构化入库的数据同学以及刚学完 Python 基础、想找一个能跑通全流程项目的入门者。这条流水线不依赖云端接口一台带普通显卡的机器就能跑难点不在模型多大而在预处理、后处理和参数配合。下面按“先跑通、再调优、最后避坑”的顺序拆开讲。2. 检测与识别两条链路怎么选YOLO 定位加 CRNN 读字符2.1 为什么把检测和识别拆成两个模型车牌场景有一个很现实的特点检测框里的内容高度结构化永远是“省份汉字 字母 数字”的固定组合长度有限、字体统一。如果用一个端到端模型直接输出字符串训练数据要求高出错后也难定位是框歪了还是字认错了。拆成两段之后检测模型只关心“车牌矩形在哪”识别模型只关心“框里的字符序列是什么”两段可以分别换、分别调。常见做法是检测用 YOLO 系列识别用 CRNN CTC。YOLO 负责回归边界框速度快、对小目标友好CRNN 用 CNN 提特征、RNN 建模字符顺序、CTC 解决不定长对齐正好匹配车牌字符数不固定的情况。两段之间用透视变换把倾斜车牌摆正这一步做不好后面识别再强也白搭。选型时还要考虑部署环境。如果只有 CPU检测可以换轻量骨干识别把 RNN 换成全卷积结构如果有 NVIDIA 显卡直接上常规版本即可。我一般会先确认推理设备再决定模型规模而不是先挑最大的模型。2.2 用 OpenCV 做车牌定位的最小可跑脚本在训练自己的模型之前先用传统方法跑一遍定位能帮你快速理解车牌的颜色和边缘特征。下面这段代码用 HSV 颜色分割加轮廓筛选把蓝色车牌区域框出来。import cv2 import numpy as np def locate_plate(image_path): img cv2.imread(image_path) # 转 HSV蓝色车牌在 H 通道约 100-124 hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) lower np.array([100, 80, 80]) upper np.array([124, 255, 255]) mask cv2.inRange(hsv, lower, upper) # 形态学闭运算把字符间的空隙连成整块 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) boxes [] for c in contours: x, y, w, h cv2.boundingRect(c) ratio w / float(h) # 车牌宽高比大致在 2.5 到 5.5 之间 if 2.5 ratio 5.5 and w 80: boxes.append((x, y, w, h)) return img, boxes if __name__ __main__: img, boxes locate_plate(car.jpg) for (x, y, w, h) in boxes: cv2.rectangle(img, (x, y), (x w, y h), (0, 255, 0), 2) cv2.imwrite(located.jpg, img)这段代码的逻辑是颜色阈值先把蓝色区域挑出来闭运算把分散的字符连成一块再用宽高比和最小宽度过滤掉误检。参数里lower和upper的 H 范围决定颜色宽容度光照偏黄时可以把 S 下限降到 60kernel的尺寸决定闭运算强度字符间距大就加宽。跑不通时先看mask图如果车牌区域是断的说明闭运算不够如果整张图全白说明阈值太松。提示传统方法只适合固定颜色和光照的场景夜间、逆光、脏污车牌会大量漏检它更适合用来生成候选框再交给模型精修。2.3 训练检测模型时的数据标注与增强参数自己训练 YOLO 检测器标注格式用 YOLO 的 txt每行类别 x_center y_center width height坐标都归一化到 0 到 1。车牌只有一个类别所以类别号恒为 0。标注时框要贴紧车牌边缘留白太多会让识别阶段的透视变换把背景也带进去。数据增强是提升泛化的关键。常用组合是随机亮度对比度、随机透视、随机缩放加平移。透视变换的幅度不要太大否则车牌被压成梯形后检测框会包含大量非车牌区域。我一般把透视幅度控制在 0.05 以内缩放范围 0.8 到 1.2。训练参数上输入尺寸建议 640batch 根据显存调学习率用余弦退火从 0.01 降到 0.0001。如果数据集里小目标多可以把输入提到 960但推理速度会明显下降。验证时重点看召回率漏检比误检更致命因为漏掉的车牌后面没有任何补救机会。3. 把车牌读出来CRNN 识别、字符集定义与后处理3.1 字符集怎么定直接决定识别上限识别模型的输出空间由字符集决定。中国车牌常见字符包括省份汉字、字母 A-Z去掉 I 和 O、数字 0-9再加上新能源车牌多一位。字符集一旦定死模型就永远认不出集合外的字符。所以第一步是把业务里可能出现的车牌类型列全蓝牌、黄牌、绿牌、白牌。如果只做蓝牌字符集可以精简到 31 个汉字加 34 个字母数字如果要做新能源得把绿牌多出来的字母位也加进去。字符集建议单独存成一个 txt每行一个字符训练和推理都读同一个文件。这样换字符集时不用改代码只改文件。索引 0 通常留给 CTC 的 blank实际字符从 1 开始编号。3.2 CRNN 推理代码与 CTC 解码下面这段是识别阶段的推理骨架输入是已经摆正的车牌小图输出是字符串。import torch import cv2 import numpy as np def decode_ctc(logits, charset): # logits: (T, C)C 含 blank indices logits.argmax(axis1) result [] prev -1 for idx in indices: if idx ! prev and idx ! 0: result.append(charset[idx - 1]) prev idx return .join(result) def recognize(plate_img, model, charset, img_h32, img_w100): img cv2.cvtColor(plate_img, cv2.COLOR_BGR2GRAY) img cv2.resize(img, (img_w, img_h)) img img.astype(np.float32) / 255.0 img (img - 0.5) / 0.5 tensor torch.from_numpy(img).unsqueeze(0).unsqueeze(0) with torch.no_grad(): logits model(tensor) # (1, T, C) logits logits.squeeze(0).cpu().numpy() return decode_ctc(logits, charset)解码逻辑是 CTC 的标准做法先取每个时间步的最大概率索引然后合并连续重复再去掉 blank。img_h和img_w必须和训练时一致改了尺寸模型就认不准。charset的顺序必须和训练时完全一致差一位就会把“京”认成“津”。如果输出全是空或重复单字先检查输入归一化是否和训练一致再检查字符集有没有错位。3.3 透视变换摆正车牌的三个参数检测框给的是倾斜矩形识别前要把它摆正。用cv2.getPerspectiveTransform加cv2.warpPerspective关键是四个角点的顺序。常见做法是先对检测框内的车牌区域做边缘检测找最小外接矩形再按左上、右上、右下、左下排序。def align_plate(img, box): x, y, w, h box roi img[y:yh, x:xw] gray cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) _, thresh cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) contours, _ cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return cv2.resize(roi, (100, 32)) rect cv2.minAreaRect(max(contours, keycv2.contourArea)) box_pts cv2.boxPoints(rect) # 按 xy 和 x-y 排序得到左上、右上、右下、左下 s box_pts.sum(axis1) d np.diff(box_pts, axis1).ravel() ordered np.array([box_pts[np.argmin(s)], box_pts[np.argmin(d)], box_pts[np.argmax(s)], box_pts[np.argmax(d)]], dtypenp.float32) dst np.array([[0, 0], [99, 0], [99, 31], [0, 31]], dtypenp.float32) M cv2.getPerspectiveTransform(ordered, dst) return cv2.warpPerspective(roi, M, (100, 32))三个关键参数阈值方法用 OTSU 自动找分割点比固定阈值稳minAreaRect拿到的角度决定旋转量目标尺寸(100, 32)必须和识别模型输入一致。如果摆正后字符还是斜的多半是角点排序错了检查argmin和argmax对应的点是否真的在左上和右下。4. 避坑与排查车牌识别翻车的五个真实原因4.1 检测框贴太紧导致字符被切现象识别结果总是少一位比如“京A12345”读成“京A1234”。原因检测框比车牌实际区域小边缘字符被裁掉。解决训练标注时框往外扩 2 到 3 个像素推理时把检测框按比例放大 1.05 倍再裁剪。这个改动很小但能救回大量边缘样本。4.2 字符集顺序错位导致系统性认错现象所有车牌的第一个汉字都错且错得很有规律。原因训练时字符集按拼音排序推理时按笔画排序索引对不上。解决字符集文件只保留一份训练和推理都从同一个路径读取改完必须重新导出模型。血泪经验是这种错不会报异常只会安静地输出错误结果。4.3 输入归一化不一致导致置信度整体偏低现象模型能出字但置信度普遍低于 0.5偶尔跳字。原因训练时用了(x-0.5)/0.5推理时只做了/255。解决把预处理写成一个函数训练和推理共用不要在两处各写一遍。归一化参数是黑匣子里最容易忽略的一环。4.4 夜间红外图颜色失真导致颜色定位失效现象白天正常夜间大量漏检。原因红外补光下蓝色车牌在 HSV 里偏灰颜色阈值分割直接失效。解决夜间链路不要依赖颜色改用检测模型直接回归或者把红外图转成灰度后做边缘检测。如果必须用颜色把 S 下限降到 30 并配合亮度判断。4.5 批量推理时显存溢出现象单张图正常一次传 32 张就报显存不足。原因识别模型输入尺寸固定但检测阶段不同图片产生的车牌数量不同拼 batch 时没有做动态填充。解决按车牌数量分组每组内填充到同一宽度或者把 batch 降到 8。后悔药是提前在推理脚本里加显存监控别等上线才发现。5. 把单张推理变成可用的批量流水线三个提速技巧5.1 检测和识别解耦成两个进程单进程串行跑检测完一张再识别一张GPU 利用率很低。常见做法是把检测和识别拆成两个进程中间用一个队列传车牌小图。检测进程只管出框识别进程只管读字两边各自 batch。这样检测的 batch 和识别的 batch 互不干扰整体吞吐能提升接近一倍。队列长度设 64 左右太长会吃内存太短会空转。5.2 用 ONNX Runtime 做 CPU 端加速没有显卡的机器上PyTorch 直接推理偏慢。把检测和识别模型都导出成 ONNX用 ONNX Runtime 的 CPU 执行提供者跑开启图优化和线程数设置。import onnxruntime as ort import numpy as np sess ort.InferenceSession(plate_rec.onnx, providers[CPUExecutionProvider]) sess.set_providers([CPUExecutionProvider], [{intra_op_num_threads: 4}]) def infer_onnx(img_tensor): input_name sess.get_inputs()[0].name out sess.run(None, {input_name: img_tensor}) return out[0]intra_op_num_threads设成物理核心数别超过超了反而抢线程。导出 ONNX 时注意 opset 版本CRNN 里的 LSTM 在低版本 opset 上可能不支持建议用 12 以上。如果导出后结果和 PyTorch 对不上先检查输入名字和维度顺序。5.3 用置信度阈值做二次过滤识别模型对模糊车牌会输出低置信度结果。把每个字符的概率取平均低于 0.6 的整条结果标记为“待人工复核”不要直接入库。这个阈值不是拍脑袋定的拿一批标注好的验证集跑一遍画准确率随阈值变化的曲线选准确率开始明显下降的那个点。我一般会留 5% 左右的复核量既能挡住大部分错读又不会让复核队列爆掉。5.4 一个我常用来验证全链路的小习惯每次改完检测或识别的任何参数我都会固定拿 20 张图跑一遍全链路记录检测召回、识别准确率和单张耗时三个数。这三个数放在一起看才能判断改动是赚了还是亏了。只看识别准确率涨了可能检测召回掉了整体反而更差。这个习惯帮我省下过好几次“改完以为变好、上线才发现变差”的返工。希望帮到你。本文还有配套的精品资源点击获取