2026/10/9 12:07:42

光伏板缺陷检测实战:数据集、YOLOv8模型与代码全链路复现

光伏板缺陷检测实战:数据集、YOLOv8模型与代码全链路复现 简介这份资源面向光伏运维、工业质检与AI算法学习者提供光伏板缺陷检测的完整数据集与配套模型覆盖裂纹、脏污、热斑、遮挡、破损等常见缺陷类型可直接对接YOLO等主流检测框架用于训练、验证与算法复现。压缩包为7z格式共2000个文件约43.16MB其中1626个txt标注文件、359张jpg与7张png图像构成训练与验证样本另有3个yaml配置、2个py脚本、2个pt权重及1个csv训练记录兼顾数据、代码与结果查看。已有64人学习下载。读者可据此快速搭建缺陷检测流程理解标注格式与模型配置借助训练曲线和预测可视化图排查效果适合无人机巡检图像分析与智能缺陷识别等场景为科研实验和工业部署提供现成起点。1. 光伏板缺陷数据集、模型含检测代码从零跑通一条可复现的检测链路光伏板缺陷检测这件事真正卡住大多数团队的从来不是模型结构而是数据。红外图像里热斑、隐裂、二极管故障、蜗牛纹这几类缺陷公开可用的标注数据少得可怜自己标又贵又慢标完还未必一致。所以当有人把「光伏板缺陷数据集、模型含检测代码」打包成一个方向抛出来时它解决的其实是三件事数据从哪来、模型怎么选、代码怎么落地。这套东西适合两类人——一类是想快速验证光伏巡检自动化可行性的算法工程师另一类是手里有无人机或手持红外设备、想把采集到的图变成缺陷报告的运维团队。下面我按自己实际跑过一遍的顺序把数据组织、模型训练、推理部署和踩坑点讲清楚能照着复现。2. 数据集怎么组织目录结构、标注格式与划分策略拿到一个光伏缺陷数据集第一件事不是急着训练而是先搞清楚它的组织方式。光伏缺陷检测的数据来源通常有三种红外热成像、可见光图像、电致发光EL图像。红外看热斑最灵可见光看表面污渍和玻璃破裂EL 看隐裂和断栅。一个完整的数据集往往混合了多种模态如果直接一股脑丢进训练模型会学混。我一般会先按模态分目录再按缺陷类别分子目录。2.1 目录结构与类别命名规范常见的组织方式是images/和labels/平行存放文件名一一对应。类别命名建议用英文小写下划线避免中文路径在部分训练框架里出问题。下面是我常用的目录骨架pv_defect_dataset/ ├── images/ │ ├── train/ │ │ ├── 0001.jpg │ │ └── 0002.jpg │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ │ ├── 0001.txt │ │ └── 0002.txt │ ├── val/ │ └── test/ └── data.yamldata.yaml是 YOLO 系列的标准配置内容大致如下# 数据集根路径训练脚本会基于它拼接 images/labels path: ./pv_defect_dataset train: images/train val: images/val test: images/test # 类别数与类别名顺序必须和标注文件里的 class_id 一致 nc: 5 names: 0: hot_spot # 热斑 1: crack # 隐裂 2: diode_fault # 二极管故障 3: snail_trail # 蜗牛纹 4: soiling # 污渍遮挡这里nc和names的顺序是硬约束。标注文件里每行是class_id x_center y_center width height坐标都是归一化到 0~1 的。如果类别顺序写错模型会把热斑学成隐裂后面再怎么调参都救不回来。2.2 标注格式转换从 VOC XML 到 YOLO TXT很多公开数据集给的是 Pascal VOC 的 XML 格式需要转成 YOLO 的 TXT。转换脚本不复杂但边界情况多。下面这个脚本处理了图片尺寸读取、坐标归一化和类别映射import os import xml.etree.ElementTree as ET from PIL import Image # 类别名到 id 的映射必须和 data.yaml 保持一致 CLASS_MAP { hot_spot: 0, crack: 1, diode_fault: 2, snail_trail: 3, soiling: 4, } def voc_to_yolo(xml_path, img_dir, out_dir): tree ET.parse(xml_path) root tree.getroot() # 从 XML 里拿文件名再去 img_dir 读真实尺寸 filename root.find(filename).text img_path os.path.join(img_dir, filename) with Image.open(img_path) as im: w, h im.size lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in CLASS_MAP: continue # 未定义类别直接跳过避免污染训练 cls_id CLASS_MAP[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 / w y_center (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h # 裁剪到 [0,1]防止标注越界导致训练报错 x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) bw min(max(bw, 0.0), 1.0) bh min(max(bh, 0.0), 1.0) lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}) out_path os.path.join(out_dir, os.path.splitext(filename)[0] .txt) with open(out_path, w) as f: f.write(\n.join(lines))逻辑上分四步解析 XML、读原图尺寸、归一化坐标、写 TXT。参数上要注意CLASS_MAP必须和data.yaml的names完全对齐坐标裁剪那两行是后悔药很多标注工具会导出略微越界的框不裁的话训练时归一化坐标超过 1 会触发断言失败。另外filename字段有的数据集写的是相对路径读图前最好做一次os.path.basename兜底。2.3 训练集、验证集、测试集的划分比例光伏缺陷有个特点类别极不均衡。热斑和污渍样本多隐裂和二极管故障样本少。如果按 8:1:1 随机划分验证集里可能一个隐裂都没有mAP 看着高但实际没用。我的做法是分层抽样保证每个类别在三个集合里都有出现稀有类别至少留 10% 进验证集。划分脚本可以用sklearn.model_selection.train_test_split的stratify参数按主类别分层。如果一张图里有多个类别就按出现次数最多的类别分层或者干脆按图级别做多标签分层。划分完一定要打印每个集合的类别分布确认没有某个类别在验证集里挂零。3. 模型选型与训练YOLOv8 在光伏缺陷上的参数怎么调数据集理顺之后模型选型反而简单。光伏缺陷检测本质是目标检测缺陷目标尺寸差异大——热斑可能占半块板隐裂可能只有几十像素宽。所以模型要兼顾小目标和大目标同时推理速度要能跟上无人机巡检的吞吐。YOLOv8 系列是目前落地最省心的选择n/s/m 三个尺度覆盖了从边缘设备到服务器的需求。3.1 为什么选 YOLOv8 而不是两阶段检测器两阶段检测器如 Faster R-CNN精度上限高但推理慢而且对小目标的召回依赖 RPN 的锚框设计调起来玄学。光伏巡检场景里一张红外图可能有几十个缺陷两阶段检测器逐个区域跑延迟直接翻倍。YOLOv8 是单阶段一次前向出所有框配合 Mosaic 增强对小目标也友好。更重要的是 YOLOv8 的工程化做得好训练、验证、导出 ONNX、部署到 TensorRT 都是一条命令的事省掉大量胶水代码。如果缺陷特别小比如 EL 图像里的微隐裂可以考虑 YOLOv8 的 P2 变体增加一个更高分辨率的检测头但代价是显存和推理时间上升。3.2 训练命令与关键超参数假设数据集已经按第 2 章的结构放好训练命令如下# 从预训练权重开始微调光伏数据量通常不够从头训 yolo detect train \ data./pv_defect_dataset/data.yaml \ modelyolov8s.pt \ epochs150 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ momentum0.937 \ weight_decay0.0005 \ warmup_epochs3 \ mosaic1.0 \ mixup0.1 \ copy_paste0.1 \ degrees10.0 \ translate0.1 \ scale0.5 \ fliplr0.5 \ flipud0.0 \ hsv_h0.015 \ hsv_s0.7 \ hsv_v0.4 \ patience30 \ project./runs/pv \ nameexp_yolov8s逐个说关键参数。imgsz640是默认值如果缺陷目标普遍小于 32 像素建议提到 960 或 1280但 batch 要相应降到 8 或 4。lr00.01是初始学习率微调场景可以降到 0.001避免把预训练权重冲垮。mosaic1.0是 YOLOv8 的默认增强把四张图拼成一张对小目标检测帮助很大但如果缺陷之间遮挡严重可以降到 0.5。mixup和copy_paste是额外增强光伏缺陷里隐裂样本少copy_paste能把稀有缺陷复制粘贴到其他图上提升召回。flipud0.0是因为光伏板有方向性上下翻转会产生不真实的缺陷分布左右翻转fliplr0.5没问题。patience30是早停验证集 30 轮不涨就停省时间。3.3 训练过程监控与指标解读训练启动后runs/pv/exp_yolov8s/下会生成results.csv和一系列曲线图。重点看三个指标metrics/mAP50、metrics/mAP50-95和每个类别的metrics/mAP50。光伏缺陷里热斑和污渍的 mAP 通常能到 0.9 以上隐裂和二极管故障能到 0.7 就算不错。如果某个类别 mAP 一直上不去先别急着调模型回去看验证集里这个类别的样本数——少于 50 张的话模型根本学不动得补数据或做更强的增强。另外看train/box_loss和val/box_loss的差距如果训练 loss 一直降但验证 loss 反弹就是过拟合加 dropout 或减 epochs。如果两个 loss 都降不下去可能是学习率太小或标注有问题用yolo detect val单独跑一遍验证集看预测框和真实框的对比图肉眼排查标注错误。4. 推理与部署从单张图片到批量巡检报告模型训完只是半成品真正落地要解决推理速度和结果输出。光伏巡检一次可能拍几百上千张图逐张跑 Python 脚本太慢得走 ONNX 或 TensorRT。4.1 导出 ONNX 与推理脚本# 导出 ONNXopset 12 兼容性最好 yolo export model./runs/pv/exp_yolov8s/weights/best.pt formatonnx opset12 simplifyTrue导出后用 ONNX Runtime 推理脚本如下import onnxruntime as ort import numpy as np import cv2 # 加载 ONNX 模型指定 CUDA 优先 providers [CUDAExecutionProvider, CPUExecutionProvider] session ort.InferenceSession(best.onnx, providersproviders) def preprocess(img_path, imgsz640): img cv2.imread(img_path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) h, w img.shape[:2] # 等比例缩放并填充到 imgsz x imgsz scale min(imgsz / w, imgsz / h) nw, nh int(w * scale), int(h * scale) resized cv2.resize(img, (nw, nh)) canvas np.full((imgsz, imgsz, 3), 114, dtypenp.uint8) canvas[:nh, :nw] resized # 归一化到 0~1 并转 NCHW blob canvas.astype(np.float32) / 255.0 blob np.transpose(blob, (2, 0, 1))[None, ...] return blob, scale, (w, h) def postprocess(output, scale, orig_shape, conf_thres0.25, iou_thres0.45): # output 形状 [1, 4nc, 8400]前 4 是 xywh后面是类别分数 preds output[0].transpose(1, 0) boxes, scores, class_ids [], [], [] for pred in preds: cls_scores pred[4:] cls_id int(np.argmax(cls_scores)) conf float(cls_scores[cls_id]) if conf conf_thres: continue x, y, w, h pred[:4] # 还原到原图坐标 x (x - (640 - orig_shape[0] * scale) / 2) / scale y (y - (640 - orig_shape[1] * scale) / 2) / scale w, h w / scale, h / scale boxes.append([x - w/2, y - h/2, w, h]) scores.append(conf) class_ids.append(cls_id) # NMS 去重 indices cv2.dnn.NMSBoxes(boxes, scores, conf_thres, iou_thres) return [(boxes[i], scores[i], class_ids[i]) for i in indices]预处理里的填充值 114 是 YOLO 系列的惯例和训练时的 letterbox 保持一致否则推理精度会掉。后处理里坐标还原那一步容易翻车如果预处理用了填充还原时必须先减去填充偏移再除以缩放比例顺序反了框会整体偏移。conf_thres0.25和iou_thres0.45是通用起点光伏缺陷里如果误检多就提到 0.4漏检多就降到 0.15。4.2 批量推理与结果汇总批量推理时把图片路径列表丢进循环每张图跑一次session.run结果写进 CSV 或 JSON。如果图片量大可以用多进程或 ONNX Runtime 的并行执行。结果汇总建议按图片名、缺陷类别、置信度、边界框坐标四列输出方便后续生成巡检报告。如果要做可视化用 OpenCV 画框并保存颜色按类别区分热斑用红色、隐裂用黄色、污渍用蓝色一眼能看出缺陷分布。5. 避坑与排查光伏缺陷检测里最容易翻车的 5 个点5.1 验证集 mAP 很高但实际巡检漏检严重现象是训练日志里 mAP50 到 0.95但拿现场图一跑隐裂基本没检出来。原因通常是验证集和现场数据分布不一致——验证集是从同一批标注数据里划的光照、角度、设备都和训练集同源而现场图可能有新的拍摄角度或不同批次的光伏板。解决办法是留一个「跨批次测试集」用不同时间、不同设备拍的图不参与训练和调参只做最终评估。如果跨批次 mAP 掉超过 20 个点说明模型过拟合了训练集的拍摄条件得加更多样的数据增强比如随机调整亮度、对比度和模拟不同红外设备的噪声。5.2 标注框越界导致训练中途报错现象是训练跑了几轮突然崩报normalized coordinates out of range或类似的断言错误。原因是标注文件里有坐标超过 1 或小于 0 的框YOLO 在计算 loss 时会校验。解决办法是在数据加载前跑一遍清洗脚本遍历所有 TXT把越界坐标裁剪到 [0,1]同时删掉宽高为 0 的无效框。第 2 章的转换脚本里已经做了裁剪但如果标注是直接手写的 TXT就得单独清洗。5.3 类别不均衡导致稀有缺陷召回率极低现象是热斑和污渍的 AP 都在 0.9 以上但隐裂的 AP 只有 0.3。原因是隐裂样本太少模型在训练时被多数类主导。解决办法有三层第一层是数据增强用copy_paste把隐裂缺陷复制到其他图上人为增加样本第二层是损失加权YOLOv8 支持通过cls参数调整分类损失的权重但更直接的是在数据集层面做重采样让稀有类别在每个 batch 里出现概率更高第三层是后处理调阈值对稀有类别单独降低置信度阈值用精度换召回。5.4 推理速度达不到无人机实时要求现象是模型在服务器上跑一张图要 200ms无人机巡检要求 50ms 以内。原因是用了 PyTorch 原生推理或 ONNX 没开 GPU。解决办法是导出 TensorRT engine用yolo export formatengine halfTrueFP16 精度下速度能提升 2~3 倍。如果还不行换更小的模型YOLOv8n 比 YOLOv8s 快一倍精度掉 3~5 个点看业务能不能接受。另外预处理和后处理也可能成为瓶颈用 OpenCV 的 GPU 版本或把预处理也塞进 ONNX 图里能省掉 CPU 和 GPU 之间的数据拷贝。5.5 不同红外设备拍的图混在一起训练效果差现象是数据集里混了两种红外设备拍的图训练出来的模型在两种设备上表现都不好。原因是不同设备的成像特性不同——分辨率、噪声水平、温度映射曲线都不一样模型学到的特征被撕裂了。解决办法是按设备分组建模每个设备单独训一个模型或者做域适应用风格迁移把一种设备的图转成另一种设备的风格再混合训练。如果数据量够直接分设备训是最稳的别偷懒混在一起。6. 进阶技巧用测试时增强和模型集成把 mAP 再抬 3 个点训练和部署都跑通之后如果还想压榨精度有两个技巧值得试测试时增强TTA和模型集成。TTA 是在推理时对同一张图做多种变换水平翻转、多尺度缩放把多次预测结果融合。YOLOv8 的val和predict都支持augmentTrue开启后 mAP 通常能涨 1~2 个点代价是推理时间翻倍。模型集成是训多个不同初始化的模型推理时把它们的预测框做加权 NMS。集成能涨 2~3 个点但部署时要同时加载多个模型显存和延迟都上去了。我一般会在验证阶段用 TTA 确认精度上限如果 TTA 比单模型高很多说明模型对输入变换敏感可以回去加对应的数据增强如果 TTA 提升很小说明模型已经比较鲁棒没必要在推理时开 TTA。模型集成只在精度要求极高、延迟不敏感的离线巡检场景用实时场景还是单模型加 TensorRT 更实际。下面是一个简单的 TTA 推理示例对水平翻转和原图分别推理再用 NMS 融合def tta_predict(session, img_path, imgsz640): # 原图推理 blob, scale, orig preprocess(img_path, imgsz) out1 session.run(None, {session.get_inputs()[0].name: blob})[0] res1 postprocess(out1, scale, orig) # 水平翻转推理 img cv2.imread(img_path) img_flip cv2.flip(img, 1) cv2.imwrite(_tmp_flip.jpg, img_flip) blob_f, scale_f, orig_f preprocess(_tmp_flip.jpg, imgsz) out2 session.run(None, {session.get_inputs()[0].name: blob_f})[0] res2 postprocess(out2, scale_f, orig_f) # 翻转结果的 x 坐标还原 w orig[0] res2_restored [] for box, score, cls in res2: x, y, bw, bh box x w - x - bw res2_restored.append(([x, y, bw, bh], score, cls)) # 合并两组结果再做一次 NMS all_boxes [r[0] for r in res1] [r[0] for r in res2_restored] all_scores [r[1] for r in res1] [r[1] for r in res2_restored] indices cv2.dnn.NMSBoxes(all_boxes, all_scores, 0.25, 0.45) return [ (all_boxes[i], all_scores[i]) for i in indices ]翻转结果的坐标还原是这里最容易错的地方水平翻转后框的 x 坐标要变成原图宽度 - x - 框宽只减 x 不减宽的话框会整体左移一个框宽。这个坑我踩过当时调了半天以为模型有问题最后发现是坐标变换写错了。TTA 的收益不是线性的翻转加原图通常够用再加多尺度收益递减而且推理时间成倍涨得权衡。最后说个习惯每次训完模型我都会拿一批现场图跑一遍把预测结果和人工标注并排看专门找那些模型置信度在 0.3~0.5 之间的框。这些框往往是模型「犹豫」的地方要么是标注漏了要么是缺陷特征不明显。把这些边界样本挑出来重新标一遍加进训练集下一轮 mAP 通常能再涨一两个点。数据质量永远比模型结构重要这个方向能不能落地八成取决于你愿不愿意在数据上反复磨。希望帮到你。本文还有配套的精品资源点击获取