
简介车牌识别作为OCR技术在智能交通领域的典型应用常被简化为文字识别问题实则需应对光照变化、角度倾斜、运动模糊等复杂场景。传统图像处理方案鲁棒性不足而基于深度学习的智能视觉技术正成为主流。在技术架构上常采用目标检测与文字识别相结合的两阶段方案先定位车牌区域再对裁剪区域进行字符序列识别。该方案具备检测与识别独立优化、部署灵活等优势广泛应用于停车场管理、电子警察、高速收费等场景。针对国内车牌场景基于YOLOv5和LPRNet的轻量化组合在精度与实时性之间实现了良好平衡配合CCPD数据集的关键信息解析与训练调优可有效提升模型的泛化能力。本文系统梳理了车牌检测识别项目的完整实现链路涵盖数据预处理、模型训练、集成部署与工程避坑细节为相关开发实践提供参考。 车牌识别这个方向说实话已经不算新鲜了但能把检测识别这一条链路真正跑通、跑稳还是一件挺有成就感的事。前阵子我基于YOLOv5和LPRNet在CCPD数据集上完整实现了一套车牌检测识别系统整个项目打包出来也就是标题里那个zip。这篇文章不打算做那种粘贴代码跑通就完事的教程我会把方案选型、数据拆解、训练细节、集成部署这些环节全部捋一遍尤其是那些文档里不会写、只有自己踩过坑才知道的细节一次性讲清楚。如果你正准备做车牌识别相关的工作不管是毕业设计、工程项目还是以学习为目的这篇文章应该能帮你少走很多弯路。1. 项目核心价值与适用人群1.1 车牌识别的典型应用场景车牌识别行业里常称为VLPRVehicle License Plate Recognition本质上是OCR的一个特殊分支但比普通OCR要复杂不少。普通OCR面对的大多是规整的印刷文字而车牌识别要面对的是户外光照变化、拍摄角度倾斜、车速带来的运动模糊、车牌本身的老化污损加上各国车牌格式差异巨大这些因素叠加在一起让车牌识别成了一个典型的看起来简单、做起来头疼的任务。常见的落地场景包括停车场出入口管理识别车牌后自动抬杆、计费这是目前最成熟的应用。电子警察和交通违章抓拍在路口抓拍车辆识别车牌后与数据库比对。高速公路收费站的ETC辅助识别当ETC设备失效或未安装时通过车牌识别作为兜底方案。园区、小区门禁通过车牌白名单实现自动放行。移动端警务通交警用手机或平板拍摄车辆照片实时识别车牌。这些场景对系统的要求其实不太一样。停车场场景相对友好车辆速度慢、拍摄距离近、光照相对可控而电子警察和高速场景车辆速度快、环境复杂对检测和识别的实时性和鲁棒性要求就高很多。我做的这个项目目标是在通用场景下有较好的表现同时保证推理速度能满足实时需求所以选型时特意考虑了轻量化和精度的平衡。1.2 为什么是YOLOv5和LPRNet的组合我第一次做车牌识别的时候用的是传统图像处理方案颜色分割定位车牌区域再对字符做模板匹配或特征分类。说实话那种方案在固定场景、固定角度下能用但换一个场景就崩鲁棒性很差。后来转向深度学习方案就面临一个选型问题用什么模型来做检测用什么模型来做识别。综合对比下来我选了YOLOv5LPRNet理由可以拆成两条线来看。检测侧YOLOv5在工业界的普及率非常高社区活跃资料多部署方案成熟。虽然现在已经有了YOLOv8甚至更新的版本YOLOv5在速度和精度之间仍然有很好的平衡而且针对车牌这种小目标检测YOLOv5的anchor机制经过调整后可以取得不错的效果。更关键的是YOLOv5的代码结构清晰改起来方便我可以在不改动核心逻辑的情况下针对车牌数据集做定制化修改。识别侧LPRNet是一个专门为车牌识别设计的轻量级网络它有两个突出的优点第一不需要字符级标注只需要车牌的整串标签即可训练这大大降低了数据标注成本第二网络结构简单参数量小推理速度极快在CPU上都能跑到实时。这一点在嵌入式部署时非常关键。两阶段方案的另一个好处是检测和识别可以独立优化。如果检测效果不好我只需要调检测模块如果识别不准我也只需要调识别模块。相比之下端到端的车牌识别模型虽然看起来更简洁但定位和识别耦合在一起调试难度和训练成本都更高。2. 整体方案与架构设计2.1 两阶段识别流水线整个系统的处理流程可以概括为输入一张图像先由YOLOv5检测模型定位出车牌区域把这块区域裁剪下来再交给LPRNet模型识别出车牌字符。用文字描述总觉得不够直观这里直接给出核心代码逻辑import cv2 import torch # 加载两个模型 detector torch.hub.load(ultralytics/yolov5, custom, pathweights/license_plate.pt) recognizer LPRNet() recognizer.load_state_dict(torch.load(weights/lprnet.pth, map_locationcpu)) 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提取特征然后通过CTCConnectionist Temporal Classification对特征序列解码。CTC的引入使得LPRNet可以做到无需字符分割也就是不需要知道每个字符的精确边界只要整串车牌的标签就能训练。LPRNet的参数量大约只有CRNN的几分之一在CPU上的推理时间可以控制在毫秒级这对实时性有要求的场景非常友好。而且LPRNet对字符倾斜、模糊、光照不均的鲁棒性比CRNN好一些因为它的网络设计中包含了空间变换网络STN模块可以对车牌区域做一定程度的校正。3. CCPD数据集深度使用3.1 CCPD数据集整体结构与组成CCPDChinese 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-154383_386473-386473_177454_154383_363402-0_0_25_28_27_29_32-18-15.jpg用连字符拆分成8段025车牌区域面积与整图面积的比值的某种编码前三位是面积等级。95_113车牌区域的宽高比例信息。154383_386473车牌边界框左上角和右下角坐标154383是左上角(x,y)386473是右下角(x,y)。这两个点是整块矩形框的精确标注。386473_177454_154383_363402四个角点的坐标。按照顺序分别是右上、右下、左下、左上这个信息可以用来做透视校正。0_0_25_28_27_29_32车牌的7个字符对应的编号这里的编号是字符映射表里的索引实际解析时要根据数据集提供的字符表转换。18亮度等级。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.txtrequirements.txt里已经列好了所有需要的依赖。需要注意的一点是PyTorch的安装建议直接从PyTorch官网用对应的pip命令装不要用requirements.txt去装因为PyTorch版本和CUDA版本的对应关系比较复杂官网会根据你的CUDA版本给出准确的安装命令。比如CUDA 11.3对应的安装命令是pip install torch1.10.0cu113 torchvision0.11.0cu113 -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 f0 {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\nCCPD的图像尺寸大部分是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。如果你还要检测其他目标按实际类别数改即可。这里有一个值得注意的地方车牌检测是单类别目标检测类别数量对模型结构的影响是最后一层输出通道数nc1时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上跑yolov5sbatch 32没问题如果你用16G显存或更小的卡建议batch 8或16同时配合梯度累积。--hyp hyp.scratch-low.yamlYOLOv5提供了多种超参数组合文件。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。预处理管线如下读取裁剪后的车牌图。将图像转换为灰度图减少颜色信息的干扰。虽然蓝底白字车牌的颜色特征是重要的先验信息但LPRNet本身是端到端学习的灰度图作为输入已经能学到足够强的特征而且灰度图对光照变化的鲁棒性更好。Resize到94×24。归一化到[0,1]区间。def preprocess_plate(plate_img): gray cv2.cvtColor(plate_img, cv2.COLOR_BGR2GRAY) resized cv2.resize(gray, (94, 24), interpolationcv2.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 NetworkSTN。这个模块放在网络最前面作用是对输入的倾斜车牌做空间矫正。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(blanklen(CHARS)-1, zero_infinityTrue)这里的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对应A24-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, dim2) 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正是通过处理这些重复和空白来实现对齐所以用上面这种去重去空白的逻辑不会把真正连续的相同字符错误合并。我还做了一版版本在解码后增加一层规则校验检查识别结果长度是否为7位不是则标记为识别失败。检查第1位是否在省份汉字列表中不在则标记为识别失败。检查第2位是否是大写字母。这层规则能拦截掉不少明显错误的识别结果。在停车场场景下识别失败重新抓拍比识别出一个错误车牌让道闸放行要安全得多。6. 检测识别系统集成与测试6.1 流水线流程设计把检测和识别两个模型串起来时最核心的设计点是检测结果和识别流程之间的衔接逻辑以及整体的异常处理策略。我在实际项目中采用的流程是接收一帧图像送入YOLOv5检测。如果检测到车牌框进入步骤3如果没检测到判定为无车牌可结合业务需要触发重新抓拍。对每个检测框裁剪车牌区域判断图像的宽度和高度是否满足识别模型的输入要求如果车牌区域过小比如宽小于30像素放大后再送识别。LPRNet识别车牌字符输出结果。对识别结果做规则校验通过则输出最终结果不通过则标记为识别异常等待下一帧重新识别。第3步里提到的放大操作很多人容易忽略。如果检测框很小直接resize到94×24会导致车牌字符严重失真识别准确率大幅下降。我发现当原始车牌区域宽度小于50像素时先放大到约150像素宽度再做灰度化和resize识别准确率能提升不少。原因是先放大再做尺寸归一化中间插值过程能保留更多字符边缘信息。6.2 性能评估与指标车牌识别系统常用的评估指标有三个检测精度mAP、识别准确率Accuracy、端到端准确率End-to-End Accuracy。检测精度方面YOLOv5在CCPD测试集上的mAP_0.5为0.985mAP_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约45ms6.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 960或1280、在训练时使用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文件名里的亮度、模糊、面积等级信息可以快速找出模型在哪些条件下表现差。比如解析测试集上识别失败样本的元数据发现大量失败样本集中在brightness18或更高的区间那就可以针对性地在训练集中增加过曝样本或者预处理时做对比度归一化。这种数据诊断思路比盲目堆模型复杂度要有用得多也更节省时间。最后再分享一个扩展方向这个项目目前只处理了单张图片如果要应用到视频流或实时监控中可以给系统加入多目标跟踪模块比如DeepSORT或者ByteTrack实现对同一车辆的视频帧持续识别这不仅能在连续帧结果不一致时自动纠错还能进一步分析车辆行驶轨迹。YOLOv5LPRNet这个组合的扩展性很好检测模块换成YOLOv8也只需要改动几行代码识别模块基本不用动。如果后续想支持新能源绿牌只需调整字符表、把LPRNet的输出长度从7位扩展到8位再补充绿牌训练数据。整体来说这套方案从学习到落地的完整度很高无论是做课程设计还是产品原型都是一条值得推荐的路径。本文还有配套的精品资源点击获取