CCPD2019光照子集:5000张暗亮车牌图与YOLO训练实战

📅 2026/8/26 9:54:35
CCPD2019光照子集:5000张暗亮车牌图与YOLO训练实战
简介目标检测中光照变化是影响模型鲁棒性的关键因素。车牌检测作为智能交通和安防场景的核心任务在夜间强光或逆光环境下常出现漏检、误检。为了提升模型对光照的适应能力困难样本挖掘成为数据构建的重要思路。CCPD2019数据集中的5000张暗亮光车牌子集正是针对这一痛点设计通过筛选低照度和高反光场景拓宽模型在光照维度上的特征分布。该子集采用LabelMe多边形标注格式保留了车牌四角点信息可灵活转换为YOLO水平框、OBB旋转框等训练格式便于结合数据增强策略进行专项调优。无论是停车场管理、出入口道闸还是智能交通项目利用该子集进行针对性训练和分组评估都能有效提升检测模型在复杂光照下的表现。本文围绕数据集结构、格式转换、训练配置及常见坑点提供了一套可落地的工程实践方法。1. 这5000张图到底值在哪CCPD2019子集的光照筛选逻辑做车牌检测的人应该都有这种体会白天大太阳底下车牌反光一片白夜间弱光下车牌糊成一团这两种场景下模型表现断崖式下跌。CCPD2019本身是国内公开车牌数据集里覆盖面比较全的一个但如果直接拿全量数据训练光照问题容易被淹没在大量正常样本里模型学不到足够强的光照鲁棒性。所以把“光线较暗或较亮”这部分单独拎出来凑成5000张子集本身就是一种很典型的困难样本挖掘思路。这套子集的设计意图很明显不是让你拿它替代完整数据集而是用它做针对性增强、做鲁棒性验证、做光照场景的专项调优。适合三类人一是正在做智能交通、停车场管理、出入口道闸项目的工程师二是用YOLO系列训练自定义数据集但总被光照变化困扰的学生和研究者三是需要快速验证数据增强策略是否有效的算法调优人员。1.1 为什么光照问题这么难搞车牌检测本质上是在一张大图上找到车牌的精确位置后续还要做字符识别。这一步如果定位不准后面识别正确率再高也白搭。光照对定位的影响主要体现在两方面对比度下降导致边缘模糊以及高光/阴影导致的纹理失真。亮光场景下金属车牌表面反光会让字符区域变成亮斑暗光场景下车牌与车身、背景的灰度差异变小目标几乎融入环境。我自己的实测经验是普通场景训练出来的模型在正常光照下mAP能到95%以上一遇到逆光或者夜间mAP直接掉到70%甚至更低。这不是模型结构的问题而是训练数据里困难样本太少。CCPD2019这个子集正好补上这一块5000张暗亮光样本足以让模型在光照维度上的特征分布明显拓宽。1.2 为什么偏偏是LabelMe格式现在业界常用的标注格式有VOC XML、YOLO TXT、COCO JSONLabelMe其实相对小众但LabelMe有一个不可替代的优势——原生支持多边形标注。车牌不是正矩形尤其是在斜视角和透视畸变下用外接矩形框会把大量背景包含进来导致训练时背景干扰大。CCPD系列本身就是用四角点标注车牌四边形LabelMe的polygon格式可以完美保留这四个关键点而不是退化成轴对齐矩形框。另一个考虑是LabelMe格式的通用性。LabelMe的JSON结构非常直白label字段标类别points字段存多边形顶点几乎没有学习成本。拿到手之后无论是转成YOLO水平框、YOLO OBB旋转框还是转成COCO的segmentation多边形代码都很好写。相比之下如果一开始就用YOLO TXT存四个归一化坐标虽然训练省事但扩展到旋转框或语义分割就麻烦了。所以这个数据集选择LabelMe格式等于把“任意二次加工”的可能性留给了使用者。2. 数据集结构解析从文件命名到LabelMe JSON拿到数据集第一件事不是急着训练先把文件结构和标注格式吃透。我自己接手过不少公开数据集很多坑都出在没搞懂格式细节就直接转换结果训练的时候发现坐标全部飘了。2.1 文件目录与命名的门道这套子集文件名带“part01”说明是分卷发布的后续大概率有part02甚至更多。序号从1到4999也就是5000张图片但这里有个小细节要注意很多数据集命名从0开始或者中间有跳号。如果你的文件是1.jpg到4999.jpg那就是正好5000张如果看到的是0.jpg到4999.jpg那实际上有5000张但编号多了一个。我个人建议拿到数据后先写个脚本统计一下图片总数和JSON文件总数确认数量对得上再往下走。目录组织方面一般有两种常见布局一种是所有图片和同名JSON放在同一个文件夹另一种是images和labels分开。LabelMe默认是图片和JSON同目录如果你拿到的是这种结构建议第一步就按训练习惯重新整理。我的习惯是建一个dataset目录里面分images和annotations图片归一化命名JSON单独存这样后续无论是转格式还是做数据划分都省事。2.2 LabelMe JSON字段逐项拆解一个标准LabelMe JSON文件长这样{ version: 5.2.1, flags: {}, shapes: [ { label: plate, points: [ [812, 524], [968, 526], [967, 556], [811, 554] ], group_id: null, shape_type: polygon, flags: {} } ], imagePath: 0001.jpg, imageData: null, imageHeight: 1160, imageWidth: 720 }字段拆开看就清楚了imagePath是图片文件名imageHeight和imageWidth是图片尺寸这两个数值在转换格式时是归一化的分母一定不能写错。shapes里是一个列表每辆车对应一个shape。label是类别名称points是多边形顶点坐标shape_type一般标成polygonflags是额外标记位这个数据集里多半是空。这里特别提醒一下imageData字段LabelMe选图保存时会把原图以base64形式塞进去但如果图比较大这个字段会变成null图片单独存。训练时需要的是图片文件和标注文件对应不需要也没必要去解析imageData里的base64内容。如果你打开JSON发现这个字段很长直接忽略即可。2.3 车牌四点的标注顺序与坑CCPD系的车牌标注是四个角点这个顺序特别重要。LabelMe的points数组是按你点击时的顺序存的而不同工具对四点顺序的约定不一样有的是左上、右上、右下、左下有的是顺时针存有的是逆时针。转换格式时如果不统一训练出的检测框会乱转尤其在做OBB旋转框时四点的顺序直接决定旋转角度计算是否正确。稳妥的做法是转换前先把四个点按几何关系重排。我在代码里会先算四个点的包围盒中心然后按每个点相对中心的角度排序统一成“左上、右上、右下、左下”的顺序。这个过程看起来多余但能避免很多莫名其妙的问题。尤其当你拿到手的标注有可能是人工点击顺序随意记录的你根本无法保证每个JSON里的顺序一致。3. 数据处理与格式转换从LabelMe到训练所需格式拿到LabelMe标注后正常情况下不会直接拿来训练。YOLOv8、YOLOv5这些主流框架默认读的是TXT格式MMDetection默认读COCO JSON。所以转换这一步绕不开。好消息是LabelMe转其他格式都很简单核心就是把points坐标按规范重新写一遍。3.1 先做一轮数据清洗转换之前我强烈建议先做一次数据完整性检查。我自己踩过这个坑训练到一半发现loss异常排查了半天才发现是一张图的标注文件里imagePath对应的图片根本不存在。检查项主要有四个图片文件是否有对应JSONJSON是否有对应图片数量是否一致每个JSON里imageWidth和imageHeight是否与图片实际分辨率一致shapes是否为空多边形点是否至少4个类别标签是否统一比如有的文件写plate有的写car_plate这种不一致要提前改掉。写个Python脚本跑一遍几秒钟就能筛出全部问题文件。对于异常文件我的处理原则是宁可删掉也不要勉强保留。5000张数据里少几张不影响大局但一条脏数据可能让模型多花几百轮去修复。3.2 LabelMe转YOLO格式实操代码YOLO格式的标注是每行一个目标五个数值class_id x_center y_center width height全部是相对于图片宽高的归一化值。对于车牌这种四边形标注最简单的转换方式是取四个点的外接矩形。代码如下import json import os def labelme_to_yolo(json_path, img_width, img_height, class_id0): with open(json_path, r, encodingutf-8) as f: data json.load(f) lines [] for shape in data[shapes]: points shape[points] xs [p[0] for p in points] ys [p[1] for p in points] min_x, max_x min(xs), max(xs) min_y, max_y min(ys), max(ys) x_center (min_x max_x) / 2.0 y_center (min_y max_y) / 2.0 width max_x - min_x height max_y - min_y x_center_norm x_center / img_width y_center_norm y_center / img_height width_norm width / img_width height_norm height / img_height lines.append(f{class_id} {x_center_norm:.6f} {y_center_norm:.6f} {width_norm:.6f} {height_norm:.6f}) return lines这份代码的逻辑不复杂但有个点要提醒归一化分母必须用图片原始宽高而不能用标注里的imageWidth和imageHeight去推。虽然这两个值理论上应该和图片一致但在手工标注或数据集下载过程中遇到过分辨率被修改但JSON没同步更新的情况。最保险的做法是用PIL或者OpenCV读取图片实际尺寸以此为准。另外如果你的标注里有多辆车class_id都填0就可以因为车牌只有一个类别。如果后续你打算区分蓝牌、黄牌、新能源绿牌那就要按label字段做映射不同label对应不同类别ID。3.3 需要旋转框再转一步OBB车牌检测里有个场景很常见车辆斜停、照片侧拍车牌在画面里是一个倾斜四边形。这时候水平框会把一大片无关背景包进来尤其是当车牌小、周围环境复杂时水平框的IoU虚高但实际定位不准。解决方式是训练旋转框模型比如YOLOv8-OBB或者MMRotate里的旋转检测算法。LabelMe转OBB格式本质上是在外接矩形基础上增加一个角度信息。旋转框格式一般是class_id x_center y_center width height angle其中angle是相对于图像x轴的角度。转换时可以直接从四个点里拟合最小外接旋转矩形。OpenCV里已经有现成函数cv2.minAreaRect拿四个点输入输出的就是(center_x, center_y), (width, height), angle。不过要注意OpenCV的angle定义和YOLOv8-OBB的angle定义不完全一致坐标轴方向、角度正负都可能相反转出来之后最好可视化验证一遍。我第一次转OBB的时候没做可视化检查结果训练出来的预测框全部长了“反骨”角度全偏了90度。后来学乖了每次转格式都随机抽几十张图把标注框画回去看看位置对不对再继续。这一步看起来多余其实省的是后面debug的功夫。4. 基于这5000张图训练车牌检测模型数据准备好了接下来就是训练环节。我用这套数据在YOLOv8上做过完整训练下面把整个过程和关键参数梳理一遍直接照着做就能跑通。4.1 环境与数据准备环境建议直接用YOLOv8官方镜像或者基于Python 3.9的虚拟环境。安装就两行命令pip install ultralytics pip install opencv-python数据目录按这个结构组织dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ └── val/图片和标注文件要一一对应同名只是后缀不同。划分比例我建议按8:1:1或者直接用train/val二分的9:1。5000张数据不算多验证集留500张左右足够看出模型效果。4.2 data.yaml与训练配置YOLOv8通过一个YAML文件描述数据集信息文件内容如下path: /path/to/dataset train: images/train val: images/val names: 0: plate这里一个容易忽略的细节是path字段。如果这个路径写错了ultralytics会去默认路径找数据然后报错找不到文件。建议用绝对路径不要用相对路径避免换机器后路径失效。训练命令如下yolo detect train \ data/path/to/data.yaml \ modelyolov8n.pt \ epochs300 \ imgsz640 \ batch16 \ lr00.01 \ augmentTrue模型选择上如果追求速度和部署轻便用yolov8n或者yolov8s如果精度优先而且GPU显存够大用yolov8m或者yolov8l。我的实测是n模型在验证集上mAP50大概能到90%左右l模型能到96%左右但推理速度差了好几倍。工业场景里我通常选s或m平衡点比较合适。4.3 针对暗亮光的增强策略这个数据集的特点就是暗光和亮光所以训练时数据增强要特别关注亮度扰动。YOLOv8的增强参数里有hsv_h、hsv_s、hsv_v分别对应色调、饱和度、明度的随机扰动。默认的hsv_v是0.4但对于这套暗亮光数据我建议加大到0.6左右让模型在明暗变化上有更强的泛化能力。另外还可以手动加一些额外的图像处理随机gamma校正、随机对比度调整、模拟夜间低照度的噪声。我写过一个简单的预处理函数在训练前对部分图片做gamma变换实验下来确实能提升夜间场景的检测效果。这些操作本质上是把数据集里的光照分布进一步拓宽让模型见过更多“极端情况”。4.4 指标怎么看到底准不准训练结束后不要只盯着mAP看要结合使用场景看具体指标。车牌检测有两个重点关注项一是小目标的召回率二是不同光照条件下的稳定性。这里有一个经验把验证集按图片亮度分成三组暗、正常、亮分别计算每组mAP。只报一个总mAP会掩盖光照差异的问题。我在实际调优中就发现过一个模型总mAP 93%但暗光组只有82%亮的组95%。如果不分组看你根本不知道模型的短板在夜间。这个分组评估的小工具不复杂用OpenCV算每张图的平均亮度按阈值归类然后分开跑验证就行但信息密度比单看一个总指标高很多。5. 我在这个数据集上踩过的坑与排查技巧这部分是纯经验分享。我在处理车牌检测数据集时尤其是这种带光照专项的场景遇到过不少让人抓狂的问题写出来给大家做个参考。5.1 标注阶段的坑LabelMe标注最麻烦的是类别名不统一。如果有多个标注员或多次标注同样的车牌可能被标成plate、car_plate、license看起来是小事但转类别ID时容易出错。处理办法是在清洗脚本里做一次类别映射把所有非预期类别统一替换掉。另一个坑是标注点顺序。前面提过一遍这里再强调一下转换前一定要把四个点按统一规则排序。尤其当一张图里有多个车牌时不同标注员点击的顺序可能完全不同不排序就直接转换的话旋转框的angle会乱。5.2 转换与训练阶段的bug常见的是转YOLO后发现归一化坐标出现大于1或小于0的值。原因一般有两种一是标注点有超出图片边界的二是坐标除以了错误的宽高。解决办法是在转换脚本里加边界裁剪点坐标限制在[0, width]和[0, height]范围内。训练阶段如果loss不稳定或收敛慢先检查标注文件和图片是否严格对齐。我遇到过一个情况是图片顺序按文件名排列但标注文件是按修改时间排列的错位之后模型相当于在随机学一堆错误映射效果自然烂。5.3 常见问题速查表问题现象可能原因解决方案训练后预测框整体偏移归一化分母用了JSON里存的宽高但图片被resize过用OpenCV重新读取图片尺寸做分母预测框是歪的或角度反了四点顺序不统一OBB角度定义不一致统一重排四点为左上、右上、右下、左下并可视化验证暗光图完全检测不到数据增强不够暗光样本占比不足加大hsv_v扰动额外加gamma校正和低照度模拟同一个车牌输出多个框NMS阈值太低或类别置信度分布宽调高NMS的iou阈值到0.5以上检查标签是否有重复目标训练时loss跳变存在异常标注如坐标越界或空标注清洗脚本逐文件检查删除异常数据验证集mAP高但实际场景差训练集和实际场景分布不一致按光照分组评估针对短板补充数据最后再分享一个我自己的习惯每次拿到这种专项数据集不要急着全量灌进模型。先挑几十张特征明显的图看一眼标注质量再拿一个小模型快速跑个10轮验证数据可用性然后再开正式训练。这套流程能让整个实验过程少走很多弯路。这个5000张的CCPD2019子集如果利用好光照信息做成一个专项增强样本池复用到其他车牌检测项目里也能明显提升模型的鲁棒性。本文还有配套的精品资源点击获取