
简介本资源面向计算机视觉学习者与智能交通领域开发者提供一套基于YOLOv8的车牌检测与识别完整实战方案帮助读者从零搭建可运行的车牌识别系统解决车牌定位、字符分割与识别等核心问题。压缩包共55个文件约32.93MB包含23张jpg与13张png图像样本、3个ipynb实战笔记、2个pt权重文件、2段mp4演示视频以及yaml配置、pb模型、csv数据与md说明文档覆盖数据准备、模型训练到推理部署的完整链路。目前已有298人学习下载。项目源码可直接用于实际环境部署与测试详细流程教程则拆解了数据集准备、模型训练与性能评估等环节并涉及不同光照、角度与遮挡条件下的识别处理思路便于读者理解算法原理、优化识别率并积累工程排错经验适合作为课程设计、毕业设计或工业开发的参考案例。1. 车牌识别项目拆解YOLOv8 检测 字符识别到底怎么串起来车牌识别这个方向很多做嵌入式、做安防、做停车场系统的朋友都绕不开。但真正动手时你会发现它不是一个模型能搞定的事——检测和识别是两套逻辑中间还夹着透视矫正、字符分割、后处理校验。标题里说的「基于 YOLOv8 实现车牌检测 车牌识别算法」核心思路就是用 YOLOv8 先把车牌框出来再对框内区域做字符级识别。这套方案的好处是检测端可以直接复用 YOLOv8 的预训练权重和成熟训练流程识别端可以选 CRNN、LPRNet 或者轻量级 CNNCTC灵活度很高。适合谁有 Python 基础、跑过至少一次 YOLO 训练、想做一个能演示能落地的完整项目的工程师。下面我从数据准备一路讲到部署推理把每个环节的参数和坑都摊开说。2. 数据准备与 YOLOv8 车牌检测训练从标注到权重文件2.1 车牌检测数据集怎么标、怎么转车牌检测本质是单类目标检测标注时只需要框出车牌区域类别名统一写成plate或license_plate。常见做法是用 labelme 或 roboflow 标注导出 YOLO 格式。YOLO 格式每行是class_id x_center y_center width height全部归一化到 0~1。如果你手头是 VOC 的 XML需要转一道转换脚本如下import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_dir, out_dir, classes): # classes [plate] if not os.path.exists(out_dir): os.makedirs(out_dir) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in classes: continue cls_id classes.index(cls_name) bbox obj.find(bndbox) x1 float(bbox.find(xmin).text) y1 float(bbox.find(ymin).text) x2 float(bbox.find(xmax).text) y2 float(bbox.find(ymax).text) # 归一化并转为中心点宽高 x_center (x1 x2) / 2.0 / w y_center (y1 y2) / 2.0 / h bw (x2 - x1) / w bh (y2 - y1) / h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}) with open(os.path.join(out_dir, xml_file.replace(.xml, .txt)), w) as f: f.write(\n.join(lines))这段脚本做了三件事解析 XML、过滤指定类别、归一化坐标并写入 txt。参数上注意classes列表顺序要和后续训练配置的names一致否则类别索引会错位。另外如果图片有旋转或裁剪标注框要同步调整不然训练时框会偏。2.2 YOLOv8 训练配置与关键参数数据集目录结构按官方要求组织dataset/ images/ train/ val/ labels/ train/ val/然后写一个plate.yamlpath: ./dataset train: images/train val: images/val nc: 1 names: [plate]训练命令用 ultralytics 的 CLI 或 Python API 都行我一般用 Python 脚本方便调参from ultralytics import YOLO model YOLO(yolov8n.pt) # 从预训练权重起步 model.train( dataplate.yaml, epochs100, imgsz640, batch16, lr00.01, lrf0.01, optimizerSGD, patience20, device0, projectruns/plate, nameexp1 )参数说明imgsz640是输入分辨率车牌通常占画面比例不大如果小目标多可以提到 1280但显存和速度要权衡batch16根据显卡调整8G 显存跑 yolov8n 可以到 32lr0初始学习率SGD 下 0.01 是常用值Adam 的话降到 0.001patience20是早停耐心值验证集指标 20 轮不涨就停。训练完在runs/plate/exp1/weights/best.pt拿到检测权重。2.3 检测效果验证与常见指标解读训练完先看results.png里的损失曲线和 mAP 曲线。车牌检测一般 mAP0.5 能到 0.95 以上才算正常如果低于 0.9 要检查标注质量。用model.val()跑验证集重点看mAP50和mAP50-95。另外实际推理时用conf0.25起步车牌场景可以适当提高到 0.4 减少误检。如果发现漏检多先看是不是小目标可以尝试imgsz1280或换 yolov8s/m。如果误检多提高conf阈值或增加负样本。3. 车牌字符识别CRNN 与 LPRNet 的选型与实现3.1 识别方案选型CRNN、LPRNet 还是 CNNCTC车牌识别本质是序列识别问题输入是矫正后的车牌图输出是字符序列。常见三条路CRNNCNNRNNCTC、LPRNet轻量 CNNCTC、以及分割单字符分类。CRNN 精度高但推理慢适合服务端LPRNet 参数量小适合边缘设备如 RK3588、Hi3516 这类分割分类对字符粘连和倾斜敏感现在用得少了。我一般推荐 LPRNet因为车牌字符集固定省份简称字母数字共约 65 类序列长度固定 7 或 8LPRNet 在精度和速度上平衡最好。3.2 车牌矫正与字符数据集生成检测框出来的车牌往往有倾斜直接送识别会掉点。常见做法是用检测框的四个角点做透视变换。YOLOv8 默认输出的是水平矩形框如果要拿角点可以训练时加关键点分支或者用 OpenCV 的minAreaRect对二值化后的车牌区域做拟合。简单场景下先做灰度、边缘检测、轮廓筛选再cv2.getPerspectiveTransform矫正import cv2 import numpy as np def rectify_plate(img, pts): # pts 是四个角点顺序左上、右上、右下、左下 pts np.array(pts, dtypenp.float32) w int(max(np.linalg.norm(pts[0] - pts[1]), np.linalg.norm(pts[2] - pts[3]))) h int(max(np.linalg.norm(pts[0] - pts[3]), np.linalg.norm(pts[1] - pts[2]))) dst np.array([[0, 0], [w, 0], [w, h], [0, h]], dtypenp.float32) M cv2.getPerspectiveTransform(pts, dst) return cv2.warpPerspective(img, M, (w, h))矫正后统一 resize 到94x24LPRNet 常用输入尺寸再生成字符标签。标签格式建议用字符索引序列比如京A12345映射成[0, 10, 1, 2, 3, 4, 5]每个字符对应一个 idCTC 训练时不需要对齐。3.3 LPRNet 训练与 CTC 损失配置LPRNet 结构简单几个卷积块 全局平均池化 1x1 卷积输出类别。训练时用 CTC 损失PyTorch 自带nn.CTCLoss。关键代码import torch import torch.nn as nn class LPRNet(nn.Module): def __init__(self, num_classes): super().__init__() self.backbone nn.Sequential( nn.Conv2d(3, 32, 3, 1, 1), nn.ReLU(), nn.MaxPool2d(2, 2), nn.Conv2d(32, 64, 3, 1, 1), nn.ReLU(), nn.MaxPool2d(2, 2), nn.Conv2d(64, 128, 3, 1, 1), nn.ReLU(), nn.MaxPool2d((2, 1)), nn.Conv2d(128, 256, 3, 1, 1), nn.ReLU(), nn.MaxPool2d((2, 1)), ) self.head nn.Conv2d(256, num_classes, 1, 1) def forward(self, x): x self.backbone(x) x self.head(x) # 输出形状 [B, C, H, W] - [T, B, C] x x.mean(dim2) # 沿高度池化 x x.permute(2, 0, 1) return x # 训练循环关键部分 criterion nn.CTCLoss(blank0, zero_infinityTrue) optimizer torch.optim.Adam(model.parameters(), lr1e-3) for imgs, labels, label_lens in dataloader: logits model(imgs) # [T, B, C] log_probs logits.log_softmax(2) input_lens torch.full((imgs.size(0),), logits.size(0), dtypetorch.long) loss criterion(log_probs, labels, input_lens, label_lens) loss.backward() optimizer.step()参数上注意blank0要和字符字典预留位置一致zero_infinityTrue防止长序列梯度爆炸。训练 50~100 epoch学习率 1e-3 起步后期降到 1e-4。验证时用贪心解码或 beam search车牌场景贪心就够。4. 检测识别串联推理从图片到车牌字符串的完整链路4.1 端到端推理脚本与后处理把检测和识别串起来流程是读图 → YOLOv8 检测 → 裁剪车牌区域 → 矫正 → 送 LPRNet → CTC 解码 → 输出字符串。完整脚本from ultralytics import YOLO import cv2 import torch import numpy as np det_model YOLO(runs/plate/exp1/weights/best.pt) rec_model LPRNet(num_classes65) rec_model.load_state_dict(torch.load(lprnet_best.pth)) rec_model.eval() chars [blank] list(京沪津渝冀晋蒙辽吉黑苏浙皖闽赣鲁豫鄂湘粤桂琼川贵云藏陕甘青宁新) list(ABCDEFGHIJKLMNOPQRSTUVWXYZ) list(0123456789) def decode_ctc(logits): # logits: [T, C] indices logits.argmax(dim1) result [] prev -1 for idx in indices: if idx ! prev and idx ! 0: result.append(chars[idx]) prev idx return .join(result) img cv2.imread(test.jpg) results det_model(img, conf0.4)[0] for box in results.boxes: x1, y1, x2, y2 map(int, box.xyxy[0].tolist()) plate_img img[y1:y2, x1:x2] plate_img cv2.resize(plate_img, (94, 24)) plate_img plate_img.astype(np.float32) / 255.0 plate_img np.transpose(plate_img, (2, 0, 1)) tensor torch.from_numpy(plate_img).unsqueeze(0) with torch.no_grad(): logits rec_model(tensor).squeeze(1) plate_str decode_ctc(logits) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, plate_str, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) cv2.imwrite(result.jpg, img)逻辑说明检测用conf0.4过滤低置信框裁剪后 resize 到识别模型输入尺寸归一化到 0~1CTC 解码时去重且去掉 blank。参数上chars列表顺序必须和训练时完全一致否则解码全错。4.2 推理速度优化与批量处理如果要做视频流或批量图片逐张推理太慢。优化点检测和识别都转 ONNX 或 TensorRT用onnxruntime或tensorrt推理批量处理时把多张车牌拼成一个 batch 送识别模型。另外检测端可以用halfTrue开 FP16速度提升明显。RK3588 这类板子建议用 RKNN 工具链转模型具体转换流程参考官方文档核心是量化时校准集要覆盖各种光照和角度。5. 避坑与排查车牌识别项目里最容易翻车的 5 个点5.1 检测框偏大导致识别串字符现象识别结果多出字符或首尾字符错。原因检测框把车牌边框甚至背景框进去了识别模型看到多余区域。解决训练检测时标注紧贴车牌字符区域推理时对检测框做 5%~10% 的内缩或者用角点回归拿更准的框。5.2 CTC 解码输出重复字符现象京A12345解成京A112345。原因CTC 去重逻辑写错或者 blank 索引和字符字典没对齐。解决确认blank0且解码时idx ! prev and idx ! 0训练时CTCLoss(blank0)。5.3 训练损失不下降或震荡现象loss 卡在 5.0 以上不降。原因学习率太大、标注有错、或者字符字典和标签不匹配。解决先过拟合 10 张图如果 loss 能降到 0.1 以下说明模型没问题再查数据学习率降到 1e-4 试检查标签里有没有字典外的字符。5.4 小目标车牌漏检严重现象远处车牌检测不到。原因输入分辨率不够车牌在 640 下只有十几个像素。解决训练和推理都提到 1280或者用切片推理SAHI把大图切小块分别检测再合并。5.5 矫正后字符变形导致识别率下降现象矫正后车牌拉伸严重识别错字。原因角点顺序错或透视变换目标尺寸比例不对。解决固定角点顺序为左上、右上、右下、左下目标宽高比按中国车牌 440:140 约 3.14:1 设置不要随意 resize 到正方形。6. 进阶技巧用 ONNX 量化把车牌识别推到 30 FPS 以上检测和识别都跑 PyTorch 在服务端还行但要上边缘设备或追求高帧率必须转 ONNX 并量化。我一般先把两个模型分别导出 ONNX再用onnxruntime的量化工具做动态量化。检测模型导出from ultralytics import YOLO model YOLO(runs/plate/exp1/weights/best.pt) model.export(formatonnx, imgsz640, halfTrue, simplifyTrue)识别模型导出torch.onnx.export( rec_model, torch.randn(1, 3, 24, 94), lprnet.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch}, output: {0: batch}}, opset_version11 )导出后做动态量化from onnxruntime.quantization import quantize_dynamic, QuantType quantize_dynamic(lprnet.onnx, lprnet_int8.onnx, weight_typeQuantType.QUInt8)量化后模型体积缩小约 4 倍推理速度提升 2~3 倍。注意检测模型量化后 mAP 可能掉 1~2 个点识别模型量化后准确率掉 0.5% 以内可接受。验证时用同一批测试图对比量化前后结果如果掉点太多改用静态量化并准备 100~200 张校准图。我自己的习惯是每次转完模型先跑 50 张回归测试确认检测框和识别字符串都和 PyTorch 版一致再上线。这套流程在 RK3588 上跑 1080p 视频检测识别能稳在 30 FPS 以上CPU 占用不到 40%。如果你也在做类似项目建议先把检测和识别分开调通再串链路最后做量化部署每一步都留回归测试能省很多后悔药。希望帮到你。本文还有配套的精品资源点击获取