ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

中国车牌数据集实战:目标检测与OCR识别的完整训练指南

中国车牌数据集实战:目标检测与OCR识别的完整训练指南 简介中国车牌数据集面向机器学习与深度学习研发人员专用于车牌检测和识别任务。资源涵盖不同角度、光照和位置场景划分训练集与测试集并带有位置与文字标注便于开展监督学习与模型泛化验证。压缩包共5633个文件以3368张jpg图像、1371个txt标注和893个xml文件为主整体约168.45MB目录含training_lp、test_lp、train_ocr及wpod_data等子模块分别对应整牌训练、测试、字符级识别训练和附加元数据可支撑从单个字符到整牌定位的完整构建流程。目前已有2243人学习。对智能交通、安防监控等应用该数据集能直接用于训练CNN检测与OCR识别模型借助图像标注、数据增强和独立的测试集可有效评估并提升系统在复杂场景下的鲁棒性与准确率。1. 中国车牌数据集到底解决什么问题检测和识别为什么必须分两条线接到一个停车场出入口识别项目第一件事不是选模型而是先盘数据。所谓「中国车牌数据集」通常不是一份文件而是一批图片外加两套标注一套用于目标检测——把车牌在画面里的位置框出来另一套用于识别——把车牌上的汉字、字母、数字读出来。很多人以为拿到数据集跑个 YOLO 就完事实际上一线项目的调试时间大头全花在数据格式转换、标注清洗和解码逻辑上。这篇就按「检测 → 识别 → 踩坑 → 验证」的顺序把一份中国车牌数据集从拿到手到训练出可用模型的完整路径讲透适合刚接触目标检测和 OCR 识别的新手也适合想快速评估这份数据集值不值得投入的熟手。2. 数据集结构拆解检测标注与识别标注的格式差异2.1 检测标注和识别标注各自长什么样先说一个最容易被忽略的前提检测和识别是两个任务标注形态完全不同。检测标注的目标是「把车牌框出来」。一张 1920x1080 的停车场画面里车牌可能只有 120x40 像素标的是矩形框的左上角和右下角坐标或者中心点加宽高。这份标注的价值在于告诉模型「车牌在哪里」不关心牌面上的字是什么。识别标注的目标是「把车牌上的字读出来」。它的形态有两种一种是整张车牌的裁剪图配一个字符串比如京A12345另一种更细把每个字符单独框出来每个框配一个字符标签。前一种适合 CRNN、PaddleOCR 这类序列识别模型后一种适合单字符分类器。这里有一个实操上的关键判断拿到数据集后先看识别标注是哪一种。如果只有整牌字符串那训练识别模型时要么走序列识别要么自己写脚本把字符坐标抠出来如果带了字符级框那就可以直接用分类器路线精度往往更高但标注成本也更高。多数公开的中国车牌数据集比如 CCPD 那类风格走的是「检测框 整牌字符串 车牌类型」的组合我一般建议先按这个结构理解数据再根据自己项目需要做裁剪。2.2 常见目录组织与三种标注格式的对照一份正规的中国车牌数据集目录一般长这样dataset/ ├── images/ │ ├── train/ │ │ ├── 000001.jpg │ │ └── ... │ └── val/ │ ├── 002001.jpg │ └── ... ├── labels_det/ │ ├── train/ │ │ ├── 000001.xml │ │ └── ... │ └── val/ │ └── ... └── labels_rec/ ├── train/ │ ├── 000001.txt │ └── ... └── val/ └── ...images放原始图片labels_det放检测标注labels_rec放识别标注。文件命名一一对应靠文件名关联。检测标注常见的格式有 VOC XML、COCO JSON、YOLO TXT 三种它们的核心字段差异如下表格式坐标表达是否带类别典型使用场景VOC XML左上角 x1,y1 和右下角 x2,y2带如plate数据标注工具默认输出COCO JSON左上角 x,y 加宽 w,高 h带在 categories 中检测框架通用格式YOLO TXT中心点 xc,yc 加宽 w,高 h全部除以图宽高归一化带以数字索引表示YOLO 系列直接吃这种格式我拿到数据集的第一件事永远是确认检测标注是哪种格式。如果原始给的是 VOC 或 COCO进 YOLO 之前必须先转成 YOLO TXT 格式转换时最容易出问题的就是坐标归一化这一步。2.3 从 VOC 格式转 YOLO 格式转换脚本与参数说明下面是我常用的转换脚本把 VOC XML 转成 YOLO TXT核心逻辑是解析 XML 里的bndbox然后做归一化import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, out_path, class_names): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.iter(object): cls obj.find(name).text if cls not in class_names: continue cls_id class_names.index(cls) box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) xc ((x1 x2) / 2) / img_w yc ((y1 y2) / 2) / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{cls_id} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}) with open(out_path, w) as f: f.write(\n.join(lines) \n) # 用法 class_names [plate] # 按数据集标注实际类别名调整 voc_to_yolo(labels_det/train/000001.xml, labels_yolo/000001.txt, class_names)这段代码的逻辑说明先从 XML 根节点读图片宽高这是归一化的分母必须和实际图片尺寸一致否则框会整体偏移然后遍历所有object只保留类别在class_names里的目标这一步的作用是过滤掉数据集里可能存在的无关标注最后把左上角右下角坐标换算成中心点加宽高全部除以图宽高得到 0 到 1 之间的小数。归一化坐标的好处是模型训练时不依赖输入图片的绝对尺寸不管原图是 800x600 还是 1920x1080统一映射到相对坐标。这里有个参数值得注意class_names的顺序一旦定下来就不能随意改动YOLO 训练时类别索引是写死在配置里的顺序乱了模型输出的类别就全错了。我习惯在转换前先统计一遍数据集里所有出现的类别名确认没有拼写差异比如plate和Plate这种大小写不一致的情况在多人标注的数据集里很常见。3. 用 YOLOv8 训练车牌检测模型从数据目录到推理的最小闭环3.1 为什么默认选 YOLO 系列而不是 Faster R-CNN车牌检测属于典型的中小目标检测画面里车牌往往只占很小一块区域背景复杂度和目标类别数都不高。常见做法是优先考虑 YOLO 系列因为它把候选框生成和分类合并在一个网络里推理速度快部署到 Jetson 这类边缘设备上也能跑实时。Faster R-CNN 在小目标上有精度优势但推理速度慢一个量级停车场道闸这种场景每秒要处理多帧性价比不如 YOLO。如果你手里的数据集框特别不规范比如有大量旋转框那可以关注 MMrotate 这类支持旋转框检测的工具链但多数车牌数据集给的是水平框YOLO 直接能处理不需要上旋转框方案。3.2 数据目录组织与 YAML 配置文件把转换好的 YOLO 格式数据整理成下面这个结构这是 YOLO 系列的标准输入plate_det/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yaml训练集和验证集的图片、标注分开存放data.yaml指向这些路径# data.yaml path: /home/user/plate_det train: images/train val: images/val names: 0: plate这个配置的含义是path指定数据集根目录train和val是相对于根目录的图片路径names是类别名列表索引从 0 开始。YOLO 训练时会自动到同名目录找labels下的 txt 标注文件所以图片目录和标注目录的名字必须配对正确。这里有一个容易忽略的点path路径建议用绝对路径用相对路径时训练进程的工作目录不同会导致找不到数据。我遇到过多次这种问题现象是训练一开始就报image not found排查半天结果是路径写成了./plate_det。3.3 训练命令与必调参数说明数据准备好后训练命令本身很短yolo detect train \ modelyolov8n.pt \ datadata.yaml \ imgsz640 \ epochs100 \ batch16 \ patience20 \ project./runs \ nameplate_v1参数说明modelyolov8n.pt表示用 Nano 版本预训练权重做迁移学习车牌类别数少、目标特征明显n 级足够显存不够时这是最稳的选择imgsz640是训练输入尺寸不要盲目调大车牌检测在这个尺寸下已经能覆盖多数场景调大到 1280 会显著增加显存占用但精度提升有限epochs100给足迭代次数配合patience20做早停即连续 20 轮验证集指标不提升就自动停止batch16在 8GB 显存的卡上是安全值。训练完看两个关键指标mAP50和mAP50-95。车牌检测这类单类目标mAP50能到 0.98 以上才算合格如果只有 0.9 左右先不要急着调模型回去查标注里有没有错框、漏框以及正样本和背景样本是否均衡。3.4 检测完做透视矫正为什么识别前必须有一张正视角图检测模型输出的是车牌在画面中的位置框但这个框里的图像往往是斜的——道闸相机装在侧面或者车辆转弯时被拍到车牌不是正对着镜头。斜着的车牌直接送进识别模型字符特征会失真识别准确率掉得厉害。所以检测之后、识别之前标准流程是做透视矫正。核心是找到车牌的四个角点然后用透视变换把这块区域拉正。角点从哪来如果数据集里标注的是四点不共线的四边形框可以直接用如果只有水平矩形框可以先用边缘检测找车牌轮廓再做最小外接矩形提取四角。后者更常用也是我推荐的做法。下面是用 OpenCV 做透视矫正的代码import cv2 import numpy as np def rectify_plate(crop_img): # 转为灰度并做边缘检测 gray cv2.cvtColor(crop_img, cv2.COLOR_BGR2GRAY) edges cv2.Canny(gray, 50, 150) contours, _ cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 找面积最大的轮廓近似为四边形 contour max(contours, keycv2.contourArea) epsilon 0.02 * cv2.arcLength(contour, True) approx cv2.approxPolyDP(contour, epsilon, True) # 如果已经是四边形取四个点做透视变换 if len(approx) 4: pts approx.reshape(4, 2).astype(np.float32) dst np.array([[0, 0], [320, 0], [320, 80], [0, 80]], dtypenp.float32) M cv2.getPerspectiveTransform(pts, dst) rectified cv2.warpPerspective(crop_img, M, (320, 80)) return rectified return crop_img # 找不到四边形时退回原图这段代码的关键逻辑在最后一步cv2.getPerspectiveTransform把检测框内检测到的车牌四角映射到一个固定 320x80 的输出区域。输出尺寸不必完全等于原始车牌比例但要保持汉字、数字的宽高比否则识别模型会看到被拉伸的字符。参数epsilon 0.02 * arcLength控制多边形近似的精度如果车牌边缘有毛刺导致近似结果不是四边形可以把这个值调大比如 0.05让轮廓更平滑。这一步在整条链路里最容易翻车但也是收益最高的一步矫正做好识别准确率能提升好几个百分点。4. 识别侧数据管线把车牌图变成字符序列4.1 识别模型的两种路线单字符分类还是序列识别检测框拉正之后进入识别环节。中国队牌的识别有两个分支方案选型时主要看数据标注形态。第一种是单字符分类路线先把车牌图切成 7 个或 8 个字符框每个框单独训练一个分类器。优点是模型简单、速度快、单字准确率高缺点是依赖字符切分质量切分一旦错位后面全错。这种路线要求数据集标注了每个字符的坐标如果你的数据集只有整牌字符串就得自己写切分算法或者借助字符间距先验——蓝牌标准是 7 个字符绿牌是 8 个可以用投影法按列切分。第二种是序列识别路线也就是常说的 OCR 方案用 CRNN 或 PaddleOCR 这类模型把整张车牌图输入模型直接输出字符串。优点是省去切分环节对字符间距不均、部分遮挡的鲁棒性更好缺点是需要专门训练骨架网络数据需求量更大。现在实战里主流做法是 PaddleOCR 中国车牌数据集微调因为 PaddleOCR 的识别模型对中文和数字的基础特征已经学得很充分用几百到几千张车牌图就能微调出可用的车牌识别模型。我个人建议如果你项目里车牌图大多是标准蓝牌且要求单字符极高准确率走第一种如果场景复杂、有绿牌、有遮挡、有倾斜残留走 PaddleOCR 微调更省事。4.2 字符切分与定长编码的落地脚本不管走哪条路线先得把数据整理成模型能吃的格式。下面是一个把整牌字符串和车牌图整理成单字符训练数据的脚本适用于字符级标注的数据集import cv2 import os def split_chars_by_projection(img_path, label_str, char_save_dir): 按垂直投影切分车牌字符 img cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) img cv2.resize(img, (440, 120)) # binarize车牌背景为深色字符为浅色做反色二值化 _, binary cv2.threshold(img, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) binary cv2.bitwise_not(binary) # 垂直投影统计每列的白点数量 col_sum binary.sum(axis0) // 255 cols col_sum 2 # 阈值过滤噪声列 # 根据连续非零区间切分候选框 chars [] start None for i, v in enumerate(cols): if v and start is None: start i elif not v and start is not None: chars.append((start, i)) start None # 按先验过滤车牌字符宽度应集中在一个范围 widths [e - s for s, e in chars] med_w sorted(widths)[len(widths) // 2] chars [(s, e) for s, e in chars if med_w * 0.5 e - s med_w * 1.5] # 按字符顺序裁剪保存 for idx, (s, e) in enumerate(chars): if idx len(label_str): break char_img img[:, s:e] cv2.imwrite(os.path.join(char_save_dir, f{label_str[idx]}_{idx:02d}.jpg), char_img)这段脚本的核心思路是利用车牌字符的垂直投影做切分把二值化后的车牌图按列统计白色像素数量字符所在列会形成明显的峰值区间字符之间的间隔列数值很低由此切出每个字符的左右边界。参数col_sum 2是噪声过滤阈值取值偏大会把细窄的字符切碎偏小会把边框噪声算进去med_w * 0.5到med_w * 1.5是宽度过滤把过宽的车牌边框残留和过窄的噪声剔除。汉字和数字的宽度差异很大这个中位数滤波是有风险的如果切分结果里汉字和数字宽度差太多建议改成先按宽度聚类再切。注意脚本里label_str[idx]的对应逻辑切分顺序是左到右标签字符串也是左到右必须保证索引对齐否则训练出来的模型等于在学错误答案。实际操作中我会把切分结果和标签打印出来人工抽查一批这是最有效的排错方式。4.3 用序列识别模型微调参数与效果观察如果走 PaddleOCR 微调路线常见做法是先把官方仓库克隆到本地然后准备三样东西训练图片列表、标注文件、配置文件。标注文件每一行是图片路径加\t加车牌字符串比如plate_crops/train/000001.jpg 京A12345 plate_crops/train/000002.jpg 苏B88888配置文件里关注几个关键参数Train.dataset.data_dir指向图片目录Train.dataset.label_file_list指向标注文件Train.loader.batch_size_per_card根据显存调Global.epoch_num从 50 起步。微调用 pretrained 模型启动会比从头训练收敛快很多通常 20 到 30 轮验证集字符准确率就能稳定下来。效果观察有个细节不要只看整牌准确率要看字符级准确率。整牌准确率要求 7 个全对才算对字符级准确率是每个字符独立算后者更能暴露模型在哪些字符上犯错——比如「0」和「O」混淆、「8」和「B」混淆。我见过不少模型整牌准确率只有 85%但字符级准确率已经到 97% 了这种情况说明模型主体能力是够的卡在少数难分字符上解决办法是针对性补充这些字符的训练样本而不是盲目加全体数据量。5. 避坑训练中国车牌数据集的五个常见问题5.1 样本失衡省份简称和字母频次悬殊现象模型训练完识别「京」「苏」「粤」这些常见省份简称很准但遇到「青」「藏」「宁」这类低频简称十次能错五次。更隐蔽的是字母分布失衡某些省份的号牌组合规则导致字母出现频率差异极大。原因中国车牌数据集天然存在分布偏差。各省汽车保有量不同采集到的样本数量自然悬殊号牌组合规则又让部分字母出现概率极低。数据集的采集源如果是某个城市的停车场或道路卡口那本地省份样本会占绝对主导。解决训练前先统计标签里的字符频次把频次最低的 20% 字符拎出来看训练样本量是否够用。不够就做针对性扩样常用手段是对这类样本做轻度仿射变换、亮度扰动和噪声注入扩样倍数控制在 3 到 5 倍之间不要扩太多否则模型会对增强后的特征过拟合。另外验证集里要保证低频字符有样本否则验证结果会虚高上真实场景就现原形。5.2 绿牌漏检双层结构和 8 位长度让检测和识别同时翻车现象蓝牌检得很好绿牌一出现就丢框或者框出来但识别字数不对。绿牌的新能源车牌是 8 位比蓝牌多一位最前面的省份简称和最后一位的省份简称在部分城市会上下错位排列形成双层结构。原因检测模型在训练时蓝牌样本占绝对多数模型学到的是蓝牌的宽高比先验大约 440x140绿牌的长宽比明显不同而且字符排列不是单行模型内部的特征响应自然偏弱。识别端的定长逻辑更直接——按 7 位设计的模型看到 8 位字符就会错位或截断。解决数据准备阶段单独把绿牌样本分成一组在训练配置里给绿牌更高的类别权重或者干脆把绿牌当作第二个类别plate_green参与检测让模型分开学两种几何先验。识别端要么直接用支持变长的序列识别模型要么在切分脚本里先判断车牌颜色和字符总数再决定走 7 位还是 8 位解码分支。我的处理习惯是检测端分两类识别端统一用序列识别绿牌的问题基本迎刃而解。5.3 类别表里的空格和特殊字符OCR 解码错位的隐蔽来源现象训练和验证指标都很漂亮一到推理就发现输出字符串里混入空字符或乱码有时候字符数量对但位置对不上。原因数据集标注文件里存在不可见字符比如制表符、行尾回车、全角空格最典型的是标注里把地方牌照的「警」「使」「领」等特殊字符边缘切割不干净标签文本里混入了空白。解码时模型按固定词表输出空格被当作一个合法类别于是串到了结果中间。这类问题在初次转换标注时会批量出现但验证集里占比低不容易引起注意。解决读标注文件时用strip()清理每一行再检查一遍标签字符串的repr()输出肉眼确认没有\t、\r、\u3000。更稳妥的做法是在构建识别词表时只加入明确见过的字符列表词表以外的输出全部映射成UNK宁可报错也不静默错位。这个排查动作放在数据加载脚本里做成断言一旦发现非法字符直接抛异常别等到训练结束才发现。5.4 数据划分泄漏同一辆车同时出现在训练集和验证集现象训练时验证集指标一路飙到 0.99甚至超过训练集部署到现场识别率却掉到 0.7。回查发现数据集里根本没问题问题出在划分策略上。原因车牌数据集经常是连续抓拍采集的同一辆车在不同角度、不同光照下拍了十几张。如果按文件随机划分同一辆车的多个样本会同时落到训练集和验证集。模型在训练时见过这辆车的特征验证时再看到它相当于开卷考试得分虚高换一辆新车就暴露真实水平。解决划分数据前先按车牌字符串对样本做分组保证同一辆车的所有图片全部进训练集或全部进验证集绝不能跨集合。代码上可以先把文件名按车牌字符串聚合再按组为单位做随机划分。这个操作应该排在所有预处理步骤的第一位因为一旦划分完成后面再做任何处理都会消耗额外的时间成本。我吃过这个亏之后每次拿到新数据集都会先问一句这批数据是按车牌去重过的吗5.5 检测框和识别裁剪尺寸不匹配现象检测框输出的裁剪图直接送识别模型绿牌和蓝牌混在一起训练识别模型总是把边框底色学进去字符区域被压缩字符准确率上不去。原因检测框是目标检测模型的输出框的尺寸和长宽比由模型倾向决定不是按识别模型期望的输入设计。蓝牌 440x140 和绿牌 440x140 的外观轮廓不同直接统一 resize 到识别输入尺寸会让字符变形尤其是绿牌的双层字符结构变形更严重。解决做一个中间适配层检测框先按长宽比判断车牌颜色类型蓝色走 480x160 的 resize 路径绿色走单独的分层矫正路径然后再统一归一化输入识别模型。不要相信检测框送出来的图能够直接消费中间加一个按颜色和长宽比分流的适配处理识别准确率能稳定提升。这一步也把检测和识别的耦合度降下来检测模型换版本了识别侧的输入逻辑不用跟着改。6. 验证与进阶字符级准确率、按省份分组的评估和一个实战习惯6.1 检测验证mAP 之外要关注小目标单独指标检测模型的常规评估看mAP50但中国车牌场景里远处车牌在画面中占比很小模型可能整体 mAP 很高小目标区间单独算却不及格。建议在验证脚本里按 GT 框面积分桶统计召回率重点看面积小于整图 2% 的档位。YOLO 训练日志里会输出按尺寸分桶的P/R如果你用的版本没分桶输出自己写脚本也不难。这个档位不达标停车场远距离识别就等着翻车。6.2 识别验证用字符级准确率定位短板整牌准确率是面向业务汇报的指标字符级准确率是面向调试的指标。建议每次训练完都输出一张字符混淆矩阵把「京」识别成「津」、「0」识别成「O」这类高频混淆找出来。如果混淆集中在形近字符上说明特征提取层已经收敛需要扩充这类负样本如果混淆分布很散说明训练不充分或数据量不足。字符级评估还有另一个价值给客户汇报时能用「字符准确率 99.2%」这种口径而不是被整牌准确率 89% 卡住商务流程。6.3 一个值得长期坚持的习惯把误识别样本回流成数据集我现在的固定流程是每个车牌识别项目上线后把线上误识别样本按周归档每周挑出置信度低或人工确认识别错误的图人工修正标签后回流到训练集。这个习惯的价值在于线上样本分布永远比静态数据集贴近真实场景——某个商场的地面反光、某个闸机的逆光角度都是公开数据集覆盖不到的。归档时按省份和车牌类型建目录方便日后分析错误集中点。这个做法做了半年后你会发现真正难倒模型的不是模型结构而是数据闭环。数据集是起点不是终点。希望这份从数据整理到验证体系的笔记能帮到你让你的车牌检测和识别项目少走几趟弯路。本文还有配套的精品资源点击获取
返回列表