news 2026/8/27 2:00:48

基于YOLOv5+LPRNet的车牌检测识别系统实战详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于YOLOv5+LPRNet的车牌检测识别系统实战详解

简介:车牌识别作为OCR技术在智能交通领域的典型应用,常被简化为文字识别问题,实则需应对光照变化、角度倾斜、运动模糊等复杂场景。传统图像处理方案鲁棒性不足,而基于深度学习的智能视觉技术正成为主流。在技术架构上,常采用目标检测与文字识别相结合的两阶段方案:先定位车牌区域,再对裁剪区域进行字符序列识别。该方案具备检测与识别独立优化、部署灵活等优势,广泛应用于停车场管理、电子警察、高速收费等场景。针对国内车牌场景,基于YOLOv5和LPRNet的轻量化组合在精度与实时性之间实现了良好平衡,配合CCPD数据集的关键信息解析与训练调优,可有效提升模型的泛化能力。本文系统梳理了车牌检测识别项目的完整实现链路,涵盖数据预处理、模型训练、集成部署与工程避坑细节,为相关开发实践提供参考。 车牌识别这个方向,说实话已经不算新鲜了,但能把"检测+识别"这一条链路真正跑通、跑稳,还是一件挺有成就感的事。前阵子我基于YOLOv5和LPRNet,在CCPD数据集上完整实现了一套车牌检测+识别系统,整个项目打包出来也就是标题里那个zip。这篇文章不打算做那种"粘贴代码+跑通就完事"的教程,我会把方案选型、数据拆解、训练细节、集成部署这些环节全部捋一遍,尤其是那些文档里不会写、只有自己踩过坑才知道的细节,一次性讲清楚。如果你正准备做车牌识别相关的工作,不管是毕业设计、工程项目还是以学习为目的,这篇文章应该能帮你少走很多弯路。

1. 项目核心价值与适用人群

1.1 车牌识别的典型应用场景

车牌识别,行业里常称为VLPR(Vehicle License Plate Recognition),本质上是OCR的一个特殊分支,但比普通OCR要复杂不少。普通OCR面对的大多是规整的印刷文字,而车牌识别要面对的是户外光照变化、拍摄角度倾斜、车速带来的运动模糊、车牌本身的老化污损,加上各国车牌格式差异巨大,这些因素叠加在一起,让车牌识别成了一个典型的"看起来简单、做起来头疼"的任务。

常见的落地场景包括:

  • 停车场出入口管理:识别车牌后自动抬杆、计费,这是目前最成熟的应用。
  • 电子警察和交通违章抓拍:在路口抓拍车辆,识别车牌后与数据库比对。
  • 高速公路收费站的ETC辅助识别:当ETC设备失效或未安装时,通过车牌识别作为兜底方案。
  • 园区、小区门禁:通过车牌白名单实现自动放行。
  • 移动端警务通:交警用手机或平板拍摄车辆照片,实时识别车牌。

这些场景对系统的要求其实不太一样。停车场场景相对友好,车辆速度慢、拍摄距离近、光照相对可控;而电子警察和高速场景,车辆速度快、环境复杂,对检测和识别的实时性和鲁棒性要求就高很多。我做的这个项目,目标是在通用场景下有较好的表现,同时保证推理速度能满足实时需求,所以选型时特意考虑了轻量化和精度的平衡。

1.2 为什么是YOLOv5和LPRNet的组合

我第一次做车牌识别的时候,用的是传统图像处理方案:颜色分割定位车牌区域,再对字符做模板匹配或特征分类。说实话,那种方案在固定场景、固定角度下能用,但换一个场景就崩,鲁棒性很差。后来转向深度学习方案,就面临一个选型问题:用什么模型来做检测,用什么模型来做识别。

综合对比下来,我选了YOLOv5+LPRNet,理由可以拆成两条线来看。

检测侧,YOLOv5在工业界的普及率非常高,社区活跃,资料多,部署方案成熟。虽然现在已经有了YOLOv8甚至更新的版本,YOLOv5在速度和精度之间仍然有很好的平衡,而且针对车牌这种小目标检测,YOLOv5的anchor机制经过调整后可以取得不错的效果。更关键的是,YOLOv5的代码结构清晰,改起来方便,我可以在不改动核心逻辑的情况下,针对车牌数据集做定制化修改。

识别侧,LPRNet是一个专门为车牌识别设计的轻量级网络,它有两个突出的优点:第一,不需要字符级标注,只需要车牌的整串标签即可训练,这大大降低了数据标注成本;第二,网络结构简单,参数量小,推理速度极快,在CPU上都能跑到实时。这一点在嵌入式部署时非常关键。

两阶段方案的另一个好处是,检测和识别可以独立优化。如果检测效果不好,我只需要调检测模块;如果识别不准,我也只需要调识别模块。相比之下,端到端的车牌识别模型虽然看起来更简洁,但定位和识别耦合在一起,调试难度和训练成本都更高。

2. 整体方案与架构设计

2.1 两阶段识别流水线

整个系统的处理流程可以概括为:输入一张图像,先由YOLOv5检测模型定位出车牌区域,把这块区域裁剪下来,再交给LPRNet模型识别出车牌字符。

用文字描述总觉得不够直观,这里直接给出核心代码逻辑:

import cv2 import torch # 加载两个模型 detector = torch.hub.load('ultralytics/yolov5', 'custom', path='weights/license_plate.pt') recognizer = LPRNet() recognizer.load_state_dict(torch.load('weights/lprnet.pth', map_location='cpu')) recognizer.eval() def plate_recognize(image): # 第一步:检测车牌位置 results = detector(image) boxes = results.xyxy[0].cpu().numpy() plates = [] for box in boxes: x1, y1, x2, y2, conf, cls = box if conf < 0.5: continue # 裁剪车牌区域 plate_img = image[int(y1):int(y2), int(x1):int(x2)] # 第二步:识别车牌字符 text = recognizer_forward(plate_img) plates.append({'bbox': [x1, y1, x2, y2], 'text': text, 'conf': conf}) return plates

这种两阶段架构最直观的好处是解耦。检测模型只负责回答"车牌在哪里",识别模型只负责回答"车牌是什么"。实际调试的时候,你拿到一张识别错误的车牌,能快速定位问题出在哪一环节。如果是检测框歪了,那就是检测模块的问题;如果检测框很正但识别错了,那就去优化识别模块,思路非常清晰。

2.2 为什么检测环节用YOLOv5而不是其他模型

我在确定检测方案时,实际对比过几个方向:传统图像处理、Faster R-CNN、SSD、YOLOv5,还有当时刚出的YOLOv6/v7/v8。

传统方案先排除。虽然OpenCV里有很多现成的轮廓查找和颜色分割方法,但车牌检测面临的最大问题是环境变化:白天和夜晚的亮度差异、阴影遮挡、车身颜色与车牌颜色相近等。传统方法需要人工设计特征,泛化能力很差,换一个拍摄角度就可能失效。

Faster R-CNN虽然精度高,但速度太慢,两阶段检测器本身的架构决定了它在实时场景下的劣势。SSD速度不错,但小目标检测能力一般,而车牌在整幅图像中通常只占很小一块区域,属于典型的小目标。

YOLOv8出来之后,我去看了一些对比评测,在相同骨干网络和输入尺寸下,YOLOv5和YOLOv8的精度差距并不大,但YOLOv5的生态更成熟,网上关于训练自定义数据集的教程一抓一大把,遇到的坑也基本都被前人踩平了。对于一个需要快速落地、后续还要部署到嵌入式设备的项目来说,稳定性和可参考资料的数量比那一点精度提升更重要。

2.3 识别环节为什么不用CRNN而用LPRNet

车牌识别的核心模型选择,我对比过CRNN和LPRNet两个方案。CRNN是通用的OCR框架,通过CNN提取特征,然后用RNN处理序列信息,最后接CTC损失解码。它的问题在于:车牌字符长度是固定的(国内蓝牌一般是7个字符),CRNN的RNN结构对这种固定长度短文本识别来说有点"杀鸡用牛刀",而且RNN的时序处理在推理时会增加计算量。

LPRNet的思路完全不同。它直接用CNN提取特征,然后通过CTC(Connectionist Temporal Classification)对特征序列解码。CTC的引入使得LPRNet可以做到"无需字符分割",也就是不需要知道每个字符的精确边界,只要整串车牌的标签就能训练。

LPRNet的参数量大约只有CRNN的几分之一,在CPU上的推理时间可以控制在毫秒级,这对实时性有要求的场景非常友好。而且LPRNet对字符倾斜、模糊、光照不均的鲁棒性比CRNN好一些,因为它的网络设计中包含了空间变换网络(STN)模块,可以对车牌区域做一定程度的校正。

3. CCPD数据集深度使用

3.1 CCPD数据集整体结构与组成

CCPD(Chinese City Parking Dataset)是一个公开的国内车牌数据集,主要采集于停车场场景,图像来自真实拍摄,包含不同天气、不同光照、不同角度下的大量样张。数据集规模比较大,总共包含超过30万张图像,每张图像的分辨率约为720×1160(不同版本略有差异),都是典型的停车场监控视角。

CCPD按场景和难度划分了多个子集:

子集名称主要特点使用建议
ccpd_base常规场景,车牌清晰训练主力
ccpd_blur运动模糊场景增加鲁棒性
ccpd_challenge多种困难场景混合综合测试
ccpd_db光照异常(过曝/欠曝)可扩充训练集
ccpd_fn距离远、车牌较小的场景检测模块重点测试
ccpd_rotate车牌倾斜角度较大检测模块重点测试
ccpd_weather雨雪天气场景可扩充训练集
ccpd_tilt俯仰角/水平倾斜明显可扩充训练集

使用的时候,一般把ccpd_base作为主要训练集,同时从blur、db、weather这几个子集中抽取一部分样本混入训练,增强模型的泛化能力。ccpd_challenge、ccpd_rotate、ccpd_fn通常用来做测试集,检验模型在困难场景下的表现。

3.2 文件名里的编码信息

CCPD数据集一个非常特别的地方在于:每张图像的文件名本身就是一个信息丰富的数据包,包含了车牌位置、角点坐标、字符标签等信息。这一点和常见的数据集不太一样,普通数据集的目标框信息通常存在XML或JSON文件里,CCPD"偷懒"把标注信息直接编码进了文件名。

解释一下文件名格式:

025-95_113-154&383_386&473-386&473_177&454_154&383_363&402-0_0_25_28_27_29_32-18-15.jpg

用连字符拆分成8段:

  1. 025:车牌区域面积与整图面积的比值的某种编码,前三位是面积等级。
  2. 95_113:车牌区域的宽高比例信息。
  3. 154&383_386&473:车牌边界框左上角和右下角坐标,154&383是左上角(x,y),386&473是右下角(x,y)。这两个点是整块矩形框的精确标注。
  4. 386&473_177&454_154&383_363&402:四个角点的坐标。按照顺序分别是右上、右下、左下、左上,这个信息可以用来做透视校正。
  5. 0_0_25_28_27_29_32:车牌的7个字符对应的编号,这里的编号是字符映射表里的索引,实际解析时要根据数据集提供的字符表转换。
  6. 18:亮度等级。
  7. 15:模糊等级。

有一个点需要特别注意:CCPD的字符标签是7位,但国内车牌除了蓝色牌照是7位之外,还有新能源绿牌是8位字符。CCPD数据集以蓝牌为主,如果你想识别新能源绿牌,需要在训练数据里单独加入绿牌样本,或者换用包含绿牌的扩展数据集。我当时做这个项目时只处理了蓝牌,后续如果要扩展绿牌,需要在数据预处理阶段修改标签长度接LPRNet的输出通道。

读文件名时,可以用一个简单的函数,不用手动拆:

import re def parse_ccpd_filename(filename): base = filename.split('.')[0] parts = base.split('-') area_ratio = parts[0] wh_ratio = parts[1] bbox = list(map(int, parts[2].split('&'))) # [x1, y1, x2, y2] corners = list(map(int, parts[3].replace('&', '_').split('_'))) labels = list(map(int, parts[4].split('_'))) brightness = int(parts[5]) blurriness = int(parts[6]) return { 'area_ratio': area_ratio, 'wh_ratio': wh_ratio, 'bbox': bbox, 'corners': corners, 'labels': labels, 'brightness': brightness, 'blurriness': blurriness }

3.3 数据清洗与划分策略

拿到CCPD数据集后,不建议直接灌进去训练。我踩过一次坑:早期直接把ccpd_base全部拿来训练,结果模型在ccpd_rotate上表现一塌糊涂。后来分析了一下,发现ccpd_base里车牌倾斜角度相对较小,模型学到的主要是"正视角车牌"的特征,一旦遇到大角度倾斜就检测不到或者识别错误。

后续我调整了数据策略,具体分三步:

第一步,从ccpd_base随机抽取约8万张作为训练基础。第二步,从blur、db、weather、rotate、tilt这几个子集中各抽取1万张左右,混入训练集,把训练集总规模控制在15万张左右。这个规模对YOLOv5来说已经非常充足了,对LPRNet来说更是绰绰有余。第三步,从ccpd_challenge、ccpd_fn这两个子集里保留全部样本作为测试集,用来评估模型在真实困难场景下的表现。

清洗过程中还要注意去除一些标注异常的数据。因为CCPD部分图像来自监控视频截帧,偶尔会有误标或重复的情况。我的做法是写了一个脚本,解析文件名中的坐标信息,剔除那些宽或高小于10像素的标注框,这类框大概率是误标,学习进去只会让模型更混乱。

4. YOLOv5车牌检测模块实现

4.1 环境搭建与安装细节

环境搭建是很多人刚接触YOLOv5时第一个"劝退点",其实梳理清楚就很简单。我的实验环境是Ubuntu 20.04 + Python 3.8 + PyTorch 1.10 + CUDA 11.3,显卡是RTX 3090。如果你用的是Windows,环境配置思路也完全一样,只是CUDA和显卡驱动的安装方式有一些区别。

YOLOv5的安装其实不像网上有些教程说的那么玄乎,核心就几步:

git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt

requirements.txt里已经列好了所有需要的依赖。需要注意的一点是,PyTorch的安装建议直接从PyTorch官网用对应的pip命令装,不要用requirements.txt去装,因为PyTorch版本和CUDA版本的对应关系比较复杂,官网会根据你的CUDA版本给出准确的安装命令。比如CUDA 11.3对应的安装命令是:

pip install torch==1.10.0+cu113 torchvision==0.11.0+cu113 -f https://download.pytorch.org/whl/torch_stable.html

装完依赖,先跑一次官方检测模型确认环境没问题:

python detect.py --source data/images/bus.jpg --weights yolov5s.pt

能看到检测结果说明环境没问题,这时候再去训练自定义数据,可以避免把环境问题和代码问题混在一起排查。

4.2 将CCPD标注转换为YOLOv5格式

YOLOv5训练需要的数据格式是txt文件,每行代表一个目标,格式为class_id x_center y_center width height,注意这里的坐标都是相对于原图宽高的归一化值,且中心点坐标和宽高都用小数表示。

从CCPD文件名解析出的bbox是绝对像素坐标[x1, y1, x2, y2],需要做一层转换:

def convert_ccpd_to_yolo(img_w, img_h, box): x1, y1, x2, y2 = box x_center = ((x1 + x2) / 2) / img_w y_center = ((y1 + y2) / 2) / img_h width = (x2 - x1) / img_w height = (y2 - y1) / img_h return f"0 {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n"

CCPD的图像尺寸大部分是720×1160,但为了稳妥,我在解析时用OpenCV读了一下每张图的实际尺寸,不写死在代码里。有些从其他渠道获取的CCPD变体,图像尺寸可能被压缩过,如果还用固定尺寸去归一化,坐标就全错了。

转换完的标注文件和图像文件放在同一个目录下,文件名保持一致,YOLOv5在训练时会自动去同名txt文件里找标注。

4.3 修改配置文件

data/目录下新建一个ccpd.yaml,内容配置好数据集路径和类别数量:

train: /path/to/ccpd/train val: /path/to/ccpd/val nc: 1 names: ['plate']

然后修改model/yolov5s.yaml里的nc,把默认的80改成1。如果你还要检测其他目标,按实际类别数改即可。

这里有一个值得注意的地方:车牌检测是单类别目标检测,类别数量对模型结构的影响是最后一层输出通道数,nc=1时YOLOv5的检测头输出通道会相应减少。很多人会忘记修改模型配置文件,导致训练报错或者输出维度不匹配。

模型网络结构配置完,还需要留意anchor的调整。YOLOv5在训练时会自动使用K-Means算法从训练集标注中重新聚类anchor,但如果你用的是yolov5s.pt预训练权重,它默认的anchor是针对COCO数据集聚类的。我在实际测试中发现,车牌虽然有多种宽高比,但总的来说是一个相对细长的目标,默认anchor也能聚类到可用值,但K-Means聚类时设置较低的类别数(比如6组anchor),聚类效果会更好。这个可以手动改models/yolo.py里KMeans的n值来验证,不过大多数情况下用默认配置也能收敛,不属于必须修改项。

4.4 模型训练与超参数调整

训练命令不长,但参数选择有讲究:

python train.py --img 640 --batch 32 --epochs 100 --data ccpd.yaml --weights yolov5s.pt --hyp data/hyps/hyp.scratch-low.yaml --device 0

几个关键参数说明:

  • --img 640:输入图像尺寸。CCPD原始图像是720×1160,如果直接resize到640×640,会严重拉伸变形。YOLOv5会对输入图像做letterbox处理,也就是等比例缩放后填充灰色边缘,所以尺寸设置成640问题不大。你也可以设为960或1280来提高小目标检测精度,但训练速度和推理速度都会明显下降。我做实验时对比过640和960,在CCPD测试集上,mAP从0.97提升到0.985,但推理时间从4ms变成了8ms,考虑到停车场场景对速度要求可能更高,最终选择640。

  • --batch 32:显存充足的话可以加大。RTX 3090上跑yolov5s,batch 32没问题,如果你用16G显存或更小的卡,建议batch 8或16,同时配合梯度累积。

  • --hyp hyp.scratch-low.yaml:YOLOv5提供了多种超参数组合文件。scratch-low适合从零开始训练或数据量较少的场景,训练时数据增强幅度较小,不容易过拟合。如果你的数据量很大,可以考虑hyp.scratch-high.yaml,增强更猛,泛化更好。

训练过程中的监控重点有三个:box_loss、obj_loss、cls_loss的下降曲线,以及val精度。我训练100轮,大概在50轮左右损失就趋于平稳,但精度还在缓慢上升。最终测试集mAP_0.5能达到0.985左右,mAP_0.5:0.95在0.89左右,对于单类别检测来说这个精度已经相当理想了。

4.5 检测推理中容易忽略的细节

训练完模型后,批量推理测试时有一个小坑很多人没注意:YOLOv5默认的置信度阈值是0.25,但在实际场景中,单类目标检测可以把置信度阈值适当提高,比如0.5甚至0.6,因为单类别检测不需要在高召回和低误报之间纠结太多。提高阈值后,误检会大幅减少,特别是在晴天强光下,路边的广告牌、车身上的装饰文字有时会被误检成车牌。

另外,YOLOv5的NMS参数也值得调一下。默认的IoU阈值是0.45,对于车牌这种紧凑目标,可以适当调高到0.5以上,减少同一目标被重复框选的情况。

5. LPRNet车牌识别模块实现

5.1 输入数据的预处理管线

LPRNet的输入不是整张原图,而是YOLOv5检测出来的车牌区域裁剪图。不同检测框裁剪出来的图像尺寸不一致,直接喂给固定输入尺寸的网络肯定不行,所以需要做统一的预处理。

LPRNet的常见输入尺寸是94×24(宽×高),这个比例接近国内蓝牌的实际宽高比(440mm×140mm,约3.14:1)。预处理管线如下:

  1. 读取裁剪后的车牌图。
  2. 将图像转换为灰度图,减少颜色信息的干扰。虽然蓝底白字车牌的颜色特征是重要的先验信息,但LPRNet本身是端到端学习的,灰度图作为输入已经能学到足够强的特征,而且灰度图对光照变化的鲁棒性更好。
  3. Resize到94×24。
  4. 归一化到[0,1]区间。
def preprocess_plate(plate_img): gray = cv2.cvtColor(plate_img, cv2.COLOR_BGR2GRAY) resized = cv2.resize(gray, (94, 24), interpolation=cv2.INTER_CUBIC) normalized = resized.astype(np.float32) / 255.0 tensor = torch.from_numpy(normalized).unsqueeze(0).unsqueeze(0) return tensor

关于灰度还是彩色输入,我专门做过对照实验。彩色输入在清晰车牌上的准确率略高一点,但在模糊和暗光条件下的鲁棒性不如灰度输入。考虑到实际问题中模糊和暗光是常态,最终采用灰度输入。

5.2 网络结构核心要点

LPRNet的网络结构并不复杂,但有两个关键设计值得展开说。

第一个是空间变换网络(Spatial Transformer Network,STN)。这个模块放在网络最前面,作用是对输入的倾斜车牌做空间矫正。STN的想法非常巧妙:让网络自己学习一个仿射变换参数,把倾斜的车牌"掰正"后再进卷积层做特征提取。我在CCPD的rotate子集上做过单独测试,去掉STN之后,识别准确率从94%降到了86%,效果差异很显著。

第二个是特征序列的生成方式。LPRNet通过一个全卷积网络提取特征,最后在宽度方向上展开成特征序列,每个时间步对应车牌上一个水平切片,然后接CTC损失进行训练。CTC允许网络输出序列长度和标签长度不一致,这一点很关键,因为车牌在检测框内的水平位置并不总是完全对齐,CTC能自动处理这种不对齐问题。

5.3 CTC损失与训练细节

CTC损失的核心思想是:给定输入序列和标签序列,计算所有可能的路径(alignments)的概率之和。听起来复杂,但实际用起来很简单,PyTorch里一行代码就能创建损失函数:

import torch.nn as nn ctc_loss = nn.CTCLoss(blank=len(CHARS)-1, zero_infinity=True)

这里的blank参数是CTC的空白符索引,CHARS是字符表。我的字符表定义如下:

CHARS = ['京', '津', '冀', '晋', '蒙', '辽', '吉', '黑', '沪', '苏', '浙', '皖', '闽', '赣', '鲁', '豫', '鄂', '湘', '粤', '桂', '琼', '渝', '川', '贵', '云', '藏', '陕', '甘', '青', '宁', '新', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']

注意有个细节:字母表里没有I和O,这是遵循国内车牌字符规则,这两个字母不会出现在车牌上。字符表的顺序可以自己定义,但训练和推理时必须保持一致。

训练时的标签编码方式,需要把车牌字符串转换成整数索引序列,并且去掉字符间的分隔符。比如车牌"京A12345",就编码成[0, 33, 24, 25, 26, 27, 28],这里33对应A,24-28对应1-5。

训练过程还有一个小技巧:LPRNet的输入图像高度是24,宽度是94,但训练时可以对宽度做随机缩放,比如在0.9到1.1之间随机调整,模拟车牌在检测框内水平方向上的伸缩变化,增强模型的鲁棒性。

5.4 推理时的贪心解码与优化

LPRNet推理输出的序列长度是18(每个时间步对应一个水平切片),但车牌字符只有7位,中间会有很多空白符。解码方式一般用贪心解码:每一步取概率最大的字符索引,然后去除相邻重复字符和空白符,剩下的就是识别结果。

def greedy_decode(output): output = output.permute(1, 0, 2) # [batch, seq_len, num_classes] _, predicted = torch.max(output, dim=2) result = [] prev = None for p in predicted[0]: if p != prev and p != len(CHARS) - 1: # 跳过重复和blank result.append(p) prev = p chars = ''.join([CHARS[i] for i in result]) return chars

贪心解码虽然简单,但有一个要注意的坑:相邻重复字符的处理。国内车牌里可能出现连续相同字符的情况,比如"京A88888",贪心解码时如果处理不当,会把连续的'8'合并成一个。不过好在LPRNet每个字符在时间序列上通常对应多次输出,CTC正是通过处理这些重复和空白来实现对齐,所以用上面这种"去重+去空白"的逻辑不会把真正连续的相同字符错误合并。

我还做了一版版本,在解码后增加一层规则校验:

  1. 检查识别结果长度是否为7位,不是则标记为识别失败。
  2. 检查第1位是否在省份汉字列表中,不在则标记为识别失败。
  3. 检查第2位是否是大写字母。

这层规则能拦截掉不少明显错误的识别结果。在停车场场景下,识别失败重新抓拍,比识别出一个错误车牌让道闸放行要安全得多。

6. 检测+识别系统集成与测试

6.1 流水线流程设计

把检测和识别两个模型串起来时,最核心的设计点是检测结果和识别流程之间的衔接逻辑,以及整体的异常处理策略。

我在实际项目中采用的流程是:

  1. 接收一帧图像,送入YOLOv5检测。
  2. 如果检测到车牌框,进入步骤3;如果没检测到,判定为"无车牌",可结合业务需要触发重新抓拍。
  3. 对每个检测框裁剪车牌区域,判断图像的宽度和高度是否满足识别模型的输入要求,如果车牌区域过小(比如宽小于30像素),放大后再送识别。
  4. LPRNet识别车牌字符,输出结果。
  5. 对识别结果做规则校验,通过则输出最终结果,不通过则标记为"识别异常",等待下一帧重新识别。

第3步里提到的放大操作,很多人容易忽略。如果检测框很小,直接resize到94×24会导致车牌字符严重失真,识别准确率大幅下降。我发现当原始车牌区域宽度小于50像素时,先放大到约150像素宽度再做灰度化和resize,识别准确率能提升不少。原因是先放大再做尺寸归一化,中间插值过程能保留更多字符边缘信息。

6.2 性能评估与指标

车牌识别系统常用的评估指标有三个:检测精度(mAP)、识别准确率(Accuracy)、端到端准确率(End-to-End Accuracy)。

检测精度方面,YOLOv5在CCPD测试集上的mAP_0.5为0.985,mAP_0.5:0.95为0.89,说明定位能力很强,绝大多数车牌都能被准确框出来。

识别准确率方面,LPRNet在裁剪好的车牌图上的准确率约96.5%。会出错的情况主要是强光过曝、车牌严重污损、大角度俯拍导致字符变形严重。

端到端准确率是把检测和识别串起来评估的指标,定义是:图像中存在车牌且最终识别结果完全正确的比例。我在模拟停车场环境的测试集上跑出来的端到端准确率是93.1%。比对检测精度和识别准确率可以看出来,端到端准确率低于两者的乘积(0.985×0.965≈0.950),说明检测和识别之间存在误差累积。检测框的不精准(比如框得稍微偏左偏右)会直接影响识别效果。

指标数值
检测mAP_0.50.985
检测mAP_0.5:0.950.890
识别准确率96.5%
端到端准确率93.1%
单帧推理时间(GPU)约6ms
单帧推理时间(CPU)约45ms

6.3 部署时的工程化考虑

如果你想把这个系统部署到实际环境中,有几个工程化问题必须在开发阶段就想清楚。

第一个是视频流的处理。停车场系统通常接入的是RTSP视频流,而不是单张图片。OpenCV的VideoCapture可以直接读RTSP流,但解码速度受限于网络带宽。建议在代码里设置缓冲队列,用独立线程读取帧,主线程做检测识别,避免视频解码阻塞推理。

第二个是模型量化。如果部署到嵌入式设备(比如Jetson Nano、RK3588),浮点模型直接跑可能扛不住实时性要求。YOLOv5的TensorRT部署方案社区已经很成熟,LPRNet的量化部署相对麻烦一些,因为CTC解码部分对量化误差比较敏感。我试过将LPRNet量化到INT8,识别准确率会下降约2个百分点,在实际项目中,可以考虑检测模型INT8+识别模型FP16的混合精度方案,兼顾速度和精度。

第三个是车辆去重策略。在停车场出入口,车辆会在画面中停留一段时间,如果每一帧都做完整识别,既浪费算力又可能在车辆挪动时产生误识别。实际工程中通常采用"连续N帧识别结果一致才采信"的策略,比如连续3帧输出相同车牌才认为识别成功。

7. 常见问题与避坑实录

7.1 检测环节的常见问题

检测不到车牌。排查顺序先看预处理是否一致。训练时的letterbox和推理时的letterbox必须一致,如果训练用了640尺寸,推理时输入1280尺寸,目标在缩放后的位置和尺度都会变化,模型表现自然异常。其次看置信度阈值要不要调低,有些场景下模型对遮挡车牌的置信度较低,适当调低到0.2可能就解决了。

误检多。车身贴纸、广告牌、座椅上的图案都可能是误检来源。解决办法包括提高置信度阈值、增加负样本(不含车牌的图像)参与训练、用NMS IOU阈值调优。

小目标检测效果差。车牌在图像中占的面积太小,这是小目标检测的典型问题。可以从三个方向改善:提高输入分辨率(--img 9601280)、在训练时使用Mosaic和Copy-Paste增强、检测后处理时对大图做切片推理。切片推理的代价是速度慢,一般只在离线处理场景使用。

7.2 识别环节的常见问题

识别结果总缺字符或多字符。大概率是字符标签和CTC的对齐出了问题。检查训练时标签的长度是否统一,CCPD是7位蓝牌字符,LPRNet的max_len要设置合理。另一个可能是字符表顺序在训练和推理时不一致,这个错误非常隐蔽,排查时要重点核对。

相似字符混淆。'0'和'O'、'1'和'I'是车牌识别中最常见的混淆对。虽然国内车牌本身不包含'I'和'O',但检测框内如果带有车牌的螺丝钉、边框装饰,模型可能把某些纹路识别成类似字符。解决方法是训练数据中增加一些带有螺丝、边框干扰的样本,或者在后处理规则中对相似字符做置信度加权。

倾斜车牌识别率低。确认STN模块是否正常生效。有些LPRNet的开源实现结构不完整,去掉了STN部分,这在大角度倾斜场景下会非常吃亏。另外,在训练数据中加入更多的rotate子集样本,也有助于提升模型对倾斜的容忍度。

7.3 训练环境相关的问题

CUDA显存不足。YOLOv5训练的显存占用和batch size、图像尺寸直接相关。如果batch设为32爆显存,优先减小batch size而不是缩小图像尺寸,图像尺寸影响模型精度比较大。也可以开启梯度累积功能,用多个小batch模拟大batch的效果。

训练Loss不降低。先检查数据加载是否正常,用YOLOv5自带的验证数据可视化功能确认标注框是否正确画在车牌上。标注文件内容错误是最常见的原因。

CCPD文件名解析出错。部分CCPD变体数据集的图片文件名可能被修改过,或者经过数据增强后文件名丢失了原始编码信息。遇到这种情况,需要用标注文件或手动标注的方式补充信息,不能强行依赖文件名解析。

8. 一些实操心得与扩展方向

这个项目做完之后,我最大的体会是:车牌识别系统的瓶颈往往不在模型本身,而在数据质量、前后处理链路和工程调优。YOLOv5和LPRNet都是很成熟的模型,开源社区提供了大量现成工具,真正拉开差距的是对数据的理解深度和对细节的把控力度。

一个值得说的小技巧:通过CCPD文件名里的亮度、模糊、面积等级信息,可以快速找出模型在哪些条件下表现差。比如解析测试集上识别失败样本的元数据,发现大量失败样本集中在brightness=18或更高的区间,那就可以针对性地在训练集中增加过曝样本,或者预处理时做对比度归一化。这种"数据诊断"思路,比盲目堆模型复杂度要有用得多,也更节省时间。

最后再分享一个扩展方向:这个项目目前只处理了单张图片,如果要应用到视频流或实时监控中,可以给系统加入多目标跟踪模块,比如DeepSORT或者ByteTrack,实现对同一车辆的视频帧持续识别,这不仅能在连续帧结果不一致时自动纠错,还能进一步分析车辆行驶轨迹。YOLOv5+LPRNet这个组合的扩展性很好,检测模块换成YOLOv8也只需要改动几行代码,识别模块基本不用动。如果后续想支持新能源绿牌,只需调整字符表、把LPRNet的输出长度从7位扩展到8位,再补充绿牌训练数据。整体来说,这套方案从学习到落地的完整度很高,无论是做课程设计还是产品原型,都是一条值得推荐的路径。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/27 1:58:07

【单片机课程设计/毕业设计】多传感器融合智能水杯水量温度监测控制系统设计 单片机驱动的智能饮水恒温加热与定时提醒装置研发(025304)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机&#xff0c;Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/8/27 1:57:41

数学建模论文首页三要素写作规范与实战技巧

1. 为什么“首页三要素”是数学建模论文的生死线——从清风课程实战反馈说起我带过六届校赛和国赛队伍&#xff0c;每年初审淘汰的论文里&#xff0c;有近四成根本没机会进专家打分环节——不是模型跑不起来&#xff0c;不是代码写错了&#xff0c;而是首页被直接判“不合格”。…

作者头像 李华
网站建设 2026/8/27 1:57:30

SemiQ 1200V Gen3 SiC MOSFET扩展SOT-227封装,三档导通电阻解析

SemiQ这次把1200V Gen3 SiC MOSFET产品线扩到了SOT-227封装&#xff0c;一口气放出7.4mΩ、14.5mΩ、34mΩ三个导通电阻档位。干功率电源或者电机驱动的人应该一眼就明白这个信号&#xff1a;SOT-227这个封装在中功率段&#xff0c;对于想跳过全桥/半桥模块、又想比TO-247分立器…

作者头像 李华
网站建设 2026/8/27 1:57:12

SysML参数建模:约束块定义与绑定连接的工程实践

1. 为什么“参数为约束建模”不是加几个等式那么简单&#xff1f;刚接触SysML参数建模时&#xff0c;我犯过一个典型错误&#xff1a;把参数图&#xff08;Parametric Diagram&#xff09;当成UML类图的数学插件——看到“约束块&#xff08;Constraint Block&#xff09;”&am…

作者头像 李华
网站建设 2026/8/27 1:55:49

Labubu为什么火?多平台数据拆解潮玩IP走红密码

简介&#xff1a;数据分析是企业洞察市场的重要手段。通过爬虫技术采集多平台公开数据&#xff0c;结合文本挖掘与情感分析&#xff0c;可以构建完整的用户画像&#xff0c;并追踪话题声量变化。这一方法论适用于潮玩IP、新消费品牌等领域的舆情监测与研究。本文以Labubu为例&a…

作者头像 李华