基于YOLOv5的车牌识别系统实战:检测、颜色分类与字符识别全流程
2026/8/26 5:41:09 网站建设 项目流程

简介:目标检测与OCR技术在智能交通和安防场景中应用广泛,车牌识别则是其中极具代表性的落地任务。一个完整的车牌识别系统通常需要解决三个层次的工程问题:先定位车牌位置,再判断车牌颜色,最后识别车牌字符。YOLOv5作为主流的目标检测框架,凭借速度与精度的平衡、成熟的部署生态,被大量用于车牌检测环节;而字符识别部分则可复用检测思路或引入CRNN等序列模型。然而真实场景中的光照变化、摄像头角度、数据集长尾分布以及边缘设备推理性能,都会直接影响识别效果。本文从三阶段技术方案出发,系统讲解基于YOLOv5构建车牌识别系统的数据集准备、模型训练参数调优、HSV与CNN融合的颜色识别策略,以及字符检测与多帧投票后处理等工程实践,并给出部署到RK系列边缘设备时的踩坑记录,帮助开发者避开常见误区。 先说一个很多新手容易踩的误区:车牌识别看起来是个“标准”的深度学习项目,网上代码一抓一大把。但真到自己动手,从环境搭建、数据集准备到那个让人头疼的“蓝牌和黄牌”分类,再到最后车牌号的精准识别,每一步都有不少暗坑。这篇文章就基于我用 YOLOv5 做的一个完整车牌识别系统(包括车牌颜色识别和车牌号识别),把整个思路、实操过程、踩坑记录都摊开讲清楚。

这套东西能做什么?简单说,输入一张车辆图片或者视频帧,它能输出两样东西:一是这辆车的车牌是什么颜色(蓝、黄、绿、白、黑等),二是车牌上的字符是什么(比如“京A12345”)。适合的场景很直观——停车场出入口、小区门禁、高速收费站、甚至一些简单的交通数据统计分析。如果你想学 YOLOv5 怎么落地,或者正准备做一个车牌识别项目交作业 / 上线,这篇文章的实操细节可以直接参考。

先说下我的最终实践结论:YOLOv5(目标检测) 负责“定位车牌在哪”,再用一个轻量分类网络负责“判断车牌颜色”,最后用另一个检测/分类模型(或者传统OCR)负责“抠出车牌号字符”。这套三阶段方案在复杂场景下的稳定性和可维护性,远好于试图一个模型端到端搞定的做法。背后的原因,后面展开聊。

1. 整体设计与思路拆解:为什么是“检测 + 分类 + 识别”三步走

1.1 车牌识别不是“一个模型”的事

很多初学者看到车牌识别,第一反应是“我拿 YOLOv5 直接识别出车牌号不就行了”。但实际落地的时候你会发现,车牌号识别本质上包含两个完全不同层次的任务:

  • 定位任务:车在哪?车牌在车身的哪个位置?这个区域大概多大?
  • 识别任务:车牌区域内的每个字符是什么?颜色是什么?

如果强行用一个模型端到端输出字符序列,模型会非常难训练,因为字符序列的长度不固定,而且车牌区域在画面中可能很小,直接端到端识别对分辨率和视角的要求极高。我在实际测试中发现,端到端模型在近距离、正对车牌的完美条件下效果不错,但摄像头只要一歪、距离一远,识别率直接崩。

所以我把任务拆开,分而治之:

  1. YOLOv5 检测车牌位置:只输出车牌的边界框(bounding box),这一步非常成熟,YOLOv5 的泛化能力足以应付不同环境。
  2. 颜色分类:把上一步剪裁出来的车牌小图,输入一个极轻量的 CNN(或直接基于 HSV 颜色空间判断),输出颜色类别。
  3. 字符识别:再把剪裁出的车牌图放大、矫正后,用另一个专门做 OCR 的模型(我用的 YOLOv5 做字符检测 + 分类,或者轻量 CNN/CRNN)输出字符序列。

这种方案的三大好处非常明显:

  • 每一段都可以单独优化。定位不准就调定位,颜色不准就调颜色,互不干扰。
  • 对算力要求更灵活。颜色分类和字符识别都可以用非常小的网络,甚至可以跑在 RK3568、RV1106 这类边缘设备上。
  • 排查问题非常直观。哪里错了,一眼就能从中间结果看出来,不至于整个模型变成黑盒。

1.2 为什么首选 YOLOv5 而不是其他检测模型

车牌检测这个任务,用 YOLOv5 是当下一个性价比极高的选择。不是说 Faster R-CNN 不行,而是从工程角度看:

  • 速度 vs 精度平衡:车牌是一个相对刚性的目标,不需要极其强大的语义理解能力。YOLOv5s 在 640x640 输入下,普通 GPU(如 3060)能跑到 100 FPS 以上,精度已经足够。Faster R-CNN 虽然精度上限可能更高,但推理速度慢一个数量级,部署到视频流场景压力很大。
  • 工程生态成熟:YOLOv5 的代码结构清晰,训练、验证、导出 ONNX/TensorRT 的流程非常顺滑,社区资料多,遇到问题很好搜到答案。这点在项目工期紧张时非常关键。
  • 小目标检测能力强:虽然有人吐槽 YOLOv5 小目标检测是短板,但车牌在监控画面里往往只占几个百分点到十几个百分点的面积,配合合适的分辨率和 anchors 设置,YOLOv5 完全能应付。

我当时还对比过 YOLOv8 / YOLOv9,但考虑到团队里其他人对 YOLOv5 的接口更熟,且 YOLOv5 的部署教程最全,最终还是选了 YOLOv5。如果你是从零开始图省事,YOLOv5 依然是个稳妥的选择。

1.3 技术方案选型对比:三阶段 vs 端到端

这里列一个我当时做技术选型时对比的表格,可以帮你在动手前想清楚:

方案核心思路优点缺点适合场景
三阶段(YOLOv5检测 + 颜色分类 + OCR)检测车牌框,分类颜色,识别字符每步可独立调优、问题定位直观、算力可控、数据要求低流程稍长,需维护多个模型工程落地、边缘设备、复杂场景
端到端(检测 + 识别一体)用一个模型直接输出车牌号和颜色流程短、部署简单训练难、识别准确率受视角/距离影响大、数据需求极高实验室Demo、条件理想场景
传统图像处理(边缘检测 + 模板匹配 + HSV颜色阈值)不用深度学习,纯 Opencv 抠图无需训练、极轻量对光线、尺度、污染、倾斜极其敏感,鲁棒性差固定角度、固定环境(如自家车库)

我最终坚持三阶段,是因为在实际业务里,摄像头角度、光线、背景都不可控,传统的颜色阈值法在傍晚和逆光时基本报废。而端到端方案在模型训练时收集“车牌号+颜色”的联合标注数据也很麻烦。拆开后,每一部分的数据集都可以单独准备、单独增强,训练难度下降了一个量级。

2. 数据集准备:比训练更关键的 60% 工作量

做深度学习项目,很多时候模型结构抄一抄很快,真正拉开差距的是数据。我这套系统的数据集由三部分组成,分别对应三个子任务。

2.1 车牌检测数据集:公开数据集 + 自采数据混合

车牌检测模型需要的数据是“图片 + 车牌框标注”。优先推荐两个公开资源:

  • CCPD(Chinese City Parking Dataset):这是中科大收集的大规模停车场车牌数据集,包含超过 20 万张图片,覆盖了多种天气、角度、距离,而且是带标注的。CCPD 一张图里通常只有一块车牌,正好适合检测任务。
  • CRPD(Chinese Road Plate Dataset):比 CCPD 更杂,包含更多道路场景,适合做泛化补充。

但公开数据集有个问题:它和你实际部署场景往往有差异。比如 CCPD 的图片大多来自停车场抬杆视角,比较正;而实际你可能要应对侧方位停车、弯道抓拍等角度。所以我自己又补充采集了大概 2000 张自采图——就在公司停车场和路边用手机拍的,各种角度和光线环境都有,然后用 LabelImg 手动标注。

注意:如果项目时间特别紧,至少也要用公开数据集先跑通训练流程,再根据现场失败的案例,针对性地补充自采数据迭代。千万不要一上来就追求“全量自采标注”,那大概率项目会死在标注上。

2.2 颜色识别数据集:不要迷信“端到端深度模型”

车牌颜色看似简单,但“蓝、黄、绿、白、黑”五类在图像里受光照和偏色影响非常大。我对颜色识别做了两条路线对比:

  • 路线 A:HSV 颜色空间 + 阈值判断。把 BGR 转 HSV,统计车牌区域内的主色调。蓝色车牌的 H 值集中在 100~124 附近,黄色在 20~35,绿色(新能源)在 35~85,白色则要看饱和度和亮度。这个办法实现起来飞快,几行代码就能出结果,而且不受训练样本限制。
  • 路线 B:训练一个小型 CNN 分类器。比如用 MobileNetV3-Small 或者一个三层卷积的简单网络,输入 32x96 的车牌裁剪图,输出 5 类颜色。

实测下来,HSV 阈值法在光照稳定的环境(地库、闸口)准确率可以到 95% 以上,但在强阳光、夜间灯光下容易翻车。CNN 分类器的鲁棒性更好,但对训练数据的覆盖要求高,尤其要覆盖不同色温下的同一种颜色。我最终的方案是双保险:优先用 CNN 分类器判断,当 CNN 的可信度低于某个阈值时,再用 HSV 作为兜底。

这里额外提醒一句:绿色车牌现在特指新能源车牌(渐变绿),但部分老式小型车也有“黄底黑字”的农用车牌。如果你只做停车场场景,绿色新能源车牌的识别是刚需,千万别漏。

2.3 车牌号识别数据:用合成数据补足长尾

车牌号识别,本质上是一个 OCR 任务。可选方案也有两条:

  • 字符检测 + 分类(YOLOv5 检测每个字符,然后再分类是哪个字
  • 序列识别(CRNN / LPRNet,直接输入图片输出字符串)

我主要用的是字符检测 + 分类这条路,因为它的流程和前面“检测车牌”高度统一,工具链全部复用 YOLOv5,工程上省事。具体做法是:先把车牌裁剪图放大,检测出每个字符的位置,再对每个字符做分类(汉字 + 字母 + 数字,总共约 70 类)。

但这里有一个非常现实的问题:汉字字符(各省简称)的样本极不均衡,比如“京”字很多,“藏”字很少,如果只用真实数据,长尾省份的识别率会非常难看。我的解决办法是合成数据

  • 用字体文件生成车牌字符图片,做随机背景、随机噪声、随机畸变;
  • 把生成的字符粘贴到真实背景里,模拟不同光照和模糊效果;
  • 用这种方法补充到每个字符类别至少 1000 张以上。

经验:合成数据不要太“完美”,要特意加一些模糊、透视变换、亮度扰动,否则模型训练出来在真实场景下遇到底噪就崩。我还试过用 OpenCV 的warpPerspective做随机四边形畸变,这招对提高真实场景鲁棒性帮助很大。

2.4 数据标注规范与格式

如果你需要自己标注数据,几个小建议:

  • 用 LabelImg 或 X-AnyLabeling,导出 YOLO 格式(class_id x_center y_center width height),坐标是归一化的。
  • 车牌检测的标注框,紧贴车牌边缘即可,不需要包含车牌周边的车身区域。框太小会丢失上下文,框太大容易引入背景干扰。
  • 字符检测的标注,注意字符间距要均匀,汉字和字母都有固定比例。我习惯把“京A12345”中每一个字符都单独标出来,包括那个“点”也单独标成一类。

顺便说下数据集目录结构,后面训练都要用:

datasets/ plate_detect/ images/ train/ val/ labels/ train/ val/ char_detect/ images/ train/ val/ labels/ train/ val/ color_cls/ train/ blue/ yellow/ green/ white/ black/ val/ blue/ yellow/ green/ white/ black/

3. YOLOv5 训练车牌检测模型:参数实操与调优记录

3.1 YOLOv5 环境安装与工程准备

YOLOv5 的安装其实没有网上说的那么玄乎,PyTorch 环境配好,把仓库拉下来就能跑。我主要说一下两个坑:

Python 和 PyTorch 版本要对上。我之前在 Ubuntu 22.04 上装 PyTorch 时,用默认的pip install torch装到了 CPU 版本,训练慢到离谱。后来老老实实去 PyTorch 官网,用对应的 CUDA 版本命令重装。比如 CUDA 11.8 对应的是:

pip install torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/cu118

YOLOv5 依赖统一安装

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

这里有一个值得注意的细节,如果你训练时报错缺少wandb,这不是必须的,直接在train.py里设置wandb_off=True或者环境变量里禁用就行,以免它老往云端传数据,拖慢节奏。

3.2 配置 YAML 文件与模型结构

训练前要改三个地方:

数据集配置data/plate.yaml):

train: datasets/plate_detect/images/train val: datasets/plate_detect/images/val nc: 1 names: ['plate']

模型配置:直接用官方yolov5s.yaml,但把nc改成 1。注意不需要为了“车牌是小目标”而无脑换成yolov5myolov5l,我测试过,yolov5s的参数量对车牌检测已经完全够用,模型太大反而在边缘设备上跑不动。

超参数配置:官方默认的hyp.scratch-low.yaml可以先用。真正常影响车牌检测效果的是img_size。我训练时用的是 640,但推理时如果现场车牌很小,可以适当把输入分辨率拉高到 960 甚至 1280,检测小目标的效果会有明显提升,代价是速度变慢。

3.3 启动训练:参数与配置记录

我最终使用的训练命令如下:

python train.py \ --data data/plate.yaml \ --cfg models/yolov5s.yaml \ --weights yolov5s.pt \ --batch-size 32 \ --epochs 150 \ --img 640 \ --device 0

几个关键参数为什么这么设:

  • --batch-size 32:在 3060 12G 显存上刚刚好。如果显存不够,降到 16 也行。批大小和 BN 层的稳定性有点关系,但 16 和 32 差距不会特别大。
  • --epochs 150:车牌检测任务相对简单,一般 100 轮左右 mAP 就收敛了。150 是为了确保完全收敛,因为后期 mAP 还有小幅度爬升。
  • --weights yolov5s.pt:用 COCO 预训练权重做迁移学习,收敛速度会快很多。这是“站在巨人肩膀上”的典型操作,千万不要从头训练。

训练时的 loss 曲线会有一个明显下降过程。我这次训练到第 60 轮左右,box_losscls_loss基本平稳,最终的验证集 mAP@0.5 达到了 99.2% 左右。之所以这么高,是因为车辆牌照检测本质上是个“单类目标检测”,任务难度比 COCO 的 80 类小得多。

3.4 训练单通道“黑白图”的冷门问题

有个热词是“yolov5训练单通道”,这里补充一句:车牌识别场景中,如果你拿到的摄像头是黑白相机(红外夜间模式),那训练和推理都要把图像统一成 3 通道。YOLOv5 的输入层默认是 3 通道,如果你只有单通道图,最简单的方法是用cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR)复制成 3 通道再送进网络。别去改模型第一层的in_channels,改完之后预训练权重就全废了。

我踩过这个坑,一开始天真地把模型改成单通道输入,然后发现 COCO 预训练权重加载报错,后面训练收敛奇慢。后来老老实实把单通道图转成三通道,问题立刻解决。这件事也给我一个教训:不要随便改网络结构,除非你真的知道自己为什么改。

3.5 导出模型并部署到边缘设备(RK3568/RV1106 的场景)

训练好的模型,最终要部署到实际环境,这里简单说下导出流程。我用的是 ONNX 中转路线:

python export.py --weights runs/train/exp/weights/best.pt --include onnx --opset 11

导出的 ONNX 可以继续转成 RKNN(瑞芯微平台)等格式。这个过程里最容易出问题的就是算子的兼容性,比如某些上采样算子在 RKNN 转换时可能不支持。我的经验是:导出 ONNX 时用--opset 11,并且在转 RKNN 前先跑一遍 onnxruntime 的推理,确认输出和 PyTorch 原模型一致,再去做平台转换,否则你根本分不清是模型问题还是转换问题。

如果你用的是 RV1106 这类轻量芯片,内存和算力都有限,YOLOv5s 可能要再剪枝或蒸馏才能跑得流畅。我当时在 RV1106 上部署时,把输入分辨率从 640 降到 416,精度损失大概 1%,但帧率从 8 提升到了 15 左右,这个 trade-off 是可以接受的。

4. 车牌颜色识别:从阈值到 CNN 的渐进方案

4.1 HSV 阈值法速成实现

从检测框里把车牌区域裁剪出来之后,第一步可以先做一个极快的 HSV 颜色判断。OpenCV 默认的颜色空间是 BGR,所以要先把裁剪图cvtColor到 HSV,然后对每个像素的 H、S、V 通道做条件过滤。

车牌颜色对应的参考阈值我做了一个速查表(注意 OpenCV 中 H 范围是 0~180,不是 0~360):

车牌颜色H 范围S 范围V 范围备注
蓝色100~12490~25560~255普通燃油车蓝牌
黄色15~3580~25580~255大型车/驾校车等
绿色(新能源)35~8540~25540~255渐变绿车牌,取主色即可
白色0~1800~30180~255军警/应急救援车
黑色0~1800~2550~60外籍车等

代码实现大概就是cv2.inRange(hsv, lower, upper),然后统计比例。哪个颜色的像素占比最高,就判为哪个颜色。

但这里有个致命的实际场景问题:当白色车牌的亮度很高时,S 会非常低;而阳光下的蓝色车牌,V 值可能被拉得很高,同时 H 值也会轻微漂移。所以纯阈值法在晴天下午 4 点到 6 点之间,蓝色和黑色误判率会显著上升。

4.2 CNN 分类器:让模型自己学颜色

为了解决阈值法鲁棒性差的问题,我在车牌裁剪图上训练了一个极小的 CNN 分类器。输入是 32x96 的 RGB 图,三层卷积 + 全连接,输出 5 类。训练集就是从 2.2 节提到的颜色分类数据集中随机挑选的。

训练这个分类器有一个小技巧:数据增强里一定要加色温扰动。用 OpenCV 的cv2.addWeighted随机调整亮度,用白平衡的方式随机改变 R、G、B 通道的比例,模拟不同摄像头、不同光线下的色偏。否则模型在测试集上 99% 准确率,一到现场就被各种偏色打回原形。

我用 PyTorch Lightning 写了个简单训练脚本,训练 50 轮,验证集准确率大约 98.5%。这里把关键的网络定义列出来:

import torch.nn as nn class ColorNet(nn.Module): def __init__(self, num_classes=5): super().__init__() self.features = nn.Sequential( nn.Conv2d(3, 16, kernel_size=3, stride=2, padding=1), nn.BatchNorm2d(16), nn.ReLU(inplace=True), nn.Conv2d(16, 32, kernel_size=3, stride=2, padding=1), nn.BatchNorm2d(32), nn.ReLU(inplace=True), nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=1), nn.BatchNorm2d(64), nn.ReLU(inplace=True), ) self.classifier = nn.Sequential( nn.AdaptiveAvgPool2d((1, 1)), nn.Flatten(), nn.Linear(64, num_classes), ) def forward(self, x): x = self.features(x) x = self.classifier(x) return x

4.3 双路融合:CNN 为主,HSV 兜底

实际部署时,我不会只信其中一个。逻辑是这样的:

  1. 先用 CNN 输出 5 个类别的概率;
  2. 如果最高概率大于 0.85,直接采用;
  3. 如果低于 0.85,则进入 HSV 阈值判断,同时参考 CNN 的第二高概率做仲裁;
  4. 如果两边说的完全不一致,并且置信度都低,则标记为“未知颜色”,等待下一帧再判断。

这套策略看起来简单,但极大降低了“单帧误判导致整个识别结果被丢弃”的概率。因为车牌识别显示给用户时,颜色错误比“识别不出”更尴尬——尤其当系统提示“蓝牌”但图上明明是绿牌的时候,用户体验会非常糟。

5. 车牌号识别:字符检测 + 分类的完整链路

5.1 为什么选择字符检测而不是 CRNN

关于车牌号识别,热词里也经常出现“车牌识别软件源代码”“yolo 车牌识别”,说明大家普遍关心的是能不能直接用 YOLO 把字符一次全识别出来。我尝试过两种方案:

  • YOLOv5 字符检测 + 分类:在车牌裁剪图上检测出每个字符的框,然后裁剪每个字符送入分类器(或直接用检测模型的 class 输出)。优点是每个字符有明确的边界框,方便做置信度过滤和字符校正;缺点是流程更长。
  • CRNN / LPRNet:直接用卷积循环网络把整张车牌图变成字符串序列。优点是一步到位,缺点是训练需要序列标注数据,且对字符间距、字数的变化比较敏感。

我选择字符检测还有一个现实原因:中国车牌字符之间是有固定间距的,检测出每个字符框之后,可以通过字符的相对位置做规则校验(比如第 1 位必须是汉字,第 2 位必须是字母),这能显著降低识别错误。CRNN 要对齐解码后做规则校验就比较费劲。

5.2 字符检测模型的训练流程

这部分和训练车牌检测模型几乎一样,只是data/char.yaml里的类别数变为 70 左右,并且names列表要按顺序把所有可能出现的字符列出来。我当时的类别列表大概是: 汉字(省份简称,共 31 个)+ 字母(24 个,去掉 I 和 O)+ 数字(10 个),再加一个特殊字符如“点”或“挂车字符”,总共约 67~70 类。

训练命令:

python train.py \ --data data/char.yaml \ --cfg models/yolov5s.yaml \ --weights yolov5s.pt \ --batch-size 64 \ --epochs 100 \ --img 320 \ --device 0

注意这里--img 320就够了,因为字符检测的输入是已经从完整图中裁剪出来的车牌区域,分辨率不需要很大。如果输入过大,不仅训练慢,推理时也会拖累整体帧率。

训练完之后,在车牌字符测试集上 mAP@0.5 大约 97%。不过字符检测的 mAP 只是一个参考,最终真正要看的是整串识别准确率。

5.3 字符分类与序列组装逻辑

检测出每个字符的框之后,需要把这些框按从左到右排列,形成最终的字符串。这一步有几个细节值得注意:

  • 按 x 坐标排序:检测模型输出的框是无序的,必须按框中心点的 x 坐标从小到大排序。
  • 过滤低置信度字符:如果某个字符框的置信度低于 0.5,我倾向于把它丢弃。但如果丢弃了第二个字符(即字母位),整个字符串就错了,所以我会加一个规则:如果第 2 位缺失且第 1 位是汉字、第 3 位是字母/数字,则用第 3 位之前的间隙位置进行一次“局部重识别”,或者直接把该帧标记为低置信度,交给后续帧投票。
  • 字符数量和车牌规则的校验:普通蓝牌是一个汉字 + 一个字母 + 5 位数字/字母(共 7 个字符);新能源车牌是 8 个字符。如果检测结果不符合这个长度,大概率是漏检或误检了。

5.4 后处理:视频流的多帧投票机制

单帧识别的准确率再高,也不可能 100%。实际视频流场景中,我引入了一个非常有效的“帧间投票”机制:

  1. 车辆进入检测区域后,系统连续抓取 N 帧(比如 10 帧);
  2. 每一帧都跑一遍完整车牌识别,得到一个“候选字符串 + 置信度”;
  3. 对候选字符串做累计投票,得票最多的字符串作为最终结果;
  4. 如果多个字符串票数相同,则取置信度加权的那个。

这个机制让整体准确率从单帧的 97% 左右,提升到 99.5% 以上。代价是会产生约 0.5 秒的识别延迟。对于停车场出入口这种场景,0.5 秒完全可接受。如果做的是高速自由流抓拍,那就不能等了,必须单帧出结果,这时就得靠更强的模型和更严的工程优化。

6. 常见问题与排查技巧实录

这部分就把我在实操过程中真正踩过、也帮别人排查过的典型问题整理成速查表,顺便给几句掏心窝子的建议。

6.1 问题速查表

现象可能原因解决方案
车牌检测漏检,小车牌看不清输入分辨率太低推理时提高--img到 960 或 1280
车牌检测框抖动,忽大忽小没有做帧间平滑对检测框做 EMA 平滑或 NMS 后处理去重
蓝色车牌被识别成黑色逆光导致车牌区域亮度过低颜色分类优先用 CNN,避免纯 HSV
绿色新能源车牌识别成蓝色渐变绿色在低饱和度下偏蓝数据集增加新能源车牌的样本比例
字符“0”和“O”混淆两者形状接近,模型难以区分后处理规则:第二位必须是字母,其他位一般为数字
汉字省份简称识别错训练样本少或字体模糊用合成数据补充长尾省份,增加模糊/噪声增强
推理帧率太低模型太大或输入分辨率过高尝试 YOLOv5s 配合 TensorRT,或降低 img 尺寸
导出 ONNX 后结果不一致预处理/后处理不一致检查归一化方式(0~255 到 0~1),检查 NMS 阈值

6.2 车牌检测框抖动的排查过程

有一次现场反馈,系统在晴天下午对某一车道的识别率突然下降。我远程一看日志,发现不是检测不到车牌,而是检测框的 y 坐标在相邻几帧里上下跳动,导致裁剪出来的车牌区域一会儿高一会儿低,字符识别不稳定。

排查过程是这样的:

  1. 先看原始检测框是否稳定——用一段离线视频可视化检测结果,发现框确实在抖。
  2. 怀疑是输入图片分辨率的问题,因为原图是 200 万像素,我 resize 到 640 后,车牌区域只有大概 20x10 像素,定位本身就存在一个像素级的波动。但对字符识别来说,这个波动经过放大之后就很致命。
  3. 解决方法是两层:一是把推理分辨率提高到 960,定位稳定性明显提升;二是对检测框做跨帧平滑,比如用指数移动平均更新框坐标,只输入平滑后的坐标给下游模块。

做完整套之后,抖动问题基本消失,识别率也稳下来了。

6.3 单通道“黑白摄像头”的适配记录

前面提过“yolov5训练单通道”热词,我再展开说一下实际案例。有一个场景是停车场夜视摄像头,输出的是红外黑白图像。颜色识别已经很难做了(因为根本没有颜色信息),字符识别倒是还能做。

我的处理是:检测和字符识别两个模型照常用三通道输入,但推理时把灰度图复制成三通道。颜色识别模块在这种场景下直接旁路掉,或者输出“未知颜色”。这样做的好处是模型不需要重新训练,代价是夜间场景无法给出车牌颜色。但只要业务上没硬性要求夜间必须识别颜色,这个妥协是可以接受的。

6.4 关于“超参数”的调参忠告

热词中出现“yolov5超参数”,说明很多人卡在这一步。我调 YOLOv5 超参数的基本原则是:默认参数优先,按业务场景微调两三个关键项,不要全盘乱改

  • 如果检测目标是小物体,可以适度增大anchor_t(默认 4.0 可能太严格),让 anchor 更容易匹配到小目标。我当时从 4.0 调到 4.5,小目标召回率略有提升。
  • hsv_hhsv_shsv_v是颜色增强参数。车牌识别场景中颜色很重要,所以我把hsv_v从默认 0.4 降到 0.2,避免训练时把颜色信息增强得太过,导致模型对颜色的敏感度下降。
  • fliplr水平翻转要谨慎,因为车牌字符是有方向性的,翻转后文字就反了。如果做字符检测,我可以接受增强,但车牌检测阶段无所谓,因为框的位置不受方向影响。字符分类阶段建议关闭fliplr=0.0

这些参数在 YOLOv5 的hyp.scratch-low.yaml里都能改。改完要多观察验证集 mAP,如果 mAP 下降,多半是参数调坏了,而不是“还没跑到收敛”。

7. 部署与工程化实践:从离线 Demo 到可用的服务

7.1 推理 Pipeline 的代码组织

整套识别系统的推理流程,我最终整理成了一个 Pipeline,用 Python 写主流程,关键模型都用 ONNX Runtime 或 TensorRT 加速。伪代码如下:

import cv2 import numpy as np import onnxruntime as ort # 加载三个模型 detect_session = ort.InferenceSession("plate_detect.onnx") color_session = ort.InferenceSession("color_cls.onnx") char_session = ort.InferenceSession("char_detect.onnx") def preprocess(img, size=(640, 640)): # 保持宽高比的 resize + letterbox ... def postprocess_boxes(outputs, conf_thres=0.5, iou_thres=0.45): # 解码 + NMS ... def recognize_plate(frame): # 1. 检测车牌 boxes = postprocess_boxes(detect_session.run(...)) if len(boxes) == 0: return None # 2. 裁剪车牌区域 plate_crop = crop_and_align(frame, boxes[0]) # 3. 判断颜色 color = color_cls(plate_crop) # 4. 检测字符 char_boxes = postprocess_boxes(char_session.run(...)) # 5. 字符排序 + 后处理 plate_number = assemble_chars(plate_crop, char_boxes) return {"color": color, "number": plate_number, "bbox": boxes[0]}

整个 Pipeline 在 CPU 上跑大概 200ms 一帧,在 3060 显卡上可以做到 20ms 左右,基本满足实时视频流需求。

7.2 项目目录结构建议

一个工程化的车牌识别项目,目录结构最好一开始就规范化。我的项目结构如下,供参考:

plate_recognition/ ├── config/ │ ├── plate.yaml │ └── char.yaml ├── data/ │ ├── images/ │ └── labels/ ├── models/ │ ├── plate_detect.onnx │ ├── char_detect.onnx │ └── color_cls.onnx ├── scripts/ │ ├── train_plate.py │ ├── train_char.py │ ├── train_color.py │ └── export_onnx.py ├── infer/ │ ├── pipeline.py │ └── utils.py └── deploy/ └── rknn_convert.py

这个结构不复杂,但能让你在项目后期不至于因为文件乱放而焦头烂额。尤其是config目录专门放训练配置,deploy目录放平台转换脚本,分工明确。

7.3 边缘设备部署的算力评估

如果你要把系统部署到 RK3568 或 RV1106 上,内存和算力需要提前算好。YOLOv5s 的 FP16 模型在 RK3568 上大约能跑到 15~30 FPS(取决于分辨率)。如果把“检测 + 颜色 + 字符”串行跑,整体帧率可能只有 10 FPS 左右,这时就需要考虑用多线程把颜色识别和字符识别放到单独的线程,或者对检测框做时间上的缓存(比如 3 帧内只做一次颜色+字符识别)。

这类边缘设备上还有一个常见问题:模型转换后精度可能轻微下降。我用 RKNN 转换后,检测 mAP 大概掉了 0.5~1 个百分点,这基本正常。如果掉得太多,优先排查归一化方式是否一致,以及是否启用了错误的量化模式(如动态量化 vs 全整型量化)。

7.4 写一个小结式的个人体会(不是 AI 总结)

这套系统从开始搭建到基本跑通,前后花了两周左右,真正让我觉得值钱的不是那些“看起来很高级”的模型结构,而是对任务拆分和数据工程的耐心。做过一遍之后,我最大的感受是:车牌识别这种“成熟项目”,代码范例到处都是,但能稳定跑在真实场景里的,一定是把每一个环节都吃透了、把每一个失败样本都认真看了的版本。

如果你也想做类似项目,我建议你把重点放在两件事上:一是数据,特别是长尾数据(不同省份的汉字、不同光照下的颜色);二是后处理规则,它会帮你挡住很多模型自身无法避免的错误。模型结构反而是最不值得纠结的部分。

最后再分享一个小技巧:调试阶段一定把中间结果可视化保存下来。我习惯每处理一张图,把“原图 + 车牌框 + 裁剪图 + 颜色标签 + 字符识别结果”拼成一张大图存到本地。这样一旦现场反馈有问题,我直接翻图就能定位是哪一步出了岔子,效率高得多。

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

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

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

立即咨询