YOLO灯光检测自训数据实战:从采集标注到训练避坑
简介这份资源面向计算机视觉入门与进阶开发者提供一套基于YOLOv5的灯光检测自训练数据与完整工程代码可用于自动驾驶、安防监控、无人机导航等场景下的灯光目标识别与定位练习。压缩包共1580个文件约603.83MB其中696张jpg图像与630个txt标注文件构成核心训练集65个yaml与14个yml负责数据与模型配置54个py脚本和15个pt权重支撑训练与推理另有png、xml、mp4等辅助素材及Docker相关文件目录结构清晰便于按模块查阅。资源中已包含自训练数据的采集、标注、增强与预训练流程以及YOLOv5不同规模模型的微调思路读者可据此复现灯光检测实验、理解锚框定位与特征提取机制并参考排错记录快速搭建自己的检测系统。目前已有252人学习下载适合希望掌握自训练数据构建与YOLOv5实战的中级开发者。1. 灯光检测自训数据从“拿现成权重跑不动”到闭环落地你拿 COCO 或 ImageNet 预训练的检测模型去跑自己场景的灯光检测十有八九会翻车。白天办公室的顶灯、夜间路面的路灯、工厂流水线上的指示灯、屏幕背光模组的亮点——这些目标在通用数据集里要么被当成“背景”要么被归到“lamp”这种粗类里框不准、漏检、误检全来了。灯光检测的本质不是“找亮的东西”而是要在过曝、光晕、反光、频闪、色温漂移这些干扰下稳定区分“光源本体”和“被照亮的物体”。通用权重学到的纹理和边缘先验跟灯光场景的成像特性对不上所以自己训练数据几乎是绕不开的一步。这篇笔记面向的是手里有相机、有采集条件、想用 YOLO 系列v5/v8 及后续版本思路一致把灯光检测做起来的工程师。我会按“数据怎么采、怎么标、怎么转、怎么训、怎么排错”的顺序讲重点放在那些真正影响 mAP 的细节上而不是复述官方文档。灯光检测这个方向数据质量的决定性远大于模型结构这一点先立住后面所有操作都围绕它展开。2. 灯光检测的数据采集与标注决定上限的一步2.1 灯光场景的采集策略与曝光控制灯光检测的数据采集和普通目标检测最大的区别在于你的目标本身是发光体相机的自动曝光会跟你作对。自动曝光遇到强光源会压暗整体画面导致光源周围的暗部细节丢失遇到大面积暗场景又会拉高增益把噪点当信号。我一般会锁定曝光三要素固定快门、固定光圈、固定 ISO用 RAW 或高质量 JPEG 采集后期再统一做色调映射。如果场景跨度大白天室内灯 夜间路灯就分场景分别锁定参数不要指望一组参数通吃。采集时要刻意覆盖几类难样本过曝到光源本体完全饱和的、光晕半径很大的、光源在画面边缘的、多个光源重叠的、光源被部分遮挡的比如灯罩、树枝、以及色温差异大的暖黄路灯 vs 冷白 LED。每类至少占总量的 5%10%否则训练出来的模型在对应场景会明显掉点。数量上单场景单类灯光我建议起步 8001500 张有效标注图多场景多类按场景分别凑够 500 张以上再合并。合成数据或自生成数据训练可以作为补充但灯光检测里合成光晕的物理真实性很难保证我一般只把它当预训练或难样本增强不替代真实采集。采集设备方面如果要做多尺度检测近处大灯和远处小灯同时存在建议至少两种焦距或两个安装高度各采一轮。灯光检测里小目标漏检是重灾区而小目标漏检往往不是模型问题是采集时目标像素太少。提前算一下你期望检测的最远距离上光源在图像里占多少像素如果小于 16×16要么拉近、要么换长焦、要么接受漏检。2.2 标注规范光源本体、光晕与“被照亮区域”的边界标注是灯光检测里最容易埋雷的环节。核心原则框光源本体不框光晕不框被照亮的物体。但实际操作中光源本体和光晕的边界在过曝时是模糊的。我的做法是以“人眼能判断的物理发光面”为准包括灯罩、灯珠、灯带的可视发光区域光晕、镜头眩光、反射光斑一律不标。如果光源被遮挡只露出一部分标可见的发光部分不要脑补完整形状。类别设计上不要一上来就分十几个细类。灯光检测的类别应该按“对你下游任务有区分意义”来定。比如做路灯巡检可以分“正常亮灯 / 闪烁灯 / 熄灭灯熄灭的按灯罩本体标”做屏幕检测可以分“亮点 / 暗点 / 亮线”。如果只是做“有没有灯”那就单类。类别越多每类需要的样本量越大标注一致性越难保证。我见过太多项目死在类别体系设计上训到一半发现两类根本分不开回头重标。标注工具用 LabelImg、CVAT、Labelme 都行关键是导出格式统一。灯光检测建议用 YOLO txt 格式class x_center y_center w h归一化因为后续训练链路最顺。标注时注意框要贴紧发光面不要留太多背景多个光源紧挨着时框可以重叠但不要合并成一个框否则模型学不会分离。标注完成后一定要做一轮交叉复核至少抽 20% 检查灯光检测的标注错误里“把反光当光源”和“光晕框太大”占绝大多数。# 数据目录结构建议YOLO 格式 lights_dataset/ ├── images/ │ ├── train/ # 训练集图片 │ ├── val/ # 验证集图片 │ └── test/ # 测试集图片可选 ├── labels/ │ ├── train/ # 对应 txt 标注 │ ├── val/ │ └── test/ └── data.yaml # 数据集配置文件上面这个结构是 YOLO 系列训练时的常见约定images 和 labels 下的子目录名要一一对应文件名不含扩展名也要一致。data.yaml 里写路径、类别数和类别名。注意路径最好用绝对路径或相对于训练脚本工作目录的路径避免训练时找不到文件。2.3 数据划分与类别平衡的实操参数训练集 / 验证集 / 测试集的比例灯光检测我一般用 7:2:1 或 8:1:1。如果样本总量少低于 1000用 8:1:1验证集只用来监控过拟合最终指标看测试集。划分时要按场景分层白天、夜间、室内、室外各自按比例分到三个集合里不要随机打乱后直接切否则可能出现验证集全是夜间、训练集全是白天的情况指标会骗你。类别平衡方面灯光检测常见的长尾是“正常灯”远多于“故障灯”。如果直接训模型会偏向多数类。处理方式有三种一是对少数类做过采样复制或增强二是用 YOLO 的类别权重参数部分版本支持三是在 loss 里对少数类加权。我一般先用过采样 增强把比例拉到 1:3 以内再训。增强手段里亮度 / 对比度扰动、高斯噪声、运动模糊对灯光检测最有效但要注意亮度扰动不要强到把光源本体变成非光源否则标注就错了。# 简单的按场景分层划分脚本伪代码思路 import os, random, shutil scenes {day: [], night: [], indoor: [], outdoor: []} # 假设文件名里带场景前缀如 day_001.jpg for f in os.listdir(all_images): for s in scenes: if f.startswith(s): scenes[s].append(f) for s, files in scenes.items(): random.shuffle(files) n len(files) n_train int(n * 0.8) n_val int(n * 0.1) train, val, test files[:n_train], files[n_train:n_trainn_val], files[n_trainn_val:] # 分别复制到对应目录同时复制同名 txt这段逻辑的关键是“先按场景分组再在组内随机划分”保证每个集合里各场景比例接近。参数上0.8/0.1/0.1 是样本少时的保守选择样本多5000可以用 0.7/0.2/0.1。复制时记得图片和标注一起搬漏一个就会在训练时报“找不到标签”。3. 从原始标注到 YOLO 训练集转换、增强与配置文件3.1 VOC / COCO 转 YOLO 格式的脚本与四个边界坑很多团队早期用 LabelImg 存成 VOC XML或者用 COCO JSON转到 YOLO txt 时容易出问题。核心转换逻辑读 XML/JSON 里的 bboxxmin, ymin, xmax, ymax算中心点和宽高再分别除以图像宽高做归一化。坑主要在边界处理一是框超出图像边界xmin0 或 xmaxwidth要裁剪到 [0, width]二是宽高为 0 或负直接丢弃三是类别名到类别 id 的映射要固定不能每次转换顺序变四是图像尺寸要从图片文件实际读取不能信 XML 里写的尺寸有时不一致。import xml.etree.ElementTree as ET from PIL import Image import os def voc_to_yolo(xml_path, img_path, class_map, out_txt): tree ET.parse(xml_path) root tree.getroot() w, h Image.open(img_path).size # 以实际图片尺寸为准 lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_map: continue cls_id class_map[name] bbox obj.find(bndbox) xmin max(0, float(bbox.find(xmin).text)) ymin max(0, float(bbox.find(ymin).text)) xmax min(w, float(bbox.find(xmax).text)) ymax min(h, float(bbox.find(ymax).text)) bw, bh xmax - xmin, ymax - ymin if bw 1 or bh 1: continue # 丢弃无效框 xc (xmin xmax) / 2 / w yc (ymin ymax) / 2 / h lines.append(f{cls_id} {xc:.6f} {yc:.6f} {bw/w:.6f} {bh/h:.6f}) with open(out_txt, w) as f: f.write(\n.join(lines))参数说明class_map 是 {normal_light:0, fault_light:1} 这样的字典必须全局一致归一化保留 6 位小数足够裁剪边界时用 max/min 而不是直接丢弃因为灯光检测里光源在画面边缘的情况很常见直接丢会损失难样本。转换后建议随机抽 10 张可视化检查确认框位置和类别没错。3.2 灯光检测的增强参数亮度、噪声与运动模糊怎么设YOLO 训练配置里有一组增强超参灯光检测要针对性调。以常见 YOLO 配置为例hsv_h色调、hsv_s饱和度、hsv_v亮度三个参数里hsv_v 最关键。灯光检测中光源本体的亮度变化范围很大适度提高 hsv_v 的上限能让模型对过曝更鲁棒但不要超过 0.5否则可能把暗光场景整体提亮到失真。hsv_s 可以设 0.5 左右应对不同色温。hsv_h 建议小一点0.015 左右因为灯光颜色变化通常不是色调旋转而是色温偏移。噪声和模糊方面灯光检测夜间图像噪点明显可以加高斯噪声增强运动模糊对车载或手持采集的路灯检测有用。但要注意增强是在线做的每次 epoch 随机所以不要设得太激进否则模型学到的分布偏离真实测试分布。我一般先用默认增强训一版看指标再针对性加。另外mosaic 增强对小目标检测有帮助但灯光检测里如果光源本身很大mosaic 拼接后可能出现不自然的光照边界可以适当降低 mosaic 概率比如 0.5。# data.yaml 示例 path: /data/lights_dataset train: images/train val: images/val test: images/test nc: 2 names: [normal_light, fault_light]这个配置文件里path 是数据集根目录train/val/test 是相对路径。nc 是类别数names 顺序必须和标注时的 class_id 一致。如果训练时报“Label class x exceeds nc”就是类别 id 超了回去检查转换脚本的 class_map。3.3 用 YOLOv8 在本地跑通灯光检测的最小命令环境准备好后ultralytics 包安装、GPU 可用最小训练命令如下。这里以 YOLOv8 为例v5 和其他版本的参数名略有差异但思路一致。# 安装依赖如果还没装 pip install ultralytics # 最小训练命令 yolo detect train \ data/data/lights_dataset/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/lights \ nameexp1参数说明model 用预训练权重yolov8n.pt 是最小模型适合先跑通效果不够再换 s/m/l。epochs100 是起步值灯光检测数据量不大时容易过拟合patience20 表示验证指标 20 轮不提升就早停。imgsz640 是默认如果小目标多可以提到 960 或 1280但显存和速度要权衡。batch 根据显存调16 是 8G 显存左右的保守值。lr00.01 是初始学习率如果 loss 震荡明显可以降到 0.005。训练过程中重点看 val 的 mAP50 和 mAP50-95灯光检测里 mAP50 通常能到 0.8 以上但 mAP50-95 会低不少因为光源边界模糊导致高 IoU 阈值下匹配困难这是正常的。4. 灯光检测训练中的避坑与排查5 个血泪经验4.1 现象训练 loss 正常下降但验证 mAP 极低原因最常见的是标注格式或类别映射错了。比如 txt 里 class_id 从 1 开始VOC 习惯但 YOLO 要求从 0 开始或者归一化时用了错误的图像尺寸。另一个可能是验证集和训练集的场景分布差异太大比如验证集全是夜间过曝图训练集没有。解决先抽 5 张验证集图片用训练好的模型推理可视化看框在哪。如果框位置完全乱检查标注转换如果框位置对但类别错检查 names 顺序。再统计训练集和验证集的亮度分布差异大就重新分层划分。4.2 现象模型把反光、光晕当成光源误检多原因标注时把光晕或反光框进去了或者负样本没有光源但有反光的图太少。灯光检测里水面反光、玻璃反射、金属高光都是常见误检源。解决回查标注把光晕和反光框去掉补充负样本特别是“有亮斑但无光源”的图负样本里不要标任何框但要让模型看到这些场景。负样本比例可以占总量的 10%20%。另外推理时适当提高置信度阈值如 0.5能压掉一部分低置信误检但会牺牲召回要权衡。4.3 现象小光源漏检严重远处路灯基本检不到原因小目标在 640 分辨率下像素太少或者训练时小目标样本占比低。灯光检测里小目标漏检往往还跟光晕有关——远处光源在图像里就是一个亮点加一圈光晕标注框如果只框了亮点模型学到的特征太弱。解决提高输入分辨率imgsz960 或 1280或者用切片推理把大图切小块分别检测再合并。数据层面过采样小目标图片或者在增强时不要过度缩放。如果光源本身就是一个点考虑把标注框适当放大到包含部分光晕让模型有更多特征可学但这会牺牲定位精度要按下游需求决定。4.4 现象训练到后期 mAP 不升反降验证 loss 上升原因过拟合。灯光检测数据集通常不大模型容量相对过剩。另外如果增强太弱模型会记住训练集的特定光源样式。解决加增强亮度、噪声、模糊、随机裁剪加权重衰减减小模型从 l 换到 s 或 n早停。如果数据量实在少考虑用合成数据或自生成数据做预训练再在真实数据上微调。灯光检测里合成光晕的物理模型如果做得不好预训练收益有限但至少能让模型先学会“亮斑”这个低级特征。4.5 现象推理速度慢达不到实时原因模型太大、输入分辨率太高、或者后处理NMS耗时。灯光检测如果用在车载或巡检设备上实时性往往是硬指标。解决换小模型n 或 s降低 imgsz用 TensorRT 或 ONNX Runtime 加速NMS 用 GPU 版本。另外如果场景里光源数量不多可以限制每张图的最大检测数减少后处理负担。灯光检测的类别少模型其实不需要很大n 模型在 640 分辨率下通常能到 100 FPS取决于硬件足够多数场景。5. 灯光检测的进阶技巧用验证集反推数据问题训练完一版后别只看 mAP 数字。我习惯做一件事把验证集里所有漏检和误检的图挑出来按场景和光照条件分类统计哪类错得最多。灯光检测里错误往往集中在几个特定模式上——比如“暖黄路灯 湿地面反光”“LED 屏 相机频闪”“多灯重叠”。找到这些模式后针对性补数据比盲目加量有效得多。具体操作用训练好的模型对验证集推理保存预测结果然后写脚本对比标注和预测输出漏检标注有、预测无和误检预测有、标注无的图片列表。按文件名里的场景前缀分组看哪组错误率高。如果某一组错误率明显高就专门去采这类场景的数据或者对这类场景做针对性增强。# 对比预测和标注输出漏检/误检图片列表思路 import os def compare(pred_dir, label_dir, img_dir, iou_thres0.5): for img_name in os.listdir(img_dir): preds load_preds(os.path.join(pred_dir, img_name)) gts load_labels(os.path.join(label_dir, img_name)) matched_gt, matched_pred match_by_iou(preds, gts, iou_thres) if len(matched_gt) len(gts): print(漏检:, img_name) if len(matched_pred) len(preds): print(误检:, img_name)这个脚本的核心是 IoU 匹配阈值 0.5 是常用值。灯光检测里如果光源边界模糊可以适当降低到 0.4否则会把定位偏差当成漏检。输出列表后人工看一遍确认是标注问题还是模型问题。如果是标注问题回去修标注如果是模型问题补数据或调增强。另一个技巧是用验证集的错误反推标注规范。比如发现模型总把“灯罩边缘”当成独立光源说明标注时灯罩和灯珠的边界不一致需要统一规范。灯光检测的标注一致性比数量更重要一个团队里不同人标的框风格差异大模型就会学乱。我一般会先让所有人标 20 张同一批图对比差异统一标准后再批量标。最后说一个我自己的习惯每次训练前先拿 10 张图过一遍标注用脚本画框可视化确认没问题再开训。这个动作花不了几分钟但能省下几小时的白跑。灯光检测这个方向数据上的每一分投入都会在指标上体现出来模型结构反而不用太折腾。希望帮到你。本文还有配套的精品资源点击获取