资讯动态

YOLOv5+PPOCR车牌识别实战:从模型训练到业务集成全复盘

发布时间:2026/10/18 18:44:35 来源:尧图企业网站定制
最近把车牌识别系统接进实际的业务流里从算法验证到线上运行折腾了大半个月总算是摸出点门道。YOLOv5负责抓车牌位置PPOCR负责抠文字这套组合拳在真实场景里确实能打。先拿个最直观的例子说吧某停车场项目里相机抓拍一张画面几百毫秒内输出车牌号码和颜色接着道闸自动抬杆、计费系统同步生成订单。整个过程一气呵成但背后的工程细节其实不少稍不注意就会翻车。这篇文章就当一次复盘把选型思路、训练细节、业务集成和踩过的坑都捋一遍给正在做类似项目的人当个参考。1. 为什么选YOLOv5 PPOCR这套组合拳1.1 车牌识别不是单纯OCR而是一个“定位识别”两步问题很多人一上来就想着找个现成的端到端车牌识别模型一张图进去直接输出车牌号省事。但实际业务流里你面对的不是干净的正脸照片而是光线复杂、视角歪斜、背景杂乱的现场抓拍图。车牌在画面里可能只占很小一块如果算法不先找到“车牌在哪里”后面识别根本无从谈起。端到端方案看着简单可它对数据的要求极高既要在海量标注里学习“定位”又要学“字符分类”两个任务耦合在一个网络里任何一个环节出问题都很难单独调优。而且不同场景的摄像头架设角度、高度、路面反光都不一样端到端模型的泛化能力往往跟不上。所以我把问题拆成了两步先用目标检测网络锁定车牌区域再对裁剪出的车牌图做字符识别。这个思路和工业界主流方案一致好处非常明显——定位和识别各自迭代检测错了我可以先调检测识别错了单独处理识别不需要牵一发动全身。1.2 YOLOv5和PPOCR各自的角色YOLOv5是目标检测模型实时性和精度平衡得非常好在嵌入式设备和GPU服务器上都能跑。车牌作为单一目标类别边界清晰、形状固定YOLOv5这种anchor-based模型特别好训练不需要复杂的trick就能拿到很高的检测召回率。我选YOLOv5还有一层考虑它的工程生态太成熟了导出ONNX、TensorRT都非常顺手后续要上性能优化省下不少力气。PPOCR是飞桨开源的OCR工具链这里主要用到它的文字识别能力。有人会问车牌识别不是字符识别吗直接整个PPOCR的文本识别模型不就行了确实可以PPOCR的识别模型本质上是一个CRNN架构的序列识别网络车牌字符正好是这种结构擅长处理的类型。用YOLOv5把车牌区域框出来后我把这块图像直接丢给PPOCR的文本识别模块识别结果就是车牌字符串。如果非要用YOLOv5和PPOCR里面的文本检测模型再搭配一遍也不是不行但没必要重复检测。YOLOv5已经专门针对“车牌”这个目标做了检测比通用文本检测更精准。PPOCR的文本检测模型是找“文字区域”它不知道什么是车牌容易把车身广告上的字也算进来。所以我的分工是YOLOv5当哨兵PPOCR当翻译官。2. 训练数据与模型调优细节2.1 车牌数据远比想象中复杂车牌虽然只有一类但内部差异很大。国内常见的有蓝底白字燃油车、渐变绿底新能源、黄底大货车、白底警车、黑底涉外车甚至还有军牌、使馆牌。字符上除了省份汉字还有字母和数字其中字母I和数字1、字母O和数字0极其容易混淆。如果你只拿普通蓝牌数据训练遇到黄底大货车直接翻车。我在做模拟项目X的时候第一步就是整理数据分类。至少保证这几种类型均衡覆盖蓝牌、新能源绿牌、黄牌、白牌、黑牌。如果业务场景里有特殊车辆还需要单独补充。图像来源可以是网络公开数据集但更推荐自己从实际闸机抓拍里沉淀。真实现场的数据永远比测试集有说服力。2.2 标注要点边界框该紧还是该松YOLOv5训练车牌的标注边界框我建议比车牌实际边缘稍微外扩1-3个像素。PaddleOCR里讲文本检测也不能卡着文字边界太紧会截掉字角。车牌本身有边框有时候还有铆钉、挂件遮挡框太紧会把特征切没了。稍微外扩一点让模型看到完整的车牌外轮廓检测框会稳定很多。另外标注时不要去包含车牌周边的车身颜色。模型学到的特征应该是“车牌的底纹和文字”而不是“前面那辆车的保险杠”。我见过不少新手标数据喜欢把整个车头框进去结果模型误检成保险杠的边角。还有一个容易被忽略的点同一辆车在不同角度、不同光照下的车牌图像尽量多样化。如果某个省牌在训练集里只出现一次模型遇到这个省的牌照大概率认不准。好的数据分布比调参重要十倍。2.3 YOLOv5训练参数怎么调我用的是YOLOv5s作为基线模型输入尺寸640x640单类别检测。如果你追求更高精度可以用YOLOv5m或YOLOv5l但推理速度会下降。实际业务里车速快、相机抓拍可能模糊我偏向用稍大一点的输入尺寸比如736或者768对小车牌目标更友好。训练轮次至少250个epoch因为车牌样本虽然不大但需要充分收敛。Mosaic增强、随机仿射、HSV扰动都打开特别是亮度增强模拟白天黑夜光照变化。车牌字符识别对颜色不敏感但检测对亮度敏感所以暗光场景的图一定要喂够。我的经验是如果测试集检测召回率上不去先查数据不要急着调anchor。YOLOv5虽然会自动计算anchor但车牌目标长宽比很固定跑K-means聚类出来的anchor基本就是1.6:1和3:1两种。如果自动聚类结果偏离这个范围可能是数据里包含太多远处小目标需要检查标注框是否存在大量漏标。2.4 PPOCR识别模型的选择与轻量改造PaddleOCR提供了det和rec两个模型我只要rec。具体来讲用PP-OCRv4的识别模型自带中英文识别字典车牌字符省份简称、字母、数字都在字典里基本不用改。但车牌识别的难点在于汉字在铭牌边缘或者图像分辨率低时容易糊成一团。PPOCR的通用识别模型对打印体的识别已经很稳定所以你拿到YOLOv5裁剪出来的车牌图后直接调用PaddleOCR的ocr接口就会输出识别文本不区分检测和识别也方便。如果你想在边缘设备跑建议把PPOCR识别模型导出成ONNX用OpenCV或者onnxruntime加载能省不少依赖。PaddleOCR自带部署工具导出之后推理速度比PaddlePaddle原生框架快不少。识别模型的输入尺寸比较固定一般高32像素宽度动态车牌裁剪图的长宽比不会太极端基本不影响。2.5 数据增强车牌图像的透视校正现场拍到的车牌不一定是正对镜头的摄像头俯仰角会造成透视形变字母被压扁。YOLOv5检测框是矩形如果车牌是斜的框内就会带着路面和车身背景。这时候PPOCR的识别效果会下降。因此在把裁剪图交给PPOCR之前我先对检测框做一次透视矫正。怎么做如果YOLOv5只输出水平矩形框就缺少车牌四个角点的信息。想精确矫正可以在YOLOv5后面加一个角点回归分支但工程量大。更省事的办法是检测框内图像用OpenCV的形态学操作找到车牌轮廓拟合出四个顶点再做透视变换。如果觉得麻烦还可以同时保留多个旋转角度的候选图分别送入PPOCR取置信度最高的一条结果。旋转角度建议±5度和±10度因为实际现场大部分车牌并不会歪太多。3. 把模型塞进业务流的具体实现3.1 业务流中的系统架构模型跑通只是第一步并入业务流才是最磨人的地方。我做的模拟项目X里业务流程是地感线圈触发相机抓拍抓拍图上传到识别服务服务返回识别结果再到后台系统做道闸控制、停车计费、场内车辆管理。我用的架构是nginx接收图片请求把图片写入Redis临时队列或直接打到本地磁盘工作进程从队列里取图做检测和识别结果写入数据库同时通过MQ给业务系统发事件。用队列是为了缓冲突发流量——早晚高峰期可能有连续十几辆车同时进场如果同步阻塞处理请求超时用户体验就很差。在实际落地中我还加了一层结果去重。同一辆车在闸机前停留几秒摄像头会连续抓拍好几张如果每次都发道闸抬杆指令就可能出现重复开闸或抬杆后又下落。通过车牌号和抓拍时间窗口做去重比如5秒内相同车牌只处理一次稳得很。3.2 模型推理代码骨架YOLOv5我用的是GitHub上的开源代码用起来很方便。核心推理逻辑大致如下import torch import cv2 import numpy as np from paddleocr import PaddleOCR # 加载YOLOv5模型可以用torch.hub或官方仓库 model torch.hub.load(yolov5, custom, pathbest.pt, sourcelocal) model.conf 0.4 # 检测置信度阈值 model.iou 0.45 # NMS IoU阈值 # 初始化PPOCR识别器只需要rec功能 ocr PaddleOCR(use_angle_clsFalse, show_logFalse, langch) def detect_and_recognize(image): # 1. YOLO检测 results model(image) # 输入BGR图像 dets results.pandas().xyxy[0] plates [] for _, row in dets.iterrows(): x1, y1, x2, y2 int(row[xmin]), int(row[ymin]), int(row[xmax]), int(row[ymax]) # 2. 裁剪车牌区域 plate_img image[y1:y2, x1:x2] # 3. 预处理不缩放原图直接识别避免字符拉伸变形 # 对倾斜车牌做透视矫正代码略 # 4. PPOCR识别 rec_result ocr.ocr(plate_img, detFalse, clsFalse) text rec_result[0][0][0] if rec_result and rec_result[0] else plates.append((x1, y1, x2, y2, text)) return plates注意一个细节ocr.ocr(plate_img, detFalse, clsFalse)表示跳过文本检测和方向分类只执行识别。这是关键因为YOLOv5已经完成了定位不需要再跑通用文本检测能省掉一半推理时间。PPOCR的识别模型输出是字符串有时候会带空格或特殊符号例如“京A 12345”需要清洗掉空格。另外PaddleOCR的ocr.ocr接口在单张图上能返回识别结果和置信度我一般会把置信度低于0.7的结果丢弃防止错误结果直接进业务库。3.3 性能优化让组合拳跑得更快业务流里最怕的是识别慢导致闸机抬起延迟车辆堵在入口。为了压性能我做了几件事第一YOLOv5和PPOCR都在GPU上加载共用CUDA上下文。如果机器只有一张显卡建议不要两个框架各自初始化显存而是通过ONNX统一导出后运行或者用PaddleOCR的GPU推理和YOLOv5的PyTorch推理同时跑注意设置显存分配比例。第二批量处理图片。高峰时段如果同时进来几十张抓拍图单张串行推理肯定不行。我做一个简单的批量队列每凑够8张图或间隔50毫秒就执行一次批量推理YOLOv5和PPOCR都支持batch输入吞吐能提高3-4倍。第三截帧抽帧优化。视频流场景里不是每一帧都需要识别。实际车速下1秒25帧的相机做5帧抽一帧完全够用车辆在画面里的位置移动不会太大重复检测没有意义。还有一个容易忽略的问题PPOCR识别模型的输入是定宽的车牌图长短不一如果直接把不同宽度的图拼成batch需要padding到同一宽度padding越多浪费越大。我按车牌宽度聚类后分桶batch比如宽度在100到150的图放一批150到200的放一批效率更高。3.4 与业务系统的接口约定识别结果要以什么数据结构交出去必须和业务系统对齐。我用的统一JSON结构大致是{ request_id: 20230615103000123, plate_no: 京A12345, plate_color: blue, confidence: 0.97, score_detail: {...}, image_url: http://storage/xxx.jpg, timestamp: 1692345600, camera_id: gate-01 }业务系统只关心plate_no和plate_color其他字段用于调试和复核。plate_color怎么来我在YOLOv5的训练里没有分类颜色所以颜色信息是用检测框区域的平均色调来判定的按HSV空间做阈值蓝色、绿色、黄色、白色、黑色。蓝牌和绿牌在色调上有明显区别白色和黑色用亮度区分。虽然不如分类网络准但应付道闸逻辑够了。回调方式上我用了MQ也可以直接HTTP回调业务系统。注意接口要有幂等性比如每次识别请求生成唯一的request_id业务系统根据这个ID去重。否则网络抖动导致重发可能出现车辆被计费两次。4. 常见问题与排查技巧实录4.1 车型距离远车牌太小识别不出来这是现场最容易碰到的问题。相机架在越高处视野越大远处车牌的像素越少。YOLOv5对40像素以下的车牌目标基本无力回天。我试过几种解法一是提高输入分辨率代价是推理变慢二是配置相机让抓拍摄像头聚焦在闸机前一个固定区域保证车牌宽度至少80像素三是叠加多个监控点门口一个广角球机用来追踪车辆入口一个枪机专门拍车牌。第三种最省事也最有效。还有一种情况是夜晚场景车灯直射车牌反光导致过曝。这没法通过算法完全解决只能靠硬件HDR或补光或者用多帧合成。图像算法能做到的是对过曝区域做一个局部直方图均衡但效果有限。我后来在相机参数上直接把光圈调小、快门调快车牌反而清晰了。4.2 汉字和易混淆字符识别错误常见的混淆是“鲁”和“鱼”、“赣”和“敢”、“皖”和“晚”以及字母O和数字0、I和1。PPOCR本身有语言模型但车牌语境比较特殊它不一定知道省份简称的语义限制。我做了两步优化第一步在识别结果后加一个规则引擎。比如第1位必须是省份汉字第2位必须是字母后面是字母数字混合。如果识别结果违反这个规则就重试或者置为疑似错误。第二步对反复出现的易混淆字符用同一辆车多帧抓拍投票。将连续几帧的识别结果放在一起每个字符位置取出现次数最多的字符。因为单帧模糊可能把“B”识别成“8”但下一帧“B”大概率是对的。这个方法非常管用能直接把最终准确率从92%拉到99%以上。多帧投票的代码逻辑不复杂维护一个字典key为request_id或者车辆跟踪IDvalue为一个列表存放同一辆车近3秒的识别结果。对每个字符位置做众数统计只有当投票结果一致的帧数超过一半且置信度达标才输出最终结果。4.3 倾斜车牌导致检测框包含干扰区域当车牌在画面里有角度时YOLO框是一个水平正矩形必然会把周围的路面、车轮、甚至旁边另一辆车的车牌角带进来。PPOCR一看到多余的文字或纹理可能输出乱码。我建议在YOLOv5输出后增加一步车牌区域轮廓筛选对裁剪图做灰度化、二值化找到车牌的大致四边形区域。如果检测框里能找到明显的矩形车牌轮廓就按这个轮廓做透视矫正再裁剪成规整的正视图。这个操作额外耗时约3毫秒但能把倾斜车的准确率提升一大截。如果轮廓找不准还有一个旁路把裁剪图分别旋转-10、0、10度三张图都送给PPOCR最终取识别置信度最高的结果。这种方法简单粗暴但确实有效。注意旋转后的图要填充灰色而不是黑色黑色填充可能会引入干扰。4.4 高并发下模型显存溢出有一阵我贪图省事YOLOv5和PPOCR都在同一个GPU上跑业务峰值时单张图推完紧接着下一批显存蹭蹭往上涨最后直接OOM。排查下来发现是两个框架的显存分配策略不同PyTorch默认会预分配很多缓存PaddleOCR也按需申请两者叠加就爆了。解决方法是给YOLOv5设置torch.cuda.empty_cache()不用频繁调用低频调用即可同时用torch.backends.cudnn.benchmark True提升卷积速度。PaddleOCR侧的GPU显存限制参数调到2GB左右在初始化OCR时指定可用显存上限。另外把YOLOv5的batch限制在16以内OCR的batch限制在8以内各自独立分配显存基本不会互相踩塌。如果还是爆显存最彻底的方案是转成ONNX格式用onnxruntime-gpu推理显存占用可控而且不用担心框架版本冲突。我有一次做某跨平台系统时两个模型都转成ONNX整体显存占用比原来PyTorchPaddle混合下少了接近一半。4.5 识别结果写入业务系统乱码或丢失这个问题不是算法问题而是编码问题。业务系统数据库用的字符集可能是GBK而识别服务返回的是UTF-8直接写入后汉字会变成问号。所以接口层统一用UTF-8编码数据库表字段也要对应UTF8MB4同时请求头里显式加上Content-Type: application/json; charsetutf-8。另外结果丢失大多是消息队列消费失败导致的。我在MQ消费逻辑里加了retry和dead letter队列识别成功但业务处理失败的消息扔进死信人工排查。这块看起来不起眼但线上稳定性就靠这些细节兜底。5. 这套方案的适用边界与扩展思考YOLOv5 PPOCR的组合在标准出入口场景下可以说完全够用。但如果你想做那种城市道路上的移动车辆抓拍或者高速上的远距离识别纯靠这套组合就有点吃力了。移动抓拍时车速快、角度随机车牌在画面里可能只有40像素而且还有运动模糊。这时候更好的思路是换更强壮的目标检测器比如YOLOv8/v5的P2层输出或更细粒度的检测头同时把识别模型也换更大尺寸的PPOCRv4 mobile server版。我个人的经验是业务系统的稳定性和用户体验通常不在算法而在工程闭环。模型精度差一点可以通过多帧投票、规则纠正来补救但业务链路断了、接口超时、消息丢了再好的AI也白搭。做一个车牌识别系统千万不要只盯着模型的map指标要通过压力测试、故障演练把全链路跑顺。还有一个可以扩展的方向把识别结果和图像数据关联起来做车辆的入出场时间统计、停车时长聚类、甚至黑名单比对。这些并不是算法问题而是数据工程问题。识别只是给业务流打了个路口真正有价值的往往在后面的数据挖掘。比如我近期在做一个测试把每辆车的车牌识别结果和场内车位状态联动实现空闲车位自动推荐识别准确率做到99.5%以后整个业务体验会有质的改变。最后说一个我踩了好几次的小坑模型更新上线要有灰度机制。训练出一个更好精度的模型不要直接全量替换先切5%的流量跑几天对比新旧模型在线上图片上的识别结果差异。有时候离线测试集表现好在线上一遇到现场特有的树叶阴影、霓虹灯色偏反而会误检。灰度对比至少观察一周确认各项指标没有回落再全量上线。做技术的人容易迷信新模型但业务上永远要先求稳再求准。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑