资讯详情

深度学习车牌识别源码解析:从目标检测到字符识别的完整流程

📅 2026/10/2 9:20:30 | 华诺云谱 👁 阅读
深度学习车牌识别源码解析:从目标检测到字符识别的完整流程
简介基于深度学习的车牌识别源码项目包定位为高分毕业设计实战资源面向计算机类专业正在完成大作业、毕业设计的学生以及需要项目实战经验的Python与深度学习初学者。压缩包共2000个文件大小约265.63MB其中以1943张JPG车牌图片为主体辅以PNG示例图、XML标注与配置、Python脚本和说明文档覆盖数据集、模型定义与可运行入口便于快速复现。项目已经导师审定并认可评审分达98分源码均本地编译调试通过可直接运行通过该项目可完整掌握车牌检测识别的数据预处理、模型搭建与调参思路适合作为课设或毕设的技术基底。内容预览中可见大量陕A、陕U等真实场景车牌图片能帮助学习者直观检验识别效果目录结构清晰便于二次开发。目前已有250人学习下载质量获得初步验证。1. 为什么毕业设计选车牌识别这份源码不只是一堆 .py我拆过的毕业设计源码里车牌识别是打开率最高的一个方向这不是偶然。任务目标清晰效果一眼能看到代码跑出来有“机器视觉”的实感更重要的是一个基于深度学习的车牌识别源码天然覆盖了目标检测、字符分类、序列识别三条技术线中期答辩能讲的东西足够多。这份源码是基于深度学习的 Python 实现包含数据预处理、模型定义、训练脚本和推理入口整体定位就是“高分毕设项目”的标准形态不是几十行的玩具而是可以拆开讲清楚每个模块。它要解决的问题很直接——把输入图片中的车牌区域先检测出来再对车牌上的字符做识别最后输出一个标准车牌字符串。适合三类人毕业设计需要主功能的学生、想快速复现检测识别链路的人、想从源码而不是从教程学深度学习的从业者。这篇笔记我会按“拆目录→讲原理→调参数→踩坑→出指标”的顺序来拆重点放在你自己拿到源码之后怎么一步步搞懂它、跑通它、改它。有些结论可能和你网上看到的教程不一样但都是我实际验证过的经验。2. 先把它拆开看目录、环境、第一个 Python 入口拿到源码我不建议双击 README 就开跑。先把项目骨架认清楚搞清楚哪些文件是训练要用的、哪些是推理要用的、哪些是坚决不能改的后面才不会被各种路径报错折磨。2.1 一份高分毕设源码常见的长相一份正经的车牌识别毕设源码不会把所有代码塞进一个文件它至少会分 models、utils、configs、weights 几个区域。我习惯先用tree扫一遍看到的结构大致是下面这样plate-recognition/ ├── configs/ │ ├── plate.yaml # 数据集路径、模型结构、超参数 │ └── inference.yaml # 推理阈值、输入尺寸、设备 ├── data/ │ ├── images/ # 测试图片 │ ├── labels/ # 标注文件VOC/COCO/YOLO 格式都有可能 │ └── dataset.txt # 训练集/测试集清单 ├── models/ │ ├── detector.py # 车牌检测网络 │ ├── recognizer.py # 车牌字符识别网络 │ └── loss.py # 检测损失 识别损失 ├── utils/ │ ├── plate_utils.py # 车牌区域裁剪、透视矫正、字符切分 │ └── visualize.py # 画框、打标签 ├── weights/ │ ├── detector.pth │ └── recognizer.pth ├── train.py ├── detect.py ├── eval.py └── requirements.txt先说最容易踩的路径问题。data/dataset.txt这种清单文件不是摆设训练脚本会逐行读取图片路径和标签路径。如果相对路径的基准没对齐最常见的报错是FileNotFoundError而且通常会在训练跑了好几百步之后才冒出来特别浪费感情。我拿到源码第一件事就是读这个清单的前十行和真实路径手动对一遍。weights目录里的 .pth 文件是模型权重决定了“能不能直接跑通”。很多 GitHub 项目不直接传权重只给下载链接但这份毕设源码如果带着权重那你能跑通就只受环境限制不用从头训练。我建议先确认 weights 文件大小是否正常一个只有几 KB 的 .pth 大概率是空壳或者损坏文件。2.2 环境依赖requirements.txt 是后悔药不是装饰品很多同学拿到源码第一件事是pip install torch随手装最新版然后开始无尽地装依赖最后怀疑源码有 bug。其实绝大多数“源码跑不了”都是版本错位来源就是 numpy、opencv-python、torch 三者组合不对。requirements.txt 里常见的写法像这样# requirements.txt 常见版本范围具体以你手里那份源码为准 numpy1.21.0 opencv-python4.5.5 torch1.10.0 torchvision0.11.0 PyYAML5.4 tqdm4.60我看到这种文件会先把 numpy 的范围记住。opencv 4.6 以上配合 numpy 1.24 在 Python 3.10 里会触发_ARRAY_API not supported的警告推理时不一定会崩但训练时数据加载器会频繁报错。所以环境上我更建议直接用 Python 3.8 或 3.9 建虚拟环境然后按 requirements 装。python -m venv venv source venv/bin/activate pip install -r requirements.txt用 venv 的好处是就算你以后要跑其他项目也不会把系统 Python 搞乱。如果你要在没有 GPU 的机器上演示torch 记得装 CPU 版代码里只要像这样判断就能自动选择设备device cuda if torch.cuda.is_available() else cpuGPU 和 CPU 版本训练出的权重是通用的差别只在推理速度和训练时间。毕设答辩现场经常只有笔记本提前用 CPU 版调好推理不至于当众翻车。2.3 第一步不是训练是把推理入口跑通训练是重体力活先别急着碰。多数毕设源码会带detect.py或demo.py作用就是把一张测试图送进去输出画好框和车牌号的图片。先把它跑通相当于先打了个底后面改代码才知道出问题的是你的改动还是原本就不稳。# detect.py 里最常见的流程具体函数名可能略有出入 import cv2 import yaml from models.detector import PlateDetector from models.recognizer import PlateRecognizer def main(): cfg yaml.safe_load(open(configs/inference.yaml, r)) det PlateDetector(cfg[det_weight], devicecfg.get(device, cpu)) rec PlateRecognizer(cfg[rec_weight], devicecfg.get(device, cpu)) img cv2.imread(data/images/blue_01.jpg) boxes, scores det.predict(img, conf_thres0.5, iou_thres0.45) for box, score in zip(boxes, scores): # 对检测框做透视矫正把倾斜的车牌拉正 warp four_point_transform(img, box) code, conf rec.recognize(warp) print(识别结果:, code, 置信度:, round(conf, 4)) draw_box_with_text(img, box, code) cv2.imwrite(output/result.jpg, img) if __name__ __main__: main()这个入口藏着整条链路最关键的三步检测predict、透视矫正four_point_transform、识别recognize。很多初学者把透视矫正忽略直接拿原始检测框送进识别器效果自然差一截。conf_thres是检测置信度阈值低于这个值的框会被丢掉iou_thres是 NMS 用的两个检测框重叠大时保留分数高的那个。真实场景里conf_thres0.5可能漏掉被遮挡的车牌改成0.25又容易把车灯、保险杠装饰件当车牌。这个参数没有绝对标准后面避坑章节我会讲怎么调。2.4 推理配置里的字段逐个看跑通一次推理后把configs/inference.yaml打开里面每个字段都值得你认识一遍。我一般把它们分成三组字段典型值作用改动影响devicecuda / cpu推理设备无 GPU 时改成 cpuimg_size640 / 736输入图片 resize 尺寸越大越容易检测小目标越吃显存conf_thres0.5置信度阈值越高越精确但漏检也变多iou_thres0.45NMS 去重阈值越高保留框越多重复检测变多det_weightweights/detector.pth检测权重路径路径错直接报错rec_weightweights/recognizer.pth识别权重路径同上而且不能和检测权重混用调参时不要一次动多个字段。我一般先把img_size固定单独调conf_thres观察框的数量和位置变化等检测框稳定之后再动识别侧。这两个模型是串联的检测框要是歪了后面识别再准也白搭。3. 从检测到识别深度学习流水线是怎么串起来的这个项目把“车牌识别”拆成两个子任务先检测车牌区域再把裁剪区域识别成字符。两个任务各自是一个深度学习模型串联起来才是完整的“车牌识别系统”。下面对应源码里的 models/detector.py 和 models/recognizer.py 展开。3.1 车牌检测YOLO 系是主流anchor 和输入尺寸先确认车牌检测本质是目标检测输入一张图输出若干个框。现在毕设源码里用 YOLOv5/YOLOv8 的占大多数也有用 Faster-RCNN 的但 YOLO 系更轻CPU 也能跑推理。YOLO 的核心是把图片划分成网格每个网格预测若干 anchor 框的偏移、置信度和类别。车牌在整张图里目标不算大对输入分辨率比较敏感。# 检测器配置项常见含义训练前必看 input_size: 640 # 输入图像边长内部会 resize 到 640x640 conf_thres: 0.5 # 置信度阈值过滤低分检测框 iou_thres: 0.45 # NMS 的 IoU 阈值用于去重 anchors: - [10, 20, 30, 60] # 小尺寸 anchor负责远距离/小车牌 - [40, 80, 80, 120] # 中尺寸 anchor输入尺寸决定了小目标的下限如果input_size是 416图片里一个只有 20 像素宽的车牌在缩小后的特征图上只剩几个点几乎不可能被检测出来。我跑车牌检测更喜欢 640 甚至 736代价是显存涨一截。anchor 要适配车牌的长宽比普通轿车车牌长宽比大约是 3:1 到 4:1如果直接用 COCO 默认 anchor检测框可能只框住车牌一半。推理端如果看到“检测框只框住一半”或“把车灯当成车牌”不要着急怪模型先去查input_size和 anchor 配置。有的源码自带 k-means 重算 anchor 的脚本训练前跑一遍让它基于你的数据集重新聚类得到更适合的比例。这一步骤对大型货车的高位车牌尤其明显因为角度更刁钻。3.2 车牌识别从裁剪图到字符串不只是多分类拿到检测框后裁剪出车牌区域下一步是识别里面的字符。国内蓝牌通常是 7 个字符省份汉字 字母 数字。如果直接把整张车牌当成一个 7 位分类任务网络输出维度会非常笨重所以常见做法是用 CRNN 或 LPRNet 这种序列识别结构。输入一张被拉正后的车牌图经过卷积特征提取和序列建模输出一个概率序列最后用 CTC 解码得到字符串。CTC 解码是源码里最容易看懵的部分我贴一个 greedy decode 的简化实现# 常见 greedy decode取每个时间步最高概率字符再去重去 blank def ctc_greedy_decode(log_probs, blank0): # log_probs shape: [seq_len, num_classes] seq log_probs.argmax(dim-1) prev blank result [] for char_id in seq: if char_id ! blank: if char_id ! prev: # 连续重复只保留一个 result.append(char_id) prev char_id return result关键参数是blank。CTC 引入空白符来处理字符与字符之间的“空隙”blank在字符表里通常放在索引 0 或最后一个。解码时连续两个相同字符会被合并成一个所以像“AA12345”这种带连续重复车牌如果编码阶段没在相同字符中间插入 blank解码就会丢字符。这不是模型不收敛是编码和解码没对齐。字符表也是重点车牌字符集包含几十个省份汉字、24 个大写字母避开 I 和 O、10 个数字。有些源码把训练字符表和推理字符表分开写两边顺序不一致推理输出就是乱码。我一般建议把字符表统一放到utils/vocab.py训练和推理引用同一个文件从根上避免“两张表”的问题。3.3 数据标注YOLO、VOC、TXT 混用是最深的坑毕设源码的数据集来源差别很大有来自 CCPD 公开数据集的有自己拍照标框的也有爬来的图片。标签格式跟着数据来源走常见有三种标签格式典型存放位置典型内容适用模型YOLO TXTlabels/xx.txtclass cx cy w hYOLOv5/v8VOC XMLannotations/xx.xml四个角点 类别名Faster-RCNN自定义 JSONlabels/xx.json检测框 车牌字符识别训练同一份源码里如果检测器训练和识别器训练用了两套不同的数据来源格式错乱的概率极高。我遇到过一个典型翻车检测器用 YOLO 格式训练好了识别器却要求整张车牌图和字符标注在同一个 txt 里结果训练识别器时加载的全是检测框坐标被当成图像标签字符全部错位。现象是 loss 从开始就在 4 到 5 之间跳动永远不会下降。这种问题没有通用解法但有一个固定动作训练任何模块之前写一段十几行的脚本抽样打印一批img_path - label人眼确认标签和图片内容能对上。我通常会在每个类别里随机抽 20 张图把检测框画出来保存再单独把标注文本打印一遍检查位置和字符是否符合常识。3.4 损失函数与训练入口两个 Loss 分开看模型定义文件里一般会有一个 loss.py里面包含检测分支和识别分支两个 Loss。检测分支沿用目标检测的 bbox 分类和回归损失识别分支多用 CTC Loss。训练时整体 loss 是两个分支的加权和但两个 Loss 的量纲差别很大检测 loss 可能在 0.01 级别CTC loss 可能在 10 级别如果源码里没有做权重平衡训练曲线会呈现出“看起来没有在下落”的假象。# 伪代码示意两个 loss 的加权方式 det_loss det_loss_fn(det_pred, det_target) # 检测分支 rec_loss ctc_loss(rec_log_probs, rec_targets) # 识别分支 total_loss det_loss 0.1 * rec_loss # 权重可视数据集调整我建议先看训练脚本里有没有类似rec_weight的参数再决定怎么读曲线。很多时候你觉得模型没收敛其实是识别 loss 被放大了十几倍导致总 loss 居高不下但检测分支其实已经收敛。更合适的做法是两个 loss 分开打印到日志文件训练时各看各的最后再合到一起评估整体效果。4. 训练与推理配置跑通之后把这些参数改成你自己的源码能跑 demo 只是“黑匣子能用”到了写毕设阶段导师大概率会问“这模型在什么数据上训练过超参数为什么这么设”这章就是把黑匣子打开按四个维度把参数讲清楚。4.1 数据划分train/val/test 别只看文件夹名一套规范的数据应该拆成三份训练集、验证集、测试集。但不少毕设源码把数据按文件序号硬切或者直接拿不同文件夹当 train/val甚至把同一批图片既放进训练集又放进验证集。这样训练出来的指标是虚高的答辩时一换真实数据就暴露。我做数据划分时会按“场景”而不是“文件名”来切比如来自同一段视频的图片放进同一个集合否则训练集和测试集里出现同一辆车同一个角度识别准确率虚高没有评估价值。# 常见划分脚本按目录或视频段划分 python tools/split_data.py \ --images data/images \ --output data/split \ --val_rate 0.15 \ --test_rate 0.1val_rate0.15表示验证集约占 15%test_rate0.1表示测试集约占 10%。如果数据量少测试集只留 5% 也可以但报告指标时一定要写清楚测试集样本数量。测试集必须是从训练阶段完全隔离出来的哪怕调参时也不能去碰它。4.2 学习率、batch size 与 anchor 的调整顺序我调参有一个固定顺序先固定 batch size 和输入尺寸再定学习率最后才动 anchor。不要一上来就改 anchor——那是模型已经跑得差不多了才做的事。常见 baseline 是 batch16、lr0.001、输入尺寸 640。如果 batch 改成 32学习率一般跟着乘以 2这是线性缩放规则。# 常见训练命令YOLO 系 python train.py \ --data configs/plate.yaml \ --batch 16 \ --epochs 150 \ --lr 0.001 \ --img 640--epochs在车牌场景里 100 到 200 基本够用重点在于学习率调度器。源码如果用的是ReduceLROnPlateau要盯住验证集 loss 的 step 参数如果是CosineAnnealing前 10 个 epoch 的 loss 波动大是正常的不用慌。显存不够时先降 batch再降输入尺寸。我一般把 batch 从 16 降到 8输入保持 640因为车牌是小目标降分辨率比降 batch 更伤检测效果。4.3 预训练权重能省一半时间但别用错任务检测器和识别器都可以用预训练权重初始化。车牌检测器用 COCO 预训练权重是常规操作因为 COCO 里已经有“车辆”等类别迁移效果好。识别器则要谨慎识别器输入是单通道灰度车牌图预训练权重通常是三通道自然图像的第一层卷积维度不一致。常见做法是在代码里把conv1.in_channels改成 1然后对三个通道的权重取平均# 迁移预训练权重的常见处理 state_dict torch.load(resnet18.pth) new_conv OrderedDict() for k, v in state_dict[conv1.weight].items(): new_conv[k] v.mean(dim1, keepdimTrue) # 三通道 - 单通道这个操作的意思是把 3 通道卷积核做一个平均变成 1 通道的初始值至少让第一层不是纯随机的。如果你的识别器用的是 LPRNet 这种从零开始设计的小网络往往不需要预训练直接随机初始化也能收敛省掉这段迁移逻辑。4.4 CPU 与 GPU 环境答辩现场怎么保证不崩答辩现场用 GPU 的概率很小多数是笔记本 CPU。推理代码里凡是写死model.to(cuda)的地方都要改成从配置读 device。另一个隐患是数据加载器设置了num_workers8在 Windows 无 GPU 环境经常报错改法很简单# 统一处理设备避免 in 里出现“cuda” device cuda if torch.cuda.is_available() else cpu model PlateRecognizer(weight_path, devicedevice)数据加载时num_workers调成 0 或 2避免 Windows 多进程加载崩溃。还有一个血泪经验图片路径不要出现中文或空格。很多代码在 Linux 上没问题到了 Windows 因为中文路径读不出图像create 到一半突然报空图像极难排查。如果答辩 PPT 要放大展示结果先把测试图放到英文路径下。5. 避坑车牌识别项目最容易翻车的五个场景这章全是别人踩过我也踩过的坑按“现象→原因→解决”的顺序写清楚。有些坑第一次跑不一定出现但换成真实场景后大概率会碰见。5.1 蓝牌识别准一换绿牌就全乱现象模型对普通蓝牌测试集的准确率在 97% 以上放一张新能源绿牌进去要么检测框完全检不到要么识别结果串成“粤B·12345”这种没有规则的乱码。原因训练数据集几乎全是蓝牌绿牌样本占比不足 1%特征被蓝牌主导绿牌本身颜色、位数、字符排列和蓝牌不完全一致模型没见识过这种分布。解决在数据清单里加入绿牌样本至少占总量的 10%并对绿色车牌做颜色扰动。训练之前显式确认训练列表里混入了绿牌路径而不是只在测试集里放了几张绿牌。绿牌样本来源不足时可以生成合成绿牌方法是用程序把蓝牌样本的 BGR 通道调整后叠到一个绿底背景上简单但有效。5.2 同一辆车连续帧识别结果飘现象对视频做推理时同一辆车的车牌每帧识别结果都不同上一帧是“京A12345”下一帧变成“京A1234”或“京B12345”。原因检测框位置轻微抖动导致裁剪出来的车牌区域每次差几个像素尤其当车牌边缘被框进去太多时字符序列产生干扰。另一个容易被忽略的原因是推理阶段开了随机增强比如翻转或亮度抖动这在验证、推理时是不该开的。解决先关掉所有随机增强固定输入尺寸。再对检测框做时序平滑把连续几帧的检测结果做一个加权平均避免单帧突发性抖动。平滑权重alpha可以取 0.6越大越相信当前帧越小越平滑。5.3 识别结果位数漂移6 位、7 位、8 位乱跳现象标准蓝牌应该是 7 个字符但模型偶尔输出 6 个或 8 个字符而且多出来的字符不是乱码就是数字。原因一是 CTC 解码对 blank 边界处理不干净连续空白符被错误合并二是训练时标签没有按固定长度填充序列对齐悄悄错位。解决先确认字符表里 blank 的索引和 CTC loss 里的blank参数一致再把识别网络输出的 log_probs 维度打印出来看seq_len是和我设置的标签最大长度匹配。更直接的做法是把标签统一 padding 到最大长度解码后做一次正则校验过滤掉非法字符。5.4 训练刚开始就 OOM或者中途显存爆炸现象训练脚本一启动就报CUDA out of memory或者训练到第 20 步显存占用随 epoch 上升直到崩溃。原因启动就 OOM 大概率是 batch size 或输入尺寸太大中途显存爆炸通常是验证阶段把整批测试集一次性塞进 GPU或者某个 loss 的中间变量没有 detach计算图越积越大。解决先把 batch 降到 4输入尺寸从 640 降到 544再检查验证循环里有没有加torch.no_grad()没加就补上。仍然爆炸就开混合精度训练。混合精度在现代 PyTorch 里就是几行代码的事情训练速度还能提升一截。5.5 中文省份字永远识别成“粤”或“京”现象车牌里数字和字母都能识别对但第一个汉字经常错而且错的总是集中在高频省份上。原因数据集中“粤”“京”样本数量远多于其他省份模型学到了先验分布并且汉字笔画复杂分辨率低时细节丢失严重。解决一方面把省份字符按类别做均衡采样采样概率和样本数量成反比另一方面改造识别网络对省份位单独输出一个 31 类分类头数字字母位再输出一个 34 类分类头。两个头分开计算损失虽然结构复杂一点但对省份字符的识别率改善很明显。6. 最后一步用固定评测脚本把“能跑”变成“能写进论文”的指标到这步源码已经不只是“能跑”了但答辩时不能只拿几张效果图说话。导师一定会问测试了多少张准确率多少召回率多少有没有和基线模型对比所以强烈建议写一个固定评测脚本 eval.py把四个关键数字算出来检测 mAP、精确率、召回率、整牌识别准确率。评测流程建议这样串先对测试集跑检测把预测框和 ground truth 做 IoU 匹配IoU 大于阈值的框才算正确然后对正确框里的车牌区域做识别比较预测字符串和标注字符串。字符级准确率按字符对错统计整牌准确率要求整串一致两者分开报。# eval.py 的核心逻辑示意 def evaluate(test_loader, det, rec, iou_thres0.5, conf_thres0.5): tp, fp, fn 0, 0, 0 char_correct, char_total 0, 0 plate_correct, plate_total 0, 0 for img, gt_boxes, gt_codes in test_loader: pred_boxes det.predict(img, conf_thresconf_thres, iou_thres0.5) matched match_boxes(pred_boxes, gt_boxes, iou_thres) for pred_box, matched_gt in zip(pred_boxes, matched): if matched_gt is not None: code, _ rec.recognize(warp_crop(img, pred_box)) for c1, c2 in zip(code, matched_gt[code]): if c1 c2: char_correct 1 char_total len(matched_gt[code]) if code matched_gt[code]: plate_correct 1 plate_total 1 print(f字符级准确率{char_correct/char_total:.4f}, 整牌准确率{plate_correct/plate_total:.4f})conf_thres和iou_thres在评测脚本里也要写死不能每次跑都随手改。评测时同一个测试集、同一组阈值才能让指标横向可比。跑完之后把结果整理成一张表写论文直接抄进去指标数值示例测试集规模检测 mAP0.50.9781000 张检测精确率0.9731000 张检测召回率0.9691000 张字符级识别准确率0.9911000 张整牌识别准确率0.9621000 张注意测试集规模要写清楚。很多毕设给的指标是“800 张里 780 张正确”但单张图里可能有多辆车、多块车牌一张图的“对错”不好定义。用上面的 eval 流程统一口径才方便复现也方便导师验证。最后说一下我的习惯每次调完任何参数我不会用 demo.py 随手挑图乱看而是强制跑一遍 eval.py 的固定脚本看指标变化再决定下一步调参方向。尤其是把conf_thres从 0.5 改成 0.3 这种小改动靠肉眼完全看不出差异但指标表里精确率和召回率的方向会告诉你阈值该往哪走。希望这个“先拆目录、固定评测、再谈效果”的习惯能帮到你至少在答辩前你心里有底知道自己这套系统的真实上限在哪里。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑