简介本资源为光伏板太阳能板缺陷检测数据集面向从事工业视觉检测、新能源运维及深度学习目标检测的开发者与研究人员可用于训练和验证裂纹、栅线、斑点三类典型缺陷的识别模型。压缩包共约2000个文件以1999个Pascal VOC格式xml标注文件和1个说明txt为主并配套对应的jpg图片与YOLO格式txt标注整体约310.4MB可直接接入主流检测框架。数据集共2400张图片标注类别为crack、grid、spot对应框数分别为892、884、852总框数2628均使用labelImg按矩形框规则标注标注准确合理。目前已有358人学习下载适合作为光伏缺陷检测课题的基线数据帮助读者快速完成数据加载、类别统计与模型训练验证节省自建数据集的时间成本。1. 光伏板缺陷检测数据集2400 张 VOCYOLO 双格式到底怎么用拿到一个标注好的光伏板缺陷数据集第一反应往往不是“太好了”而是“这 2400 张到底够不够、三个类别怎么分、VOC 和 YOLO 两套格式我该用哪个”。光伏板也叫太阳能板的缺陷检测在产线质检和电站巡检两个场景里需求都很硬隐裂、断栅、热斑、污渍、缺角这些缺陷如果靠人眼在 EL 图像或可见光图像上一张张翻效率和一致性都撑不住。这个数据集的价值就在于它把 2400 张图连同 VOC 的 XML 标注和 YOLO 的 TXT 标注一起给了你三个类别直接可用省掉了最耗时的标注环节。它适合两类人一类是想快速跑通 YOLO 训练流程、验证自己环境是否正常的工程师2400 张的规模在单卡上几小时就能出第一版权重另一类是做光伏质检落地、需要先拿一个基线模型摸清缺陷可分性的团队。需要提前说清楚的是2400 张、3 类别属于中小规模数据集它能帮你验证 pipeline但不要指望直接拿去上线覆盖所有工况。下面从格式、划分、训练、排错一路讲到我实际用下来最省事的做法。2. VOC 与 YOLO 双格式先搞懂两套标注的对应关系2.1 VOC 的 XML 和 YOLO 的 TXT 差在哪VOC 格式每张图对应一个 XML 文件里面用object记录每个目标的类别名和边界框框的坐标是绝对像素值xmin、ymin、xmax、ymax。YOLO 格式每张图对应一个 TXT 文件每行一个目标格式是class_id x_center y_center width height四个坐标全部是相对图像宽高的归一化值范围 0 到 1。这个差异决定了你换框架时不能直接混用必须做一次转换。很多人第一次拿到双格式数据集会疑惑既然都给了为什么还要懂转换因为实际项目里你经常只拿到其中一种或者标注质量在一种格式里更好。更关键的是YOLO 训练时如果类别 id 和你的data.yaml里的names顺序对不上模型会学得莫名其妙——这类问题不会报错只会让 mAP 一直上不去属于典型的玄学翻车。2.2 用脚本在两种格式之间互转下面这个脚本把 VOC 的 XML 批量转成 YOLO 的 TXT转换前你需要先确定类别到 id 的映射顺序这个顺序必须和后面训练用的data.yaml完全一致。import os import xml.etree.ElementTree as ET # 类别顺序必须和 data.yaml 的 names 一致顺序错了 mAP 会莫名偏低 classes [crack, broken, stain] # 按你数据集实际类别名替换 class_to_id {name: i for i, name in enumerate(classes)} def convert_bbox(size, box): # size: (w, h)box: (xmin, ymin, xmax, ymax) dw, dh 1.0 / size[0], 1.0 / size[1] x_center (box[0] box[2]) / 2.0 * dw y_center (box[1] box[3]) / 2.0 * dh w (box[2] - box[0]) * dw h (box[3] - box[1]) * dh return x_center, y_center, w, h def xml_to_txt(xml_path, txt_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text.strip() if name not in class_to_id: continue # 跳过未登记类别避免 id 越界 bbox obj.find(bndbox) box (float(bbox.find(xmin).text), float(bbox.find(ymin).text), float(bbox.find(xmax).text), float(bbox.find(ymax).text)) xc, yc, bw, bh convert_bbox((w, h), box) lines.append(f{class_to_id[name]} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) with open(txt_path, w) as f: f.write(\n.join(lines)) # 批量处理 xml_dir annotations out_dir labels os.makedirs(out_dir, exist_okTrue) for fn in os.listdir(xml_dir): if fn.endswith(.xml): xml_to_txt(os.path.join(xml_dir, fn), os.path.join(out_dir, fn.replace(.xml, .txt)))逻辑说明convert_bbox做的是绝对坐标到归一化中心点坐标的换算这是 VOC 转 YOLO 的核心。参数上classes列表的顺序就是最终 id 的映射务必和训练配置对齐:.6f保留六位小数是 YOLO 官方推荐的精度位数太少在目标很小时会有明显误差。如果转换后某些 TXT 是空文件说明那张图里所有类别都不在classes里需要回头核对类别名拼写。2.3 反向转换和格式校验反过来 YOLO 转 VOC 时把归一化坐标乘回图像宽高即可注意xmax、ymax要重新算成x_center w/2再乘宽高。转换完一定要做一次校验随机抽 20 张图用可视化脚本把框画出来肉眼确认框的位置和类别没错。我一般会写一个简单的 OpenCV 脚本把 TXT 里的框画到图上这一步能挡掉大部分坐标写反、宽高互换的低级错误。import cv2 def draw_yolo(img_path, txt_path, names): img cv2.imread(img_path) h, w img.shape[:2] with open(txt_path) as f: for line in f: cid, xc, yc, bw, bh line.split() cid int(cid) x1 int((float(xc) - float(bw) / 2) * w) y1 int((float(yc) - float(bh) / 2) * h) x2 int((float(xc) float(bw) / 2) * w) y2 int((float(yc) float(bh) / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, names[cid], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(check.jpg, img)参数说明names就是类别名列表顺序和训练配置一致画框时用绿色、线宽 2 是为了在光伏板这种偏灰偏蓝的底图上足够醒目。校验通过后再进入划分和训练能省掉后面大量排查时间。3. 2400 张 3 类别怎么划分与增强才不浪费3.1 训练集验证集测试集的划分比例2400 张、3 类别平均每类 800 张左右但实际分布往往不均匀缺陷检测里小类别样本少是常态。我一般按 7:2:1 划分即训练 1680、验证 480、测试 240。如果某个类别总数低于 300就要考虑在划分时做分层抽样保证验证集和测试集里每个类别都有足够样本否则验证指标会剧烈波动你根本判断不了模型到底学没学会。划分脚本要按图像文件名而不是按标注文件来分避免出现图片和标签对不上的情况。下面这段按类别分层抽样的思路先统计每张图包含哪些类别再按比例分配。import os, random, shutil from collections import defaultdict random.seed(42) # 固定种子保证划分可复现 img_dir images label_dir labels out_root dataset_split # 统计每张图的主类别取第一个目标类别作为分层依据 img_to_cls {} for fn in os.listdir(label_dir): if not fn.endswith(.txt): continue with open(os.path.join(label_dir, fn)) as f: lines [l for l in f if l.strip()] if lines: img_to_cls[fn.replace(.txt, .jpg)] int(lines[0].split()[0]) groups defaultdict(list) for img, cls in img_to_cls.items(): groups[cls].append(img) for cls, imgs in groups.items(): random.shuffle(imgs) n len(imgs) n_train int(n * 0.7) n_val int(n * 0.2) splits {train: imgs[:n_train], val: imgs[n_train:n_train n_val], test: imgs[n_train n_val:]} for split, files in splits.items(): os.makedirs(f{out_root}/{split}/images, exist_okTrue) os.makedirs(f{out_root}/{split}/labels, exist_okTrue) for img in files: shutil.copy(os.path.join(img_dir, img), f{out_root}/{split}/images/{img}) shutil.copy(os.path.join(label_dir, img.replace(.jpg, .txt)), f{out_root}/{split}/labels/{img.replace(.jpg, .txt)})逻辑说明random.seed(42)是后悔药保证你每次划分结果一致方便对比不同模型。分层依据取第一个目标类别是一种简化如果一张图里多类别混杂严重可以改成按类别集合分层。参数上 7:2:1 是中小数据集的稳妥选择数据量上万后可以往 8:1:1 调。3.2 光伏缺陷场景该用哪些增强光伏板图像的增强不能照搬通用方案。水平翻转、垂直翻转一般安全因为缺陷方向和板子朝向没有强绑定但大角度旋转要谨慎隐裂这类细长缺陷旋转后可能和真实分布不符。亮度、对比度扰动很有用因为电站巡检图像的光照差异极大。Mosaic 和 MixUp 在 YOLO 训练里默认开启对 2400 张这种规模能明显提升泛化但如果你的缺陷非常小Mosaic 把小目标拼在一起可能让模型更难学这时可以调低mosaic概率。我一般会先跑一版默认增强看验证集 mAP 曲线如果训练集 loss 降得很快但验证集不涨说明增强不够或过拟合再针对性加。不要一上来就把所有增强拉满那样你根本不知道是哪个增强起了作用。3.3 小类别样本不均衡的处理3 类别里如果某一类只有一两百张直接训练会被大类别压制。常见做法有三种一是过采样小类别在划分时重复复制二是在损失里给小类别更高权重三是用 YOLO 自带的类别权重参数。我一般先用过采样简单可控配合验证集看小类别的 recall 有没有起来。如果过采样后验证集小类别指标还是趴着不动再考虑改损失权重因为改损失容易把大类别指标拉下去属于拆东墙补西墙。4. 用 YOLO 训练这套数据集的完整命令与参数4.1 data.yaml 怎么写训练前先建好data.yaml路径、类别数、类别名三样必须对。下面是一个模板path指向你划分后的数据集根目录。path: ./dataset_split train: train/images val: val/images test: test/images nc: 3 names: [crack, broken, stain]参数说明nc是类别数必须和names长度一致写错会直接报错或静默学错names的顺序必须和前面转换脚本里的classes完全一致这是最容易翻车的地方。路径用相对路径时注意训练命令的工作目录否则会找不到图。4.2 训练命令与关键参数以 YOLOv8 为例一条命令就能起训但参数值得逐项确认。yolo detect train \ datadataset_split/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/pv_defect \ nameexp1逻辑说明modelyolov8n.pt用预训练权重起步2400 张这种规模从零训很容易欠拟合预训练能省大量时间。imgsz640是通用起点如果你的缺陷在图上占比很小可以提到 1024但显存和速度要重新评估。batch16在单张 8G 显存卡上比较稳显存不够就降到 8 并配合梯度累积。patience20是早停验证集 20 轮不涨就停避免过拟合。lr00.01是初始学习率微调预训练模型时如果 loss 震荡厉害可以降到 0.001。训练过程中重点看三个东西训练集和验证集的 box_loss 是否同步下降、mAP50 是否稳定上升、混淆矩阵里有没有类别被系统性混淆。如果验证集 mAP 在某个 epoch 后掉头向下基本就是过拟合早停会帮你截住。4.3 训练完怎么验证和导出训练结束后先看runs/pv_defect/exp1下的结果图重点是confusion_matrix.png和results.png。然后用测试集跑一次评估yolo detect val \ modelruns/pv_defect/exp1/weights/best.pt \ datadataset_split/data.yaml \ splittest \ imgsz640参数说明splittest明确用测试集不要用验证集冒充测试集否则指标虚高。best.pt是验证集最优权重last.pt是最后一轮一般用 best。导出部署格式时yolo export modelbest.pt formatonnx可以拿到 ONNX方便后续集成到产线推理服务。5. 训练这套光伏数据集最容易踩的坑5.1 类别 id 和 names 顺序错位现象训练能跑loss 也降但 mAP 始终在 0.1 到 0.2 徘徊混淆矩阵里类别几乎全混在一起。原因转换脚本里的classes顺序和data.yaml的names顺序不一致模型学到的 id 对应关系是错的。解决把两处的类别列表逐字比对最好把类别定义抽到一个公共文件里两边都引用它从源头消除不一致。5.2 图片和标签文件名对不上现象训练日志里出现大量ignoring corrupted image或某类别样本数远少于预期。原因VOC 的 XML 和图片同名但转成 TXT 后如果改了扩展名规则或者划分时只复制了图没复制标签就会对不上。解决写一个校验脚本遍历 images 目录检查每个图是否有同名 TXT缺的列出来补齐。这个检查放在训练前比训练后排查省事得多。5.3 小目标缺陷被 resize 吃掉现象大缺陷检测得不错隐裂、细小断栅这类小目标 recall 极低。原因原图分辨率高缺陷只占几十个像素resize 到 640 后目标只剩几个像素特征几乎消失。解决把imgsz提到 1024 或 1280或者用切片推理把大图切成小块分别检测再合并。提分辨率前先确认显存够不够就减小 batch。5.4 验证集指标虚高现象验证集 mAP 很漂亮一上测试集或真实产线就崩。原因划分时没有分层或者验证集和训练集来自同一批连续拍摄的图像分布过于接近模型记住了背景而不是缺陷。解决划分时按拍摄批次或电站分组让验证集包含训练集没见过的背景同时用测试集做最终判断不要用验证集调参调到过拟合。5.5 增强过猛导致训练不稳定现象训练前期 loss 剧烈震荡甚至出现 NaN。原因Mosaic、MixUp、大角度旋转叠加加上学习率偏高梯度爆炸。解决先把mosaic概率调低或关闭学习率降到 0.001确认训练稳定后再逐步加回增强。BN 层在 batch 很小时也容易不稳定batch 不要低于 8。6. 把 2400 张用到极致从基线到可复现的进阶技巧数据集规模有限时决定最终效果的往往不是模型多大而是你有没有把数据用干净。我自己的习惯是第一版永远用最小配置跑通全流程确认格式、划分、训练、评估这条链路没有断点再谈调优。具体做法是先用yolov8n加 50 epoch 跑一个基线记录 mAP50 和每个类别的 recall把它当作后续所有改动的参照物。没有基线你后面换模型、改增强、调学习率都是盲猜。进阶方向上有三个我实际验证过有效的点。第一是难例挖掘用基线模型在训练集上推理把置信度低或漏检的图挑出来人工复核标注是否有误很多时候 mAP 上不去是标注质量问题而不是模型问题。第二是切片推理光伏板图像分辨率高整图 resize 会丢小目标把图切成带重叠的子图分别检测再合并小目标 recall 通常能涨一截代价是推理变慢。第三是交叉验证2400 张做 5 折每折都训一个模型最后集成或取平均指标比单次划分更可信也能帮你判断模型方差有多大。下面这个切片推理的合并逻辑核心是把子图坐标映射回原图并做 NMS 去重。def merge_slices(dets, slice_size, overlap, orig_shape): # dets: 每个子图的检测结果 [(x1,y1,x2,y2,score,cls,offset_x,offset_y)] boxes [] for x1, y1, x2, y2, score, cls, ox, oy in dets: boxes.append([x1 ox, y1 oy, x2 ox, y2 oy, score, cls]) # 按类别分组做 NMSIoU 阈值 0.5 是常用起点 keep nms(boxes, iou_thres0.5) return keep参数说明slice_size是子图边长一般取 640 或 1024overlap是重叠像素取 slice_size 的 10% 到 20%太小会漏掉跨边界的缺陷太大则重复检测多、NMS 压力大。iou_thres0.5是通用值如果你的缺陷密集且相互靠近可以提到 0.6 减少误删。最后说一个我踩过的坑不要因为数据集只有 3 类别就觉得简单光伏缺陷的类间差异有时候比类内差异还小污渍和隐裂在低分辨率下几乎一样。我现在的习惯是任何新数据集到手先花半小时把每个类别随机抽 20 张图看一遍对缺陷长什么样、背景有多杂心里有数再动手写代码。这个习惯帮我省下的返工时间比任何调参技巧都多。希望帮到你。本文还有配套的精品资源点击获取