
简介在计算机视觉工程落地中车牌识别是典型的细分场景其核心难点不在于模型结构而在于数据质量与任务拆解。检测与识别两个阶段对数据粒度要求不同检测模型关注目标位置识别模型关注字符序列。实际项目中中国车牌因包含31个省份汉字、特殊字符及多种号牌类型蓝牌、绿牌、黄牌、白牌通用OCR数据集难以直接迁移。本文从数据集构建出发梳理了检测与识别数据的标注格式与解耦设计介绍了基于YOLOv8的检测训练流程以及CRNN/LPRNet配合CTC Loss的字符识别方案并针对透视校正、字符混淆、夜间场景增强等工程问题给出可落地的解决方案。面向停车场道闸、违停抓拍、出入口管理等应用场景这套数据与训练路径能显著降低重复踩坑成本帮助开发者快速构建端到端的中国车牌识别系统。 做车牌识别项目踩过不少坑最头疼的从来不是模型选型而是数据。市面上开源的车牌数据集一大堆但真正能直接拿来训练中国车牌检测和识别的少之又少。要么是国外车牌要么是合成图要么只有检测框没有字符标注拿来就得返工清洗。这次我把自己一直在用的一套中国车牌数据集整理出来了包含检测和识别两套数据同时把实际跑通的训练流程和踩坑记录一并整理成文希望给正在做出入口管理、违停抓拍、停车场道闸这类项目的朋友省点时间。这套数据集的核心价值在于它把“检测”和“识别”两个阶段的数据解耦了检测数据支持蓝牌、绿牌、黄牌、白牌等不同号牌类型标注格式兼容YOLO和VOC识别数据则是按“省份简称字母数字”的字符序列组织可以直接喂给CRNN或LPRNet这类OCR模型。下面我从数据体系、车牌特征、检测训练、识别训练、问题排查五个方面完整拆解一遍。1. 项目整体设计与数据体系拆解1.1 为什么单独做中国车牌数据集很多初学者上来就找那种“一张图带一个车牌框和一行字符串”的端到端数据集但真正落地时这种数据反而不好用。原因在于检测和识别两个任务对数据的粒度要求完全不同。检测模型只需要知道“车牌在哪”标注是矩形框和类别识别模型则需要把车牌区域裁出来精确到每个字符的位置或序列。如果混在一起检测模型会学到多余的信息识别模型又会被背景干扰。拆开之后每个模型都能用最合适的数据量去训练后续换网络结构、调增广策略也互不影响。另外中国车牌本身有很强的地域特性。省份简称是31个汉字加上军警、使馆等特殊牌字符集远超一般OCR任务。国外数据集里根本没有这些汉字类别直接用国外预训练模型迁移识别准确率会卡在“省简称”这一关这是我最开始踩过最深的坑。1.2 数据集的目录结构与标注格式这套数据集从目录结构上就区分了两条独立管线china_plate_dataset/ ├── detection/ │ ├── images/ │ │ ├── train/ │ │ ├── val/ │ │ └── test/ │ ├── labels/ │ │ ├── train/ │ │ ├── val/ │ │ └── test/ │ └── classes.txt └── recognition/ ├── images/ │ ├── train/ │ └── val/ └── labels.txt检测部分的标注采用YOLO格式每个txt文件与图片同名一行对应一个车牌目标# 类别ID 中心点x 中心点y 宽度 高度均为归一化坐标 0 0.513282 0.281250 0.084375 0.043750classes.txt 内容0 plate如果要用VOC格式我也额外转了一份XML标注字段就是常见的 filename、size、object/bndbox适合跑SSD或Faster R-CNN。识别部分的每张图片是已经裁剪好的车牌区域labels.txt 每行记录图片名和对应车牌字符串img_0001.jpg 京A12345 img_0002.jpg 沪B88888这里要特别说明一个原则车牌字符串中的汉字、字母、数字都是一个独立的类别数据集在生成label时直接拼接完整字符串而不是按字符切分后单独存放。这样做的目的是给CRNN/LPRNet这类序列识别模型用它们可以在训练时自己学习字符切分位置不依赖外部切分器。如果你打算用传统的字符分割分类器方案再写个脚本按固定宽度切图就好。1.3 车牌类别体系与标签定义检测部分目前只有 plate 一个类别但为了后续扩展我建议大家在设计之初就预留号牌类型字段。实际视频监控场景里蓝牌、绿牌新能源、黄牌大型车、白牌军警的检测难度差异很大颜色是区分它们的重要线索。我在数据集中给每张检测图片加了扩展属性比如 plate_type 标记是 blue、green、yellow 还是 white方便以后做多类别检测。识别部分的字符集包含省份简称汉字京、津、冀、晋、蒙、辽、吉、黑、沪、苏、浙、皖、闽、赣、鲁、豫、鄂、湘、粤、桂、琼、渝、川、贵、云、藏、陕、甘、青、宁、新以及使、领、警、学、港、澳等特殊字符大写字母A-Z不含I和O因为容易与数字1和0混淆数字0-9总计70个类别左右比MNIST那种10分类任务复杂了一个量级。字符集的定义要尽量跟实际场景对齐如果做的是停车场项目可以把“学”“挂”加上如果只做普通道路抓拍不涉及特种车辆可以精简字符集提高单类准确率。2. 中国车牌的核心特征与检测识别难点2.1 车牌规格差异蓝牌、绿牌、黄牌、白牌中国不同号牌类型的尺寸和颜色差异特别大这直接影响检测模型的Anchor设计和输入分辨率。小型汽车蓝牌440mm × 140mm字符为白色背景为蓝色最常见。新能源绿牌480mm × 140mm比蓝牌长40mm渐变绿色背景字符为黑色。大型汽车黄牌440mm × 140mm但字符排列和蓝牌不同常有两行结构。军警白牌白底黑字或红字对比度低夜间反光强。颜色特征是检测阶段最重要的先验。YOLOv8训练时如果把绿牌和蓝牌混在一起当一类模型容易在低光照条件下漏检绿色车牌因为绿色和背景树叶、草地的颜色相近。我的做法是在数据增强环节把HSV饱和度扰动调低一点同时给绿牌样本单独做颜色保留增强避免模型过度依赖颜色而忽略纹理。尺寸差异则影响标注框的宽高比。蓝牌和绿牌都是长条形宽高比约3.14:1黄牌两行结构接近1.5:1。如果统一用640×640输入训练小目标车牌30×10像素很容易被下采样抹掉需要把输入分辨率提高到960或1280或者用多尺度训练。2.2 汉字字符与隔音符的处理识别中国车牌真正的拦路虎是汉字。英文字母和数字只有36类汉字有31个省份简称加若干特殊字而且很多汉字字形相近比如“渝”和“豫”、“鄂”和“粤”在低分辨率下人眼都容易看错模型更是经常混淆。还有一个容易忽略的字符——车牌上的小圆点“·”也就是隔音符。它位于车牌第二位字母之后起到分隔省份代码和编号的作用。在识别任务里隔音符如果被当成一个字符输出会导致序列长度多一位CTC解码时可能出错。我的处理方案是在数据标注阶段统一把隔音符的位置保留为一个特殊类别“-”让模型学会预测它推理阶段再把它从结果里删掉。这样比直接忽略它更稳因为模型知道那个位置有个东西不会乱输出。2.3 检测与识别任务的边界设计系统时一定要明确检测和识别各自负责什么。检测模型只回答“车牌在哪”不关心内容识别模型只回答“这个车牌是什么”不关心位置。两者串起来才是完整的识别链路。如果在推理时直接把检测框裁出来送识别模型会有一个问题检测框往往比实际车牌大一圈包含车身边缘、保险杠纹理等干扰。所以我在检测后加了透视校正步骤把检测框内的区域做四点对齐矫正成标准矩形再送识别。这一步对识别准确率的提升非常明显尤其在车牌有倾斜角度的情况下不做校正直接识别准确率可能从97%掉到90%以下。3. 基于YOLOv8的车牌检测模型训练全流程3.1 环境准备与数据划分我用的是YOLOv8训练框架是ultralytics。环境配置很简单建议用Python 3.9以上版本安装torch和ultralytics包pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics数据划分比例我按8:1:1切分训练集、验证集、测试集互不重叠。需要注意一个问题同一辆车在不同帧出现的图片不能既出现在训练集又出现在测试集。我见过有人随手随机划分结果测试集里包含了训练集同一场景的临近帧mAP虚高到99%一换真实场景就崩。稳妥做法是按“拍摄场景”或“车辆ID”分组划分保证数据独立性。划分完后检查一下每张图都有对应的txt标注YOLO格式要求图片和标签同名且txt文件里每一行都不能有负坐标或越界值。3.2 配置数据文件与训练参数创建plate.yamlpath: /path/to/china_plate_dataset/detection train: images/train val: images/val test: images/test nc: 1 names: [plate]训练命令yolo detect train dataplate.yaml modelyolov8s.pt epochs100 imgsz960 batch16 device0几个参数的选择逻辑我展开说一下imgsz960车牌是小目标默认640输入会让小尺寸车牌的特征在浅层就被压缩。提高到960后mAP0.5能提升2-3个点但显存占用约增加一倍。我实测8GB显存跑yolov8s的batch16还是够的再大就要梯度累积了。modelyolov8s.pts版本是精度和速度的平衡点。m版本精度更高但推理速度慢不适合后续部署到Jetson或树莓派。如果算力允许直接yolov8m更省心。epochs100实际训练到60轮左右loss就平稳了后面30轮主要靠余弦学习率衰减微调。batch16取决于显存不够就减半同时把workers调到8。训练日志里我重点关注两个指标train/box_loss是边界框回归损失val/box_loss是验证集上的回归损失。如果train loss一路下降但val loss在后期反弹就是过拟合了需要加数据增强或降低模型复杂度。3.3 训练结果评估与模型导出训练完成后用测试集评估yolo detect val modelruns/detect/train/weights/best.pt dataplate.yaml splittest我自己的测试集结果大概是这样指标数值mAP0.50.984mAP0.5:0.950.873精确率 Precision0.972召回率 Recall0.965对于单类目标检测来说这个结果已经可以上线了。实际部署时我把模型导出成TensorRT引擎yolo export modelbest.pt formatengine device0 halfTrue imgsz960导出后推理速度在Jetson Orin Nano上大概是200FPS实测1080P视频流每帧检测耗时5ms左右。导出时有个坑TensorRT引擎跟GPU型号和TensorRT版本强绑定换机器必须重新导出。我一开始在开发机上导出好了部署到另一台服务器上直接加载失败报错提示版本不匹配后来老老实实每台机器单独跑一次导出脚本才解决。4. 车牌字符识别从CRNN到LPRNet的选型与训练4.1 识别模型的输入预处理透视校正与字符切分识别阶段的输入质量直接决定上限。检测框裁出来的图可能有倾斜、透视变形直接送CRNN效果很差。我写了一个四点透视校正模块利用车牌的矩形轮廓做对齐import cv2 import numpy as np def order_points(pts): rect np.zeros((4, 2), dtypefloat32) s pts.sum(axis1) rect[0] pts[np.argmin(s)] rect[2] pts[np.argmax(s)] diff np.diff(pts, axis1) rect[1] pts[np.argmin(diff)] rect[3] pts[np.argmax(diff)] return rect def four_point_transform(image, pts): rect order_points(pts) (tl, tr, br, bl) rect widthA np.sqrt(((br[0] - bl[0]) ** 2) ((br[1] - bl[1]) ** 2)) widthB np.sqrt(((tr[0] - tl[0]) ** 2) ((tr[1] - tl[1]) ** 2)) maxWidth max(int(widthA), int(widthB)) heightA np.sqrt(((tr[0] - br[0]) ** 2) ((tr[1] - br[1]) ** 2)) heightB np.sqrt(((tl[0] - bl[0]) ** 2) ((tl[1] - bl[1]) ** 2)) maxHeight max(int(heightA), int(heightB)) dst np.array([[0, 0], [maxWidth - 1, 0], [maxWidth - 1, maxHeight - 1], [0, maxHeight - 1]], dtypefloat32) M cv2.getPerspectiveTransform(rect, dst) warped cv2.warpPerspective(image, M, (maxWidth, maxHeight)) return warped校正后我会统一缩放到160×64或192×64作为识别模型输入。这个宽高比是根据车牌字符数定的7-8个字符加上间隔正好对应这个比例。4.2 标签编码与CTC Loss识别模型我推荐直接用序列识别框架不要走“先切字符再分类”的老路。原因很简单车牌在低分辨率、强反光、遮挡情况下字符之间可能粘连切分位置稍微偏一点就全错。CRNN和LPRNet都支持端到端序列预测用CTC Loss自动对齐输入序列和输出标签。标签编码时把每个字符映射成整数索引例如char_set 京蒙JiaB...0123456789- # 实际按数据集字符集整理 char_to_idx {c: i for i, c in enumerate(char_set)} label 京A12345 encoded [char_to_idx[c] for c in label]CTC Loss在训练时需要把标签转成序列格式PaddleOCR和PyTorch里都有现成实现。我用的是PaddleOCR的CRNN模型训练命令接近python tools/train.py -c configs/rec/rec_chinese_plate_train.yml如果自己用PyTorch实现核心是定义好输入特征序列长度。以resnet18为backbone输入160×64经过下采样后特征序列长度在20-30之间大于车牌最大字符数8个字符隔音符9保证CTC有足够的时间步来对齐。4.3 训练要点字符类别平衡与数据增强识别数据集的数量和分布对准确率影响巨大。我一开始只收集了约1万张真实车牌图结果“云”“贵”“陕”这类低频省份简称几乎不出现模型一遇到就乱猜。后来我做了两件事一是从公开的中国车牌数据集比如CCPD补充样本重点挑选省份分布均匀的子集。二是做字符级数据增强包括高斯噪声模拟夜间传感器噪声运动模糊模拟车速过快时的拖影随机亮度/对比度扰动模拟逆光透视轻微扰动模拟不同拍摄角度局部遮挡模拟在字符区域随机加小色块增强幅度不能过大我试过把亮度扰动范围设成±80结果模型把正常样本也认成过曝准确率反而掉了。最终把亮度扰动控制在±30角度扰动控制在±5度内效果最稳。训练到验证集准确率超过98%后我会做一轮“困难样本挖掘”把验证集里识别错误的图片挑出来单独复制到训练集对应文件夹并重复训练2-3个epoch。这个操作能在不增加太多数据的情况下把常见易混字符对0/O、1/I、渝/豫的准确率拉起来。5. 常见问题与排查技巧实录5.1 检测框偏移与漏检检测框偏移的最常见原因是标注本身不齐。我做过一次复核发现大约有3%的历史标注框没有完全包住车牌边缘尤其是新能源绿牌边框颜色和周围车身颜色接近标注员容易把框画得偏下。训练出来的模型在边缘样本上就会系统性偏移。排查方法很简单训练完用模型跑一遍训练集把置信度高于0.9但IoU低于0.6的样本抽出来看基本都是标注质量差。处理方式是重新标注这一小部分数据而不是盲目调模型参数。漏检问题则多出在过小目标上。如果图片里车牌只有20×8像素任何检测器都很难稳定检出。我的处理是双路检测对整帧跑一次960分辨率检测同时对画面下半区域车辆通常出现的区域做一次2倍放大后再检测融合两次结果。推理时间增加不到一倍小目标召回率能提升5%以上。5.2 识别混淆省简称与数字相似字符识别阶段最常见的问题是字符混淆。我整理了一个高频混淆对列表混淆对原因对策0 / O大写字母O不在车牌字符集中但模型没见过就乱输出标签里禁止出现O训练时把O样本全部改为01 / I同样字符集明确排除I增加数字1的样本量减少字母I干扰渝 / 豫字形相似低分辨率下难以区分针对这两个汉字做局部细节增强放大字符区域鄂 / 粤右边结构相似收集更多两省样本特别是不同字体下的样本京 / 广繁体旧式车牌与普通车牌混用确认场景是否需要支持旧牌按需加入对应数据一个实用技巧推理阶段做词典约束。车牌字符串第二位必须是字母第一位必须是汉字字符集和位置信息是高度受限的。我在CTC解码后加了一层正则校验import re pattern r^[\u4e00-\u9fa5][A-Z][A-Z0-9]{5}$ plate 京A12345 if not re.match(pattern, plate): # 修正或丢弃触发二次识别 pass这个规则能在不损失召回的情况下把“京A1234”这类缺字符输出直接拦截掉明显减少错误识别。5.3 数据不平衡与夜间场景增强真实场景数据天然倾斜白天蓝牌占80%夜间绿牌和黄牌样本少。直接训练出来的模型一到晚上就各种漏检和误识别。我的做法有三个维度过采样把夜间图片复制2-3份配合亮度降低增强参与训练。合成夜间样本用白天图片做gamma校正和加噪声模拟暗光环境。合成样本不能太多否则模型可能学不到真实夜间的红外反光特征。专门收集这个最治本但没有捷径。我花了两周时间蹲了三个停车场和管理处才凑出约2000张夜间真实样本。2000张看起来不多但配合增强足以让夜间的漏检率下降一半以上。5.4 评价指标怎么算才靠谱做车牌检测识别一定不能只看单个指标。我见过有人只用mAP0.5评估模型效果看起来很好一部署就翻车。原因是mAP0.5只要求IoU超过0.5就算检测对对于车牌这种需要精确裁切的场景IoU 0.5意味着框可能偏了一半后续识别模型吃进一堆背景准确率自然下降。正确做法是同时关注mAP0.75和识别端到端准确率。端到端准确率定义为检测框IoU ≥ 0.75且识别字符串完全正确才记为一次正确识别。这个指标才是能在真实业务里直接对标的性能。我给自己定的目标是场景端到端准确率白天顺光≥ 99%白天逆光≥ 95%夜间普通照明≥ 93%夜间强反光≥ 88%达不到就回炉用“检测→识别→错误样本回灌”的闭环迭代。每次迭代大约能提升0.5-1.5个点的端到端准确率比不停换网络结构更有效。结语最后分享一个我反复验证过的结论在车牌检测识别这个场景里数据质量和数据分布的重要性要高于模型结构。YOLOv8和CRNN的组合已经足够强大真正决定项目成败的是标注是否精确、字符集是否完备、增强策略是否贴近真实场景。我后续还打算把数据集扩展到老旧污损车牌和特种车辆车牌这两个方向是目前收集难度最大但也最有价值的补充。如果你手头也有特殊场景的车牌数据欢迎拿去和这套数据集合并大家一起把国内车牌识别的基础数据做得更扎实。本文还有配套的精品资源点击获取