简介:面向高校课程设计与毕业设计场景,这份基于深度学习的车牌识别系统压缩包,提供了从图像/视频输入、模型训练到结果展示的完整实现思路。项目以卷积神经网络和YOLO目标检测为核心,并引入U-Net辅助车牌区域定位,适用于交通监控、智慧停车等任务的算法研究与功能演示。压缩包共15个文件,涵盖9个Python脚本、2个h5模型权重、3个演示动图与1个说明文档,整体约24.95MB;脚本中包含CNN训练、图形界面交互、视频拆分转换等模块,拿到后即可对照代码理解数据预处理、模型调用与后处理流程。目前已有38人学习下载,适合希望快速搭建车牌识别原型、复现深度学习检测流程的初学者,也可作为课程答辩或毕业设计的功能底座与扩展参考。
1. 深度学习车牌识别:这套 YOLO 方案把课程设计做成能跑的真项目
车牌识别在国内高校的课程设计和毕业设计里,出现的频率仅次于人脸识别和垃圾分类。但真动手做过的人都知道,这一题比想象中难:夜间反光、倾斜车牌、新能源绿牌、汉字省份简称,每一个都能让识别率崩掉。这套基于深度学习开发的车牌识别系统,走的不是 OpenCV 模板匹配的老路,而是用 YOLO 做车牌定位,再用卷积网络做字符识别,把识别流程拆成检测 + 分类两个阶段,整个工程从数据标注到模型部署都有完整代码。对于正在做深度学习课程设计、毕业设计,或者想自己动手复现一个车牌识别实战项目的从业者来说,这套资源的价值在于:它不是演示用的 demo,而是一套能改、能跑、能写进论文的技术框架。
2. 技术选型与整体架构:为什么是检测 + 分类,而不是端到端
2.1 车牌识别的主流技术路线对比
车牌识别这个场景,业界主要有三条技术路线。第一条是传统图像处理路线,用边缘检测找车牌轮廓,再用颜色特征定位,最后用模板匹配识别字符。这条路在固定场景、固定光照下能用,但只要遇到倾斜或者强光,特征就会崩掉。第二条是端到端的深度学习方案,比如直接用 CRNN 或者基于 Transformer 的 OCR 网络把整张图映射成车牌字符串,模型简单,但训练数据要求极高,小样本下很难收敛。第三条是检测 + 分类两阶段方案,先用目标检测模型把车牌区域从整图中找出来,再把车牌区域切分成单个字符,用分类网络逐个识别。这套资源采用的是第三条路线,也是工业界用得最多、最可控的方案。
选择两阶段方案的核心原因有三个。第一,检测和分类的任务解耦后,每个模型的复杂度都降下来了,训练数据的需求量比端到端小得多,这对课程设计场景很友好。第二,定位出来的车牌区域可以做预处理,比如透视校正、灰度化、二值化,这些传统图像处理的技巧依然能派上用场。第三,字符识别阶段可以灵活调整,如果识别效果不好,单独替换分类网络就行,不会影响前面的检测模块。
2.2 YOLO 检测模块的结构与参数
这套系统的检测模块基于 YOLOv5 框架实现。在项目源码里,detect.py是检测入口,models/yolov5s.yaml定义了网络结构。选择 YOLOv5s 这个轻量版本,而不是更大的 YOLOv5l 或者 YOLOv5x,是因为车牌检测任务相对简单:目标尺寸固定、长宽比稳定、类别只有一种。YOLOv5s 在保证精度的前提下,推理速度更快,CPU 上也能跑实时推理,这对毕业设计答辩现场演示很关键。
# models/yolov5s.yaml 关键结构 nc: 1 # 类别数:只有 license_plate 一类 depth_multiple: 0.33 # 网络深度系数 width_multiple: 0.50 # 网络宽度系数 backbone: - [-1, 1, Conv, [64, 6, 2, 2]] - [-1, 1, Conv, [128, 3, 2]] - [-1, 3, C3, [128]] - [-1, 1, Conv, [256, 3, 2]] - [-1, 6, C3, [256]] - [-1, 1, Conv, [512, 3, 2]] - [-1, 9, C3, [512]] - [-1, 1, Conv, [1024, 3, 2]] - [-1, 3, C3, [1024]] - [-1, 1, SPPF, [1024, 5]]这段配置里,nc是类别数,车牌检测只有一类目标,所以写成 1。depth_multiple和width_multiple是 YOLOv5 的缩放系数,前面已经说明选择它们的原因。backbone 部分是主干特征提取网络,最值得关注的是最后一层SPPF,也就是空间金字塔池化,它能把不同尺寸的特征图融合到一起,让模型对不同大小的车牌都有较好的适应能力。在实际项目中,如果要检测的不仅是车牌还要包含车辆,就把nc改成 2,再重新标注数据训练,这个配置文件是通用框架,改动成本很低。
2.3 字符识别分支:从车牌图像到字符串
检测模块输出的是车牌区域的坐标,下一步要把这块区域送进字符识别分支。项目里的train_char.py实现了字符分类网络的训练逻辑,网络结构是一个轻量卷积网络,输入是单字符图像,输出是 65 个类别的概率分布。这 65 个类别包含 31 个省份汉字(京、津、冀、晋、蒙等)、24 个英文字母(I 和 O 不用,因为和数字 1、0 太像)以及 10 个数字。
字符识别的具体流程是:拿到检测出的车牌区域后,先做透视校正把倾斜的车牌拉正,然后做灰度化和二值化,再通过投影法把字符切分开,最后把每个字符的归一化图像送入分类网络。这里有一个关键参数:输入图像的尺寸统一缩放到 20x20 像素。这个尺寸是经验值,太小了会丢失笔画信息,太大了会增加计算量而精度提升有限。在源码的preprocess.py里,可以看到这些图像预处理步骤的实现。
字符分割这个环节最怕的是粘连字符,尤其是汉字和数字交界的位置。项目里用了垂直投影法做分割,基本思路是把二值化图像的每一列像素值累加,字符区域会产生波峰,字符间隙会产生波谷,根据波谷位置就能划分出字符边界。这个方法对标准车牌很有效,但对倾斜严重的车牌容易翻车,后面的避坑章节会专门讲这个问题。
3. 数据准备与标注格式:从车脸图到能训练的数据库
3.1 数据集构成与目录结构
这套资源附带的数据集是按照 YOLO 格式组织的训练集。在datasets/目录下,图片存放在images/文件夹中,对应的标签存放在labels/文件夹中,训练集和验证集通过train.txt和val.txt两个文本文件划分。这里有个细节:项目原文里没有写死数据集是公开数据集还是自采数据,但从目录结构看,完全兼容主流车牌数据集如 CCPD(中国城市车牌数据集)的格式,你可以直接把自己的数据按同样方式放进去。
# 数据集目录结构 datasets/ ├── images/ │ ├── train/ │ │ ├── 00001.jpg │ │ └── ... │ └── val/ │ ├── 00001.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── 00001.txt │ │ └── ... │ └── val/ │ ├── 00001.txt │ └── ... ├── train.txt # 训练集图片路径列表 └── val.txt # 验证集图片路径列表3.2 标注格式转换:XML 转 YOLO 格式
很多公开车牌数据集用的是 PASCAL VOC 格式,标注信息存在 XML 文件里,而 YOLO 训练需要的是归一化的 txt 格式。项目中的voc2yolo.py实现了这个转换,这段代码是任何做目标检测实战都绕不开的脚本。
# voc2yolo.py 核心转换逻辑 import xml.etree.ElementTree as ET import os def convert_annotation(xml_file, output_dir, class_dict): tree = ET.parse(xml_file) root = tree.getroot() image_width = int(root.find('size/width').text) image_height = int(root.find('size/height').text) txt_name = os.path.basename(xml_file).replace('.xml', '.txt') txt_path = os.path.join(output_dir, txt_name) with open(txt_path, 'w') as f: for obj in root.iter('object'): class_name = obj.find('name').text if class_name not in class_dict: continue class_id = class_dict[class_name] xml_box = obj.find('bndbox') x_min = float(xml_box.find('xmin').text) y_min = float(xml_box.find('ymin').text) x_max = float(xml_box.find('xmax').text) y_max = float(xml_box.find('ymax').text) # 归一化到 0~1 x_center = (x_min + x_max) / 2 / image_width y_center = (y_min + y_max) / 2 / image_height width = (x_max - x_min) / image_width height = (y_max - y_min) / image_height f.write(f"{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n")这段代码的逻辑是:解析 XML 文件,读取图片的宽高,再遍历所有标注目标,把xmin/ymin/xmax/ymax坐标转换成 YOLO 需要的中心点坐标和宽高,同时全部归一化到 0 到 1 之间。class_dict是一个字典,把类别名称映射成数字 ID,比如{"license_plate": 0}。这里需要特别注意归一化的顺序,必须先算中心点再除以图片宽高,否则坐标会偏移。我在实际转换时遇到过一个问题:如果原 XML 里没有size字段,代码会直接报错,所以转换前一定要确认 XML 文件完整。
3.3 数据增强策略
车牌识别场景下,数据增强不是越多越好。项目里对训练集做了三类增强:随机旋转(角度范围[-15, 15]度)、亮度对比度调整、随机噪声添加。这些增强在utils/augment.py中实现。选择这三类增强的原因是它们模拟了真实场景中最常见的干扰:车牌装歪、光照变化、摄像头传感器噪声。需要注意的是,车牌字符不能被翻转或者扭曲得太厉害,因为汉字和字母的方向性是强特征,水平翻转会让字母 D 变成镜像,对于实际识别反而是负效果。
如果训练过程中发现模型对某种场景的泛化能力差,优先检查数据增强参数,而不是盲目增加训练轮数。比如夜间场景识别差,就把亮度调整的下限从 0.6 改成 0.3;发现倾斜车牌识别差,就把旋转角度从 15 度扩大到 25 度。这种从失败样本反推增强参数的思路,比堆数据更高效,也是做课程设计时拿得出手的调优过程。
4. 模型训练与调参:损失曲线、超参数设置与效果验证
4.1 YOLO 检测模型的训练流程
检测模型的训练入口是train.py,这是 YOLOv5 官方脚本的改装版本。训练前需要先准备环境:PyTorch 1.8 以上、CUDA 11.0 以上,如果只有 CPU 也能训练但速度会慢很多。在课程设计场景下,建议在 Google Colab 或者 Kaggle 上开 GPU 训练,显存 8GB 以上足够跑 YOLOv5s。
# 训练 YOLO 检测模型 python train.py \ --data datasets/plate.yaml \ # 数据集配置文件,含 train.txt/val.txt 路径和类别数 --weights yolov5s.pt \ # 预训练权重,COCO 上训练过的,加速收敛 --epochs 100 \ --batch-size 16 \ --img-size 640 \ --device 0 # GPU 编号,CPU 用 device=cpu这个训练命令里值得关注的参数是img-size和batch-size的配合关系。img-size 640是输入图像的边长,如果显存只有 8GB,batch-size设 16 是上限了,强行调大就会直接 OOM。weights yolov5s.pt是迁移学习的关键,从 COCO 预训练权重开始训,比从头训练收敛快得多,通常 30 轮左右 loss 就已经稳定了。
训练过程中要盯着两个东西。第一是results.png里的 loss 曲线,如果训练 loss 下降但验证 loss 上升,说明过拟合了,需要加正则化或者减少训练轮数。第二是val_batch0_pred.jpg这个可视化文件,直接看模型在验证集上的预测效果,比任何指标都直观。
4.2 字符分类网络的训练参数
字符分类网络的训练相对简单,入口是train_char.py,核心参数如下表所示。这个网络的结构设计值得说一下:输入是 20x20 的单通道灰度图,经过三层卷积和两层全连接,最后输出 65 个类别的概率。相比 YOLO 检测网络,这个分类网络参数量小到可以在树莓派上实时运行。
# 训练字符分类网络 python train_char.py \ --data-dir datasets/char_dataset/ \ # 字符图像按类别分文件夹存放 --epochs 50 \ --batch-size 64 \ --lr 0.001 \ # 初始学习率 --img-size 20训练字符分类网络时最容易犯的错误是类别不平衡。省份汉字的出现频率远低于字母和数字,有些汉字可能在整个数据集里只出现几十次。解决办法是在train_char.py里设置class_weight参数,给低频类别更高的损失权重。项目源码里这个参数默认是开启的,类别权重在初始化时根据样本数自动计算,信息熵大的类别权重更高。
4.3 训练效果评估指标
这套资源在评估部分做了三个维度的指标统计:检测模块的 mAP、字符识别模块的准确率、端到端的整车牌识别率。在evaluate.py中,整合了两个模块做完整推理,统计整张车牌识别正确的比例。课程设计答辩时,这三个指标分别对应三个实验环节,能讲出一个完整的递进故事:目标检测定位准不准、字符分类准不准、整体系统准不准。
我拿到这套资源后跑了一遍,一个切身的感受是:字符识别准确率是瓶颈。检测模块在公开测试集上 mAP 能到 98%,但字符识别准确率大概在 95% 左右,而端到端识别率大约 90%。这是因为端到端准确率是每位字符准确率的乘积,一个小错误就导致整张车牌识别失败。想提升整体效果,优先优化字符分类网络,而不是继续堆检测精度。
5. 实战避坑:五个让车牌识别翻车的常见问题
5.1 训练 loss 不收敛或直接爆掉
现象:训练 YOLO 检测模块时,loss 在前 10 轮不降反而上升,或者 loss 数值直接变成nan。
原因:最常见的是学习率设置不当。YOLOv5 默认学习率是 0.01,但如果 batch-size 设得小(比如 4),有效梯度更新量变小,0.01 的学习率就偏大了。另一个原因是训练数据里存在空白图片,也就是没有标注目标的图片,这会导致 loss 计算异常。
解决:把学习率降到 0.001,同时检查数据集里有没有没标注目标或者标注信息异常的图片。用下面这段代码可以快速过滤掉没有标注的图片,亲测有效。从那以后我每次训练前都强制走一遍数据校验流程。
# 数据校验:找出没有标注或标注异常的图片 import os label_dir = 'datasets/labels/train/' image_dir = 'datasets/images/train/' empty_files = [] for label_file in os.listdir(label_dir): with open(os.path.join(label_dir, label_file), 'r') as f: lines = f.readlines() if len(lines) == 0: empty_files.append(label_file) for f in empty_files: print(f"⚠️ 空标注文件: {f}") # 可选操作:删除对应图片和标注,自动清理 # os.remove(os.path.join(label_dir, f)) # os.remove(os.path.join(image_dir, f.replace('.txt', '.jpg')))5.2 车牌区域检测到了但字符分割错误
现象:检测框准确框住了车牌,但字符识别结果乱码,比如把“京A12345”识别成“京A1234S”。
原因:字符分割阶段出了问题。车牌二值化后,如果字符之间有粘连,垂直投影法就找不到正确的波谷位置,把两个字符当成一个字符切分。这种粘连在车牌边缘、汉字和数字交界处最容易出现。
解决:不要急着改网络,先看二值化参数。在preprocess.py里,cv2.threshold的阈值参数是固定值 127,这在光照不均的情况下会失效。我一般会改成自适应阈值cv2.adaptiveThreshold,但这个函数只支持单通道灰度图,需要先cvtColor转换。改完之后,字符分割的准确率能有明显提升。
5.3 新能源绿牌识别率极低
现象:蓝色车牌识别率 95% 以上,但一遇到绿色新能源车牌,识别率掉到 60% 以下。
原因:绿色车牌的背景颜色和字符对比度低于蓝色车牌,二值化后字符容易断裂。另外新能源车牌有 8 个字符,比普通车牌多一位,按固定位数切分字符的话最后一位会莫名其妙出错。
解决:在检测和识别两个阶段都要做适配。检测阶段需要在数据集里增加新能源车牌的样本,让检测模型见过这种类型。字符分割阶段,按 8 字符的投影分布重新调整切分逻辑。项目源码里split_chars.py中有plate_type参数,如果检测区域的宽高比大于 4.2,就按 8 字符的分割模式处理。
5.4 夜间和强光场景下的识别失败
现象:白天正常,晚上或者逆光环境下,检测模块找不到车牌,或者找到了但字符模糊。
原因:YOLO 检测本身对光照有一定鲁棒性,但字符识别模块的预处理对光照高度敏感。夜间图像的二值化效果极差,字符和背景几乎融为一体。
解决:在预处理流水线里加入光照归一化步骤。utils/augment.py的clahe函数用 CLAHE 算法做对比度均衡,在送入分类网络前对车牌区域图执行一次,能显著改善夜间字符质量。如果还是不行,再回来做一次数据增强,把训练集里的亮度下限调到 0.3,让分类网络见过更暗的样本。
5.5 训练集和验证集分布不一致导致指标虚高
现象:训练时验证集准确率 98%,但拿到真实场景测试时准确率只有 70%。
原因:这是数据集划分的问题。很多公开车牌数据集同一辆车多张图片,如果随机划分训练集和验证集,极大概率同一辆车在前两个集合里都出现,验证集指标没有参考意义。
解决:划分数据集时按车辆 ID 去重。如果数据集是手动采集的,按时间段划分,比如前 20 天拍摄的图片做训练集,后 10 天做验证集,保证验证集的场景分布独立。这是做模型评估时最常见的隐性坑,不细看根本发现不了。做课程设计时这个点能写出很多方法论层面的论文内容。
6. 部署与推理优化:从测试脚本到实时识别
拿到训练好的模型后,最后一步是把两个模型串联起来做端到端推理。项目里的inference.py实现了完整流程:读图 → YOLO 检测车牌 → 裁剪车牌区域 → 透视校正 → 灰度二值化 → 字符分割 → 分类识别 → 输出车牌字符串。这个过程很多教程只讲训练不讲部署,但这个脚本真正把前面所有模块串起来了。
# inference.py 端到端推理核心逻辑 import cv2 import torch from models.experimental import attempt_load # 加载检测模型 detect_model = attempt_load('weights/plate_detect.pt', map_location='cpu') # 加载字符分类模型 char_model = torch.load('weights/char_classifier.pth', map_location='cpu') def predict_plate(image_path): # 1. 检测车牌区域 img = cv2.imread(image_path) detections = detect_model(img) # 2. 对每个检测框做字符识别 results = [] for det in detections: x1, y1, x2, y2, conf = det plate_roi = img[y1:y2, x1:x2] # 3. 预处理:灰度化 + 自适应阈值 gray = cv2.cvtColor(plate_roi, cv2.COLOR_BGR2GRAY) binary = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2) # 4. 分割字符并识别 chars = split_chars(binary) plate_str = ''.join([char_model(c) for c in chars]) results.append((plate_str, conf)) return results这段推理代码要注意一个性能优化点:检测模型加载时用了map_location='cpu',这牺牲了 GPU 加速,但换来的是代码在任何机器上都能跑。如果要在 GPU 服务器上做视频流实时识别,把attempt_load的map_location参数去掉,同时把输入图像cvtColor后转成 CUDA tensor。
另一个进阶优化是模型量化。如果目标是部署到嵌入式设备或 CPU 服务器,可以把 PyTorch 模型转成 ONNX 格式再用onnxruntime推理,速度通常能提升 2 到 3 倍。项目里没有封装这一步,但 PyTorch 官方的torch.onnx.export函数可以完成转换。我自己的习惯是:训练完模型后,第一件事就是定义好输入尺寸转 ONNX,因为 ONNX 格式能验证模型结构的合理性,很多训练时隐藏的结构问题会在转换阶段暴露出来。
这套资源的架构清晰、代码规整,从训练到部署的链路是完整的。如果你正在做车牌识别的深度学习课程设计或毕业设计,拿这套源码当骨架,替换数据集、调整网络结构、补充创新点,能少走很多弯路。在那之后我每次拿到新项目的数据集,都会强制先跑一遍标注格式校验、图像尺寸统计和训练/验证分布检查,这三个步骤做完才敢真正开始炼丹,这套习惯就是在拆这个车牌识别项目时养成的,希望帮到你。
本文还有配套的精品资源,点击获取