ARTICLE DETAIL

资讯详情

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

车牌检测数据集构建与VOC、COCO、YOLO格式转换及YOLO训练实战

车牌检测数据集构建与VOC、COCO、YOLO格式转换及YOLO训练实战 简介目标检测是计算机视觉领域的核心任务之一其本质是定位并分类图像中的目标对象。在交通场景中车牌检测作为典型的小目标检测应用对数据质量与格式规范要求极高。VOC、COCO、YOLO是目标检测领域最通用的三种标签格式分别以XML、JSON和TXT文件存储标注信息理解它们的坐标定义与归一化逻辑是进行多框架训练的前提。构建一个规范的车牌数据集需要合理规划目录结构、采集多样化的样本、严格标注并同步划分训练集与验证集。基于YOLOv8等主流框架只需配置好YAML文件即可高效完成训练与评估。本文从数据准备、格式转换、划分策略到训练调优系统梳理车牌检测数据集从零到一的完整流程帮助读者在车辆管理、停车场道闸等场景中快速落地可用模型。 车牌子检测这个活儿业内做的人不少但真正能把数据集从零到一整理明白的说实话不多。最近我在折腾一个车牌检测项目从数据采集到模型训练走了一整轮。因为任务里涉及了不同框架的格式需求所以我一开始就按VOC、COCO、YOLO三种格式把数据全部准备了一份还配套写了划分脚本和训练教程。这套东西打包整理好正好可以拿出来给正在做车牌识别或者类似的小目标检测任务的朋友做个参考。我自己把整个流程跑通之后实测下来非常顺配合当前主流的YOLO版本跑训练基本没有障碍这就是我写这篇东西的由来。这篇内容主要围绕“车牌目标检测数据集”的构建和训练展开适合正在做交通场景识别、车辆管理、停车场道闸项目或者准备用目标检测入门练手的人。不管你手里目前用的是哪一套标注工具最后都能在本文里找到对应格式的转换思路和训练方法。我先把整体思路捋清楚再针对性拆解各种格式的细节差异最后讲实操流程、常见问题和排坑经验尽量让你从拿到图片到最后跑出模型权重全程不走弯路。1. 车牌检测项目的数据准备全流程车牌检测这个任务外形上看起来就是一个普通的目标检测但它有一些特殊性。车牌本身面积在整张图里通常占比很小是典型的小目标再加上拍摄角度、光照变化、污损、遮挡等情况数据质量对最终模型的泛化能力影响非常大。所以当你手上只有1000张图片时数据怎么选、标签怎么打、格式怎么整非常考验前面的规划能力。1.1 数据集结构规划与文件组织我拿到素材后做的第一件事不是着急写训练代码而是先把整个数据集的目录结构规划好。良好的目录结构能让你后续在多个框架之间切换时省掉大量时间。我最终采用的目录结构大致是这样plate_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels_voc/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels_coco/ │ ├── train.json │ ├── val.json │ └── test.json ├── labels_yolo/ │ ├── train/ │ ├── val/ │ └── test/ ├── class_names.txt ├── split_data.py └── train.py因为后续要同时兼顾VOC、COCO、YOLO三种格式所以我把三种标签分开放置。图片在images目录下统一保管划分脚本定义清楚之后每次生成训练、验证、测试集时三种格式会同步按同样的比例进行划分保证同一个图片ID在三份标签中对得上。这里有个很容易踩的坑如果只改图片不同步改标签或者划分时随机种子不一致最后标签和图像对应不上训练时会报“no labels found”错误排查起来很崩溃。1.2 图片采集与标注的要点1000张图片看起来不多但如果每张图里都有1到3个车牌那么有效样本量其实可以支撑一个初步可用的检测模型。我采集图片时尽量覆盖了不同场景小区地下车库入口、路侧停车位、高速收费站附近、十字路口等。拍摄角度上尽量保证有正面、侧面、俯视时间上有白天、傍晚、夜间车牌颜色上涵盖蓝牌、绿牌、黄牌。这些多样性直接决定模型在真实环境里的表现尤其车牌检测这种场景非常吃环境泛化。标注这个环节我强烈建议用labelImg或者X-AnyLabeling来做。标注时要注意一个细节尽量将车牌完整框住不要留太多背景也不要切掉车牌边缘。如果车牌倾斜严重需要贴着车牌实际外接矩形打框。部分图片的车牌被车头装饰物遮挡一部分这类图片不要丢可以适当保留因为真实场景里遮挡是常态。但遮挡面积超过50%的图片建议剔除保留太多这种样本反而会教坏模型。每张图片最好都核对一下是否有漏标或者错标。我踩过的一个坑是车尾的两个车牌号同时出现但只标了其中一个模型训练出来后对同一场景下的第二个车牌漏检率特别高。所以批量标注完成后要专门花一点时间用可视化脚本把标注框都画出来检查一遍这一步省不掉。2. 三种格式的标签差异与转换逻辑VOC、COCO、YOLO这三套格式是目标检测领域最常用的标签体系。很多新手拿到数据集后第一反应就是“格式怎么这么多种我到底用哪个”然后就卡住了。其实它们的本质都是描述“图片里某个目标在哪里、是什么类别”只是记录方式不同。理解了这个本质你就知道转换不过是在不同描述语言之间做翻译。2.1 VOCO格式XML标注文件VOC格式源自PASCAL VOC竞赛用XML文件存储标签信息。每个XML文件对应一张图片文件名通常和图片名保持一致。核心结构是这样的annotation folderimages/folder filenameIMG_0001.jpg/filename size width1920/width height1080/height depth3/depth /size object nameplate/name bndbox xmin752/xmin ymin451/ymin xmax1035/xmax ymax541/ymax /bndbox /object /annotation注意这里的xmin/xmax/ymin/ymax都是像素坐标的绝对值。也就是说不管图片原始尺寸是多少标注的坐标都直接以“图片上的实际像素位置”来记录。第一次转换格式的人很有可能会把这里的绝对坐标直接当成YOLO的坐标来用结果训练出来的模型完全没办法收敛。VOC格式最大的优势是非常直观人眼直接能看懂而且主流标注工具默认导出的就是这个格式。VOC里还有一个比较隐蔽的地方是difficult标签。如果标注工具里有“困难样本”这种标记选项保存时可能会生成一段difficult1/difficult字段部分转换脚本如果处理不当会把这个样本跳过导致训练数据缺失。我一般会做转换时直接丢弃这个字段因为车牌检测任务里难样本本身也是有用信息。2.2 COCO格式JSON标注文件COCO数据集带火了这种JSON格式的标注方式。它会用一个大的JSON文件来描述整个数据集的全部图片和全部标注信息而不是一张图一个文件。结构分两块images数组存图片信息ID、文件名、宽高annotations数组存标注信息图片ID、类别ID、bbox坐标、面积、是否遮挡等。下面是一个典型对象{ images: [ { id: 1, file_name: IMG_0001.jpg, width: 1920, height: 1080 } ], annotations: [ { id: 1, image_id: 1, category_id: 1, bbox: [752, 451, 283, 90], area: 25470, iscrowd: 0 } ], categories: [ {id: 1, name: plate} ] }这里有一个非常容易搞混的点COCO的bbox是[x, y, width, height]格式不是[xmin, ymin, xmax, ymax]。x和y是左上角坐标width和height是目标框的宽和高。如果你在转换时直接把VOC的xmax和ymax填进来那么框的尺寸就会偏大整整一圈训练时还好评估时mAP直接崩给你看。COCO格式在目标检测领域被大量框架原生支持比如Detectron2、MMDetection都默认使用它。如果你后续打算在这些框架上跑模型把数据整理成COCO格式是很有好处的。2.3 YOLO格式TXT标注文件YOLO格式是目前使用最广泛的标注格式之一因为它非常简单且存储效率高。每张图片对应一个同名的.txt文件每一行表示一个目标格式为class_id x_center y_center width height这里的x_center、y_center、width、height不是像素值而是“归一化”数值。换句话说都是用真实像素值除以图片的宽或高使数值落在0到1之间。例如某张图宽1920、高1080目标框左上角在(752, 451)、右下角在(1035, 541)那么x_center (752 1035) / 2 / 1920 0.4654 y_center (451 541) / 2 / 1080 0.4593 width (1035 - 752) / 1920 0.1474 height (541 - 451) / 1080 0.0833最终写入TXT文件的那一行就是0 0.4654 0.4593 0.1474 0.0833。这里尤其要注意类别ID从0开始编号。比如只有一个类别plate那它的ID就是0。新手最常见的错误是把类别ID写成1因为人眼看到的是“第一个类别”这就导致训练时类别错位。YOLO格式还有一个目录组织上的规则YOLO系列框架训练时通常要求图片目录和标签目录名称要对应。比如你给images/train/目录下的图片配标签那么标签文件也应该放在labels/train/目录下。这样做的好处是框架在做数据加载时能用一张图片路径直接替换掉images这个路径前缀为labels来找到对应的TXT文件。2.4 格式转换的实操脚本在做格式转换时我写了一个统一的脚本输入是VOC的XML目录输出三种格式。这里把核心逻辑贴出来方便你根据自己的数据情况修改import os import json import xml.etree.ElementTree as ET from glob import glob def voc_to_yolo(xml_file, class_names, target_dir): tree ET.parse(xml_file) root tree.getroot() filename root.find(filename).text img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) txt_path os.path.join(target_dir, os.path.splitext(filename)[0] .txt) lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_names: continue cls_id class_names.index(name) box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(txt_path, w) as f: f.write(\n.join(lines))转换成COCO格式时有两类数据需要重点核对一是图片ID要全局唯一不能两张图一个ID二是标注ID也要唯一递增。很多人在自己做小数据集时觉得ID无所谓随便写写就行等到模型训练任务里要用COCO格式的评估工具时发现ID冲突导致评估脚本直接报错那时候再来查就很麻烦。写转换脚本的过程中我还养成了一个习惯转换完成后做一次逆向检查。也就是说把生成的YOLO格式TXT重新画到图片上看框的位置是否合理。这个步骤耗时不多但能立刻发现归一化方向是否写反、中心点坐标是否写错等问题。3. 数据集的划分逻辑与训练实操一个数据集的划分方式直接影响模型训练的可靠性和评估结果的可信度。我见过不少人在这个环节比较随意直接抽查一部分图片当验证集就开训了这种做法在小数据集上特别危险很容易因为样本分布不均匀导致验证集完全不能反映真实情况。3.1 train/val/test 的比例设置与分组原则1000张图片的规模我建议这样划分训练集700张、验证集200张、测试集100张。这个比例兼顾了训练样本的充足性和验证评估的稳定性。如果再加大训练集比例测试集就会过小最终评估的mAP波动就会非常明显反之训练集过少则模型学不到位。划分时有一个重要的原则同车同场景的图片要尽量放在同一个集合里。如果同一辆车的多张照片同时出现在训练集和验证集那验证的时候模型相当于“开卷考试”实际部署效果会被严重高估。为了处理这种情况最好先按图片所属的“场景”或者“车辆ID”做分组再在组级别进行划分而不是简单地把每张图片独立随机分配。对于路边抓拍的车牌数据这个操作尤为重要因为通常一个车牌会出现在多张连续帧里。3.2 划分脚本的设计与边界条件处理一个健壮的数据集划分脚本不仅要完成数据集合划分还要处理文件缺失、空标签等边界情况。我写的split_data.py核心逻辑是import os import random import shutil random.seed(42) image_dir images train_ratio, val_ratio 0.7, 0.2 test_ratio 1 - train_ratio - val_ratio all_images [f for f in os.listdir(image_dir) if f.endswith(.jpg) or f.endswith(.png)] random.shuffle(all_images) train_split int(len(all_images) * train_ratio) val_split int(len(all_images) * (train_ratio val_ratio)) train_files all_images[:train_split] val_files all_images[train_split:val_split] test_files all_images[val_split:]我加了几层保护逻辑检查同名XML/TXT/JSON是否都存在缺失则跳过该样本。该图片对应的标签文件如果内容为空就单独记一个日志不放进训练集。空标签本身不是完全没用但当前阶段放进训练集会干扰正常样本的分布。生成三个集合的文件清单保存为train.txt、val.txt、test.txt不仅方便自定义训练脚本读取也方便以后排查“这张图到底有没有进训练集”。3.3 YOLO训练流程实操当前YOLO系列中YOLOv8和更新的版本是最常被选用的框架。官方仓库安装训练一条龙下来非常顺不需要去魔改源码。命令行方式下pip install ultralytics然后把数据集的配置写到一个YAML文件里path: /path/to/plate_dataset train: images/train val: images/val test: images/test names: 0: plate这个YAML文件是整个训练配置的核心。要特别注意path路径要写绝对路径如果你在远程服务器上跑路径写错会导致各种报错。另外names里的数量和顺序必须和标签文件里的类别ID完全对应一个类别时一定是从0开始顺序写错或者漏写模型训练出来就等于瞎搞。准备好这些之后就可以启动训练了yolo detect train dataplate.yaml modelyolov8n.pt epochs100 imgsz640 batch16 device0如果你机器性能有限可以考虑把model参数换成yolov8n.pt或者yolov8s.pt这两个是轻量版单卡训练和推理速度都快很多。车牌属于小目标理论上把imgsz提升到800或者960效果会更好但也要看你的GPU显存能不能撑住不然很容易在训练过程中报CUDA out of memory。训练结束后可以在runs/detect/train/weights/目录下找到best.pt和last.pt。best.pt是验证集上表现最好的权重一般就选它做后续推理。3.4 模型评估指标分析与调优用下面的命令做验证集评估yolo detect val dataplate.yaml modelruns/detect/train/weights/best.pt评估结果里重点看这几个指标mAP50IoU阈值取0.5时的平均精度。车牌这种有明显边缘的目标mAP50一般能做到0.95以上才算合格。mAP50-95IoU从0.5到0.95取一系列阈值后的平均精度更严格能反映框的定位精度。precision检出的框里真正是车牌的比例过低就说明误检多。recall所有车牌里被成功检出的比例过低就说明漏检多。如果precision低常见原因是背景太复杂、模型把某些纹理误判成车牌或者训练样本里负样本没有车牌的图片不足。如果recall低那就要考虑是不是小目标场景下特征不够可以加大imgsz或者在数据增强里增加马赛克增强的权重。车牌检测里还有一个常见问题就是蓝牌和黄牌的颜色特征差异较大如果模型整体recall低可能是某个颜色的车牌训练样本偏少需要针对性地增加对应颜色的图片。4. 训练过程中的典型问题与排查心得这个数据集虽然只有1000张图片但跑起来之后该遇到的问题一个都不会少。我把实际训练过程中踩过的几个代表性坑整理出来希望你能直接跳过。4.1 标签格式错误导致的训练崩溃YOLO训练中最常见的报错之一是这样的assertion failed: labels should not be empty或者训练能跑但loss一直不降val指标全是0。这种情况十有八九是标签解析出了问题。我自己排查时遇到过一个典型的情况我写的VOC转YOLO脚本没有处理好XML文件里的冗余空格导致标签框坐标解析出来是None最终写进TXT的坐标全是0。这种错特别阴险因为不报错但模型根本学不到东西。排查方法很简单随便打开一个TXT文件看坐标数据是否为0如果为零立即检查原始XML里是否有异常内容。4.2 数据增强对车牌任务的双刃剑效应YOLO框架默认会开启很多数据增强策略比如随机旋转、平移、缩放、色彩扰动等。这些增强对小目标检测任务通常是有益的但车牌有自己的特殊情况。车牌文字和底色的对比度本身就很高如果过度使用色彩扰动会让模型过分依赖颜色特征当真实场景中出现光线变化时反而性能下降。我实跑时发现将hsv_h、hsv_s、hsv_v这三个颜色增强参数适当调低比如hsv_h0.01能明显提升模型在黄昏和夜间场景下的稳定性。车牌检测里平移和旋转增强倒是可以开大一点因为真实抓拍中车牌确实经常是倾斜的。默认参数下模型也能收敛但对车牌这种特殊任务手动调整一下还是很有必要。4.3 样本不平衡带来的误检漏检1000张图中如果蓝色车牌有800张绿色车牌只有100张黄色车牌也只有100张那么训练出来的模型对蓝牌的检测效果会远好于其他颜色。这就导致了部署后蓝牌基本都框得住绿牌偶尔漏检黄牌甚至完全偏见。遇到这种情况最简单的处理是收集更多对应类别的图片或者对样本少的颜色做复制粘贴增强。这里提醒一下不要用简单的复制粘贴来凑数因为模型会对重复样本产生过拟合更好的方式是做适当翻转、亮度调整等变换后再加入训练集。4.4 小目标检测性能不足的优化思路车牌在整张图中的像素面积通常比较小。如果摄像头安装角度高车牌宽度可能只占图像宽度的十分之一不到对应的目标框大约只有30到50像素宽。YOLOv8默认的neck和head对小目标有一定的感知能力但如果你的场景里车牌总是很小有以下几种优化手段把imgsz从640提升到960。更大的分辨率意味着小目标对应的特征图格子更多模型有更多机会提取到有用特征。使用多尺度训练也就是开启scale0.9等参数让模型在训练时看到不同尺寸的车牌。部署推理时用Tiling方式也就是把大图切块检测再合并结果。这种方式虽然麻烦一些但往往能立竿见影地提升小目标的召回率。5. 后续优化与扩展方向1000张图片的数据集训练出来的模型可以作为一个v1版本使用但真正部署到生产环境还需要持续迭代。几个切实可行的扩数据方向一是收集不同省市的样本因为不同省份车牌字体风格其实存在一定差异虽然都不大但积累多了模型更稳二是增加夜间红外场景的图片地下车库出入口的抓拍经常是红外补光这种光照条件跟白天RGB图像差异极大三是补充雨雾天气的样本这在户外场景里对性能影响很大。模型结构方面如果你的部署环境是嵌入式设备或Jetson系列优先选用YOLOv8n或者YOLOv8s如果硬件资源充分且对精度要求高可以试一下YOLOv8m或更大版本。对比下来m版本在车牌任务上的表现通常比n版本在mAP50-95上高出3到5个百分点但推理时间也会相应增加。另一个可以做的方向是级联一个车牌字符识别模型。检测模型只告诉你“车牌子在哪”但如果要做违停识别、出入口控制这样的业务你还需要知道车牌的具体号码。一般是先用检测模型裁出车牌区域再交给OCR模型识别。这一步如果做成端到端管线在实际项目里的复用价值会非常高。最后分享一点实战体会在我自己跑这套数据集的过程中最大的体会其实是整理数据格式所花的时间往往会超出你的预期。很多教程默认你已经有了干净的数据集但实际上从零到一构建一个规范的数据集才是大头。建议无论你现在跑什么框架都尽早把数据统一成标准格式并做好备份。还有一个小技巧写转换脚本时不要一次性把所有格式都转换完可以先转换YOLO格式训练出一个小模型做冒烟测试确认没有问题后再补上VOC和COCO的转换这样能更早暴露问题避免返工。另外训练的时候要多看日志YOLO训练过程的loss曲线是判断模型是否正常学习的重要窗口。如果box_loss和cls_loss一直在下降但val指标没有明显变化多半是验证集划分有问题或者标签和图片不对应。遇到这种问题不要急着调参先把数据检查一遍往往能发现真正的病根。数据干净了模型效果自然就上来了。本文还有配套的精品资源点击获取
返回列表