ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于YOLOv5与LPRNet的车牌检测识别源码实战解析

基于YOLOv5与LPRNet的车牌检测识别源码实战解析 简介基于机器学习实现的车牌检测算法源码包面向计算机、人工智能、大数据等专业正在完成课程设计、期末大作业或毕业设计的学生也适合具备一定Python与深度学习基础的技术学习者进行算法复现与二次开发。资源经严格调试下载即可运行共包含68个文件核心为27个Python脚本覆盖CCPD数据集处理、YOLOv5车牌定位、CRNN/LPRNet字符识别、模型训练、ONNX导出及OpenVINO推理等完整链路另含18个YAML模型配置、2个pth/pt权重、字体、图片和Shell脚本整体约19.59MB。项目目录划分清晰train.py、detect_plate.py、demo.py等入口可帮助使用者从训练到推理逐步理解车牌定位与字符识别的实现细节并为后续部署或算法改进提供可修改基础。目前已有254人学习下载适合作为课程设计、毕设选题的完整参考工程。1. 一份能直接跑的车牌检测源码包检测、识别、部署三条线一次讲透如果你正在做车牌检测相关的课程设计或毕设大概率会搜到“基于机器学习的车牌检测算法源码.zip”这类资源。它不是什么黑匣子拆开看就是 YOLOv5 检测车牌位置再用 LPRNet 或 CRNN 识别车牌字符中间用 ccpd_process.py 处理 CCPD 数据集最后通过 export.py 导出 ONNX甚至能切到 OpenVINO 推理。这份工程有完整的 train.py、demo.py 和预训练权重调试过能直接跑。适合有 Python 基础、想把检测和识别全链路走通的人。下文我会按源码目录、数据转换、检测训练、识别训练、常见坑、端到端验证的顺序把每条命令和参数讲明白。2. 源码架构与数据准备检测/识别两条线以及 CCPD 转 YOLO2.1 为什么车牌检测要拆成检测和识别两步车牌检测和车牌识别是两个维度的问题。检测要回答“车牌在哪”是一个目标定位任务YOLO 系列能直接输出带置信度的矩形框识别要回答“车牌是什么内容”需要把裁剪后的区域映射成字符串。两个任务对特征的要求不同检测看重边缘和形状识别看重字符结构。强行用单一模型做端到端比如直接回归出字符串在真实场景下会相互干扰——检测框偏移一点识别就跟着错。工程上拆开的好处是检测和识别可以各自换模型、各自调参互不拖累。这个项目里检测侧跑的是 YOLOv5识别侧提供 LPRNet 和 CRNN 两个选择。LPRNet 是轻量级卷积网络适合 CPU 和边缘设备CRNN 用 CNNRNNCTC精度更高但参数量大。源码包里的 detect_plate.py 负责调用检测模型plateNet.py 和 LPRNet.py 分别是两种识别网络的定义这种模块化设计很常见也是比较好复现的结构。2.2 源码目录里的文件分工拿到 zip 解压后不要急着跑 demo.py先把核心文件对应关系搞清楚。我拆过这个包典型结构整理如下文件职责detect_plate.py检测入口加载 YOLOv5 权重输出车牌框坐标onnx_infer.pyONNX 格式推理用于部署阶段openvino_infer.pyOpenVINO 格式推理走 Intel 平台export.py把 PyTorch 权重导出为 ONNX / OpenVINOccpd_process.pyCCPD 原始数据转换生成检测标签和识别图plateNet.py / LPRNet.py识别网络定义CRNN 和 LPRNet 两种alphabets.py字符集定义训练识别模型必看plateLabel.py标签编码/解码处理字符串和索引互转train.py识别模型训练脚本demo.py端到端演示脚本加载检测识别fonts / weights / data字体、权重、数据资源目录重点先看 alphabets.py。里面定义的是车牌字符集包括省份汉字、字母、数字的排列顺序。这个顺序直接决定了训练时模型的输出维度也决定了推理时解码的正确性。一旦你改了字符集模型的输出头尺寸也要跟着改所以训练前必须确认字符表覆盖了你需要识别的中文字符。2.3 CCPD 数据转换ccpd_process.py 怎么用CCPDChinese City Parking Dataset是公开的中国停车场车牌数据集。它的特殊之处在于每张图片的文件名就携带了标注信息形如025-95_113-154383_386473-...里面包含了车牌框的左上、右下坐标、倾斜角度等。所以训练前必须用 ccpd_process.py 把这些信息解析出来转成 YOLO 格式的 txt 标签同时把车牌区域裁剪出来合并成识别模型的训练集。我拿到数据后的常规做法是先跑一次转换python ccpd_process.py \ --src ./CCPD \ --dst ./data/ccpd_yolo \ --img-size 640 \ --train-ratio 0.9--src是 CCPD 原始数据目录--dst是输出目录--img-size指定检测图的目标尺寸--train-ratio是训练集比例剩下 10% 当验证集。脚本内部会解析文件名坐标生成images/和labels/两个子目录还会输出val.txt和train.txtYOLOv5 训练时直接引用这两个 txt 或改成对应 yaml 即可。这里有个容易踩的细节CCPD 原图分辨率通常在 720p 以上直接做检测训练显存占用会很大所以--img-size 640是默认安全值。如果显存够可以用 768 或 960 提高小目标检出率如果显存紧张降到 416 也能跑只是远处小号牌会漏检。转换完检查一下输出的 txt 标签用文本编辑器打开里面是类别 cx cy w h归一化坐标数值在 0~1 之间才算正常。裁剪出的车牌会单独放在data/ccpd_rec/下供识别模型训练。注意--train-ratio不要直接设 1.0否则后面没有验证集训练曲线没法参考。我一般留 0.9 的比例保证验证图片数量不低于几百张。另外转换脚本遇到倾斜车牌时通常会根据文件名里的四点坐标做透视矫正输出水平对齐的车牌图这是识别模型能正常训练的关键。如果输出目录里没有矫正后的 crop 文件夹就要手动在 ccpd_process.py 里找透视变换的逻辑确认它已经被调用。3. 训练 YOLOv5 车牌检测模型从 train.py 到 ONNX 导出3.1 数据组织与训练命令YOLOv5 的目录结构相对固定。转换完成后把data/ccpd_yolo/images和data/ccpd_yolo/labels放进 yolov5 工程的datasets/下然后写一个ccpd.yamltrain: datasets/ccpd_yolo/train.txt val: datasets/ccpd_yolo/val.txt nc: 1 names: [plate]nc: 1表示只有一个类别车牌names列表必须是字符串且顺序与类别 id 对应。YOLOv5 会自动读取 txt 中每行第一列的类别 id这里只有 0。如果你的数据里包含蓝牌、黄牌、绿牌等不同类型想单独区分那就要在转换脚本里按车牌底色打不同标签nc相应改成 2 或 3。但大多数课程设计要求不高统一成一个类更省心。启动训练python train.py \ --data ccpd.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --device 0--weights yolov5s.pt是从官方仓库下载的预训练权重用它做迁移学习能显著缩短收敛时间。如果 GPU 显存只有 6G把--batch降到 8或者改用yolov5n.pt更轻量。--device 0表示用第一张显卡没有 GPU 就写cpu但训练速度会慢很多100 epoch 可能要十几个小时。训练过程中YOLOv5 会打印每轮耗时、box loss、obj loss、cls loss以及 P、R、mAP50、mAP50-95 等指标。第一次训练时一般到第 30~50 轮 mAP50 会明显上升。如果 50 轮后仍低于 0.5大概率是数据量不够或标签有问题回到第二章检查转换脚本。3.2 训练指标怎么看别被 mAP 骗了不少人看到训练集 mAP 很高就以为完事了。实际上训练集指标再好也不代表模型能用在真实场景。重点看验证集 mAP50 和验证集 loss。如果验证 loss 在第 60 轮后不再下降训练 loss 还在降说明过拟合已经开始。常用解决办法是加大数据增强YOLOv5 自带的 hyp 参数里hsv_h、fliplr、scale都可以调。也可以直接用官方调好的增强参数python train.py ... --hyp hyp.scratch-low.yamlhyp.scratch-low.yaml适合小数据集增强幅度更保守。如果过拟合严重还可以加入早停YOLOv5 有--patience参数例如--patience 20表示 20 轮验证指标不提升就自动停止。对于课程设计mAP50 达到 0.85 以上就足够演示了不需要追求极致。我实际跑这个案例时用 5000 张 CCPD 转出来的图batch 16 训练 80 轮验证集 mAP50 能到 0.92 左右框已经压得很紧。3.3 导出 ONNX 与 OpenVINO训练结束后runs/train/exp/weights/best.pt就是最佳权重。接下来用 export.py 导出部署格式python export.py \ --weights runs/train/exp/weights/best.pt \ --include onnx \ --opset 12导出完成后检查best.onnx是否生成。注意--opset 12是为了兼容 OpenVINO 转换opset 设太高比如 15 或 16可能导致 OpenVINO 解析失败。如果导出时提示某些算子不支持可以换回--opset 11。OpenVINO 导出有两种路径新版 YOLOv5 官方 export.py 直接支持python export.py --weights best.pt --include openvino它会调用 OpenVINO 的 Model Optimizer把 ONNX 转成 IR 格式.xml.bin。转之前要装好openvino-devpip install openvino-dev没有装的话可以手动用mo命令mo --input_model best.onnx --output_dir openvino_model用 ONNX 或 IR 格式推理速度通常比 PyTorch 快一截尤其 CPU 上明显。这也是这个项目里onnx_infer.py和openvino_infer.py存在的原因。导出后最好用脚本加载模型推理一张测试图对比 PyTorch 和 ONNX 输出框是否一致确认精度没有损失。我一般是抽 50 张图统计两套框架输出框的 IoU平均 IoU 在 0.98 以上就算合格。4. 训练 LPRNet/CRNN 车牌识别模型字符集、CTC 与推理串接4.1 模型选型LPRNet 与 CRNN识别模型的目标是把车牌裁剪图映射成字符串。常见选型有 LPRNet 和 CRNN 两种。LPRNet 是专门为车牌识别设计的网络结构轻只有几十 MB 参数CPU 上也能实时。CRNN 先用 CNN 提取特征再用双向 LSTM 建模序列最后用 CTC 对齐精度通常高于 LPRNet但计算量更大。项目里同时提供了LPRNet.py和plateNet.py后者大部分情况是 CRNN 的变体。训练时通过--arch参数切换python train.py --arch crnn --train_data data/ccpd_rec/train --val_data data/ccpd_rec/val如果设备性能一般我建议先用 LPRNet 跑通流程再换 CRNN 拉高精度。LPRNet 对输入图片高度要求是 24 或 32宽度可以任意所以训练生成长宽比合适的裁剪图很重要。CRNN 一般要求输入高 32宽度按最长字符长度等比例缩放。项目里ccpd_process.py已经做了裁剪和缩放训练时直接指定--img_h 32即可。4.2 训练命令与字符表alphabets.py 决定了能识别什么识别模型训练前停一下先打开alphabets.py。这个文件是模型的“输出词典”里面定义了你希望网络输出的全部字符。车牌识别场景至少包含以下内容31 个省份简称汉字京、津、冀、晋、蒙、辽、吉、黑、沪、苏、浙、皖、闽、赣、鲁、豫、鄂、湘、粤、桂、琼、渝、川、贵、云、藏、陕、甘、青、宁、新24 个字母I 和 O 容易与数字 1、0 混淆实际车牌中也不会出现所以一般排除10 个数字0~9alphabets.py里通常用一个列表或字符串按顺序存放这些字符比如ALPHABETS 京津冀晋蒙辽吉黑沪苏浙皖闽赣鲁豫鄂湘粤桂琼渝川贵云藏陕甘青宁新ABCDEFGHJKLMNPQRSTUVWXYZ0123456789第一项是空白占位符因为 CTC 会用到 blank 符号通常用 0 或_表示实际字符从索引 1 开始。修改alphabets.py后模型的输出通道数num_classes len(ALPHABETS)会变必须重新初始化模型。训练命令示例python train.py \ --arch lprnet \ --chr_file alphabets.py \ --train_data data/ccpd_rec/train \ --val_data data/ccpd_rec/val \ --batch_size 64 \ --epochs 200 \ --lr 0.01 \ --img_h 32--chr_file指向字符表脚本会根据其长度创建输出层。--batch_size在显存充足时可以设 128。--lr 0.01配合 Adam 优化器比较常见。训练过程中验证集准确率一般会缓慢爬升如果到第 100 轮还没超过 50%检查字符表顺序是否与plateLabel.py的编码函数一致。我习惯在训练前先用脚本打印几个随机样本的编码结果确认字符串转索引再转回来能够保持一致。4.3 从检测框到车牌字符串plateLabel.py 与 CTC 解码训练好的识别模型拿到的输入不是整张图而是检测框裁剪出的车牌区域。完整流程是detect_plate.py 输出框坐标 → 根据坐标在原图上裁剪必要时做透视矫正→ 缩放到识别模型要求的尺寸 → 前向推理 → 用 CTC 解码得到字符串。plateLabel.py里的encode函数负责把字符串转成索引序列decode函数负责把模型输出的概率序列转回字符串。CTC 解码有个关键逻辑需要合并相邻相同字符并去除 blank 标记。例如模型输出[京, 京, _, A, _, A, 1]解码后应为京A1而不是京京AA1。实际推理时假设识别网络输出维度是T * C时间步 × 字符类别数解码时用torch.argmax取每个时间步概率最大的字符然后做合并和去除 blank。这一套逻辑如果没写好就会出现识别结果里夹着重复字符或空格。项目里onnx_infer.py和openvino_infer.py都内置了解码逻辑调用时只要保证输入预处理一致即可。如果是从零写的推理脚本解码函数建议写成下面这样先合并再删空def ctc_decode(sequence, blank0): decoded [] prev None for idx in sequence: if idx ! prev and idx ! blank: decoded.append(idx) prev idx return .join(decoded)这个函数把所有逻辑压缩成一个循环blank默认索引 0恰好对应alphabets.py首位占位符。如果字符表顺序变了这里也要同步改。5. 避坑与常见问题训练、导出、推理三阶段的踩坑记录5.1 现象训练 loss 降到 0.1但测试集识别全错原因这是最常见也最隐蔽的坑。训练脚本里对输入图像做了归一化比如除以 255 或减均值除方差而推理脚本没有做或者两者参数不一致。模型在训练分布上拟合得“很好”到了部署分布上就崩。另一个常见原因是字符表顺序在训练后被改动过模型记住的索引和新的字符表对不上。解决把训练和推理的预处理代码逐行对照。一般车牌识别输入要归一化到[0,1]或[-1,1]确保train.py和detect_demo.py/onnx_infer.py用的是同一个 transform。我习惯把所有预处理统一封装成一个函数放在plateLabel.py里让所有脚本引用同一个入口从根上避免漂移。5.2 现象ONNX 导出报错或导出后推理结果错误原因PyTorch 转 ONNX 时某些动态维度如 batch size、序列长度没有指定或 opset 版本不支持某些算子。特别是 LPRNet 里的 RNN 结构在不同 opset 下支持度不一样。解决导出时显式指定动态轴python export.py --weights best.pt --include onnx --dynamic --opset 12或者在脚本里设置dynamic_axes{input:[0,2], output:[0]}把宽度维度也设为动态。如果报“Resize”算子错误换成--opset 11往往能解决问题。导出后别急着部署先用onnxruntime跑一张图对比 PyTorch 输出误差大于 1e-3 就要查输入顺序。5.3 现象识别结果多了空格或重复字符原因CTC 解码实现有问题。常见错误是只做argmax没有合并重复或者把 blank通常是字符表首位_或数字 0当作普通字符输出。字符表里如果预留了_作为 blank但解码时没有过滤它就会出现空格。解决检查解码函数确保按“合并相邻相同 → 去除 blank”的顺序处理。模板代码就是上一章写的ctc_decode确认blank的位置与alphabets.py严格对应通常是 0。调试时打印一下原始索引序列能直观看出重复和 blank 的位置。5.4 现象OpenVINO 推理结果漂移同一张图推理结果不一致原因大部分是预处理差异。PyTorch 推理可能用了归一化到[0,1]而 OpenVINO 中间表示IR要求输入也是[0,1]或特定 mean/std。如果输入是 BGR 顺序OpenVINO 默认也可能读成 RGB导致颜色通道错位检测框自然不准。解决在openvino_infer.py里检查输入图像的读取方式确认使用cv2.cvtColor(img, cv2.COLOR_BGR2RGB)或保持一致。同时把归一化参数写死为测试过的值不要依赖外部配置文件。另一个坑是 IR 模型的输入输出张量名转换后可能叫input、output代码里要用实际名字别写死。5.5 现象demo 检测框偏移或漏检原因YOLOv5 推理时输入尺寸和 letterbox 处理与训练不一致。训练时--img 640但 demo 脚本可能只做了简单 resize没有按长边缩放和 padding。另外 NMS 阈值设太高比如 0.7会过滤掉大量低置信度的真实框。解决复用训练时的数据加载逻辑。YOLOv5 的detect.py里有一个letterbox函数必须跟训练时完全一致。NMS 阈值conf_thres默认设为 0.25iou_thres设为 0.45。当车牌比较小时可以把conf_thres降到 0.2。如果框总是偏大或偏小检查 anchors 用的是不是best.pt对应的 anchors改了模型结构后 anchors 没更新就会现系统性偏移。6. 进阶写一个端到端验收脚本跑通才算数拿到这个源码别只盯着单模块 demo。我会建议写一个端到端验收脚本把检测 识别串起来在几百张测试图上统计真实准确率。这样无论是答辩还是自查心里都有底。脚本逻辑很简单遍历测试图片调用检测模型得到框裁剪车牌图调用识别模型得到字符串与真实车牌比对。下面是一个参考实现import cv2, glob, json from detect_plate import detect_plate from plateNet import init_rec_model, recognize det_model detect_plate(weightsweights/best.pt) rec_model init_rec_model(weights/plate_net.pth) def eval_accuracy(img_dir, gt_file): gt json.load(open(gt_file)) total, correct 0, 0 for img_path in glob.glob(img_dir /*.jpg): img cv2.imread(img_path) boxes det_model(img, conf_thres0.25, iou_thres0.45) if not boxes: continue x1, y1, x2, y2 boxes[0] crop img[y1:y2, x1:x2] pred recognize(rec_model, crop) if pred gt[img_path]: correct 1 total 1 return correct / max(total, 1) print(端到端准确率: {:.4f}.format(eval_accuracy(data/test, gt.json)))这里有个关键点如果检测框本身漏检这张图就不算入分母但这会掩盖检测模型的短板。更严格的做法是只要 GT 里有车牌而检测没框出来就记一次错误。我建议在统计时把这种情况单独记为“漏检”在答辩时能解释清楚。跑通脚本后接下来可以做三件提速优化。第一批量推理把多张车牌裁剪图拼成一个 batch 送进识别网络能显著提高吞吐。第二导出 ONNX 后用onnxruntime-gpu跑 CUDA 推理比 CPU 快 5 倍以上或者用半精度 FP16。第三把检测模型输入尺寸固定成 640识别模型输入高度固定 32宽度改动态减少 padding 带来的无效计算。从那以后我每次拿到这类检测识别项目都会先花半小时写一个端到端脚本再谈调参和部署。没有端到端准确率做标尺单点指标再漂亮都是自欺欺人。希望这个套路能帮你在课程设计或答辩时少走弯路真刀真枪把项目验收过去。本文还有配套的精品资源点击获取
返回列表