资讯详情 光伏板缺陷检测数据集:2400张VOC+YOLO双格式训练实践
📅 2026/10/18 13:04:17
简介面向光伏组件缺陷检测与质检模型训练这份数据集覆盖2400张光伏板表面图像针对crack裂纹、grid网格、spot斑点三类常见缺陷提供Pascal VOC与YOLO双格式标注标注框合计2628个。标注工作基于labelImg完成采用矩形框规则清晰反映缺陷位置与类别适用于训练裂纹、网格纹和斑点识别模型。压缩包共2000个文件其中1999个XML标注文件构成标注主体另含TXT文件包体约310.4MB可直接用于目标检测模型训练、格式转换或标注规范学习。目前已有369人学习下载。数据集统一了VOC与YOLO两种主流格式减少预处理成本适合新能源领域算法工程师、科研人员及目标检测初学者开展模型训练与缺陷识别实验。1. 光伏板缺陷检测数据集2400张双格式到底能帮你省多少事做过光伏电站巡检或者组件产线质检的人都知道真正卡住项目的往往不是模型选型而是数据。现场拍回来的图光照乱、角度歪、缺陷小自己标几百张就得熬夜标完还要转格式、核对坐标一套流程下来两周就没了。这个光伏板太阳能版缺陷检测数据集VOCYOLO双格式、2400张、3个类别正好把数据准备这段最磨人的路给铺好了。它的定位很直接拿来即训省掉从零标注和格式转换的时间适合正在做工业视觉落地、目标检测入门或者想快速验证缺陷检测方案的从业者。这篇笔记就把数据集结构、格式转换、训练配置和几类高频翻车点一次讲透。2. 先看清数据集里有什么目录结构、标注文件与3个类别拿到任何数据集第一件事都不是急着跑训练而是把目录结构、标注格式、类别定义弄清楚。这一步跳过去后面八成要返工。常见的数据集会同时给VOC和YOLO两套目录这套2400张的光伏板数据集也是这个布局一份数据两种格式方便你在不同框架之间切换。2.1 VOC格式的目录结构与xml标注长什么样VOC格式的目录结构一般是这样的dataset/ ├── VOCdevkit/ │ ├── VOC2007/ │ │ ├── JPEGImages/ # 原始图片jpg格式 │ │ ├── Annotations/ # xml标注文件和图片同名 │ │ ├── ImageSets/ │ │ │ └── Main/ # train.txt / val.txt训练验证划分Annotations里的xml文件记录了每张图的目标类别和真实框坐标。打开一个xml核心内容长这样annotation filenamepv_00123.jpg/filename size width1280/width height960/height /size object namecrack/name bndbox xmin312/xmin ymin418/ymin xmax845/xmax ymax602/ymax /bndbox /object /annotation这里name是类别名bndbox是左上角和右下角坐标单位是像素。xml的优点是可读性强、信息完整图片尺寸都写在里面坐标不需要额外换算就能看懂。缺点是文件多、解析慢训练框架一般不直接用xml要先转成txt。检查数据集质量时我一般会把xml里的坐标画回图片上用OpenCV一行代码就能验证标注框位置是否贴合缺陷区域。这一步能发现不少标注偏移问题尤其是光伏板反光导致的边界不准。像素级核对虽然枯燥但确实是数据质量控制里最有效的一步。2.2 YOLO格式的txt标注与归一化坐标YOLO系列框架用的标注格式是每个图片对应一个txt文件每行一个目标格式为类别id x_center y_center width height注意这四项都是归一化坐标即除以图片宽高后的比例值范围0到1。比如一张1280×960的图上有裂纹目标像素坐标为xmin312, ymin418, xmax845, ymax602对应的txt内容就是0 0.4520 0.5313 0.4164 0.1917换算逻辑不复杂x_center (xmin xmax) / 2 / widthwidth (xmax - xmin) / widthy轴同理。YOLO目录下每个txt和图片同名放在labels文件夹里目录结构一般是dataset/ ├── images/ │ ├── train/ # 训练图片 │ └── val/ # 验证图片 ├── labels/ │ ├── train/ # 训练图片对应的txt │ └── val/ # 验证图片对应的txt这个结构同时也是框架训练时默认的图片和标签路径约定训练配置里的train和val路径直接指到这两个目录即可。2.3 3个类别分别对应什么缺陷训练前要做什么检查标题里写了3个类别光伏板场景下最常见的三类缺陷是隐裂、热斑、脏污遮挡。具体到这2400张里类别定义和分布需要先确认清楚我一般拿到数据集先跑一个统计脚本import os # 遍历labels目录统计各类别数量 label_dirs [train, val] class_count {} # 类别id - 数量 for split in label_dirs: label_path flabels/{split} for fname in os.listdir(label_path): with open(os.path.join(label_path, fname)) as f: for line in f: cls_id int(line.split()[0]) class_count[cls_id] class_count.get(cls_id, 0) 1 print(class_count)运行结果如果出现某一类别数量只有另一种的十分之一那这个数据集在类别层面是不够均衡的后面训练要额外加类别权重或者数据增强来补偿。还有一个值得养成的习惯确认图片尺寸是否统一。光伏板巡检图有的来自无人机、有的来自手持设备尺寸可能从640×480到4000×3000都有。尺寸不统一的话直接送进训练会影响batch组成通常的做法是先统一缩放到一个固定尺寸比如1280×960再进训练流程。如果这个数据集在分布上比较均衡隐裂、热斑、遮挡样本量差距不超过3倍那就可以直接进入格式转换和训练环节。3. VOC与YOLO格式互转一份能直接改用的Python脚本VOC格式适合查看和理解YOLO格式适合直接训练。框架不同需要的格式也不同。比如Faster R-CNN这类两阶段模型通常支持VOC风格的数据YOLO系列则只吃txt标记。实际项目中两种格式之间的转换我用下面的脚本就能完成VOC转YOLO的工作。3.1 两种格式的坐标换算关系两种格式之间转换的核心只有一条数学关系VOC的xmin, ymin, xmax, ymax是像素坐标YOLO的x_center, y_center, width, height是归一化坐标。# VOC像素坐标 - YOLO归一化坐标 x_center (xmin xmax) / 2.0 / img_width y_center (ymin ymax) / 2.0 / img_height box_width (xmax - xmin) / img_width box_height (ymax - ymin) / img_height反过来YOLO转VOC也容易乘回去就行。容易写错的地方有两个一是分母用错有人会把x_center除以图片宽度但y_center却除以高度两个坐标必须各自对应各自的维度二是坐标越界归一化后数值大于1或者小于0的情况多发生在标注本身就有问题时转换脚本里最好加一条断言检查。3.2 转换脚本VOC转YOLO完整可用的VOC转YOLO脚本如下用Python标准库加xml.etree实现不依赖第三方包import os import xml.etree.ElementTree as ET # 类别映射按训练配置里的类别顺序定义 CLASS_MAPPING {crack: 0, hotspot: 1, dust: 2} def voc_to_yolo(xml_file, output_txt): 将单个VOC xml转换为YOLO txt tree ET.parse(xml_file) root tree.getroot() # 读取图片宽高xml里有记录 size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in CLASS_MAPPING: print(f警告: 未知类别 {name} 已跳过) continue # 解析边界框 bndbox obj.find(bndbox) xmin int(bndbox.find(xmin).text) ymin int(bndbox.find(ymin).text) xmax int(bndbox.find(xmax).text) ymax int(bndbox.find(ymax).text) # 坐标换算 x_center ((xmin xmax) / 2.0) / img_w y_center ((ymin ymax) / 2.0) / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h # 越界检查越界时收回到边界 x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) lines.append(f{CLASS_MAPPING[name]} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) # 写入txt with open(output_txt, w) as f: f.write(\n.join(lines)) # 批量转换 xml_dir Annotations txt_dir labels os.makedirs(txt_dir, exist_okTrue) for xml_name in os.listdir(xml_dir): if not xml_name.endswith(.xml): continue txt_name os.path.splitext(xml_name)[0] .txt voc_to_yolo(os.path.join(xml_dir, xml_name), os.path.join(txt_dir, txt_name))脚本里几个参数值得说清楚CLASS_MAPPING必须和YOLO训练配置里的类别顺序保持完全一致类别id从0开始顺序错了模型学到的含义就全错了。越界检查用min/max收边是防止标注框超图片边界的容错但如果原始标注本身偏差很大收边只是止血不改错。如果xml里size标签缺失或为0需要加个判断来跳过否则宽高为0时换算会直接报错。3.3 转换后必须做的三件事核对坐标、抽样可视化、统计类别转换脚本跑通不算完。我的经验是转换之后必须做三件事缺一件都可能带病训练第一是核对坐标随机挑几个xml的数值手算出归一化坐标和txt里的值对比。第二是抽样可视化把txt坐标画回图片上用OpenCV的rectangle画框人工看有没有框错位、框大小异常、目标应该在的位置却没有框。第三是统计类别数量和图片数量确保转换过程没有丢文件每个图片名都有对应的txt类别数量分布和xml时期一致。import cv2 # 抽样可视化把txt标注画回原图 img_path images/train/pv_00123.jpg txt_path labels/train/pv_00123.txt img cv2.imread(img_path) h, w img.shape[:2] with open(txt_path) as f: for line in f: parts line.strip().split() cls_id int(parts[0]) x_center, y_center, box_w, box_h map(float, parts[1:]) xmin int((x_center - box_w / 2) * w) ymin int((y_center - box_h / 2) * h) xmax int((x_center box_w / 2) * w) ymax int((y_center box_h / 2) * h) cv2.rectangle(img, (xmin, ymin), (xmax, ymax), (0, 255, 0), 2) cv2.imwrite(check_vis.jpg, img)这一步没什么玄学就是把标注画出来肉眼能看出框得对不对。看得多了就会发现真正坑人的不是转换而是原标注本身就不准。转换只是把错误的坐标从一个格式搬到了另一个格式。4. 用这份数据集跑通YOLO训练从yaml配置到训练命令格式转换完成、质量控制做完接下来就是用这份缺陷检测数据集训练模型。以YOLO系列框架为例整个训练流程可以拆成三部分写数据集配置文件、调训练命令、盯训练日志。这里不限制具体使用v5还是v8配置思路是通用的。4.1 数据集配置yaml怎么写YOLO训练需要一份yaml文件来告诉框架类别有几个、类别名是什么、图片和标注在哪。结合这个2400张的数据集yaml配置长这样path: /your/absolute/path/to/dataset train: images/train val: images/val nc: 3 names: 0: crack 1: hotspot 2: dustpath一定要写绝对路径相对路径经常会在不同工作目录下启动训练时报找不到图片。train和val是相对path的路径指向图片目录框架会自己找对应的labels目录下的txt。这也就要求图片和txt必须保持同名扩展名可以不同主名必须严格一致。如果数据集里给了train.txt和val.txt的划分文件也就是显式的图片路径列表也可以用那种方式让yaml里的train和val直接指向这两个txt文件路径。两种写法都常见区别只是前者按目录自动找图后者按列表精确控制。4.2 训练命令与三个关键参数配置写好之后训练命令本身不复杂yolo detect train \ modelyolov8s.pt \ datapv_defect.yaml \ epochs100 \ imgsz1280 \ batch16 \ device0 \ projectruns/pv_defect三个参数我建议重点关注imgsz在这类光伏板缺陷数据集上非常关键。光伏组件的裂纹、热斑在整张巡检图里都是小目标图像缩得太小会把缺陷细节抹掉常见的做法是设1280而不是默认的640。epochs在数据集只有2400张时设100轮比较合理太少欠拟合太多容易过拟合后面看验证集loss收敛情况再增减。batch受显存限制视频记忆不足就减半batch32效果通常比16好但显存不够时硬上会导致训练中断。还有一点modelyolov8s.pt的意思是加载这个预训练权重作为初始值这对2400张的小数据集帮助很大。如果从头训练收敛速度和最终精度都会差不少。如果你的场景对速度要求极高后续可以换nano或tiny版本但缺陷检测一般更看重召回率初学者用s起步比较合适。4.3 训练过程的日志怎么看训练日志输出每一轮的box_loss、cls_loss、dfl_loss以及验证集上的mAP50和mAP50-95。大部分人在训练时有两个盲区一是只关注mAP不关注loss二是看到loss下降就以为一切正常。正确的看法是结合两者来判断。train的loss持续下降但val的loss在某一轮开始回升这是过拟合的典型信号需要提前终止或者加数据增强。mAP50达到0.9以上不代表模型真的能上产线mAP50-95才有区分度它考察的是不同IoU阈值下的平均表现对定位精度更敏感。光伏板缺陷检测里框偏一点可能就意味着漏掉一个细小裂纹所以mAP50-95才是更诚实的指标。训练结束后框架会生成weights/best.pt和weights/last.pt。best.pt是按验证集指标挑出来的最优权重后续推理、测试、部署都用它。历史教训是别把自己训练的权重和预训练权重搞混推理时加载错文件会得到完全不符预期的结果还以为是模型不行。5. 光伏板缺陷检测的常见翻车点与排查方法这类数据集在训练和使用过程中翻车的地方挺有规律给大家几条具体的踩坑记录。5.1 现象训练出来的模型mAP很高但实际挑出来的缺陷全是同一类原因类别分布失衡多数类样本充足少数类被淹没。图像中隐裂细长、数量又少模型倾向把精力放在好学的类别上导致少样本类别几乎不预测。解决先跑类别统计脚本确认分布处理方法是给少样本类别加大数据增强权重比如对隐裂这类细长目标做随机旋转和随机裁剪或直接做类别重采样把少样本类在每轮训练中的出现次数提上来。对比一下处理前后的PR曲线少样本类别的召回率改善会比较明显。5.2 现象训练loss正常收敛但验证集mAP50-95极低原因小目标缺陷在全图占比太小特征在下采样过程中丢失。巡检图分辨率高如果直接缩到640训练裂纹可能只剩几个像素宽。这类问题在光伏板这种高分辨率大图场景里尤其常见。解决把imgsz提到1280以上同时确保模型有足够的下采样结构来保留细节。还可以在训练时开启马赛克增强让模型在训练过程中看到更多小尺寸对象的上下文信息即利用拼接图来增加目标相对尺寸的多样性。如果显存不够减小batch而不是降分辨率。5.3 现象模型训练一切正常但对新场景的图片预测效果差原因数据分布漂移。训练集里都是晴天正午拍的图实际应用时客户给的图是阴天、逆光、黄昏拍的光伏板在画面中的色调和反光特征完全不同模型没见过当然预测不好。解决训练时加入光照扰动比如HSV色彩空间的随机增强让模型见过更多亮度和色温变化。实测下来把hsv_h、hsv_s、hsv_v从默认值略微加大对户外光伏场景的泛化能力提升明显。另外可以在自己的数据上补拍一批不同时段的图做微调数量不需要多50到100张就能把分布拉回来。5.4 现象验证集指标稳定但同一张图反复测试结果不一样原因训练/验证集划分不当或者验证集太小且图片有相关性。2400张数据集如果只留5%做验证即120张每轮验证的波动会很大指标忽高忽低你很难判断一个改动到底是变好还是变坏。解决用至少20%的数据做验证集大概500张左右且确保同一块光伏板的不同图片不要同时出现在训练集和验证集里否则模型对拍摄对象有记忆验证指标会虚假地高。划分时可以按图片拍摄时间或板号来分组而不是纯随机划分。5.5 现象推理速度达标但漏检率在产线上不可接受原因漏检的直接原因往往是NMS阈值设得过高或模型置信度阈值设得过高。训练后的默认置信度阈值是0.25实际场景里如果只取0.5以上的结果低置信度的小缺陷全被滤掉。解决应用时先下调到0.1到0.15把候选框全部召回再通过NMS去重看漏检能不能降下来然后结合误报率一起调优。这类小目标缺陷检测核心矛盾是低置信度目标里既有真缺陷也有背景误检阈值选择需要根据产线误报容忍度来定。6. 一个实用验证技巧用混淆矩阵和批量推理定位模型短板训练完成之后验证工作不要只看验证集上的mAP建议直接在自己的图片上跑批量推理并输出混淆矩阵。YOLO框架训练结束后自带混淆矩阵图在验证集上按类别统计了真实类别和预测类别的匹配关系。如果你发现隐裂这一类在混淆矩阵里大量被预测为背景说明模型对隐裂的召回不足优先加数据而不是调参。如果隐裂经常被预测成热斑说明这两类在视觉特征上有混淆此时需要回去检查标注是否有误看是不是两类样本在裁剪后的局部图像上确实相似。批量推理脚本的思路很直接遍历一个文件夹的图片保存每张图的检测结果再把预测框画出来人工翻看from ultralytics import YOLO # 加载训练好的权重 model YOLO(runs/pv_defect/weights/best.pt) # 对测试目录所有图片做推理并保存结果 results model.predict( sourcetest_images/, conf0.15, # 调低置信度阈值提高召回 saveTrue, save_txtTrue, imgsz1280 ) # 输出每张图的类别统计 for r in results: boxes r.boxes cls_counts {} for cls_id in boxes.cls.tolist(): cls_name model.names[int(cls_id)] cls_counts[cls_name] cls_counts.get(cls_name, 0) 1 print(r.path, cls_counts)conf0.15是刻意调低的目的是看低置信度区间里有多少真缺陷。画出来的图如果有一堆框打在光伏板边框、铝框、电线这些背景上说明模型学到了错误特征回去检查训练标注里是否有这类背景区域被画进了框或者验证集里有没有类似图片干扰判断。如果画出来的框比预想中少很多且丢失的确实是细长隐裂那就回到了小目标处理的问题上考虑切图检测方案把大图切成小块分别检测后再合并。我自己的习惯是模型训练结束先不急着部署用三组数据验证——验证集、全新拍摄的图、故意加入噪声和模糊的图。三组跑完模型的真实水平基本就有数了。测试新图像数据时记得控制图片尺寸有些推理设备的缩放策略会导致小目标被进一步压缩实际使用时要保持和训练一致的处理流程。希望这篇笔记能帮你少走几段弯路早日跑通自己的光伏板缺陷检测方案。本文还有配套的精品资源点击获取