☰
YOLOv5+PPOCR车牌识别实战:从模型训练到业务集成全复盘
2026/10/18 18:44:25 网站建设 项目流程

最近把车牌识别系统接进实际的业务流里,从算法验证到线上运行,折腾了大半个月,总算是摸出点门道。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', path='best.pt', source='local') model.conf = 0.4 # 检测置信度阈值 model.iou = 0.45 # NMS IoU阈值 # 初始化PPOCR识别器,只需要rec功能 ocr = PaddleOCR(use_angle_cls=False, show_log=False, lang='ch') 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, det=False, cls=False) 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, det=False, cls=False)表示跳过文本检测和方向分类,只执行识别。这是关键,因为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或者车辆跟踪ID,value为一个列表,存放同一辆车近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,整体显存占用比原来PyTorch+Paddle混合下少了接近一半。

4.5 识别结果写入业务系统乱码或丢失

这个问题不是算法问题,而是编码问题。业务系统数据库用的字符集可能是GBK,而识别服务返回的是UTF-8,直接写入后汉字会变成问号。所以接口层统一用UTF-8编码,数据库表字段也要对应UTF8MB4,同时请求头里显式加上Content-Type: application/json; charset=utf-8。

另外,结果丢失大多是消息队列消费失败导致的。我在MQ消费逻辑里加了retry和dead letter队列,识别成功但业务处理失败的消息,扔进死信,人工排查。这块看起来不起眼,但线上稳定性就靠这些细节兜底。

5. 这套方案的适用边界与扩展思考

YOLOv5 + PPOCR的组合,在标准出入口场景下可以说完全够用。但如果你想做那种城市道路上的移动车辆抓拍,或者高速上的远距离识别,纯靠这套组合就有点吃力了。移动抓拍时车速快、角度随机,车牌在画面里可能只有40像素,而且还有运动模糊。这时候更好的思路是换更强壮的目标检测器(比如YOLOv8/v5的P2层输出或更细粒度的检测头),同时把识别模型也换更大尺寸的PPOCRv4 mobile server版。

我个人的经验是:业务系统的稳定性和用户体验通常不在算法,而在工程闭环。模型精度差一点可以通过多帧投票、规则纠正来补救,但业务链路断了、接口超时、消息丢了,再好的AI也白搭。做一个车牌识别系统,千万不要只盯着模型的map指标,要通过压力测试、故障演练把全链路跑顺。

还有一个可以扩展的方向:把识别结果和图像数据关联起来,做车辆的入出场时间统计、停车时长聚类、甚至黑名单比对。这些并不是算法问题,而是数据工程问题。识别只是给业务流打了个路口,真正有价值的往往在后面的数据挖掘。比如我近期在做一个测试,把每辆车的车牌识别结果和场内车位状态联动,实现空闲车位自动推荐,识别准确率做到99.5%以后,整个业务体验会有质的改变。

最后说一个我踩了好几次的小坑:模型更新上线要有灰度机制。训练出一个更好精度的模型,不要直接全量替换,先切5%的流量跑几天,对比新旧模型在线上图片上的识别结果差异。有时候离线测试集表现好,在线上一遇到现场特有的树叶阴影、霓虹灯色偏,反而会误检。灰度对比至少观察一周,确认各项指标没有回落,再全量上线。做技术的人容易迷信新模型,但业务上永远要先求稳,再求准。

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

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

立即咨询