简介:中国车牌数据集是一份面向车牌检测与识别任务的专业数据资源,适合机器视觉、深度学习方向的开发者和研究人员使用,可覆盖不同角度、光照、位置等多场景下的车牌定位、字符切分与识别验证。压缩包共包含5633个文件,以3368张jpg图像为主,配以1371个txt标注和893个xml标签文件,整体大小约168.45MB;训练集含1200张图像,测试集含1000张图像,目录下还划分train_ocr、training_lp、test_lp、wpod_data等模块,分别对应单字符训练数据、整体车牌图像及可能的车型等扩展元数据,便于按检测、OCR识别、模型评估等环节灵活使用。目前已有2244人学习下载。txt与xml中注明车牌位置、字符内容等信息,可支撑卷积神经网络等模型的训练、数据增强和泛化性能测试,尤其适用于智能交通、社区安防中的自动车牌识别系统搭建,也可作为算法对比与实验教学的可靠基准数据集。
1. 中国车牌数据集:检测与识别两类数据,为什么不能共用一套标注
拿到一份中国车牌数据集,很多人的第一反应是“既有检测框,又标了车牌号,一套标注不就能训练两个模型了吗”。实际做下来你会发现,检测模型对着一张图只回答“牌在哪”,识别模型要回答“牌上写的是什么”,两者对数据的敏感点完全不同:检测关心场景里有没有被遮挡、暗光、形变的车牌,识别关心字符清不清晰、裁得正不正、字体是哪一代。把同一个标注直接拿去喂两个模型,最常见的结果是检测 mAP 挺好看,接上字符识别模块后整体翻车,蓝牌绿牌误判、省份简称识别串字。这篇就顺着“中国车牌数据集包含检测和识别数据”这句话,拆开两类数据各自的要求、标注字段差异和落地做法,给一套能照着复现的整理与验证流程。适合正在自建数据集、或者想在公开数据集基础上扩展的工程师,目标只有一个:让检测和识别数据各归各,且能联合评估。
2. 检测数据与识别数据差在哪:一张车牌图的两种切法
2.1 检测模型要的是“难找的车牌”,不是“清晰的车牌”
检测任务(目标检测)评估的是“目标定位”能力,所以检测数据集的每一张图,标注的核心是矩形框x, y, w, h,以及这个框对应的类别。对于中国车牌数据集,类别通常就三类:蓝色单层牌、黄色单层牌、新能源绿牌,部分地区会加“黑色港澳牌”和“白色军牌”,但公开数据集里蓝牌占绝对多数。
关键问题在图像的“难度分布”。检测模型在训练时需要看到大量“不容易找”的样本:车牌在画面里占比很小、强光反光、夜间暗光、被护栏遮挡三分之一、倾斜超过 30 度、雨天泥点盖住字符。这些场景对识别模型来说是噪音,对检测模型来说恰恰是主料。我在整理数据时有个朴素标准:检测数据集的多样性优先级高于清晰度优先级,宁要 100 张“找起来费劲”的图,不要 1000 张“一眼就看出来”的图。因为检测模型在下游实际部署时,遇到的输入大多是抓拍机里角度刁钻、环境复杂的帧,而不是停车场闸机前端正停着的车。
2.2 识别模型要的是“规整的字符”,不是“现场原图”
车牌识别(License Plate Recognition,常见做法是检测后接一个裁剪再识别)面对的输入已经是方块图,模型要做的是把 7 个字符序列(新能源是 8 位)逐个认出来。这时候影响精度的主要因素变成:字符与背景对比度、字符是否被裁掉边缘、车牌倾斜校正是否到位、字体是否符合标准规范(比如大陆车牌统一用 60mm 高、90mm 宽的字符,但不同造牌批次会有细微差异)。
所以识别数据集的组织方式和检测数据集完全不同。我一般把识别数据单独切成“字符图 + 序列标签”两层,目录结构类似:
recognition/ images/ 皖A12345.jpg 京B88888.jpg labels/ 皖A12345.txt每个labels文件内容就是一行纯文本,写皖A12345。注意这里没有检测框——识别模型输入的图是已经裁剪好的,宽高比固定为 440:140 之类,不需要再告诉模型“车牌在哪里”。如果你拿到的原始数据集只提供了整张实景图和检测框,第一步就得先把“检测标注”换算成“裁剪小图”,再单独建识别集。这个环节是后续所有踩坑的源头,第四章给脚本。
2.3 为什么不能拿一套标注同时训练两个模型:数据形态与损失函数都不兼容
直接原因有三条。第一,检测标注里的框即使很准,裁出来的图也可能是歪的、带背景的、分辨率偏低的,这些图直接进识别模型,相当于拿 JPEG 压缩过度的图去跑 OCR,字符特征被背景和压缩噪声稀释。第二,检测模型训练时需要对整张图做随机裁剪、翻转、颜色抖动等增强,这些操作对“目标是否存在”是合理的,但对“字符序列内容”却是破坏性的——左右翻转后“皖A”会变成“A皖”的镜像顺序,模型学到的字符空间关系全乱。第三,检测模型输出的是边界框坐标与类别置信度,识别模型输出的是字符序列概率,两者的损失函数(Smooth L1 + BCE / CrossEntropy 逐字符)和评价指标(mAP / 字符准确率)根本不是一个量纲。硬把两张任务塞进同一个训练管线,最后只能得到一个“两个任务都做但都做不稳”的模型。
3. 一份中国车牌数据集的目录与标注字段,按这套标准组织才不会返工
3.1 目录分层:images 放原图、labels 放检测框、recognition 放裁剪图
动手整理之前,先定目录规范。常见做法是三层结构顶层分为train/、val/、test/三个集合,每个集合内再按任务拆分:
cndataset/ train/ images/ # 原图,jpg或png labels/ # 检测标注,YOLO txt格式 recognition/ # 从检测框裁出来的车牌小图 rec_labels.txt # 小图文件名 + 序列标签,每行一对 val/ ...检测标注格式优先用 YOLO txt(每行class x_center y_center width height,均为归一化值),因为 YOLOv8、YOLOv5 等主流工具直接吃这个格式,省去转换。格式示意见下:
# class 0=蓝色牌, 1=绿色牌, 2=黄色牌 0 0.483 0.371 0.126 0.048 1 0.712 0.554 0.134 0.0513.2 检测标注字段:类别、归一化中心坐标、宽高、难例标志
除常规字段外,我强烈建议多加一个字段:difficult或occlusion,标这个样本是不是难例。做法是在 YOLO txt 行尾追加一个整数 0/1,0 表示正常,1 表示遮挡/模糊/暗光。YOLO 官方格式不认这个尾巴,但你可以写个脚本在加载时过滤:
# parse_yolo_with_difficult.py # 读取YOLO标注文件,解析带difficult标志的行,用于后续按难度分层评估 import os def parse_label(path): boxes = [] with open(path, 'r') as f: for line in f: parts = line.strip().split() if len(parts) < 5: continue cls = int(parts[0]) x_c, y_c, w, h = map(float, parts[1:5]) difficult = int(parts[5]) if len(parts) > 5 else 0 boxes.append((cls, x_c, y_c, w, h, difficult)) return boxes if __name__ == "__main__": boxes = parse_label("data/train/labels/0001.txt") easy = [b for b in boxes if b[5] == 0] hard = [b for b in boxes if b[5] == 1] print(f"easy={len(easy)}, hard={len(hard)}")这段代码的逻辑很直白:把标注行拆开后,第 6 个字段作为难例标志读出来。实际使用中,这个难以标志的标注可以由“检测框置信度低但人工确认是对的”的样本来生成,也可以在初筛阶段由成本较低的先验规则(面积占比小于 1%、亮度方差低)自动打标再人工复核。这样做的价值在于:最终评估 mAP 时,可以分别看 easy / hard 两个子集,识别模型在 hard 图上掉多少,心里有数。
3.3 识别数据字段:文件名与序列标签对齐,字符集映射表单独维护
识别数据这边,rec_labels.txt里每一行是相对路径 标签文本,二者用空格隔开。注意不要把标签直接写在文件名里,理由有两个:一是中文省份简称在部分文件系统里编码不一致,跨机器拷贝会出现乱码;二是后续如果要做字符级评估,需要把标签拆成单字与字符集索引做映射,集中维护一份charset.txt更稳。
# rec_labels.txt 示例 train/recognition/皖A12345.jpg 皖A12345 train/recognition/京B88888.jpg 京B888883.4 数据量的经验区间:检测 8k 张起步,识别 10 万张不嫌多
公开数据集的体量可以参考 CCPD 这类 China City Plate Dataset 的规模,但自建数据集的量级取决于你的场景复杂度。我给一个经验值:检测端,单一城市单一光照条件 3000~5000 张能到 95% 以上 mAP;要覆盖夜间、雨天、多省车牌,至少 15000~20000 张。识别端就完全不一样了——字符识别本质上是序列分类,字符组合有 31 个省份简称 × 字母 × 数字,数据量不足时生僻组合永远学不好。最稳的做法是:识别裁剪图 8 万张起步,生僻省份简称(藏、新、蒙等)单独保证每类至少 2000 张,否则字符准确率到 98% 后很难再往上拉,差的那 2% 全在这种低频字符上。
4. 从公开数据集到自有数据:检测标注换识别图的转换脚本与参数
4.1 用 CCPD 类公开数据做冷启动:获取、划分与去重
如果你没有现成实景图,起步阶段从公开的中国车牌数据集(如 CCPD)拉数据是合理选择。CCPD 提供整图 + 六位坐标的 JSON 标注,但要注意它的场景主要是安徽合肥及周边道路,省份简称 “皖” 占比极高——这是一个显著先验,模型很容易过拟合到这个简称上。拿到原始数据后,第一步不是喂训练,而是分层抽样。
常见做法是按“车辆类型 + 拍摄高度 + 光照”三个维度做分层,避免全随机导致训练集全是正午、测试集全是傍晚。CCPD 文件名自带天气和倾斜信息,可以用字符串匹配来分组;如果数据是你自己采集的,建议在采集记录里就写清时间戳和地点,后续按这些字段抽样会方便得多。
4.2 检测框到识别图:坐标解析、欧氏距离校验与裁剪
CCPD 的 JSON 标注格式和 YOLO 不一样,它存的是车牌的四个角点坐标[x1, y1, x2, y2, x3, y3, x4, y4]。要从角点得到检测框,不能直接取 min/max,因为车牌在图片里往往是斜的,直接取外接矩形会把无关背景包进去。常规做法是先算最小外接矩形,再用投影变换把斜车牌拉正,最后按固定宽高比抠图。下面给一个能跑的转换脚本骨架:
# ccpd_to_recognition.py # 将CCPD角点标注转为YOLO检测框,同时抠出校正后的车牌识别图 import json import cv2 import numpy as np from pathlib import Path def ccpd_pts_to_label(points, img_size): """四角点 -> 归一化检测框(取最小外接矩形)""" xs = [p[0] for p in points] ys = [p[1] for p in points] x_min, x_max = min(xs), max(xs) y_min, y_max = min(ys), max(ys) w = x_max - x_min h = y_max - y_min x_c = (x_min + x_max) / 2 / img_size[0] y_c = (y_min + y_max) / 2 / img_size[1] w_n = w / img_size[0] h_n = h / img_size[1] return 0, x_c, y_c, w_n, h_n def crop_and_warp(img, pts, out_size=(440, 140)): """四点透视校正,把斜的车牌拉成水平矩形""" pts = np.array(pts, dtype=np.float32) rect = cv2.boundingRect(pts) # 用四个角点直接做透视变换 dst_pts = np.array([[0, 0], [out_size[0]-1, 0], [out_size[0]-1, out_size[1]-1], [0, out_size[1]-1]], dtype=np.float32) M = cv2.getPerspectiveTransform(pts, dst_pts) warped = cv2.warpPerspective(img, M, out_size) return warped # 使用示例(假设img和pts已就绪) # yolo_label = ccpd_pts_to_label(pts, (img_w, img_h)) # rec_img = crop_and_warp(img, pts)这段代码有两个关键点:一是角点顺序必须固定为“左上、右上、右下、左下”,CCPD 的角点是顺时针给的,直接用作透视变换源点会拉出镜像图;二是crop_and_warp的输出尺寸固定为440x140,这是大陆车牌 440mm×140mm 规格的像素化近似,识别模型输入统一到这一尺寸可以省去训练时的 resize 噪音。实际使用时,当你发现抠出来的小图里车牌两侧还带着大量车漆,通常是角点顺序错了或透视矩阵的方向反了,这时先去打印pts的坐标顺序,别急着调网络。
4.3 识别数据的伪标注与人工修正闭环
识别模型的训练离不开标注的序列标签。如果你在做冷启动时拿到的数据没有现成车牌号,常见做法是先用一个开源 OCR 模型跑一遍,生成伪标签(pseudo label),再人工抽检修正。这步有利有弊:优点是速度快,缺点是把模型的系统性错误带进了训练集——比如它把“皖”统一识别成“晚”,你会学到一批“晚A12345”。所以伪标注之后必须有修正闭环:
# verify_rec_labels.py # 对recognition目录中的小图做二轮校验:计算字符级置信度,低置信度样本抽出来人审 from PIL import Image import numpy as np def per_char_conf(labels, probs): """labels: 字符串; probs: (seq_len,) 每个字符的概率""" low_conf = [] for i, (char, p) in enumerate(zip(labels, probs)): if p < 0.85: low_conf.append((i, char, p)) return low_conf # 假设某样本的字符概率如下 labels = "皖A12345" probs = np.array([0.99, 0.96, 0.92, 0.88, 0.43, 0.78, 0.55]) issues = per_char_conf(labels, probs) print("需要人工复核的字符位置:", issues)这段验证脚本的作用是把“人看”聚焦到模型真正不确定的地方,而不是把 10 万张图全部铺开人工看。阈值 0.85 是我常用的起步值,调高到 0.95 会显著增加复核量,调低到 0.7 容易漏掉系统性错误。如果你发现某个位置连续大量低置信度,并且错误字符都集中在同一个字上,那基本可以判断是伪标注模型的系统性盲区,需要针对那个字符单独补充干净样本。
5. 避坑记录:中国车牌数据集整合时的 5 个常见翻车点
5.1 坑一:检测框裁出来的图喂给识别模型,字符全歪
现象:检测模型在测试集上 mAP 过了 0.95,但同一批图的裁剪小图送进识别模型,字符准确率只有 70% 出头。典型的错误输出是“皖A12345”被识别成“皖A1234G”或者“皖A1234F”,数字与字母混淆,且错的位置不固定。
原因:检测框是从整图目标检测输出得到的轴对齐矩形,而车牌在实景图里往往有旋转。直接把轴对齐矩形裁剪,车牌字符在裁剪图里是斜的,识别模型没有做过透视校正,对斜字符的泛化能力远低于水平字符——训练时喂的是440x140规整图,测试时喂的是歪斜图,分布不匹配。
解决:训练识别模型时不要只用校正后的规整图,按 20% 比例混入未经透视校正、有轻微旋转的裁剪图,让识别模型见到少量带角度输入,增强鲁棒性。同时,检测后处理环节加一个角点回归分支,输出四角点再做批量透视校正,不要用轴对齐矩形硬裁。
5.2 坑二:省份简称不均衡,模型把“皖”学成了默认答案
现象:验证集上整体字符准确率 97%,拆开按省份统计,发现“皖”“苏”“浙”都在 99% 以上,“藏”“新”“蒙”只有 80% 上下,且错误模式都是把罕见简称识别成常见简称。
原因:公开数据集里各省分布极不均衡,CCPD 以“皖”为主,自采数据也容易集中在本地城市。识别模型本质是在学字符分布先验,训练集中“皖”出现 5 万次、“藏”出现 500 次,模型当然倾向于“不认识就猜皖”。
解决:做训练集划分之前,先按省份简称做一次分布统计。对低于阈值(我一般设 1000 张)的省份单独做复制增强,或者在 loss 里按字符频率加权。最治本的办法是补充采集数据,但若短期补不了,可以用“字符级类别均衡采样”暂时止血:每个 batch 里强制保证至少 5% 的样本来自低频率省份,防止模型遗忘罕见组合。
5.3 坑三:车牌颜色类别在 YOLO 训练时被“误伤”
现象:绿牌新能源车的检测框经常偏大,把车灯或保险杠包进去,且置信度比蓝牌低一截。看训练日志,发现 green 类别的 loss 明显高于 blue。
原因:新能源绿牌是渐变绿色(小客车)或黄绿双色(大客车),在灰度空间下和深色车身区分度小;而 YOLO 的输入通常会做灰度归一化,颜色对比度损失了一部分。加上数据集中蓝牌占 90% 以上,模型把更多容量花在了蓝牌特征上。
解决:训练前对绿牌样本做颜色空间增强——在 HSV 空间里随机调整 S 和 V 通道,把绿色的饱和度范围扩大;更直接的方法是把绿牌的样本权重提上去,在 loss 里给 green 类别的分类项乘一个1.5的系数。这两个改动我实测下来,绿牌 mAP 能从 0.88 提到 0.94 左右,蓝牌不掉点。
5.4 坑四:rec_labels 的标签和图片文件名错位,训练时“沉默”地学错
现象:训练 loss 正常下降,但验证时识别准确率比预期低 15 个百分点,且错误杂乱无章,不像字符混淆。
原因:整理识别数据集时用脚本批量重命名,但rec_labels.txt的映射文件更新滞后,图片皖A12345.jpg里存的内容实际是京B88888的裁剪图,标签却写着“皖A12345”。模型把两个不同目标硬凑到一起學,loss 被迫在矛盾标签间折中,但表面上 loss 曲线依然平滑下降,极具迷惑性。
解决:每次生成映射文件后跑一遍检查脚本,把图片文件名和标签内容做一致性校验。做法很简单:对每张图用现成的 OCR 模型做一次推理,把输出的 top-1 字符串和标签做编辑距离计算,距离大于 2 的直接挑出来人工确认。这个校验只用于防错位,不需要全量复核,耗时通常几分钟。
5.5 坑五:测试集来自同一段视频,评估分数虚高
现象:检测 mAP 0.97,识别准确率 98%,一上线到客户现场的摄像头就掉到 85%。翻查数据来源,发现训练集和测试集是从同一条视频里按帧间隔抽出来的,前后帧的车牌、场景高度相似,模型相当于在做“开卷考试”。
原因:同一段视频连续帧之间的相关性太高,相邻 5 帧里的环境光照、车辆位置几乎没变,模型记住的是场景而非车牌本身。这样划分训练集和测试集,相当于测试集泄漏进了训练集,评估结果不能反映真实场景。
解决:划分数据时不能随机抽帧,要以“车辆 ID + 拍摄时段 + 地点”为分组单位,保证同一个车辆实体不会同时出现在训练集与测试集。如果你用的是公开数据集,也要按拍摄源分组,而不是按文件名单个划分。这条是“数据泄漏”里最常见的形态,排查起来最耗时,所以最佳防守是在采集阶段就给每段视频编号,划分时按编号切分。
6. 数据集的成色评估:按省份、颜色与难例分组的三套验证方法
6.1 分组 mAP:检测模型按省份与颜色拆开评估
评估检测模型不要只看一个整体 mAP,那样会掩盖少数民族省份和绿牌样本上的短板。标准做法是把测试集按省份简称 + 颜色两个维度分组,每个组单独算 AP,然后画一张表对比。我常用的脚本逻辑如下:预测结果按标注中的省份标签打组,分别调sklearn.metrics.average_precision_score计算每组的 AP 值。实际操作中还有个更轻量的替代——直接用 YOLO 训练日志里每个类别的per-class AP,那个值就是按类别拆的,但不会按省份拆。所以想按省份拆,就得在标注里预留省份字段,这就是第三章强调难例标志要单独维护的原因。
6.2 字符级混淆矩阵:识别模型的诊断手段
识别模型的整体准确率只有十进制一个数,看不出问题在哪位字符。我一般把识别结果和真值逐位对齐,生成74x74(省份简称 31 + 字母 24 + 数字 10 + 特殊字符若干)的混淆矩阵。矩阵里某个格子的值异常高,比如“皖”被识别成“晚”,说明训练集里这两个字符对应的图像特征区分度不足;如果是“0”和“O”混淆,那是字符形态本身接近,需要检查数据里这两类的字体是否混用了(车牌字符里字母 I 和 O 不允许出现,但数字 0 在绿牌里常见,和字母 O 的混淆是经典问题)。每次训练迭代后输出混淆矩阵,对比哪类错误收敛慢,比盯 loss 曲线直观得多。
6.3 端到端联合评测:检测框与识别结果的联动指标
最后一个验证方法是把检测和识别模型串成完整链路,用端到端指标评判整条数据链路的价值。做法是:对一张实景图先检测,拿到检测框后裁剪,再送识别模型,定义“端到端正确”为检测框与真值的 IoU 大于 0.75 且识别结果完全等于真值标签。这个指标比单独看 mAP 和字符准确率都严格,因为它把两个模型各自的误差叠加起来,更接近线上真实表现。我在整理数据集时有个教训:单模型指标好看,不代表链路可用,检测框偏大或偏小都会让识别模型看到的内容偏离训练分布。所以每次更新数据集,先跑一轮端到端联合评测,如果掉点超过 2 个百分点,就回溯去看是检测框变了还是识别图变了,而不是急着调训练超参。这条习惯帮我挡掉了不少“数据集越加反而效果越差”的返工。最终你要记住:检测数据决定模型看得到什么,识别数据决定模型读得懂什么,你花在拆分这两类数据上的时间,都会在联合评测的数字上找回来。希望这篇能帮你少踩几个我踩过的坑。
本文还有配套的精品资源,点击获取