光伏板缺陷检测实战:从数据集构建到模型训练与部署
简介面向光伏运维、工业质检与科研场景的光伏板缺陷检测资源配套标注图像与可直接对接YOLO的模型及检测代码覆盖裂纹、脏污、热斑、遮挡、破损等常见缺陷适合需要快速搭建缺陷识别系统的AI工程师与运维团队。压缩包共2000个文件以1626个txt标注文件与359张jpg图像为主体含3个yaml配置、2个py检测脚本、2个pt权重模型及结果统计csv总大小约43.16MB。已有61人学习浏览。借助训练日志与验证集预测图使用者可直观评估模型效果并复用相关脚本与配置省去从零构建数据管线的时间直接开展算法验证或无人机巡检图像的智能识别测试助力提升电站运维效率、降低人工成本。1. 光伏板缺陷检测这件事卡在第一步的不是模型而是数据光伏板缺陷数据集、模型和检测代码这组词连在一起说明你要做的不是算法演示而是一条能落地的缺陷检测流水线。光伏电站巡检里最现实的问题不是“要不要用AI”而是“缺陷长什么样、模型从哪学、代码怎么跑起来”。热斑、隐裂、断栅、脏污遮挡这四类缺陷人工看EL或者红外图能认但让模型认就得先有干净的数据集、合适的模型结构、能直接改的推理和训练代码。这篇文章按“数据从哪来、模型怎么选、代码怎么改、坑在哪”的顺序把整条链路讲透适合刚接手光伏缺陷检测项目、手里只有一批现场图片却没有现成方案的工程师照着落地。2. 光伏板缺陷数据集公开资源清单与自建数据的整理方法2.1 公开数据集的可用性判断别被论文里的mAP骗了做光伏板缺陷检测第一步永远是数据盘点。公开数据集里比较常见的是EL图像数据集、无人机可见光拍摄的组件表面缺陷集、以及部分红外热像数据集。EL图像适合做隐裂和断栅检测因为隐裂在EL下呈现为黑色条带对比度高但EL图像拍摄条件苛刻需要暗室和电致发光激励设备现场复现成本高。可见光图像适合做脏污、遮挡、玻璃破碎这类表面缺陷采集简单但缺陷对比度低容易受光照角度影响。红外图像对热斑敏感但分辨率偏低小目标缺陷容易丢。用公开集之前要有三个判断第一图像分辨率是否和你现场拍摄设备一致公开集里常见的是300x300到600x450的EL图而你现场用工业相机可能拿到2000x2000以上的原图直接用公开集训练会导致尺度失配第二缺陷类别是否对齐有的数据集把缺陷分为“裂纹/断栅/短路”三类有的分成“热斑/隐裂/脏污”三类类别定义不一致时不能硬套第三背景复杂度公开集大多是实验室条件下拍的背景干净而现场图像里有边框、接线盒、支架阴影这些干扰会让模型训练时学到错误特征。我一般拿到公开集后会先做一次可视化抽样把每个类别的图像打印成拼图人工确认缺陷形态和现场照片是否接近这个步骤省不了。2.2 自建数据集的最小方案手机拍摄加标注的落地流程公开集不够用的时候自建数据几乎是必经路径。最经济的方案是用手机或普通数码相机拍摄组件表面配合EL检测仪导出EL图像。拍摄时固定角度、固定距离保证缺陷在画面中的尺寸比例相对稳定。对现场采集的原始图像先用脚本做统一裁剪和重命名按“类别缩写_日期_序号”的规则命名避免后面训练时文件名混乱。数据清洗是自建集里最重要的动作。原始照片里会有大量重复帧、过曝帧、对焦模糊帧这些图进训练集纯属灾难。我常用一个简单的清晰度筛选脚本import cv2 import numpy as np import os def filter_blurry_images(src_dir, dst_dir, threshold100.0): 用拉普拉斯方差判断图像清晰度低于阈值的直接丢弃。 阈值100是经验值对可见光光伏板照片比较合适 EL图像可以放到60-80EL图本身纹理少方差天然偏低。 os.makedirs(dst_dir, exist_okTrue) for fname in os.listdir(src_dir): if not fname.lower().endswith((.jpg, .png, .jpeg)): continue path os.path.join(src_dir, fname) img cv2.imread(path, cv2.IMREAD_GRAYSCALE) if img is None: continue laplacian_var cv2.Laplacian(img, cv2.CV_64F).var() if laplacian_var threshold: cv2.imwrite(os.path.join(dst_dir, fname), img) else: print(f丢弃模糊图: {fname}, 方差{laplacian_var:.2f})逻辑说明拉普拉斯算子提取图像二阶梯度方差大说明边缘信息丰富图像清晰方差小说明图像平滑大概率是失焦或过曝。这个脚本的作用是自动剔除低质量图避免人工一张一张看。阈值的选择取决于图像内容——光伏板表面栅线密集可见光图方差普遍在200以上阈值设100能安全过滤EL图纹理少方差集中在60-120之间阈值设太低会把清晰图误删设太高又留不住有效样本需要先跑几张小批量试出分布再定。清洗后的数据用LabelImg或X-AnyLabeling标注。缺陷检测优先画矩形框而不是多边形分割因为矩形框标注速度快、类间一致性高而分割标注在隐裂这种细长缺陷上容易产生标注者之间的主观差异。标注类别控制在5类以内热斑、隐裂、断栅、脏污、正常。注意正常样本必须进训练集否则模型会把所有光伏板区域都当成缺陷推理时误检率会完全失控。2.3 数据划分与格式转换VOC、YOLO、COCO三种格式的取舍标注完的原始数据是VOC格式一个XML文件对应一张图片。训练检测模型时YOLO系用txt格式每个txt文件存归一化后的框坐标。转换脚本是必须自己写的因为标注工具导出的格式几乎不可能直接满足训练框架要求。import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_dir, img_dir, out_dir, class_names): VOC XML转YOLO txt的常用脚本。 坐标归一化时注意YOLO要求cx, cy, w, h全部除以图像宽高 而不是除以各自对应的边。这里踩过坑w除以宽、h除以高 但cx和cy必须要分别除以宽和高顺序不能错。 os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() img_width int(root.find(size/width).text) img_height int(root.find(size/height).text) txt_lines [] for obj in root.findall(object): class_name obj.find(name).text if class_name not in class_names: continue class_id class_names.index(class_name) bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) 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 txt_lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {box_width:.6f} {box_height:.6f}) txt_name os.path.splitext(xml_file)[0] .txt with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(txt_lines))参数说明class_names的顺序必须固定训练时配置文件的类别列表要和这里一致否则框全部错位。建议按“hotspot, crack, broken_grid, dust, normal”这样的字母序排方便在tensorboard里看类别统计。转换后抽查5-10个txt把坐标画回原图检查这是防呆操作因为有的标注工具生成的width标签在size节点下脚本路径写错时静默返回None坐标会乱。3. 模型选型检测模型和分割模型怎么搭配才算合理3.1 热斑和脏污用检测就够了YOLO系模型的适用边界光伏板缺陷检测里热斑、脏污、遮挡这类缺陷形态完整、边界清晰用目标检测模型就能满足需求。YOLO系模型经过这么多代迭代工程生态已经非常成熟训练脚本、部署工具链、量化方案都齐全是落地首选。模型结构上常见的轻量版本会把C2f模块的宽度倍数设为0.33、深度倍数设为0.33参数量大概在3M左右适合边缘设备推理标准版本在同类参数配置下精度更高但显存占用和推理延迟都会上升。选哪个取决于部署设备如果跑在电站本地的工控机上CPU推理为主选轻量模型加OpenVINO加速如果有NVIDIA显卡标准模型加TensorRT是更好的组合。检测模型处理不了的是细长型缺陷。隐裂在EL图像里是毫米级宽度的黑色线条可能跨越几十厘米标注框会同时包含裂纹和正常区域模型学到的特征不纯断栅是连续的细线缺失宽度通常只有像素级。这种缺陷用分割模型更合适因为分割模型对每个像素做分类能保留细长结构的拓扑。实践中可以检测加分割混合使用先用检测模型框出疑似区域再用分割模型在该区域内做像素级判断。代价是链路变长、推理耗时增加如果现场是无人机巡检后离线分析这个方案完全可行。3.2 轻量模型在高分辨率光伏板图像上的表现切图策略高分辨率图像直接缩放输入模型会丢失小目标。2000x2000的EL图缩放到640x640一条原本10像素宽的隐裂缩到3像素宽模型很难识别即使识别出来框的定位精度也差。常见做法是滑窗切图把大图切成若干640x640或832x832的patch每个patch独立推理再用NMS合并结果。import cv2 import numpy as np def sliding_window_crop(image, crop_size640, stride480): 滑窗切图crop_size是切图尺寸stride是步长。 步长小于切图尺寸是为了让相邻patch有重叠区域 避免缺陷正好被切图边界切成两半。重叠率建议在25%-50%之间。 h, w image.shape[:2] crops [] boxes [] for y in range(0, h - crop_size 1, stride): for x in range(0, w - crop_size 1, stride): crop image[y:y crop_size, x:x crop_size] crops.append(crop) boxes.append((x, y, x crop_size, y crop_size)) # 处理右边界和下边界保证图像边缘不遗漏 if w % stride ! 0: x w - crop_size for y in range(0, h - crop_size 1, stride): crops.append(image[y:y crop_size, x:x crop_size]) boxes.append((x, y, x crop_size, y crop_size)) if h % stride ! 0: y h - crop_size for x in range(0, w - crop_size 1, stride): crops.append(image[y:y crop_size, x:x crop_size]) boxes.append((x, y, x crop_size, y crop_size)) return crops, boxes参数说明stride/crop_size的比值就是重叠率比值0.75表示25%重叠0.6表示40%重叠。重叠率过高会增加推理次数和NMS合并的复杂度过低会让跨边界的缺陷在任意一个patch里都不完整。对EL图像隐裂通常是长条状建议重叠率放到40%以上对热斑这种团状缺陷25%就够。切图后推理完要把框坐标映射回原图坐标即加上patch在原图中的偏移量再做跨patch的NMS阈值设0.45左右比较稳。3.3 低显存环境下跑模型的可行路径现场工况经常没有好显卡低显存跑模型是一个绕不开的问题。几种方案按优先级排序第一用轻量模型加输入分辨率控制把推理分辨率从640降到512或416显存占用能下降一半代价是mAP略降第二开启梯度检查点训练时计算图不保存中间激活值反向传播时重新计算显存占用直接腰斩代价是训练时间增加30%左右第三混合精度训练用FP16替代FP32显存减半且速度提升但要注意loss变成NaN时关闭相关算子或者做loss缩放。推理阶段还可以用ONNX加动态batch把batch固定为1减少临时显存。对光伏板缺陷检测这种大多数时候是离线分析的任务不需要追求训练集全部塞进显存用小batch加多iteration一样能收敛。4. 检测代码的可复现训练流程从配置文件到mAP曲线4.1 数据组织与模型配置训练代码的工程结构建议参考常见检测框架的组织方式datasets目录下按train和val分开存放图像和标签models目录放模型定义和配置文件runs目录存放训练日志和权重。数据集按下面的目录结构组织这一步直接影响训练脚本是否能直接跑通datasets/ solar_panel/ images/ train/ val/ labels/ train/ val/ data.yamldata.yaml是最关键的配置文件里面定义类别数量和类别名称。yaml文件里路径必须用绝对路径相对路径在训练脚本从不同目录启动时经常解析失败。类别名称的顺序必须和2.3节转换脚本里的class_names保持一致否则训练出的模型框标签全错。4.2 训练启动命令与batch参数的选择使用YOLO系列的检测框架时训练启动命令格式如下yolo detect train \ --model yolov8n.pt \ --data datasets/solar_panel/data.yaml \ --epochs 300 \ --batch-size 16 \ --imgsz 640 \ --device 0 \ --patience 50 \ --save-period 10参数说明batch-size按显存调12G显存跑yolov8n用16没问题跑标准模型建议降到8。imgsz取640是平衡点切图后的小patch用640训练和推理保持一致性不要训练用640推理用832这会导致框偏移。patience是早停参数验证集mAP连续50轮不上升就停止训练避免无效训练时间。save-period设10表示每10轮保存一次权重方便后面回溯找最佳checkpoint这个选项在长时间训练时非常重要因为最后一次epoch的权重不一定是最好的早停或验证集指标波动时中间checkpoint能救急。训练启动后要观察三个指标box_loss持续下降说明定位在收敛cls_loss下降说明分类在学习val_mAP50和val_mAP50-95的差距不能过大。mAP50高但mAP50-95低说明模型框定位不准可能需要调整标注框质量或增加定位损失权重。4.3 数据增强参数对缺陷检测的影响数据增强参数直接决定模型能不能在光伏板这个特定场景里泛化。默认增强里有随机翻转、缩放、色彩抖动但对光伏板缺陷要格外小心。水平翻转可以开因为缺陷方向不限垂直翻转建议谨慎因为光伏板栅线方向固定是竖直的垂直翻转后栅线方向变了模型可能学到错误的纹理偏置旋转增强在可见光缺陷上可用但在EL图像上不要开大角度旋转EL图的缺陷方向往往和生产工艺相关旋转90度或180度会让模型混淆缺陷形态。色彩增强方面HSV扰动中的饱和度扰动可以开到0.5因为现场拍摄时光照变化确实大但色调扰动不要开大光伏板EL图像是灰度图色调扰动没有意义可见光图像上色调扰动过大会让蓝色板面变成其他颜色模型学到的是颜色而不是纹理。对光伏板这个场景我的经验是把hsv_h设为0.01hsv_s设为0.5hsv_v设为0.4translate设为0.1scale设为0.3fliplr设为0.5flipud设为0.0mosaic设为1.0。mosaic增强在训练前期帮助很大能把小目标样本混合到大图上后期可以关掉或降低到0.5因为mosaic产生的拼接边界会让模型学到不真实的边缘响应。4.4 训练过程中的可视化监控与权重选择训练过程需要实时看验证集效果不能只看loss。训练到50轮左右把最近的权重拿出来对验证集做一次推理输出带框的可视化图片重点看三类错误漏检——缺陷没框出来原因是样本太少或特征不明显需要补数据误检——正常区域被框出来原因是标注缺失或背景干扰需要检查标注有没有漏标正常区域重复框——一个缺陷被框多次原因是NMS阈值太低或anchor设置不合理。这个检查比任何metric都直观。权重选择上不要默认用last.pt要看best.pt是否真的对应验证集最优mAP。有的框架best.pt按总loss保存而不是按mAP保存导致best.pt的检测效果不一定最好。我习惯在训练结束后写一个批量验证脚本遍历save-period保存的权重在验证集上逐个跑mAP选出最优权重作为最终模型。这个操作需要的时间不长但对最终模型质量的影响很大。5. 光伏板缺陷检测避坑清单数据、训练、部署三层的血泪经验5.1 数据层标注框不贴合缺陷边缘模型学的是框内噪声现象训练出的模型能框出缺陷但框明显比缺陷实际范围大一圈mAP50高、mAP50-95低精细化定位完全不可用。原因标注时为了赶进度标注框画得宽松框内包含了大量正常光伏板区域模型把正常区域的纹理特征也学进了缺陷特征里。解决返工标注所有框收紧到紧贴缺陷边缘对隐裂这种细长缺陷框要尽量贴合裂纹走向宁可在端部留一点余量也不要包进大面积正常区域。5.2 数据层类别极度不均衡正常样本占比过高导致严重偏向现象训练完成后模型对热斑很灵敏但对断栅几乎不识别验证集里断栅类别的recall只有10%左右。原因现场采集时热斑照片好拍断栅必须把组件翻过来或在特定光照下才看得到断栅样本量只有热斑的十分之一。解决一是用数据增强复制少样本类别对断栅图做平移、小幅旋转、亮度抖动变相扩容二是调整损失函数的类别权重让模型对少样本类别加大惩罚力度三是如果还有缺口的去公开数据集里找相近形态的断栅图补充光伏EL图里的断栅形态在数据集之间通用性很强。5.3 训练层模型在验证集上的表现远超实际现场效果现象验证集mAP50有0.92一拿到现场拍摄的新照片上漏检率和误检率都明显偏高。原因训练集和验证集来自同一个电站、同一个拍摄批次图像的光照条件、拍摄角度、缺陷形态高度一致模型相当于背下了这批数据的特征。解决部署前必须做跨域验证用另一个电站或不同季节拍摄的图像做测试。如果跨域效果差说明模型泛化能力不足需要在训练集里加入多光伏板的多样数据。用t-SNE对最后一层特征做可视化能看到训练域和测试域的特征分布如果明显分离就是典型的过拟合到数据域。5.4 部署层GPU推理正常但CPU推理慢到不可接受现象用GPU推理一张640x640图像耗时20ms换成电站工控机CPU推理直接卡到800ms。原因模型计算量对CPU来说确实大加上没有做推理优化。解决先用ONNX导出模型再用OpenVINO做推理优化。OpenVINO可以把CPU推理加速到300ms以内代价是精度有极小损失。如果还不能接受就换更轻量的模型参数量从3M降到1M以下。注意不要为了速度把输入分辨率降太低640降到416会损失对隐裂这种小目标的召回。5.5 部署层热斑在红外图和可见光图上表现完全不一样模型混用现象用可见光训练的模型去推理红外热像图几乎全部漏检。原因热斑在可见光图上表现为颜色斑块、阴影或玻璃破碎痕迹在红外图上表现为温度异常导致的亮斑两者的像素特征完全不同。解决要么把红外图和可见光图分开建模要么在训练集里同时加入两种模态并给标签加一个模态前缀。最佳方案是按模态拆成两个模型因为模态差异比缺陷形态差异更大一个模型硬学两种输入会导致特征冲突。6. 验证模型真本事用混淆矩阵和坏图抽检判断能不能部署验证阶段不要只盯着mAP这一个数字。训练结束后导出混淆矩阵按类别看每个缺陷的precision和recall这个能告诉你哪些类别拖后腿。对待漏检率高的类别先确认是标注样本不够还是特征不清晰再决定补数据还是模型换结构。对误检率高的类别检查是不是标注缺失导致模型把正常区域学成了缺陷。做一次坏图抽检把现场拍到的最差图像拿出来严重过曝的、欠曝的、有雨滴的、有堆积灰尘的、拍摄角度极偏的。逐张推理看结果这是判断模型能否部署的最直接方式。如果坏图里有大量误检考虑在推理前加一个图像质量预检模块用5.1节的清晰度筛选脚本同款逻辑先把低质量图拦下来不走检测避免产生垃圾结果。推理阶段的后处理还有两个值得注意的坑。第一个是NMS阈值光伏板缺陷检测里缺陷框往往紧密相邻比如多条隐裂线挨在一起阈值设太高会保留重复框设太低会把相邻缺陷合并成一个建议在验证集上对阈值做一个0.35-0.55的网格搜索。第二个是置信度阈值的设定缺陷检测任务的置信度阈值建议比通用检测任务低一些因为光伏板缺陷特征差异大部分真实缺陷的置信度天然不高设成0.45左右能减少漏检配合现场的复检流程消化误检。部署完成后还会接触到模型融合的空间。多个模型对同一张图做推理用加权NMS合并结果或者不同类型的模型投票决定是否标记为缺陷。这种方式能明显提升对隐裂这种难检测缺陷的召回率代价是推理时间翻倍。如果项目是无人机巡检后离线分析这个代价完全可以接受如果是实时在线检测则建议保持单模型加优化推理的路线。最后提一个我的习惯每次训练完把训练配置、数据划分的随机种子、预处理参数、最优权重四项信息存成一个固定的实验记录文件。光伏板缺陷检测调试周期长、现场条件差异大隔两周回来再调参时大概率会忘记当时的参数组合没有记录就只能从头试。实验记录文件写到项目根目录下就行哪天需要回滚最优模型时这份记录就是最后的后悔药。这套流程在我做过的几个光伏板检测项目里基本没翻过车希望帮到你。本文还有配套的精品资源点击获取