基于YOLOv5的车牌识别系统实战:检测、颜色分类与字符识别全流程
简介目标检测与OCR技术在智能交通和安防场景中应用广泛车牌识别则是其中极具代表性的落地任务。一个完整的车牌识别系统通常需要解决三个层次的工程问题先定位车牌位置再判断车牌颜色最后识别车牌字符。YOLOv5作为主流的目标检测框架凭借速度与精度的平衡、成熟的部署生态被大量用于车牌检测环节而字符识别部分则可复用检测思路或引入CRNN等序列模型。然而真实场景中的光照变化、摄像头角度、数据集长尾分布以及边缘设备推理性能都会直接影响识别效果。本文从三阶段技术方案出发系统讲解基于YOLOv5构建车牌识别系统的数据集准备、模型训练参数调优、HSV与CNN融合的颜色识别策略以及字符检测与多帧投票后处理等工程实践并给出部署到RK系列边缘设备时的踩坑记录帮助开发者避开常见误区。 先说一个很多新手容易踩的误区车牌识别看起来是个“标准”的深度学习项目网上代码一抓一大把。但真到自己动手从环境搭建、数据集准备到那个让人头疼的“蓝牌和黄牌”分类再到最后车牌号的精准识别每一步都有不少暗坑。这篇文章就基于我用 YOLOv5 做的一个完整车牌识别系统包括车牌颜色识别和车牌号识别把整个思路、实操过程、踩坑记录都摊开讲清楚。这套东西能做什么简单说输入一张车辆图片或者视频帧它能输出两样东西一是这辆车的车牌是什么颜色蓝、黄、绿、白、黑等二是车牌上的字符是什么比如“京A12345”。适合的场景很直观——停车场出入口、小区门禁、高速收费站、甚至一些简单的交通数据统计分析。如果你想学 YOLOv5 怎么落地或者正准备做一个车牌识别项目交作业 / 上线这篇文章的实操细节可以直接参考。先说下我的最终实践结论YOLOv5(目标检测) 负责“定位车牌在哪”再用一个轻量分类网络负责“判断车牌颜色”最后用另一个检测/分类模型或者传统OCR负责“抠出车牌号字符”。这套三阶段方案在复杂场景下的稳定性和可维护性远好于试图一个模型端到端搞定的做法。背后的原因后面展开聊。1. 整体设计与思路拆解为什么是“检测 分类 识别”三步走1.1 车牌识别不是“一个模型”的事很多初学者看到车牌识别第一反应是“我拿 YOLOv5 直接识别出车牌号不就行了”。但实际落地的时候你会发现车牌号识别本质上包含两个完全不同层次的任务定位任务车在哪车牌在车身的哪个位置这个区域大概多大识别任务车牌区域内的每个字符是什么颜色是什么如果强行用一个模型端到端输出字符序列模型会非常难训练因为字符序列的长度不固定而且车牌区域在画面中可能很小直接端到端识别对分辨率和视角的要求极高。我在实际测试中发现端到端模型在近距离、正对车牌的完美条件下效果不错但摄像头只要一歪、距离一远识别率直接崩。所以我把任务拆开分而治之YOLOv5 检测车牌位置只输出车牌的边界框bounding box这一步非常成熟YOLOv5 的泛化能力足以应付不同环境。颜色分类把上一步剪裁出来的车牌小图输入一个极轻量的 CNN或直接基于 HSV 颜色空间判断输出颜色类别。字符识别再把剪裁出的车牌图放大、矫正后用另一个专门做 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 车牌检测数据集公开数据集 自采数据混合车牌检测模型需要的数据是“图片 车牌框标注”。优先推荐两个公开资源CCPDChinese City Parking Dataset这是中科大收集的大规模停车场车牌数据集包含超过 20 万张图片覆盖了多种天气、角度、距离而且是带标注的。CCPD 一张图里通常只有一块车牌正好适合检测任务。CRPDChinese Road Plate Dataset比 CCPD 更杂包含更多道路场景适合做泛化补充。但公开数据集有个问题它和你实际部署场景往往有差异。比如 CCPD 的图片大多来自停车场抬杆视角比较正而实际你可能要应对侧方位停车、弯道抓拍等角度。所以我自己又补充采集了大概 2000 张自采图——就在公司停车场和路边用手机拍的各种角度和光线环境都有然后用 LabelImg 手动标注。注意如果项目时间特别紧至少也要用公开数据集先跑通训练流程再根据现场失败的案例针对性地补充自采数据迭代。千万不要一上来就追求“全量自采标注”那大概率项目会死在标注上。2.2 颜色识别数据集不要迷信“端到端深度模型”车牌颜色看似简单但“蓝、黄、绿、白、黑”五类在图像里受光照和偏色影响非常大。我对颜色识别做了两条路线对比路线 AHSV 颜色空间 阈值判断。把 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 torch2.0.1 torchvision0.15.2 --index-url https://download.pytorch.org/whl/cu118YOLOv5 依赖统一安装git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt这里有一个值得注意的细节如果你训练时报错缺少wandb这不是必须的直接在train.py里设置wandb_offTrue或者环境变量里禁用就行以免它老往云端传数据拖慢节奏。3.2 配置 YAML 文件与模型结构训练前要改三个地方数据集配置data/plate.yamltrain: datasets/plate_detect/images/train val: datasets/plate_detect/images/val nc: 1 names: [plate]模型配置直接用官方yolov5s.yaml但把nc改成 1。注意不需要为了“车牌是小目标”而无脑换成yolov5m或yolov5l我测试过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_loss和cls_loss基本平稳最终的验证集 mAP0.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_classes5): super().__init__() self.features nn.Sequential( nn.Conv2d(3, 16, kernel_size3, stride2, padding1), nn.BatchNorm2d(16), nn.ReLU(inplaceTrue), nn.Conv2d(16, 32, kernel_size3, stride2, padding1), nn.BatchNorm2d(32), nn.ReLU(inplaceTrue), nn.Conv2d(32, 64, kernel_size3, stride2, padding1), nn.BatchNorm2d(64), nn.ReLU(inplaceTrue), ) 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 x4.3 双路融合CNN 为主HSV 兜底实际部署时我不会只信其中一个。逻辑是这样的先用 CNN 输出 5 个类别的概率如果最高概率大于 0.85直接采用如果低于 0.85则进入 HSV 阈值判断同时参考 CNN 的第二高概率做仲裁如果两边说的完全不一致并且置信度都低则标记为“未知颜色”等待下一帧再判断。这套策略看起来简单但极大降低了“单帧误判导致整个识别结果被丢弃”的概率。因为车牌识别显示给用户时颜色错误比“识别不出”更尴尬——尤其当系统提示“蓝牌”但图上明明是绿牌的时候用户体验会非常糟。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就够了因为字符检测的输入是已经从完整图中裁剪出来的车牌区域分辨率不需要很大。如果输入过大不仅训练慢推理时也会拖累整体帧率。训练完之后在车牌字符测试集上 mAP0.5 大约 97%。不过字符检测的 mAP 只是一个参考最终真正要看的是整串识别准确率。5.3 字符分类与序列组装逻辑检测出每个字符的框之后需要把这些框按从左到右排列形成最终的字符串。这一步有几个细节值得注意按 x 坐标排序检测模型输出的框是无序的必须按框中心点的 x 坐标从小到大排序。过滤低置信度字符如果某个字符框的置信度低于 0.5我倾向于把它丢弃。但如果丢弃了第二个字符即字母位整个字符串就错了所以我会加一个规则如果第 2 位缺失且第 1 位是汉字、第 3 位是字母/数字则用第 3 位之前的间隙位置进行一次“局部重识别”或者直接把该帧标记为低置信度交给后续帧投票。字符数量和车牌规则的校验普通蓝牌是一个汉字 一个字母 5 位数字/字母共 7 个字符新能源车牌是 8 个字符。如果检测结果不符合这个长度大概率是漏检或误检了。5.4 后处理视频流的多帧投票机制单帧识别的准确率再高也不可能 100%。实际视频流场景中我引入了一个非常有效的“帧间投票”机制车辆进入检测区域后系统连续抓取 N 帧比如 10 帧每一帧都跑一遍完整车牌识别得到一个“候选字符串 置信度”对候选字符串做累计投票得票最多的字符串作为最终结果如果多个字符串票数相同则取置信度加权的那个。这个机制让整体准确率从单帧的 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 坐标在相邻几帧里上下跳动导致裁剪出来的车牌区域一会儿高一会儿低字符识别不稳定。排查过程是这样的先看原始检测框是否稳定——用一段离线视频可视化检测结果发现框确实在抖。怀疑是输入图片分辨率的问题因为原图是 200 万像素我 resize 到 640 后车牌区域只有大概 20x10 像素定位本身就存在一个像素级的波动。但对字符识别来说这个波动经过放大之后就很致命。解决方法是两层一是把推理分辨率提高到 960定位稳定性明显提升二是对检测框做跨帧平滑比如用指数移动平均更新框坐标只输入平滑后的坐标给下游模块。做完整套之后抖动问题基本消失识别率也稳下来了。6.3 单通道“黑白摄像头”的适配记录前面提过“yolov5训练单通道”热词我再展开说一下实际案例。有一个场景是停车场夜视摄像头输出的是红外黑白图像。颜色识别已经很难做了因为根本没有颜色信息字符识别倒是还能做。我的处理是检测和字符识别两个模型照常用三通道输入但推理时把灰度图复制成三通道。颜色识别模块在这种场景下直接旁路掉或者输出“未知颜色”。这样做的好处是模型不需要重新训练代价是夜间场景无法给出车牌颜色。但只要业务上没硬性要求夜间必须识别颜色这个妥协是可以接受的。6.4 关于“超参数”的调参忠告热词中出现“yolov5超参数”说明很多人卡在这一步。我调 YOLOv5 超参数的基本原则是默认参数优先按业务场景微调两三个关键项不要全盘乱改。如果检测目标是小物体可以适度增大anchor_t默认 4.0 可能太严格让 anchor 更容易匹配到小目标。我当时从 4.0 调到 4.5小目标召回率略有提升。hsv_h、hsv_s、hsv_v是颜色增强参数。车牌识别场景中颜色很重要所以我把hsv_v从默认 0.4 降到 0.2避免训练时把颜色信息增强得太过导致模型对颜色的敏感度下降。fliplr水平翻转要谨慎因为车牌字符是有方向性的翻转后文字就反了。如果做字符检测我可以接受增强但车牌检测阶段无所谓因为框的位置不受方向影响。字符分类阶段建议关闭fliplr0.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_thres0.5, iou_thres0.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 总结这套系统从开始搭建到基本跑通前后花了两周左右真正让我觉得值钱的不是那些“看起来很高级”的模型结构而是对任务拆分和数据工程的耐心。做过一遍之后我最大的感受是车牌识别这种“成熟项目”代码范例到处都是但能稳定跑在真实场景里的一定是把每一个环节都吃透了、把每一个失败样本都认真看了的版本。如果你也想做类似项目我建议你把重点放在两件事上一是数据特别是长尾数据不同省份的汉字、不同光照下的颜色二是后处理规则它会帮你挡住很多模型自身无法避免的错误。模型结构反而是最不值得纠结的部分。最后再分享一个小技巧调试阶段一定把中间结果可视化保存下来。我习惯每处理一张图把“原图 车牌框 裁剪图 颜色标签 字符识别结果”拼成一张大图存到本地。这样一旦现场反馈有问题我直接翻图就能定位是哪一步出了岔子效率高得多。本文还有配套的精品资源点击获取