☰
光伏板缺陷检测数据集:2400张VOC+YOLO双格式训练实践
2026/10/18 13:04:06 网站建设 项目流程

简介:面向光伏组件缺陷检测与质检模型训练,这份数据集覆盖2400张光伏板表面图像,针对crack(裂纹)、grid(网格)、spot(斑点)三类常见缺陷提供Pascal VOC与YOLO双格式标注,标注框合计2628个。标注工作基于labelImg完成,采用矩形框规则,清晰反映缺陷位置与类别,适用于训练裂纹、网格纹和斑点识别模型。压缩包共2000个文件,其中1999个XML标注文件构成标注主体,另含TXT文件,包体约310.4MB,可直接用于目标检测模型训练、格式转换或标注规范学习。目前已有369人学习下载。数据集统一了VOC与YOLO两种主流格式,减少预处理成本,适合新能源领域算法工程师、科研人员及目标检测初学者开展模型训练与缺陷识别实验。

1. 光伏板缺陷检测数据集:2400张双格式到底能帮你省多少事

做过光伏电站巡检或者组件产线质检的人都知道,真正卡住项目的往往不是模型选型,而是数据。现场拍回来的图光照乱、角度歪、缺陷小,自己标几百张就得熬夜,标完还要转格式、核对坐标,一套流程下来两周就没了。这个光伏板太阳能版缺陷检测数据集,VOC+YOLO双格式、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> <filename>pv_00123.jpg</filename> <size> <width>1280</width> <height>960</height> </size> <object> <name>crack</name> <bndbox> <xmin>312</xmin> <ymin>418</ymin> <xmax>845</xmax> <ymax>602</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的图上有裂纹目标,像素坐标为xmin=312, ymin=418, xmax=845, ymax=602,对应的txt内容就是:

0 0.4520 0.5313 0.4164 0.1917

换算逻辑不复杂:x_center = (xmin + xmax) / 2 / width,width = (xmax - xmin) / width,y轴同理。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 = f"labels/{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_ok=True) 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: dust

path一定要写绝对路径,相对路径经常会在不同工作目录下启动训练时报找不到图片。train和val是相对path的路径,指向图片目录,框架会自己找对应的labels目录下的txt。这也就要求图片和txt必须保持同名,扩展名可以不同,主名必须严格一致。

如果数据集里给了train.txt和val.txt的划分文件,也就是显式的图片路径列表,也可以用那种方式,让yaml里的train和val直接指向这两个txt文件路径。两种写法都常见,区别只是前者按目录自动找图,后者按列表精确控制。

4.2 训练命令与三个关键参数

配置写好之后,训练命令本身不复杂:

yolo detect train \ model=yolov8s.pt \ data=pv_defect.yaml \ epochs=100 \ imgsz=1280 \ batch=16 \ device=0 \ project=runs/pv_defect

三个参数我建议重点关注:imgsz在这类光伏板缺陷数据集上非常关键。光伏组件的裂纹、热斑在整张巡检图里都是小目标,图像缩得太小会把缺陷细节抹掉,常见的做法是设1280而不是默认的640。epochs在数据集只有2400张时设100轮比较合理,太少欠拟合,太多容易过拟合,后面看验证集loss收敛情况再增减。batch受显存限制,视频记忆不足就减半,batch=32效果通常比16好,但显存不够时硬上会导致训练中断。

还有一点,model=yolov8s.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( source="test_images/", conf=0.15, # 调低置信度阈值提高召回 save=True, save_txt=True, imgsz=1280 ) # 输出每张图的类别统计 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)

conf=0.15是刻意调低的,目的是看低置信度区间里有多少真缺陷。画出来的图如果有一堆框打在光伏板边框、铝框、电线这些背景上,说明模型学到了错误特征,回去检查训练标注里是否有这类背景区域被画进了框,或者验证集里有没有类似图片干扰判断。如果画出来的框比预想中少很多,且丢失的确实是细长隐裂,那就回到了小目标处理的问题上,考虑切图检测方案,把大图切成小块分别检测后再合并。

我自己的习惯是:模型训练结束先不急着部署,用三组数据验证——验证集、全新拍摄的图、故意加入噪声和模糊的图。三组跑完,模型的真实水平基本就有数了。测试新图像数据时记得控制图片尺寸,有些推理设备的缩放策略会导致小目标被进一步压缩,实际使用时要保持和训练一致的处理流程。

希望这篇笔记能帮你少走几段弯路,早日跑通自己的光伏板缺陷检测方案。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询