简介这份资源面向计算机视觉入门与进阶开发者聚焦基于YOLOv5的灯光检测任务提供一套可直接复用的自训练数据与工程代码。包内共1580个文件以696张jpg图像与630个txt标注文件构成数据集主体另含65个yaml配置、54个py脚本、15个pt权重及若干png、xml、mp4等辅助素材压缩包约603.83MB覆盖数据采集、标注、增强到模型训练与验证的完整链路。资源中保留了训练日志与配置文件便于读者复现灯光目标检测流程理解锚框预测、特征提取与类别区分等关键环节并可按需微调YOLOv5s、m、x等不同规模模型。目前已有252人学习下载适合希望掌握自训练数据构建与灯光检测实战的读者参考借鉴。1. 灯光检测自己造数据从零跑通一套能用的训练集做灯光检测这行的十有八九都卡在同一个地方公开数据集里全是白天街景一到夜间、隧道口、地下车库这种场景模型直接集体翻车。我最早接的一个地下车库车位灯检测项目拿 COCO 预训练权重微调白天 mAP 能到 0.85晚上掉到 0.4 都不到漏检的灯比检出来的还多。后来才想明白问题不在模型在数据——灯光检测的核心特征光斑、光晕、过曝边缘、色温偏移在通用数据集里几乎没有覆盖。所以「自己训练数据灯光检测」这件事本质是让你从数据源头把场景特征喂进去而不是指望调参救场。这篇笔记面向的是手上有具体灯光检测需求、愿意自己标注和训练的人从数据采集、标注规范、增强策略一路讲到 YOLOv8 训练和排错中间该踩的坑我都替你踩过了。2. 灯光检测的数据从哪来采集、合成与标注规范灯光检测和普通目标检测最大的区别在于你要检测的「灯」在图像里往往是一个高亮区域边界模糊、形态随曝光变化甚至同一盏灯在不同帧里像素分布都不一样。这就决定了数据采集和标注不能照搬通用流程。2.1 真实采集相机参数比数量更重要我一般会先固定一套采集参数再谈拍多少张。灯光场景的动态范围极大白天和夜间的曝光策略必须分开设定否则你标出来的框在训练时会被模型当成噪声。常见做法是夜间场景用低 ISO100-400 短曝光1/500s 以上避免灯芯过曝成一团白白天逆光场景反而要适当提高曝光补偿让灯具轮廓和背景有可分离的梯度。分辨率不低于 1920×1080因为小灯在低分辨率下可能只有几个像素标注框和背景几乎无法区分。采集时按场景分层不要随机拍。我的分层维度是场景类型地下车库 / 隧道 / 夜间道路 / 室内厂房、灯具类型圆形吸顶灯 / 长条灯管 / 点状指示灯 / 车灯、光照条件全暗 / 半亮 / 逆光 / 雨雾。每个组合至少 200 张总量控制在 2000-5000 张起步太少模型学不到光斑特征太多标注成本吃不消。# 采集时用脚本按场景自动分目录避免后期整理 # 目录结构dataset_raw/{scene}/{light_type}/{timestamp}.jpg python capture.py --scene garage --light_type ceiling --interval 0.5 --count 300这段脚本的逻辑是按场景和灯具类型分目录存储interval 控制连拍间隔避免相邻帧过于相似count 是每个组合的目标张数。参数上interval 不建议低于 0.3 秒否则相邻帧差异太小等于变相减少数据多样性。2.2 合成数据用 Blender 或游戏引擎补长尾真实采集最难覆盖的是极端场景——比如灯被部分遮挡、雨夜光晕扩散、多灯重叠。这时候合成数据训练或自生成数据训练就派上用场了。我一般用 Blender 搭一个简单场景一个平面背景 若干发光球体/圆柱体调整发光强度、色温和相机角度批量渲染。# Blender Python 脚本片段批量渲染不同光照条件下的灯光场景 import bpy import random for i in range(500): # 随机化灯光位置、强度、色温 light bpy.data.objects[LightSource] light.location (random.uniform(-5, 5), random.uniform(-5, 5), random.uniform(2, 4)) light.data.energy random.uniform(50, 500) light.data.color (random.uniform(0.8, 1.0), random.uniform(0.7, 0.95), random.uniform(0.5, 0.9)) # 随机化相机角度 bpy.data.objects[Camera].rotation_euler (random.uniform(0, 0.5), 0, random.uniform(0, 6.28)) bpy.context.scene.render.filepath f/output/synth_{i:04d}.png bpy.ops.render.render(write_stillTrue)逻辑说明每轮循环随机化光源的三维位置、发光强度和 RGB 色温同时随机相机俯仰角和偏航角模拟不同视角下的灯光形态。参数上energy 范围对应从暗到过曝color 的 RGB 范围偏向暖色温实际灯具多为 2700K-6500K。合成数据的标注框可以直接从渲染时的物体边界框导出省去人工标注。注意合成数据不能单独用于训练必须和真实数据混合比例控制在 1:3 到 1:5 之间。纯合成数据训出来的模型在真实场景下会出现严重的域偏移表现为把反光地面误检成灯。2.3 标注规范灯光检测的框怎么画才不坑自己灯光检测的标注有两个高频翻车点一是框画得太紧把光晕切掉二是把反光当灯标了。我的标注规则是以灯芯最亮区域为中心框的边界扩展到光晕外沿可见处通常比灯芯大 1.5-2 倍。如果光晕扩散到无法判断边界比如雨夜就按灯芯的 2 倍画并在标注属性里标记blurheavy训练时可以对这类样本降权。反光问题更隐蔽。地下车库地面有水渍时灯的倒影和真灯在图像上几乎一样。我的处理方式是只标直接可见的光源倒影一律不标但在数据集中保留这些帧让模型学会区分「光源本体」和「反射光」。如果倒影误检率超过 10%就需要专门加一批带倒影的负样本。# 用 labelImg 或 CVAT 标注后检查框尺寸分布是否合理 import json import numpy as np with open(annotations.json) as f: data json.load(f) widths [ann[bbox][2] for ann in data[annotations]] heights [ann[bbox][3] for ann in data[annotations]] print(f框宽均值: {np.mean(widths):.1f}, 标准差: {np.std(widths):.1f}) print(f框高均值: {np.mean(heights):.1f}, 标准差: {np.std(heights):.1f}) # 如果标准差过大说明标注尺度不一致需要统一标准这段检查脚本的作用是发现标注尺度漂移。如果框宽标准差超过均值的 50%说明不同标注员对光晕边界的判断差异太大需要重新对齐标准。3. 把标注转成 YOLOv8 能吃的格式转换脚本与四个边界坑数据标完了下一步是转成 YOLOv8 训练自己的数据集所需的格式。YOLOv8 用的是 YOLO 格式的 txt 标注每行class_id x_center y_center width height全部归一化到 0-1。看起来简单但实际转换时有四个坑几乎每个人都会踩。3.1 从 VOC / COCO 转 YOLO 格式的完整脚本如果你用的是 labelImg 标出来的 VOC XML或者从 COCO JSON 转过来下面这个脚本可以直接用。import os import xml.etree.ElementTree as ET import json def voc_to_yolo(xml_path, img_w, img_h, class_map): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in class_map: continue cls_id class_map[cls_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_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h # 边界裁剪防止超出 0-1 x_center max(0, min(1, x_center)) y_center max(0, min(1, y_center)) w max(0, min(1, w)) h max(0, min(1, h)) lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) return lines class_map {ceiling_light: 0, tube_light: 1, indicator: 2, car_light: 3} # 遍历所有 XML 并写入对应的 txt for xml_file in os.listdir(annotations_xml): img_name xml_file.replace(.xml, .jpg) img_w, img_h 1920, 1080 # 替换为实际图像尺寸 lines voc_to_yolo(os.path.join(annotations_xml, xml_file), img_w, img_h, class_map) with open(flabels/{xml_file.replace(.xml, .txt)}, w) as f: f.write(\n.join(lines))逻辑说明脚本读取 VOC XML 中的每个 object提取类别名和边界框坐标先转为中心点宽高再除以图像宽高做归一化。class_map 是你自己定义的类别索引必须和训练时的 data.yaml 一致。边界裁剪那几行是防止标注框超出图像范围导致 YOLO 训练时报错。参数上img_w 和 img_h 必须和实际图像尺寸一致不能硬编码。如果你的数据集里图像尺寸不统一建议先统一 resize 到同一尺寸再转换否则归一化会出错。3.2 四个边界坑越界框、空标注、类别不平衡、文件名不匹配越界框标注时如果框超出了图像边界比如灯在画面边缘被截断归一化后坐标可能小于 0 或大于 1。YOLOv8 对越界坐标不会报错但训练时这些框会被静默忽略导致某些位置的灯永远学不到。解决方式就是在转换脚本里做裁剪如上所示。空标注有些图像里没有灯对应的 txt 文件应该是空的但不能没有这个文件。YOLOv8 要求每张图都有对应的 txt空文件表示负样本。如果直接跳过不生成训练时会报「找不到标签文件」。类别不平衡灯光检测里吸顶灯可能占 80%指示灯只有 5%。直接训练会导致模型对少数类几乎无响应。我的做法是在 data.yaml 里不处理而是在训练时用cls损失权重或者对少数类做过采样。YOLOv8 本身没有内置的类别权重参数所以过采样更实际——把少数类的图像在文件列表中重复 3-5 次。文件名不匹配图像是IMG_001.jpg标注是IMG_001.xml转换后是IMG_001.txt但 YOLOv8 要求图像和标签在同一目录结构下文件名除扩展名外完全一致。如果图像经过重命名或 resize 后没同步更新标签名训练时就会大量报「missing label」。# 检查图像和标签文件名是否一一对应 for img in images/*.jpg; do labellabels/$(basename $img .jpg).txt if [ ! -f $label ]; then echo 缺失标签: $label fi done这段 bash 脚本遍历所有图像检查对应的 txt 是否存在。跑一遍就能发现所有文件名不匹配的问题比训练时报错后再回头找高效得多。3.3 data.yaml 怎么写路径、类别数和名称的对应关系YOLOv8 训练自己的数据集依赖一个 data.yaml 文件里面定义了训练集、验证集路径和类别信息。path: /home/user/light_detection # 数据集根目录 train: images/train # 训练集图像目录相对 path val: images/val # 验证集图像目录 nc: 4 # 类别数 names: # 类别名称顺序必须和 class_map 一致 0: ceiling_light 1: tube_light 2: indicator 3: car_lightpath 是根目录train 和 val 是相对路径。YOLOv8 会自动在 labels 目录下找对应的 txt目录结构必须是images/train/xxx.jpg对应labels/train/xxx.txt。nc 和 names 的索引必须和转换脚本里的 class_map 完全一致否则模型学到的类别会错位。提示如果验证集 mAP 一直上不去先检查 data.yaml 里的路径是否写对。我见过太多人因为 train 路径写成了绝对路径导致 YOLOv8 找不到文件训练直接静默失败。4. YOLOv8 训练灯光检测模型参数怎么设、什么时候停数据准备好了接下来是训练。YOLOv8 训练自己的数据集命令很简单但参数设不对结果可能差出一倍 mAP。这一章讲清楚每个关键参数的含义和我实际用的值。4.1 从预训练权重开始yolov8n 还是 yolov8m灯光检测的模型选型取决于你的部署环境。如果跑在边缘设备Jetson、树莓派用 yolov8n参数量小、推理快但小目标检测能力弱。如果跑在服务器 GPU 上用 yolov8m 或 yolov8lmAP 通常能高 5-10 个点。我一般先用 yolov8n 跑一轮 baseline确认数据管道没问题再换 yolov8m 正式训练。预训练权重用 COCO 的yolov8n.pt或yolov8m.pt不要从零训灯光检测的数据量撑不起从零训练。# 第一轮用 yolov8n 快速验证数据管道 yolo detect train datadata.yaml modelyolov8n.pt epochs50 imgsz640 batch16 # 第二轮换 yolov8m 正式训练 yolo detect train datadata.yaml modelyolov8m.pt epochs200 imgsz640 batch8逻辑说明第一轮 50 epochs 足够看出数据有没有问题比如 loss 不下降、mAP 为 0。如果第一轮正常第二轮换大模型、加 epochs。batch 根据显存调整yolov8m 在 8GB 显存上 batch8 比较稳。4.2 灯光检测的必调参数imgsz、lr0、mosaicimgsz默认 640但灯光检测里小灯可能只占十几个像素640 下几乎看不清。我的经验是至少 960如果显存够就上 1280。代价是训练速度下降约 2-3 倍但小目标召回率能提升 15% 以上。lr0初始学习率默认 0.01。灯光检测的数据集通常不大几千张0.01 容易震荡。我一般降到 0.001配合 cosine 调度loss 曲线更平滑。mosaicYOLOv8 默认开启 mosaic 增强把四张图拼成一张。这对灯光检测有帮助因为拼接后会出现更多灯的组合形态。但如果你的灯都是大目标mosaic 可能让灯被裁切反而有害。判断标准是如果训练集里灯的框平均面积超过图像面积的 10%就关掉 mosaicmosaic0.0。# 灯光检测的推荐参数组合 yolo detect train \ datadata.yaml \ modelyolov8m.pt \ epochs200 \ imgsz960 \ batch8 \ lr00.001 \ lrf0.01 \ mosaic0.5 \ mixup0.1 \ patience30 \ device0参数说明lrf 是最终学习率因子0.01 表示最终 lr lr0 * 0.01。mixup 是图像混合增强0.1 表示 10% 的概率做混合对灯光检测有助于提升对光晕重叠的鲁棒性。patience30 表示 30 个 epoch 没有提升就早停避免过拟合。4.3 训练过程怎么看loss 曲线和 mAP 的对应关系训练启动后重点看三个指标box_loss、cls_loss、mAP50。box_loss 下降但 cls_loss 不降说明模型能定位灯但分不清类别通常是类别不平衡或标注类别错误。cls_loss 下降但 box_loss 震荡说明标注框尺度不一致回到第 2 章检查标注规范。mAP50 在 50 epoch 后还低于 0.3基本可以停了不是参数问题就是数据问题。我的一般预期是2000 张真实数据 1000 张合成数据yolov8m 在 100 epoch 左右 mAP50 能到 0.75-0.85。# 用训练输出的 results.csv 画 loss 和 mAP 曲线 import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/detect/train/results.csv) fig, axes plt.subplots(1, 2, figsize(12, 4)) axes[0].plot(df[epoch], df[train/box_loss], labelbox_loss) axes[0].plot(df[epoch], df[train/cls_loss], labelcls_loss) axes[0].legend() axes[1].plot(df[epoch], df[metrics/mAP50(B)], labelmAP50) axes[1].legend() plt.savefig(training_curve.png)这段脚本读取 YOLOv8 训练日志画出 loss 和 mAP 曲线。如果 box_loss 和 cls_loss 同步下降且 mAP 稳步上升说明训练健康。如果 loss 下降但 mAP 不动大概率是过拟合需要加数据或加增强。5. 灯光检测训练的避坑与排查5 个血泪教训这一章是我自己踩过的坑按「现象 → 原因 → 解决」写每条都对应真实翻车场景。5.1 现象夜间图像 mAP 正常白天逆光全部漏检原因训练集里夜间样本占 90%白天逆光只有零星几张。模型学到了「灯 暗背景上的亮斑」白天逆光下背景本身就很亮模型无法区分。解决按光照条件分层采样白天逆光场景至少占 20%。如果真实数据不够用合成数据补调整背景亮度模拟逆光。5.2 现象模型把路灯和车灯混为一类原因标注时类别定义模糊路灯和车灯在远距离下形态相似标注员自己都分不清。解决重新定义类别边界。我的做法是按安装位置分固定安装的是路灯/吸顶灯移动的是车灯。如果实在分不清合并成一个「光源」类先保证检测率后续再做细分类。5.3 现象训练 loss 正常下降但验证集 mAP 始终为 0原因data.yaml 里的 val 路径写错或者验证集的标签文件缺失。YOLOv8 在验证时找不到标签mAP 计算为 0 但不报错。解决跑一遍第 3.2 节的文件名检查脚本确认验证集每张图都有对应 txt。另外检查 data.yaml 里 val 路径是相对 path 的不是绝对路径。5.4 现象推理时同一盏灯出现多个重叠框原因NMS非极大值抑制的 IoU 阈值设太高灯光检测里光晕导致同一盏灯产生多个候选框IoU 超过默认的 0.7 就被保留。解决推理时降低 IoU 阈值到 0.5 甚至 0.4。命令里加iou0.5。如果还有重叠说明标注时同一盏灯被标了多次回到标注数据去重。5.5 现象模型在训练集上 mAP 0.95测试集只有 0.5原因过拟合。灯光检测数据集通常场景单一模型记住了背景而不是灯的特征。解决加数据增强mosaic、mixup、随机亮度对比度加 dropoutYOLOv8 里通过dropout0.1设置或者直接加数据。我的经验是训练集和测试集 mAP 差距超过 0.2 就是过拟合优先加数据而不是调模型。6. 灯光检测的进阶技巧用物理先验和合成数据把 mAP 再推一截到这一步你的模型应该能跑了。但如果想把 mAP 从 0.8 推到 0.9或者想解决一些顽固的漏检下面两个技巧值得试。6.1 把计算成像的物理先验塞进数据增强灯光检测的本质是检测「发光体」而发光体在图像上的表现遵循光学规律光斑大小和距离成反比光晕强度和曝光时间成正比。普通的数据增强旋转、缩放不改变这些物理关系但你可以设计物理一致的增强。具体做法是对每张训练图随机模拟不同的曝光时间。曝光时间翻倍光斑面积扩大约 1.4 倍亮度提升但不过曝。用 OpenCV 实现一个简单的曝光模拟import cv2 import numpy as np def simulate_exposure(img, factor): 模拟曝光变化factor 1 增加曝光 1 减少曝光 # 线性调整亮度 adjusted np.clip(img.astype(np.float32) * factor, 0, 255).astype(np.uint8) # 对高亮区域做光晕扩散模拟过曝 if factor 1.5: blur cv2.GaussianBlur(adjusted, (15, 15), 0) mask adjusted 200 # 只对高亮区域做扩散 adjusted[mask] blur[mask] return adjusted # 在数据加载时随机应用 img cv2.imread(light.jpg) augmented simulate_exposure(img, factornp.random.uniform(0.5, 2.0))逻辑说明factor 控制曝光倍数大于 1.5 时对高亮区域做高斯模糊模拟过曝时的光晕扩散。这样模型见到的灯光形态更丰富对真实场景中不同曝光下的灯更鲁棒。参数上高斯核大小 15 是经验值灯越大核越大。6.2 用合成数据做难例挖掘合成数据的另一个用法不是直接训练而是做难例挖掘。先用真实数据训一个 baseline 模型然后用合成数据推理找出模型置信度低但实际有灯的样本把这些样本加入训练集。这比随机加合成数据有效得多因为合成数据里的难例是可控的——你可以专门生成遮挡、重叠、极端色温的场景。我的一般流程是真实数据训 baseline → 合成 500 张极端场景 → 用 baseline 推理 → 挑出漏检的 100 张 → 人工确认后加入训练集 → 重新训练。这一轮下来mAP 通常能再涨 3-5 个点。6.3 验证方法用混淆矩阵和 PR 曲线定位问题训练完之后别只看 mAP。YOLOv8 会自动生成混淆矩阵和 PR 曲线在runs/detect/val/目录下。混淆矩阵能告诉你哪两个类别在互相误检PR 曲线能告诉你哪个类别的召回率拖了后腿。我的习惯是如果某个类别的 AP 明显低于其他类先看混淆矩阵确认是被误检成别的类还是单纯漏检。被误检就调整类别定义或加负样本漏检就加该类数据或提高 imgsz。最后说个我自己的教训灯光检测这行数据质量比模型结构重要十倍。我见过太多人花一周调 YOLOv8 的参数mAP 涨了 0.5 个点但花一天重新标 200 张难例mAP 直接涨 5 个点。所以如果你刚开始做先把标注规范和场景分层做扎实模型用默认参数都能出不错的结果。希望帮到你。本文还有配套的精品资源点击获取
