基于YOLOv8与LPRNet的车牌识别系统源码解析与实战
简介这是一套面向计算机、电子信息等专业学生与算法初学者的车牌识别完整项目源码采用 YOLOv8 负责车牌区域检测、LPRNet 完成字符识别可运行于课程设计、期末大作业或毕业设计场景帮助读者理解目标检测与序列识别串联的工程实现思路。压缩包共 60 个文件约 36.15MB包含 13 个 Python 脚本、3 个 pt 与 1 个 pth 模型权重、3 个 Vue 前端文件及 json、yaml、md 等配置与说明文档覆盖数据生成、标注转换、数据集划分、模型训练与 Flask 推理服务等环节目录按 YOLOv8、LPRNet、Flask 应用与前端界面分层组织。目前已有 1171 人学习下载适合希望快速跑通车牌识别流程、借鉴检测与识别模块拆分方式的读者参考调试。1. 车牌识别系统源码拆包YOLOv8 加 LPRNet 这套组合到底能跑出什么车牌识别这个场景很多人第一反应是调个 OCR 接口就完事。但真到项目里尤其是课程设计、毕设或者需要离线部署的场合接口方案往往卡在费用、网络和可控性上。这套基于 YOLOv8 和 LPRNet 的车牌识别系统 python 源码走的是完全本地化的路线YOLOv8 负责把车牌从整张图里框出来LPRNet 负责把框出来的车牌图识别成具体字符。两个模型各司其职中间用裁剪逻辑串起来整个流程不依赖任何外部服务。拿到压缩包后目录结构其实已经把技术路线交代得很清楚。根目录下有app.py和main.py前者是 Flask 入口后者是训练或推理的主脚本。runs目录存放 YOLOv8 的训练输出utils里是通用工具函数output_lpr和model分别对应 LPRNet 的输出和权重。数据集相关脚本有generate_lpr_data.py、batch_resize_images.py、create_annotations.py、split_dataset.py覆盖了从原始图片到训练格式的完整链路。前端部分是一个 Vue 项目src、vite.config.js、package.json都在说明这套系统带了一个可视化界面不是纯命令行工具。适合谁用如果你正在做计算机视觉相关的课程设计或毕设需要一套能演示、能改、能写进论文的完整系统这套源码的完成度是够的。如果你是想学 YOLOv8 训练和 LPRNet 识别的工程落地两个模型的衔接逻辑也值得拆一遍。但如果你指望下载完直接跑出商用级精度那得先看看自己的数据和场景匹配度后面会细说。2. 环境搭建与依赖安装从零把 Flask 加 Vue 跑起来2.1 Python 侧环境与核心依赖这套系统对 Python 版本没有特别苛刻的要求但 YOLOv8 依赖 ultralytics 包LPRNet 部分依赖 PyTorch所以 Python 3.8 到 3.10 是比较稳的区间。我一般会先建一个干净的虚拟环境避免和系统里已有的包打架。python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate pip install ultralytics torch torchvision opencv-python flask numpy pillow这几行命令里ultralytics是 YOLOv8 的官方库装完之后yolo命令就能直接用。torch和torchvision是 LPRNet 的推理基础注意版本要匹配CPU 版本和 CUDA 版本装法不同。opencv-python负责图像读写和预处理flask是后端服务numpy和pillow是数据处理的基础库。装完之后建议验证一下python -c from ultralytics import YOLO; print(YOLO ok) python -c import torch; print(torch.__version__)如果第一条报错大概率是 ultralytics 没装好或者 Python 版本不兼容。第二条能打印出版本号说明 PyTorch 环境没问题。这里有个细节如果你用的是 CPU 版本推理速度会明显慢于 GPU但功能不受影响。GTX1660Ti 这个级别的显卡跑 YOLOv8 推理是够用的训练的话显存会紧张一些。2.2 前端 Vue 项目的启动前端部分是一个标准的 Vite 加 Vue 项目目录里有package.json和vite.config.js。启动之前需要先装 Node.js版本建议 16 以上。cd 前端目录 npm install npm run devnpm install会根据package-lock.json锁定版本安装依赖这一步如果卡住通常是网络问题可以换国内镜像源。npm run dev启动开发服务器默认会在localhost的某个端口起服务。前端启动后需要确认它请求的后端地址和 Flask 的地址一致这个配置一般在vite.config.js的 proxy 或者前端代码的请求基址里。2.3 Flask 后端的启动与接口对接app.py是 Flask 的入口文件里面定义了上传图片、调用模型、返回识别结果的接口。启动方式很直接python app.py默认情况下 Flask 会监听 5000 端口。启动后前端上传的图片会先存到uploads目录然后走 YOLOv8 检测裁出车牌区域再送入 LPRNet 识别最后把结果返回给前端展示。这里有一个容易忽略的点app.py里加载模型权重的路径是写死的还是从配置读的。如果是写死的你需要确认model目录下的权重文件路径和代码里一致。常见做法是把 YOLOv8 的.pt文件和 LPRNet 的.pth文件都放在model目录下代码里用相对路径引用。提示Flask 默认是单线程的如果同时有多张图片请求会排队处理。课程设计演示够用但如果你要做并发测试需要加threadedTrue或者用 gunicorn 部署。3. YOLOv8 车牌检测训练自己的数据集与参数调优3.1 数据集准备与标注格式转换YOLOv8 训练需要的数据格式是每张图片对应一个.txt标注文件每行格式为类别 中心x 中心y 宽度 高度坐标都是归一化后的值。这套源码里提供了create_annotations.py和split_dataset.py说明作者已经考虑了从原始标注到 YOLO 格式的转换。如果你手头是 Labelme 标注的 JSON 文件常见做法是先转成 YOLO 格式再划分训练集和验证集。create_annotations.py大概率就是做这个转换的split_dataset.py负责按比例切分。运行顺序一般是先转换再切分python create_annotations.py --input_dir images_lpr --output_dir annotations python split_dataset.py --data_dir annotations --train_ratio 0.8--input_dir指向原始图片目录--output_dir是转换后的标注输出目录。--train_ratio 0.8表示 80% 做训练20% 做验证。这两个脚本的具体参数名需要打开文件确认因为不同作者的命名习惯不一样。3.2 YOLOv8 训练命令与关键参数YOLOv8 的训练入口非常简洁一行命令就能启动yolo detect train datadataset.yaml modelyolov8n.pt epochs100 imgsz640 batch16datadataset.yaml是数据集配置文件里面需要写明训练集和验证集的路径、类别数和类别名称。modelyolov8n.pt用的是 nano 版本参数量最小适合课程设计这种对精度要求不是极致的场景。如果显存够可以换成yolov8s.pt或yolov8m.pt。epochs100是训练轮数车牌检测这种单类别任务通常 50 到 100 轮就能收敛。imgsz640是输入尺寸车牌在图中占比较小的话可以适当调大。batch16是批次大小显存不够就往下调。训练完成后权重会保存在runs/detect/train/weights/best.pt。这个文件就是后续推理要用的模型。训练过程中可以观察损失曲线如果验证集损失一直不降可能是学习率太大或者数据标注有问题。3.3 推理与车牌区域裁剪训练好的模型用来做推理时核心是拿到检测框的坐标然后把车牌区域裁出来from ultralytics import YOLO import cv2 model YOLO(runs/detect/train/weights/best.pt) results model(test.jpg) for r in results: boxes r.boxes for box in boxes: x1, y1, x2, y2 box.xyxy[0].tolist() plate_img cv2.imread(test.jpg)[int(y1):int(y2), int(x1):int(x2)] cv2.imwrite(plate_crop.jpg, plate_img)这段代码的逻辑是YOLO 返回的boxes里包含每个检测框的坐标xyxy格式是左上角和右下角。用这两个坐标从原图上切片就得到了车牌区域。裁出来的图会送给 LPRNet 做字符识别。这里有个细节裁剪时最好留一点边距因为检测框有时候会贴得太紧导致车牌边缘字符被切掉。注意如果检测框坐标出现负数或者超出图片范围切片会报错。稳妥的做法是加一个 clamp 操作把坐标限制在图片尺寸内。4. LPRNet 字符识别从车牌图到具体号码的完整链路4.1 LPRNet 的网络结构与输入要求LPRNet 是一个轻量级的车牌识别网络核心思路是用 CNN 提取特征然后用 CTC 损失做序列识别不需要先做字符分割。这意味着它可以端到端地输出车牌号码对于中国车牌这种字符数不固定的场景很友好。输入方面LPRNet 通常要求车牌图缩放到固定尺寸常见的是 94x24 或者 120x32。这套源码里batch_resize_images.py应该就是做批量缩放的。LPRNet 的输出是一个字符序列解码后就是车牌号。字符集一般包括数字、字母和省份简称具体字符集定义在代码里需要和训练时保持一致。4.2 模型加载与推理代码LPRNet 的推理代码需要先加载网络结构和权重import torch from model.LPRNet import LPRNet # 路径根据实际结构调整 lprnet LPRNet(class_numlen(chars), dropout_rate0) lprnet.load_state_dict(torch.load(model/lprnet.pth, map_locationcpu)) lprnet.eval() plate_img cv2.resize(plate_img, (94, 24)) plate_img plate_img.astype(float32) / 255.0 plate_img (plate_img - 0.5) / 0.5 plate_tensor torch.from_numpy(plate_img).unsqueeze(0).permute(0, 3, 1, 2) with torch.no_grad(): output lprnet(plate_tensor) preds output.argmax(dim2).squeeze().tolist()class_num是字符集大小必须和训练时一致。load_state_dict加载权重map_locationcpu保证在无 GPU 环境下也能加载。预处理部分做了归一化均值和方差是 0.5这是 LPRNet 论文里的标准做法。unsqueeze(0)增加批次维度permute把通道维度换到前面因为 PyTorch 要求 NCHW 格式。解码部分需要根据字符集把索引映射回字符并且去掉 CTC 的空白标签和重复字符。这部分逻辑在utils目录下应该有现成的函数直接调用即可。4.3 检测与识别的串联逻辑把 YOLOv8 和 LPRNet 串起来整个流程是原图 → YOLOv8 检测 → 裁剪车牌 → 预处理 → LPRNet 推理 → 解码 → 输出车牌号。这套源码的main.py或者app.py里应该已经实现了这个串联。实际跑的时候有几个地方容易出问题。一是裁剪区域如果太小LPRNet 识别会丢字符二是如果一张图里有多个车牌需要循环处理每个检测框三是 LPRNet 的输出长度不固定解码时要处理好变长序列。常见做法是设置一个最大长度超出部分截断不足部分补空白。提示如果识别结果出现大量重复字符检查 CTC 解码逻辑是否正确去重。如果识别结果全是空白检查输入图片的归一化参数是否和训练时一致。5. 避坑与排查这套源码跑不起来时先看这几个地方5.1 模型权重路径不对导致加载失败现象启动app.py时报FileNotFoundError或者KeyError提示找不到权重文件或键不匹配。原因代码里写死的权重路径和实际文件位置不一致或者 LPRNet 的state_dict键名和网络定义不匹配。解决先确认model目录下有哪些文件然后把代码里的路径改成绝对路径或正确的相对路径。如果是键名不匹配打印lprnet.state_dict().keys()和加载的权重键名对比通常是因为网络结构定义时多了一层或少了一层。5.2 图片上传后识别结果为空现象前端上传图片后后端返回空字符串或者报错。原因YOLOv8 没有检测到车牌导致裁剪区域为空LPRNet 收到空图后输出异常。解决先单独跑 YOLOv8 推理确认模型能检测到车牌。如果检测不到可能是模型没有在车牌数据上训练过或者置信度阈值设得太高。在推理代码里把conf参数调低试试比如model(test.jpg, conf0.25)。5.3 前端请求跨域被拦截现象前端页面能打开但上传图片时浏览器控制台报 CORS 错误。原因Flask 后端没有配置跨域头前端和后端端口不同浏览器默认拦截。解决安装flask-cors在app.py里加两行from flask_cors import CORS CORS(app)如果不想装额外包也可以手动在响应头里加Access-Control-Allow-Origin。5.4 训练时显存不足报 CUDA out of memory现象YOLOv8 训练刚开始就报显存不够。原因batch设得太大或者imgsz太大超出显卡容量。解决把batch降到 8 或 4imgsz从 640 降到 416 试试。如果还是不够换yolov8n.pt这个最小模型。GTX1660Ti 是 6GB 显存跑yolov8n加batch8加imgsz416是比较稳的组合。5.5 LPRNet 识别结果乱码或重复现象识别出来的车牌号有明显重复字符或者全是同一个字符。原因CTC 解码逻辑有问题或者输入图片的预处理和训练时不一致。解决检查解码函数是否正确处理了空白标签。另外确认输入图片的尺寸和归一化参数LPRNet 对输入分布比较敏感均值和方差不对会导致输出完全乱掉。6. 进阶技巧用 batch_resize_images.py 做数据增强与模型微调这套源码里有一个容易被忽略的脚本batch_resize_images.py。很多人以为它只是批量改尺寸但实际上它可以配合数据增强做很多事情。我一般会用它先把原始车牌图统一缩放到 LPRNet 需要的尺寸然后在缩放过程中加入随机裁剪、亮度抖动和轻微旋转相当于一个轻量级的数据增强管道。具体做法是改一下脚本里的 resize 逻辑import cv2 import numpy as np import os def augment_and_resize(img_path, output_dir, target_size(94, 24)): img cv2.imread(img_path) h, w img.shape[:2] # 随机亮度抖动 brightness np.random.uniform(0.8, 1.2) img np.clip(img * brightness, 0, 255).astype(np.uint8) # 随机小角度旋转 angle np.random.uniform(-3, 3) M cv2.getRotationMatrix2D((w//2, h//2), angle, 1.0) img cv2.warpAffine(img, M, (w, h), borderModecv2.BORDER_REPLICATE) # 缩放到目标尺寸 img cv2.resize(img, target_size) filename os.path.basename(img_path) cv2.imwrite(os.path.join(output_dir, filename), img)这段代码的逻辑是先读图然后做亮度抖动和旋转最后缩放到 LPRNet 的输入尺寸。亮度抖动范围 0.8 到 1.2 是经验值再大就可能过曝或过暗。旋转角度控制在正负 3 度以内因为车牌本身是矩形旋转太大会引入黑边。borderModecv2.BORDER_REPLICATE用边缘像素填充旋转后的空白区域比填黑色更自然。用增强后的数据微调 LPRNet通常能提升 2 到 5 个百分点的识别准确率尤其是在光照不均匀或者拍摄角度有偏差的场景下。微调时学习率要调小比如从 0.001 降到 0.0001训练轮数不用太多10 到 20 轮就够。还有一个技巧是把 YOLOv8 检测置信度较低的样本单独拿出来人工确认后加入训练集。这些样本往往是模型的薄弱环节补进去之后检测和识别都会更稳。我一般会在runs/detect目录下找conf在 0.3 到 0.5 之间的检测结果这些就是边界样本。从那以后我每次拿到新的车牌数据都会先跑一遍batch_resize_images.py做增强再送进 LPRNet 微调最后用一批没见过的图做验证。这套流程走下来模型在新场景的适应速度会快很多。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →