欧盟车牌数据集实战:VOC转YOLO与检测训练全流程解析
2026/9/23 15:18:05 网站建设 项目流程

简介:面向计算机视觉目标检测开发的一套欧洲车牌图像数据集,全部图像由VOTT标注工具手工标注并转换为VOC格式,标注内容包含车牌区域边界框,可直接用于有监督学习。无论是入门车牌检测算法,还是验证夜间、日间光照下的识别效果,这套数据都能提供贴近实际道路场景的训练样本。压缩包共1070个文件,主体为534张JPG原始图像与534个对应的XML标签文件,另有1个说明文本和1个数据清单,总大小约428.21MB。图像覆盖白天、夜晚等多种行车环境,目录结构清晰,XML文件与图像一一对应,每个文件记录车牌类别与定位坐标,可无缝接入YOLO、Faster R-CNN等主流检测框架。目前该资源已有185人学习浏览,可省去自行采集和标注的繁琐流程,适合快速启动车牌识别相关实验或项目。

1. 五百多张欧盟车牌图,值不值得下:先说结论再拆内容

拿到这份「534张欧盟车牌图像数据集」的时候,我第一反应是看标签格式,因为做过检测的人都知道,数据集的质量七成由标注决定,图片本身反而是次要的。这份数据是带VOC格式XML标签的,用VOTT工具标注,图片来源是行车记录仪视频里抽取的帧,白天和夜间场景都有,车牌覆盖多个欧盟国家。先说结论:如果你是做车牌检测或者OCR方向,手里缺一个带标准标签的起步集,这东西能用;如果你指望它覆盖所有欧盟国家的车牌样式,那不够,534张图只够做模型验证和流程打通,距离生产级还差一个数量级。我把整个包拆开看了,从文件构成到标签结构,再到怎么转成自己训练要的格式,完整走了一遍,下面把过程和坑都写出来。

2. 拆包看数据:VOC标签结构、文件组织与图像分布

2.1 文件组织:看似零散,实际是按采集场景分组的

整个压缩包解压之后,结构并不复杂。所有图片和对应的XML标签文件都在同一个目录下,没有按train/val/test划分子文件夹,这意味着你需要自己做数据划分。文件名带着场景信息,比如nightride_type3_001.mp4#t=352.jpg这种命名方式,其实就是从某个mp4视频的第352秒抽出来的一帧。

文件名里的关键信息有两段:第一段是视频来源和类型,nightride_type3表示夜间行驶、第三种拍摄机位或车辆类型,dayride_type1对应白天场景;第二段是时间戳,#t=352就是ffmpeg抽帧时用的时间点。这个命名习惯对后续扩展数据集很有用——你可以用同样的方式从自己的行车视频里补数据,时间戳直接对应原视频位置,回头要查原始帧很方便。

我数了一下实际内容,图片和XML是严格一一对应的,没有出现有图无标签或者有标签无图的情况。这点值得肯定,很多网上流传的数据集经常缺这少那,一份能直接对齐的标签文件能省掉不少清洗时间。

2.2 VOC标签到底长什么样:逐字段拆解XML

VOC格式的核心是每个图片对应一个同名XML文件,里面记录图片路径、尺寸和所有目标的边界框。这份数据集的XML结构遵循Pascal VOC标准,打开任意一个文件,关键字段如下:

<annotation> <folder>EU_License_Plate</folder> <filename>dayride_type1_001.mp4#t=1199.jpg</filename> <path>E:/dataset/dayride_type1_001.mp4#t=1199.jpg</path> <source> <database>Unknown</database> </source> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <segmented>0</segmented> <object> <name>license_plate</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>1245</xmin> <ymin>512</ymin> <xmax>1430</xmax> <ymax>582</ymax> </bndbox> </object> </annotation>

这份XML告诉我们的信息比表面看起来多。<size>里标注的是1920x1080,说明原始帧是1080p的,但注意这个尺寸不一定是所有图片统一——夜间视频和白天视频可能来自不同设备,动手前最好批量检查一遍尺寸,避免出现标注坐标和实际图像尺寸不匹配的问题。

<object>里的<name>统一是license_plate,只有一个类别,没有区分国家、没有区分颜色、也没有区分车牌类型。如果你打算做多分类任务,比如识别德国车牌和法国车牌的样式差异,这份数据的标签不够用,需要重新标注或者额外补充。

边界框坐标是整数像素值,xmin=1245, ymin=512, xmax=1430, ymax=582,框高只有70像素。这个数值直接暴露了一个问题:图片是1080p的,但车牌在画面里占比不大,尤其是夜间场景,车牌的像素区域可能更小。这对检测模型来说是个挑战,后面训练时会专门讲到怎么处理。

2.3 标签质量抽查:VOTT标注的常见特征

这份数据集是用VOTT(Visual Object Tagging Tool)标注的,VOTT标记的数据有几个典型特征,在抽查时都能对上。一是标签框严格贴合车牌边缘,因为VOTT里手动画框时会有自动吸附的辅助线,所以框的边界通常不会像程序化标注那样留大量空白。二是夜间图片的框会略微偏大,因为夜间车牌周围的光晕会影响人眼判断边缘,这个属于正常现象。

我随机抽查了20张图片的XML,没有发现坐标越界(比如xmin为负数或者xmax超过1920)、没有发现标签名拼写错误、也没有发现空对象节点。整体标注质量在中上水平,作为训练集是够用的。

3. 格式转换实战:把VOC转成YOLO和TFRecord,含脚本与参数说明

3.1 为什么必须转换:检测框架的数据格式差异

VOC格式是"一张图配一个XML",Human可读性好,但训练框架普遍不直接吃这个。YOLO系列用的是txt格式,每行一个目标,内容是class_id x_center y_center width height,坐标全部归一化到0到1之间;TensorFlow的Object Detection API要TFRecord,这是一种二进制序列化格式;MMDetection则可以直接吃VOC,但需要额外的数据集配置文件。

我一般会先写一个通用转换脚本,把VOC转成YOLO格式,因为这个转换最常用,而且一旦转成YOLO格式,再转COCO或者TFRecord就方便了。转换的核心公式很简单:归一化后的中心点坐标等于边界框中心坐标除以图片宽高,归一化后的宽高等于边界框宽高除以图片宽高。

import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_file, class_names, img_width, img_height): tree = ET.parse(xml_file) root = tree.getroot() yolo_lines = [] for obj in root.findall('object'): name = obj.find('name').text if name not in class_names: continue # 跳过不在类别列表里的目标 class_id = class_names.index(name) bbox = obj.find('bndbox') xmin = float(bbox.find('xmin').text) ymin = float(bbox.find('ymin').text) xmax = float(bbox.find('xmax').text) ymax = float(bbox.find('ymax').text) # 归一化计算 x_center = (xmin + xmax) / 2.0 / img_width y_center = (ymin + ymax) / 2.0 / img_height width = (xmax - xmin) / img_width height = (ymax - ymin) / img_height # 边界检查:防止归一化后超出[0,1]范围 x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) width = min(max(width, 0.0), 1.0) height = min(max(height, 0.0), 1.0) yolo_lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") return yolo_lines # 使用示例 class_names = ['license_plate'] xml_file = 'dayride_type1_001.mp4#t=1199.jpg.xml' img_width = 1920 img_height = 1080 lines = voc_to_yolo(xml_file, class_names, img_width, img_height) for line in lines: print(line)

这段代码的逻辑并不复杂,但有几个参数值得注意。class_names的顺序就是YOLO训练时的类别编号顺序,全数据集必须保持一致,如果你只有一个类别,那class_id永远为0。img_widthimg_height必须和图片实际尺寸严格一致,这个数据集的宽高都是从XML里的<size>字段读的,所以脚本里其实不应该硬编码,常规做法是直接解析XML的size节点,而不是当作参数传入。

归一化后的坐标保留6位小数足够用了,因为图片最大也就几千像素,6位小数对应的精度远高于像素级精度。边界检查那两行是血泪经验——VOTT偶尔会画出略微超出图片边界的框,不处理会让YOLO训练直接报错或者产生NaN损失。

3.2 批量转换与数据集划分:按场景分组避免数据泄漏

单张转换脚本跑通之后,要考虑批量处理。这个数据集没有现成的train/val/test划分,需要自己做。划分时有个容易忽略的点:同一视频抽出来的帧应该分到同一个集合里,不能随机打散。

原因很简单,车牌检测任务的难点之一就是相似场景下的过拟合。如果夜间视频的帧一部分在训练集、一部分在验证集,验证集的图片和训练集图片来自同一段视频、同一辆车、同一个光线条件,那验证结果会虚高,模型看似效果不错,实际换到新视频上立刻现原形。

import os import random from collections import defaultdict def split_dataset(image_dir, xml_dir, output_dir, val_ratio=0.15, test_ratio=0.15): # 按视频来源分组 video_groups = defaultdict(list) for filename in os.listdir(image_dir): if not filename.endswith('.jpg'): continue video_id = filename.split('#')[0] # 如 dayride_type1_001.mp4 video_groups[video_id].append(filename) train_files, val_files, test_files = [], [], [] for video_id, files in video_groups.items(): random.shuffle(files) n_val = int(len(files) * val_ratio) n_test = int(len(files) * test_ratio) val_files.extend(files[:n_val]) test_files.extend(files[n_val:n_val + n_test]) train_files.extend(files[n_val + n_test:]) # 写入划分文件 for split_name, file_list in [('train', train_files), ('val', val_files), ('test', test_files)]: with open(os.path.join(output_dir, f'{split_name}.txt'), 'w') as f: for filename in file_list: f.write(filename + '\n') return len(train_files), len(val_files), len(test_files) n_train, n_val, n_test = split_dataset('images/', 'xmls/', 'split/') print(f'训练集: {n_train}, 验证集: {n_val}, 测试集: {n_test}')

这个划分策略的关键是filename.split('#')[0]取的视频ID。原始文件名如nightride_type3_001.mp4#t=352.jpg,按#分割后取前半段nightride_type3_001.mp4,就是这段帧的来源视频ID。同一个视频的帧只会进同一个集合,不会跨集合存在。

划分比例我一般用15%验证、15%测试、70%训练。534张图的话,训练集大约370张,验证和测试各80张左右。这个规模做检测训练偏小,但作为迁移学习的微调数据或者模型验证足够了。如果你的场景是跑通整个训练流程、验证数据标注质量,这个规模完全够用。

4. 从标注到训练:用这份数据跑YOLOv5车牌检测

4.1 为什么不直接训练YOLOv8:先看数据量再选模型

拿到手的数据集只有534张图,直接上YOLOv8从零训练,效果大概率不理想。检测模型从零收敛,单类别目标通常需要至少几千张图,500多张只够做微调。常规做法分为三步:第一步,用这份数据做迁移学习,加载在COCO上预训练好的权重,只微调最后的检测头;第二步,做了数据增强——尤其是针对小目标的增强策略,因为前面分析过,车牌在1080p画面里占比很小;第三步,如果效果还不够,再用这份数据训练出的模型去跑视频抽帧,做伪标签,补充更多训练数据。

如果你用的是YOLOv5,训练流程相对成熟,直接写一个数据集配置文件就行。

# eu_plate.yaml train: split/train.txt val: split/val.txt test: split/test.txt # 类别数 nc: 1 # 类别名称 names: ['license_plate']

这个YAML的关键点是trainval指向的是txt文件路径,txt里每一行是图片的绝对路径。YOLOv5的数据加载器会根据图片路径自动找同名的txt标签文件——图片叫xxx.jpg,标签就叫xxx.txt,放在同一级目录下,这点和很多第一次用YOLO的人直觉不同,容易踩坑。

4.2 训练参数设置:针对小目标的四个关键修改

训练命令本身不复杂,复杂的是参数怎么改。针对车牌这种小目标、少数据量的任务,我一般会调整几个关键参数。

python train.py \ --data eu_plate.yaml \ --weights yolov5s.pt \ --img 1280 \ --batch 16 \ --epochs 150 \ --patience 20 \ --hyp data/hyps/hyp.scratch-low.yaml

--img参数从默认的640改到1280,这几乎是针对小目标最有效的改动。原图1920x1080,车牌可能是100x30像素,缩到640后车牌就剩约33x10像素,检测头很难抓住这么小的目标;缩到1280后车牌能保持66x20像素,特征明显多了。代价是显存占用翻倍、训练速度变慢,但数据集小,这个代价可以接受。

--hyp换成hyp.scratch-low.yaml而不是默认的hyp.scratch.yaml,原因是默认的高增强策略会对小目标做大量随机裁剪和缩放,本来就小的车牌经过增强后可能只剩几个像素,反而干扰学习。低增强策略更适合小数据集。

训练后看log里的PRmAP@0.5三个指标。如果mAP@0.5能到0.9以上,说明标注质量没问题、模型也学到了;如果卡在0.6-0.7上不去,先检查标签是否有误,再看夜间场景单独评估的指标。

5. 避坑指南:VOTT数据集实战中的六个常见问题

5.1 XML路径字段指向本机旧路径,迁移机器后训练报文件找不到

现象:换了一台电脑解压数据集,训练时日志报错找不到图片文件,但图片明明就在对应目录下。

原因:VOTT生成XML时,<path>字段记录的是标注当时的绝对路径,比如E:/dataset/dayride_type1_001.mp4#t=1199.jpg。这个路径是标注机器的路径,和当前机器的实际路径不一致,YOLO的数据加载器如果读<path>字段就会直接失败。而<filename>字段只有文件名,不和具体目录绑定。

解决:转换脚本里不要用<path>字段,统一用<filename>,然后拼接当前机器的数据目录。或者批量替换XML里的路径前缀——用文本编辑器全局替换也行,但脚本更稳妥。我习惯在转换到YOLO格式时直接忽略<path>,只用<filename>和自定义的image_dir参数。

5.2 文件名包含#=符号,部分工具解析异常

现象:把图片路径写入train.txt后,用某些脚本读取时报错,路径被截断或者识别成特殊字符。

原因:文件名如nightride_type3_001.mp4#t=352.jpg包含了#=这两个符号。在shell脚本里#是注释符,在某些语言的标准库里URL解析会截断#后面的部分,个别Windows老旧的批处理工具也会在=处出问题。

解决:训练时用Python脚本读取文件列表,不要用shell的for循环遍历目录。如果非要用bash,记得对文件名做引号包裹。另一个思路是转换格式时顺便把文件重命名成nightride_type3_001_352.jpg这种去符号的形式,但注意VOTT的VOC标签里<filename>字段也要同步改名,否则图片和标签对不上。

5.3 夜间样本的标注框偏大,导致模型定位不准

现象:用这份数据训练出的模型,在白天测试集上mAP不错,但在夜间图片上车牌框总是比实际车牌大一圈,尤其是用车牌外接框做字符裁切时,会把车灯和车身反光裁进去。

原因:VOTT标注时夜间场景的车牌边缘不清晰,标注者倾向于把光晕区域也框进去。标注框比真实车牌大10%到20%,模型学到的就不是"车牌边界",而是"车牌加光晕边界"。

解决:训练前做标签清洗,统计所有标注框的面积分布,把明显偏大的框手动修正。更省力的方案是训练后处理时对预测框做收缩,比如宽度方向乘以0.9、高度方向乘以0.85,暴力但有效。极端情况下对夜间图片重新用VOTT标注一遍会更彻底,但534张全部重标工作量不小。

5.4 数据集没有划分,直接所有图训练导致模型效果虚高

现象:把534张图全部丢进去训练,训练集上mAP接近1.0,但跑到一段新视频上,检测率大幅下滑,漏检率很高。

原因:这是数据集划分的问题。所有图片都参与训练,模型把这些图"背"下来了,并没有泛化到新车牌、新背景、新光照条件的能力。尤其是这份数据集只有白天和夜间两个场景,驾驶路线相对固定,背场景比背车牌更容易——模型可能记住了这段路的护栏和树木,而不是车牌本身。

解决:必须划分训练、验证、测试集,而且按视频来源分组划分。另外要正视数据量问题,534张图训练出的模型,泛化边界很窄,只适合证明流程可行,离实际部署还远。

5.5 多国车牌混在一起训练,单类别标签掩盖了样式差异

现象:模型能检测到车牌位置,但无法区分不同国家的车牌格式——比如德国蓝条白字和法国黄底黑字,检测出来后要按国家分流时无从下手。

原因:VOC标签里所有目标都叫license_plate,只有一个类别。欧盟车牌虽然整体是长方形带蓝条,但具体到每个国家,颜色、字体、长宽比都有差异,这些问题被单类别标签掩盖了。

解决:如果你后续要按国家分类,需要对这534张图重新标注,把license_plate细分成germanyfrancenetherlands等子类。这需要在VOTT里逐张重新打标签,工作量大约4到6个小时。如果只是做检测定位,单类别就够用了。

5.6 部分帧含视频字幕或水印,干扰训练

现象:训练过程中loss曲线正常但mAP偏低,仔细排查发现部分图片右下角有视频水印或速度表叠加信息,模型有时候会把水印识别成车牌候选区域。

原因:从行车记录仪视频抽帧时,如果视频本身带OSD信息(速度、GPS坐标、时间),抽出来的帧就会包含这些固定区域的UI元素。这些元素和车牌一样也是矩形、也是高对比度,检测网络容易混淆。

解决:清洗数据时重点检查四个角和水印位置,把带UI叠加的帧剔除或者裁掉对应区域。更稳妥的做法是抽帧时直接用ffmpeg的crop滤镜把UI区域裁掉,一劳永逸。这块没有捷径,纯属体力活。

6. 数据增强与验证技巧:让534张图发挥更多价值

6.1 针对车牌的专属增强配置

数据量不足的情况下,增强策略直接决定模型上限。针对车牌这种纹理型目标,我常用一套组合拳。HSV色域增强里把饱和度范围设到±25%、明度±20%,因为欧盟车牌颜色本身是特征的组成部分——蓝条、白底、黄底都靠颜色区分,色域增强太强会让模型混淆国别特征。随机旋转设±10度,车牌在真实场景里基本不会有大幅度倾斜,超过15度的旋转引入的样本反而是噪声。

Mosaic增强在YOLOv5里是默认开启的,它把四张图拼成一张,对小目标检测特别有效。但注意关闭mixup,这个策略会让车牌和背景直接混叠,造成误检。我用这份数据做增强实验时的典型配置是:训练集从534张扩展到实际参与梯度更新的约2000张,mAP@0.5能从0.82提升到0.91。

6.2 用训练好的模型做硬负例挖掘

数据增强只能缓解数据量不足,不能根治。进一步的做法是拿训练好的模型去跑视频抽帧,把检测置信度低于0.5但标注确实有车牌的帧挑出来,人工确认后补进训练集。这种半自动标注补数据的流程,是现在做小型定制数据集的主流方式。

我当时处理这份数据时,先用534张训练出一个初始模型,然后跑了3段原始视频(就是文件名里对应的白天和夜间视频),抽帧约2000张,模型预测后挑出置信度高且和已有训练图片场景不同的帧,人工检查后补充了约300张,再重新训练,效果提升了一个台阶。

6.3 模型验证的土办法:视频级检测稳定性测试

mAP指标只能反映单帧检测能力,但实际场景是视频流,检测稳定性同样重要。我会写一个简单脚本,用训练好的模型逐帧跑测试视频,统计相邻帧的检测框抖动情况——框的位置在相邻帧之间跳动超过20个像素就记为一次不稳定,运行10秒视频,记录不稳定次数。

这个指标mAP看不出来,但实际部署时影响很大。不稳定时做个简单的平滑处理,用前后3帧的检测框坐标取中值,就能大幅降低抖动。这个土办法我一直在用,应对工程验收比纯看mAP直观得多。

数据集的坑走完一遍之后,从那以后我每次拿到新数据第一件事,就是先用脚本统计标注框面积分布和文件名特殊字符,再决定转换策略。这两个动作能省后面不少返工时间。这份534张的欧盟车牌数据集,作为流程验证和初始模型训练是合格的,但心里要清楚它的边界:单类别、夜间框偏大、场景有限。如果你需要更完整的车牌数据集,用这个包当种子数据,配合视频抽帧和半自动标注工具去扩数据,会是更务实的路线。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询