拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于深度学习的太阳能光伏板积灰识别:四分类图像任务实战

简介这份资源是面向计算机相关专业学生与深度学习入门者的太阳能光伏板积灰识别实战项目可用于毕业设计、课程设计或期末大作业重点解决光伏板表面灰尘的自动分类识别问题。项目采用自制灰尘数据集完成四分类任务方法上覆盖普通数据增广、AutoAugment数据增强、ResNet骨干网络、监督对比学习损失以及多种常用深度学习算法便于读者对比不同策略对识别效果的影响。压缩包共241个文件以208张jpg图像样本为主体另含12个py源码文件、若干zip与7z子包、md说明文档及模型权重文件整体约119.03MB目录结构清晰方便按数据、代码与模型模块分别查阅。该资源经导师指导并获98分评审认可已有199人学习适合希望快速复现完整训练流程、理解数据增强与对比学习落地方式的读者参考借鉴。1. 光伏板积灰识别四分类图像任务为什么值得用深度学习重做一遍光伏电站运维里有一件很磨人的事组件表面落灰、鸟粪、落叶、积雪肉眼巡检靠人爬屋顶或走阵列效率低还危险。传统做法是用红外热成像找热斑或者用人工抽检判断污染程度但这两条路都有硬伤——热斑受光照和负载影响大人工抽检覆盖不了全站。基于深度学习的太阳能光伏板积灰识别本质是把「板面状态」当成一个图像四分类问题干净、轻度积灰、重度积灰、异物遮挡。输入是一张组件表面照片输出是类别标签和置信度。这个方案适合有 Python 基础、想跑通一个完整深度学习项目的人也适合光伏运维团队做算法预研。它不追求论文级创新追求的是能复现、能部署、能解释为什么这么分。2. 四分类任务的数据集构建与预处理从原始照片到可训练张量2.1 为什么四分类比二分类更贴近运维决策二分类只回答「脏不脏」但运维要的是「要不要现在洗」。轻度积灰可能等一场雨重度积灰必须安排清洗异物遮挡则要立刻处理因为局部遮挡会引发热斑效应。四分类把决策粒度对齐到动作粒度这是这个项目最核心的选型理由。类别定义建议按遮挡面积占比来切干净遮挡5%、轻度5%~20%、重度20%~50%、异物局部硬遮挡或明显非灰尘物体。边界样本宁可单独标注也不要硬塞进某一类否则模型学到的决策面会模糊。2.2 数据采集与增强的实操参数采集时尽量覆盖不同光照、不同角度、不同组件类型。每类至少 300 张起步四类合计 1200 张以上否则四分类的类间边界很难稳住。增强不要无脑上积灰识别对颜色和纹理敏感翻转和轻微旋转可以强色彩抖动会破坏灰尘的灰度特征。下面是一段用 OpenCV 和 Albumentations 做预处理的代码把原始照片统一到 224×224 并做基础增强。import cv2 import albumentations as A from albumentations.pytorch import ToTensorV2 # 训练增强只做几何变换和轻微亮度调整避免破坏灰尘纹理 train_transform A.Compose([ A.Resize(224, 224), A.HorizontalFlip(p0.5), A.Rotate(limit15, p0.5), A.RandomBrightnessContrast(brightness_limit0.1, contrast_limit0.1, p0.3), A.Normalize(mean(0.485, 0.456, 0.406), std(0.229, 0.224, 0.225)), ToTensorV2() ]) # 验证/测试只做 resize 和归一化保证评估一致性 val_transform A.Compose([ A.Resize(224, 224), A.Normalize(mean(0.485, 0.456, 0.406), std(0.229, 0.224, 0.225)), ToTensorV2() ]) def load_and_check(path): img cv2.imread(path) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) return img这段代码里Resize(224,224)是为了对齐 ImageNet 预训练模型的输入尺寸HorizontalFlip和Rotate只做几何扰动因为积灰的分布方向没有语义RandomBrightnessContrast的幅度压到 0.1是为了模拟不同日照但又不让模型把亮度当成类别线索。Normalize用的均值和方差是 ImageNet 的统计量如果你从零训练可以改成自己数据集的统计值但用预训练权重时不要改。验证集必须用val_transform否则评估结果会虚高。2.3 数据集划分与类别不平衡处理按 7:2:1 划分训练、验证、测试且要保证同一块组件在不同集合里不重复出现否则模型会记住组件背景而不是积灰特征。如果某一类样本明显偏少用WeightedRandomSampler给少数类更高采样权重而不是简单复制图片。下面这段代码展示如何按类别权重构建采样器。import os import torch from torch.utils.data import Dataset, DataLoader, WeightedRandomSampler from PIL import Image class PVPanelDataset(Dataset): def __init__(self, root_dir, transformNone): self.samples [] self.transform transform self.class_to_idx {clean: 0, light: 1, heavy: 2, foreign: 3} for cls_name, idx in self.class_to_idx.items(): cls_dir os.path.join(root_dir, cls_name) for fname in os.listdir(cls_dir): self.samples.append((os.path.join(cls_dir, fname), idx)) def __len__(self): return len(self.samples) def __getitem__(self, index): path, label self.samples[index] img Image.open(path).convert(RGB) if self.transform: img self.transform(imagenp.array(img))[image] return img, label # 计算类别权重并构建采样器 def make_sampler(dataset): labels [s[1] for s in dataset.samples] class_counts np.bincount(labels) class_weights 1.0 / class_counts sample_weights [class_weights[l] for l in labels] return WeightedRandomSampler(sample_weights, num_sampleslen(sample_weights), replacementTrue)class_to_idx的顺序要和后续模型输出层一致不要随意调换。WeightedRandomSampler的replacementTrue表示允许重复采样这是为了把少数类拉到和多数类相近的出现频率。注意采样器只作用于训练集验证集和测试集必须用原始分布否则评估指标会失真。3. 用 CNN 搭四分类模型从 ResNet 迁移到自定义分类头3.1 为什么选 ResNet18 而不是从零搭网络积灰识别不是 ImageNet 那种千类问题四分类的类间差异主要体现在纹理和局部遮挡上。ResNet18 的残差结构能稳住梯度参数量约 11M在单张 8G 显存的卡上就能跑推理速度也够边缘部署。从零搭一个五六层卷积网络不是不行但需要更多数据和更长的训练周期对复现者不友好。常见做法是加载 ImageNet 预训练权重把最后的全连接层换成四输出再解冻后面两个 stage 做微调。3.2 模型定义与训练循环的关键参数下面这段代码用 torchvision 加载 ResNet18替换分类头并定义训练循环。关键参数包括学习率、优化器、损失函数和冻结策略。import torch import torch.nn as nn import torchvision.models as models from torch.optim import AdamW from torch.optim.lr_scheduler import CosineAnnealingLR def build_model(num_classes4, freeze_backboneTrue): model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) if freeze_backbone: for param in model.parameters(): param.requires_grad False # 替换全连接层输入维度是 ResNet18 的 512 model.fc nn.Sequential( nn.Dropout(0.3), nn.Linear(512, num_classes) ) return model def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss, correct, total 0, 0, 0 for imgs, labels in loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() outputs model(imgs) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() correct (outputs.argmax(1) labels).sum().item() total labels.size(0) return total_loss / len(loader), correct / total # 初始化 device torch.device(cuda if torch.cuda.is_available() else cpu) model build_model(num_classes4, freeze_backboneTrue).to(device) criterion nn.CrossEntropyLoss(label_smoothing0.1) optimizer AdamW(filter(lambda p: p.requires_grad, model.parameters()), lr1e-3, weight_decay1e-4) scheduler CosineAnnealingLR(optimizer, T_max20)freeze_backboneTrue时只训练新的全连接层适合数据量小于 2000 张的情况如果数据量够可以在第 5 个 epoch 后解冻layer3和layer4把学习率降到 1e-4。label_smoothing0.1是为了防止模型对边界样本过度自信积灰的轻度与重度之间本来就有模糊地带。AdamW的weight_decay设 1e-4比 SGD 更容易调。CosineAnnealingLR的T_max设成总 epoch 数让学习率平滑降到接近零。3.3 训练过程中的监控指标与早停策略不要只看准确率四分类任务里少数类的召回率更重要。建议每个 epoch 记录混淆矩阵和每类 F1。如果验证集 loss 连续 5 个 epoch 不降就停。下面是一个简单的早停实现。class EarlyStopping: def __init__(self, patience5, min_delta1e-4): self.patience patience self.min_delta min_delta self.counter 0 self.best_loss None self.stop False def __call__(self, val_loss): if self.best_loss is None: self.best_loss val_loss elif val_loss self.best_loss - self.min_delta: self.counter 1 if self.counter self.patience: self.stop True else: self.best_loss val_loss self.counter 0 return self.stoppatience5是经验值数据噪声大可以调到 7。min_delta设 1e-4 是为了忽略正常波动。早停触发后回滚到验证集 loss 最低的那组权重不要用最后一个 epoch 的权重。4. 积灰识别项目避坑从数据标注到推理部署的 5 个血泪教训4.1 现象验证集准确率 95%测试集掉到 70%原因同一块组件在不同集合里重复出现模型记住了组件背景和拍摄角度没学到积灰特征。解决按组件 ID 划分数据集确保同一组件的所有照片只出现在一个集合里。如果数据采集时没记录组件 ID至少按拍摄日期和位置做分组划分。4.2 现象模型把「重度积灰」和「异物遮挡」频繁混淆原因异物类样本里混入了大量深色灰尘团标注边界不清。解决重新定义异物类只保留明显非灰尘物体鸟粪、落叶、线缆深色灰尘归到重度积灰。如果两类确实难分考虑加一个「疑似异物」类或者用多标签输出。4.3 现象训练 loss 震荡准确率上不去原因学习率太大或者 batch size 太小导致梯度噪声大。解决先把学习率降到 1e-4 试batch size 至少 16。如果显存不够用梯度累积模拟大 batch。另外检查归一化参数是否和预训练权重匹配用错均值方差会让模型收敛变慢。4.4 现象推理时单张图片耗时超过 500ms原因模型没转成推理模式或者输入尺寸太大。解决推理前调model.eval()并加torch.no_grad()把输入固定到 224×224。如果还要更快用 ONNX Runtime 或 TensorRT 做量化ResNet18 在 INT8 下可以压到 50ms 以内。4.5 现象部署到边缘设备后模型输出全是同一类原因预处理不一致。训练时用了 ImageNet 归一化部署时忘了减均值除方差或者通道顺序从 RGB 变成了 BGR。解决把预处理代码封装成一个函数训练和推理共用同一份不要各写各的。5. 进阶技巧用 Grad-CAM 验证模型到底在看哪里5.1 为什么需要可解释性验证积灰识别模型很容易走捷径如果训练集里重度积灰的照片恰好都偏暗模型可能学会「暗重度」而不是真的看灰尘纹理。Grad-CAM 可以把模型关注区域热力图叠加到原图上一眼看出它是在看板面还是看背景。这个技巧在项目验收和运维解释时特别有用。5.2 Grad-CAM 的实现与结果解读下面这段代码用 pytorch-grad-cam 库对单张图片生成热力图。如果没有这个库可以用pip install grad-cam安装。from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.image import show_cam_on_image import numpy as np import cv2 # 加载训练好的模型并设为 eval model.eval() target_layers [model.layer4[-1]] # ResNet18 最后一个残差块 cam GradCAM(modelmodel, target_layerstarget_layers) # 准备输入 rgb_img cv2.imread(test_heavy.jpg)[:, :, ::-1] # BGR 转 RGB rgb_img cv2.resize(rgb_img, (224, 224)) / 255.0 input_tensor val_transform(imagergb_img)[image].unsqueeze(0).to(device) # 生成热力图target_category 指定要看哪一类 grayscale_cam cam(input_tensorinput_tensor, targetsNone) grayscale_cam grayscale_cam[0, :] visualization show_cam_on_image(rgb_img.astype(np.float32), grayscale_cam, use_rgbTrue) cv2.imwrite(cam_output.jpg, visualization[:, :, ::-1])target_layers选layer4[-1]是因为最后一层卷积保留了空间信息又足够高层。targetsNone表示用模型预测的类别也可以传targets[2]强制看重度积灰类。生成的热力图中红色区域是模型最关注的地方。如果红色集中在板面灰尘区域说明模型学对了如果红色在边框、天空或地面说明模型走了捷径需要重新检查数据或加遮挡增强。5.3 用热力图反推数据问题的一个实例我自己的习惯是每训完一版模型随机抽 20 张验证集图片跑 Grad-CAM按类别分组看。有一次发现「干净」类的热力图总是集中在组件边框的金属反光上原因是干净类照片大多在强光下拍摄边框反光成了类别线索。后来在增强里加了随机遮挡边框的 Cutout模型才回到看板面。这个排查过程比看准确率有用得多因为准确率不会告诉你模型为什么对。注意Grad-CAM 的热力图分辨率是 7×7 上采样到 224×224定位精度有限不要用它做像素级标注只用来判断关注区域是否合理。5.4 从四分类到更细粒度的迁移思路如果四分类跑通后想继续做可以把类别换成遮挡面积百分比回归或者加一个「清洗建议」输出。迁移时保留 ResNet18 主干把分类头换成回归头损失函数从 CrossEntropy 换成 MSE 或 Huber。数据标注成本会上升但运维价值更高。我一般会先用四分类验证特征提取能力再决定要不要往细粒度走。希望帮到你。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门