☰
基于YOLOv8与LPRNet的车牌识别系统源码解析与实战
2026/10/1 17:41:58 网站建设 项目流程

简介:这是一套面向计算机、电子信息等专业学生与算法初学者的车牌识别完整项目源码,采用 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 dev

npm 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 默认是单线程的,如果同时有多张图片请求,会排队处理。课程设计演示够用,但如果你要做并发测试,需要加threaded=True或者用 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 data=dataset.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16

data=dataset.yaml是数据集配置文件,里面需要写明训练集和验证集的路径、类别数和类别名称。model=yolov8n.pt用的是 nano 版本,参数量最小,适合课程设计这种对精度要求不是极致的场景。如果显存够,可以换成yolov8s.pt或yolov8m.pt。epochs=100是训练轮数,车牌检测这种单类别任务,通常 50 到 100 轮就能收敛。imgsz=640是输入尺寸,车牌在图中占比较小的话,可以适当调大。batch=16是批次大小,显存不够就往下调。

训练完成后,权重会保存在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_num=len(chars), dropout_rate=0) lprnet.load_state_dict(torch.load('model/lprnet.pth', map_location='cpu')) 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(dim=2).squeeze().tolist()

class_num是字符集大小,必须和训练时一致。load_state_dict加载权重,map_location='cpu'保证在无 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', conf=0.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 或 4,imgsz从 640 降到 416 试试。如果还是不够,换yolov8n.pt这个最小模型。GTX1660Ti 是 6GB 显存,跑yolov8n加batch=8加imgsz=416是比较稳的组合。

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), borderMode=cv2.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 度以内,因为车牌本身是矩形,旋转太大会引入黑边。borderMode=cv2.BORDER_REPLICATE用边缘像素填充旋转后的空白区域,比填黑色更自然。

用增强后的数据微调 LPRNet,通常能提升 2 到 5 个百分点的识别准确率,尤其是在光照不均匀或者拍摄角度有偏差的场景下。微调时学习率要调小,比如从 0.001 降到 0.0001,训练轮数不用太多,10 到 20 轮就够。

还有一个技巧是:把 YOLOv8 检测置信度较低的样本单独拿出来,人工确认后加入训练集。这些样本往往是模型的薄弱环节,补进去之后检测和识别都会更稳。我一般会在runs/detect目录下找conf在 0.3 到 0.5 之间的检测结果,这些就是边界样本。

从那以后我每次拿到新的车牌数据,都会先跑一遍batch_resize_images.py做增强,再送进 LPRNet 微调,最后用一批没见过的图做验证。这套流程走下来,模型在新场景的适应速度会快很多。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询