news 2026/10/30 2:48:49

车牌识别数据集实战:从数据体检到YOLO与CRNN训练避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
车牌识别数据集实战:从数据体检到YOLO与CRNN训练避坑指南

简介:这份训练数据集面向计算机视觉开发者与深度学习学习者,聚焦车牌检测与识别任务,可用于交通监控、智能停车、自动驾驶等场景的算法开发与模型优化。包内共1296个文件,以650张jpg车牌图片和646个xml标注文件为主,图片与标注一一对应,覆盖蓝牌、黄牌、新能源绿牌及少量白牌、黑牌,兼顾不同颜色与字体差异带来的识别难点;压缩包约228.08MB,已整理打包,省去自行采集与标注的成本。目前已有1862人学习下载。读者可直接用于YOLO、SSD等检测模型与CRNN、LSTM等识别模型的训练,并按训练集、验证集、测试集划分开展实验,结合数据增强与正则化手段调优,快速搭建从预处理到字符识别的完整流程。

1. 车牌检测识别数据集:从“能跑通”到“敢上线”之间差了什么

做车牌识别项目,最容易被低估的环节不是模型结构,而是数据。很多人拿到一份车牌检测识别训练数据集,第一反应是直接丢进 YOLO 跑一遍,mAP 看着还行就以为成了,结果一上车载视频或者路口抓拍就翻车——蓝牌白天识别率 98%,一到新能源绿牌、夜间逆光、大角度倾斜就掉到六成以下。这份数据集的价值不在于“有图有标注”,而在于它把车牌检测和车牌字符识别两个任务的数据组织在一起,覆盖蓝牌、黄牌、新能源绿牌等常见类型,适合用来做检测框回归加字符序列识别的联合验证。它适合两类人:一是刚接手车牌识别 Demo、需要一份能直接训练的数据起步的开发者;二是已经有模型、想补绿牌和复杂光照样本做增量迭代的熟手。下面按“数据长什么样、怎么转格式、怎么训、坑在哪”的顺序拆开讲。

2. 先看清数据组织:检测框与字符标注是两套逻辑

2.1 检测任务和识别任务对标注的要求完全不同

车牌检测识别训练数据集这类资源,通常同时包含两类标注信息:一类是车牌在整图中的位置框,用于训练检测器;另一类是车牌区域内的字符内容,用于训练识别器。这两套标注的坐标系和粒度不一样,混在一起处理是新手最常见的翻车点。检测标注关心的是x_min, y_min, x_max, y_max这样的绝对像素坐标或归一化坐标,识别标注关心的是裁剪后的车牌小图上“京A12345”这样的字符序列,以及字符级或序列级的对齐信息。

我一般会先把数据集按任务拆成两个视图:检测视图保留原图和框,识别视图保留裁剪图和文本标签。不要试图用一套标注文件同时喂两个模型,除非你用的是端到端的多任务网络,那也需要在数据加载层做字段映射。判断一份数据集是否“干净”,先看它的目录结构是否把images、labels_det、labels_rec分开,再看标注文件里有没有混入空框、越界框、字符数与图片不匹配的记录。常见做法是写一个统计脚本,先跑一遍分布,再决定怎么划分训练集和验证集。

2.2 用脚本做一次数据体检,别急着开训

拿到数据后第一件事不是配置训练参数,而是体检。下面这段脚本做三件事:统计图片数量与标注数量是否对齐、检查框是否越界、统计车牌类型分布(蓝牌、黄牌、绿牌)。假设检测标注是 YOLO 格式的 txt,每行class cx cy w h为归一化值。

import os from collections import Counter from pathlib import Path IMG_DIR = Path("dataset/images") LBL_DIR = Path("dataset/labels_det") CLS_NAME = {0: "blue", 1: "yellow", 2: "green"} # 按数据集实际类别顺序改 img_files = {p.stem for p in IMG_DIR.glob("*.jpg")} lbl_files = {p.stem for p in LBL_DIR.glob("*.txt")} print("图片数:", len(img_files), "标注数:", len(lbl_files)) print("有图无标:", len(img_files - lbl_files)) print("有标无图:", len(lbl_files - img_files)) cls_counter = Counter() bad_boxes = 0 for txt in LBL_DIR.glob("*.txt"): for line in txt.read_text().strip().splitlines(): parts = line.split() if len(parts) != 5: bad_boxes += 1 continue c, cx, cy, w, h = int(parts[0]), *map(float, parts[1:]) cls_counter[CLS_NAME.get(c, c)] += 1 # 归一化框必须在 (0,1) 内,且宽高不能为 0 if not (0 < cx < 1 and 0 < cy < 1 and 0 < w <= 1 and 0 < h <= 1): bad_boxes += 1 print("类别分布:", dict(cls_counter)) print("异常框数量:", bad_boxes)

这段脚本的逻辑很直白:集合差集找图文不匹配,逐行解析找格式错误和越界框,Counter 统计类别。参数上唯一要改的是CLS_NAME的映射,必须和数据集data.yaml里的names顺序一致,否则统计出来的“绿牌数量”其实是别的类。跑完如果发现绿牌样本占比低于 10%,而你的实际场景绿牌占比很高,那就别指望直接训出来的模型在绿牌上表现好,得先做重采样或者针对性增广。这一步花十分钟,能省掉后面几小时的无效训练。

3. 转成 YOLO 与 CRNN 可用的格式:两条流水线各走各的

3.1 检测标注转 YOLO:坐标归一化和类别映射

如果数据集原始标注是 VOC 的 XML 或者 COCO 的 JSON,需要转成 YOLO 的 txt。转换的核心是绝对坐标转归一化中心点坐标,公式是cx=(xmin+xmax)/2/W、w=(xmax-xmin)/W,y 方向同理。下面以 VOC XML 为例,转换时顺手做类别名到索引的映射。

import xml.etree.ElementTree as ET from pathlib import Path W, H = 1920, 1080 # 若每张图尺寸不同,需用 PIL 逐图读取真实宽高 CLS_MAP = {"blue": 0, "yellow": 1, "green": 2} def voc_to_yolo(xml_path, out_dir): tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.findall("object"): name = obj.find("name").text.strip() if name not in CLS_MAP: continue bnd = obj.find("bndbox") xmin = float(bnd.find("xmin").text) ymin = float(bnd.find("ymin").text) xmax = float(bnd.find("xmax").text) ymax = float(bnd.find("ymax").text) cx = (xmin + xmax) / 2 / W cy = (ymin + ymax) / 2 / H w = (xmax - xmin) / W h = (ymax - ymin) / H lines.append(f"{CLS_MAP[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") out = Path(out_dir) / (Path(xml_path).stem + ".txt") out.write_text("\n".join(lines)) for xml in Path("dataset/annotations").glob("*.xml"): voc_to_yolo(xml, "dataset/labels_det")

这里有个血泪经验:W, H千万别写死成固定值。很多数据集里图片尺寸不统一,写死会导致框整体偏移,训练 loss 看着在降但检测框就是不准。正确做法是用PIL.Image.open(img_path).size逐图取宽高。另外CLS_MAP里没出现的类别直接跳过还是报错,取决于你的业务——如果数据集里有“双层黄牌”“使馆牌”这类长尾类,建议先保留并单独统计,别一上来就丢,丢了后面想补还得重新对齐。

3.2 识别标注整理成 CRNN 的“图片路径 + 文本”清单

字符识别这条线,常见做法是 CRNN 或 CTC 系网络,输入是裁剪后的车牌小图,标签是字符序列。需要生成一个清单文件,每行图片相对路径\t文本标签。如果数据集给的是整图加框加文本,得先按框裁剪出小图再生成清单。

import cv2 from pathlib import Path def crop_and_build_list(img_dir, lbl_dir, out_img_dir, list_path): Path(out_img_dir).mkdir(parents=True, exist_ok=True) records = [] for txt in Path(lbl_dir).glob("*.txt"): img_path = Path(img_dir) / (txt.stem + ".jpg") img = cv2.imread(str(img_path)) if img is None: continue h, w = img.shape[:2] for i, line in enumerate(txt.read_text().strip().splitlines()): parts = line.split() if len(parts) < 6: # 假设格式: cls cx cy w h text continue cx, cy, bw, bh = map(float, parts[1:5]) x1 = int((cx - bw / 2) * w); y1 = int((cy - bh / 2) * h) x2 = int((cx + bw / 2) * w); y2 = int((cy + bh / 2) * h) crop = img[max(0, y1):min(h, y2), max(0, x1):min(w, x2)] if crop.size == 0: continue out_name = f"{txt.stem}_{i}.jpg" cv2.imwrite(str(Path(out_img_dir) / out_name), crop) records.append(f"{out_name}\t{parts[5]}") Path(list_path).write_text("\n".join(records)) crop_and_build_list("dataset/images", "dataset/labels_rec", "dataset/rec_images", "dataset/rec_list.txt")

裁剪时max(0, ...)和min(...)的边界保护不能省,标注框偶尔会超出图片边缘,不裁剪直接切片会得到空数组,写文件时静默失败,最后清单里少了一堆样本还查不出原因。文本标签里如果含空格或特殊分隔符,清单的分隔符要换成制表符并确保标签本身不含制表符。生成完清单后,统计一下字符集大小和每个字符的出现频次,字符集里出现频次个位数的生僻字符,要么合并要么剔除,否则 CTC 训练时这些类基本学不动。

4. 训练配置与验证:检测和识别分开调,别一锅炖

4.1 检测侧的关键参数与绿牌召回

检测模型用 YOLO 系是性价比最高的选择。配置文件里真正影响车牌场景的几个参数:imgsz建议不低于 640,车牌在整图中占比小,分辨率太低框会糊;cls损失权重可以适当调高,因为类别不均衡(蓝牌多、绿牌少);数据增广里mosaic对车牌有帮助,但mixup要慎用,两张图叠在一起车牌字符会互相污染,识别侧会学到脏标签。下面是一个训练命令的示例,参数按实际路径改。

yolo detect train \ data=plate_det.yaml \ model=yolov8n.pt \ imgsz=640 \ epochs=100 \ batch=16 \ cls=1.2 \ mosaic=1.0 \ mixup=0.0 \ project=runs_plate \ name=det_v1

cls=1.2是提高分类损失权重,缓解类别不均衡;mixup=0.0是主动关掉,理由上面说了。训练完别只看总体 mAP,要按类别看召回率,尤其是绿牌那一类的recall。如果绿牌召回明显低于蓝牌,两个方向:一是对绿牌样本做过采样,在data.yaml里用重复路径或者写个采样权重文件;二是针对绿牌做颜色增广,调整 HSV 的色调范围,让模型对绿色区域的敏感度上来。验证时用混淆矩阵看蓝牌和绿牌有没有互相误判,这两种牌在低分辨率下颜色特征接近,误判很常见。

4.2 识别侧的字符集与序列长度

识别模型训练前,先把字符集固定下来。中文车牌字符集包括省份汉字、字母、数字和少量特殊字符,新能源绿牌是 8 位,比蓝牌多一位。序列长度max_len要按最长车牌设,设短了长车牌直接被截断,设长了 CTC 解码时空白符占比高、收敛慢。常见做法是统计清单里标签的最大长度,向上取整再加 1 作为max_len。训练时batch可以比检测侧小,因为裁剪图尺寸小;学习率用 1e-3 起步,CTC loss 前期下降慢是正常的,别急着调大学习率,容易震荡。

验证识别效果不能只看整体准确率,要拆成“整牌完全正确率”和“单字符准确率”两个指标。整牌完全正确率才是业务真正关心的,单字符准确率高但整牌正确率低,说明错误集中在某几个易混字符上,比如0和O、8和B。把验证集里识别错的样本导出来看,按错误字符对做统计,比盲目加数据有效得多。

5. 避坑与排查:这几处翻车我见过太多次

5.1 现象:训练 loss 正常下降,验证集检测框整体偏移

原因:坐标转换时图片宽高写死,或者训练时imgsz与标注归一化基准不一致。解决:转换脚本里逐图读取真实尺寸,训练配置的imgsz只影响缩放,不影响归一化基准,但数据加载时的 letterbox 填充要确认没把框算错。排查方法是取一张验证图,把预测框和标注框画在同一张图上目视对比,偏移是系统性的还是随机的,一眼能看出来。

5.2 现象:绿牌识别率远低于蓝牌,混淆矩阵里绿牌被大量判成蓝牌

原因:绿牌样本占比低,且绿牌底色在低光照下偏暗,颜色特征和蓝牌接近。解决:先统计绿牌占比,低于 15% 就做过采样;再做 HSV 色调增广,把绿色区间的样本做亮度扰动;最后检查标注里绿牌类别索引有没有和蓝牌写反,这种低级错误在手工整理的数据集里并不少见。

5.3 现象:识别模型训练时 loss 不降或者降到某个值卡住

原因:字符集里混入了标签中不存在的字符,或者清单文件里有空标签、标签含分隔符。解决:训练前跑一遍字符集与标签的交叉校验,列出“字符集有但标签无”和“标签有但字符集无”的字符,前者删掉,后者补进字符集并重新生成索引。空标签行直接过滤,别指望模型学会输出空序列。

5.4 现象:检测框裁出来的车牌小图边缘被切掉,字符识别错位

原因:检测框本身贴得紧,裁剪时没有留边距,字符笔画被切。解决:裁剪时按框宽高的 5% 到 10% 向外扩一圈,再裁。扩边后如果超出图片边界,用边缘像素复制填充,别用黑边,黑边会让识别模型学到“黑边等于车牌边界”的伪特征。

5.5 现象:验证集指标很好,实际视频流上帧间结果跳变严重

原因:单帧检测没有做时序平滑,视频里相邻帧的框位置和识别结果独立输出。解决:在推理侧加简单的跟踪或投票,连续 N 帧里同一位置的车牌识别结果取众数,检测框做滑动平均。这不是数据集的问题,但拿这份数据训出来的模型直接上视频流,不做后处理一定会遇到。

6. 增量迭代的一个实用技巧:用错例反哺数据集

数据集不是训完就扔的一次性资源。我习惯在验证阶段把识别错的样本按“检测漏检”“框偏移”“字符错认”三类归档,每类攒够一定数量后,回到数据集里找同类场景补样本。具体做法是给验证脚本加一个错例导出逻辑:预测框与标注框 IoU 低于阈值记为漏检或偏移,识别文本与标签编辑距离大于 0 记为字符错认,导出时保留原图、标注和预测结果三份信息。

def iou(box_a, box_b): # box: [x1, y1, x2, y2] xa = max(box_a[0], box_b[0]); ya = max(box_a[1], box_b[1]) xb = min(box_a[2], box_b[2]); yb = min(box_a[3], box_b[3]) inter = max(0, xb - xa) * max(0, yb - ya) area_a = (box_a[2]-box_a[0]) * (box_a[3]-box_a[1]) area_b = (box_b[2]-box_b[0]) * (box_b[3]-box_b[1]) return inter / (area_a + area_b - inter + 1e-6) def collect_bad_cases(preds, gts, iou_thr=0.5): bad = [] for p, g in zip(preds, gts): if iou(p["box"], g["box"]) < iou_thr: bad.append(("det", p, g)) elif p["text"] != g["text"]: bad.append(("rec", p, g)) return bad

这段逻辑的关键在阈值iou_thr,车牌场景建议设 0.5 而不是通用的 0.45,因为车牌框本身窄长,IoU 对轻微偏移很敏感,阈值太低会把正常框也判成错例,导出量爆炸反而没法看。导出的错例按场景打标签(夜间、逆光、倾斜、遮挡),攒到几十条就做一轮增量训练,学习率调小到原来的十分之一,只训几个 epoch。从那以后我每次拿到新的车牌数据,都强制先跑一遍体检脚本和错例归档,再决定是补数据还是调模型——数据的问题用模型补,往往是白费力气。希望这套流程能帮你在自己的项目里少走几段弯路。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/30 2:48:36

C#自绘图表源码接入实践:GDI+坐标映射、交互与高DPI导出

简介&#xff1a;EeceChart.zip 是一份面向 C# 开发者与数据分析可视化学习者的实战项目包&#xff0c;围绕 Excel 数据读取、曲线绘制和交互式图形操作展开&#xff0c;可用于桌面端小型分析工具的原型搭建与技能练习。压缩包共 216 个文件&#xff0c;约 46.67MB&#xff0c;…

作者头像 李华
网站建设 2026/10/30 2:48:36

DRV8711步进电机驱动工程实战:从AD工程到寄存器配置与避坑指南

简介&#xff1a;这份资源面向从事步进电机驱动开发的嵌入式工程师与学习者&#xff0c;围绕TI的DRV8711驱动器&#xff0c;提供从硬件到软件的完整工程参考&#xff0c;帮助解决双极性步进电机的精准控制与驱动电路设计问题。压缩包共182个文件&#xff0c;约4.98MB&#xff0…

作者头像 李华
网站建设 2026/10/30 2:47:45

R语言期末设计报告通关指南:从数据清洗到建模的完整流程与评分要点

简介&#xff1a;这份R语言期末设计报告面向正在学习R语言、需要完成课程设计或想系统梳理数据分析流程的高校学生与自学者&#xff0c;围绕数据获取、清洗、统计建模到可视化呈现的完整链路展开&#xff0c;可作为课程作业参考与实战模板。压缩包共24个文件&#xff0c;约5.29…

作者头像 李华
网站建设 2026/10/30 2:46:14

好好学习C语言

学习之路还很长&#xff0c;一次失误并不能代表什么&#xff0c;我能成功&#xff0c;我会成功&#xff0c;我必成功&#xff01;

作者头像 李华
网站建设 2026/10/30 2:45:08

重视资料保密 个人量化软件推荐与比较

重视研究资料管理的个人&#xff0c;可以比较掘金量化、牛股王股票和米筐研究工具。愿意本地开发并管理代码&#xff0c;可了解掘金&#xff1b;希望通过现有设置研究股票&#xff0c;可查看牛股王股票&#xff1b;愿意理解程序与数据许可&#xff0c;可考察米筐。本文比较表达…

作者头像 李华
网站建设 2026/10/30 2:43:39

n8n学习(一)——Integrate AI Models in n8n:邮件自动化与模型接入

课程记录&#xff1a;用 n8n 把邮件自动分类并送出去&#xff0c;以及在没有现成节点时&#xff0c;用 HTTP 接入 Claude、OpenAI 和 Gemini。邮件工作流一条完整流程是&#xff1a;Gmail Trigger 监听收件箱。可以只处理未读邮件。AI 读主题和正文&#xff0c;分成发票、销售线…

作者头像 李华