
简介面向绝缘子表面污染物识别任务这份已标注分类数据集可支撑深度学习图像分类模型训练与验证适合电力巡检、计算机视觉方向的研究者和学习者使用。数据覆盖灰尘、脏污、正常等四种类别约14400张图片并预先划分好训练集与测试集各类别图片分目录存放json文件记录具体类别标签便于直接开展分类实验。压缩包共2000个文件以jpg图片为主体1998个另附1个python脚本和1个json标注文件python脚本可对数据集进行可视化预览json文件辅助解析标签与划分信息整体包体约267.75MB。目前已有83人学习。借助博主的配套项目还可与基于CNN的分类网络、图像分割以及yolov5改进等方案衔接从单一分类任务扩展到检测、分割等更多应用场景是一份兼顾基础实验与进阶扩展的数据资源。1. 绝缘子污染物模拟图像分类数据集我建议你先把它当测试台绝缘子污染物模拟图像分类数据集我建议你把它当作迁移学习和模型验证的“测试台”来用约 14400 张已标注图像灰尘、脏污、正常等四分类训练集和测试集已经划分好还附带 show 脚本。做输电线路巡检和电力设备视觉检测的人总会遇到分类模型在公开数据集上达标、到自己绝缘子图片上就露馅的尴尬这个数据集正好提供一个能快速复现的对照场景。但别急着解压跑训练先确认四件事json 里类别顺序、目录结构、每类样本量、图片尺寸。下面按我的习惯一步步拆开。2. 四分类标注与目录结构先把数据读透再谈训练2.1 json 类别映射先看标签再定损失函数很多下载下来的分类数据集都带一个 annotations.json 或 label_map.json这个数据集也不例外。json 里通常记录的是类别 id 和类别名的映射以及可能每张图的标注信息。我的习惯是解压后第一件事就是把它读出来而不是直接开跑训练脚本。import json from collections import Counter with open(annotations.json, r, encodingutf-8) as f: ann json.load(f) # 常见结构一class_names annotations 列表 class_names ann.get(class_names, []) print(类别列表:, class_names) # 常见结构二图片路径列表 类别id if annotations in ann: img_labels [(item[image], item[class_id]) for item in ann[annotations]] print(标注条数:, len(img_labels)) counter Counter([label for _, label in img_labels]) print(每个类别的样本数:, counter)这段代码把最关键的类别顺序打印出来。为什么先看这个因为后面用 ImageFolder 或者自己写 Dataset 时类别 id 默认是按文件夹名称排序的如果 json 里的 id 顺序和文件夹排序不一致训练时标签就可能错位。比如 json 里“0normal,1dust,2dirty,3other”而磁盘上文件夹排序是“dirty,dust,normal,other”那么错误率会在 50% 以上而且模型自己很难察觉。从文件名前缀 isolador_vidro_semTextura_512x 也能看出这批图是同一批玻璃绝缘子在不同污染模拟条件下的截图。因为文件名里带着 semTextura说明图片是去掉表面纹理的简化模拟图这对训练来说是一个重要信息——模型学到的可能是颜色和灰度分布而不是纹理细节。后面做增强和迁移学习时需要考虑到这一点。2.2 训练测试目录确认 ImageFolder 能不能直接用再看目录结构。摘要里说“划分了训练集、测试集存放各自的同一类数据图片”通常做法是dataset/ ├── train/ │ ├── normal/ │ ├── dust/ │ ├── dirty/ │ └── other/ └── test/ ├── normal/ ├── dust/ ├── dirty/ └── other/用下面的脚本验证实际落盘结构和样本量import os for split in [train, test]: base f./dataset/{split} subdirs [d for d in os.listdir(base) if os.path.isdir(os.path.join(base, d))] print(split, 类别:, subdirs) total 0 for cls in subdirs: files os.listdir(os.path.join(base, cls)) print( , cls, len(files)) total len(files) print( total:, total)这一步可以顺带确认两个事一是 train/test 里类别是否一致二是每类的样本量是否均衡。如果发现 train 里有 normal 而 test 里没有或者 test 里多了一个类那你训练出来的模型在评估时一定会崩。我遇到过好几次类似的数据集——作者重新整理了目录但忘了同步两边的类别集合最后靠这个脚本才定位到问题。2.3 show 脚本可视化人眼先过一遍show 脚本是这份资源里比较贴心的部分。它的作用就是按类别把图片铺成网格让你一眼看到四类长什么样。常见用法是先看 helppython show.py --help如果脚本写得规范会看到类似下面这样的一组参数python show.py --data_dir ./dataset --mode grid --sample_per_class 8 --save_dir ./preview参数说明--data_dir指向数据集根目录--mode控制展示模式grid表示按类别网格展示--sample_per_class控制每个类别抽取几张--save_dir把拼接好的预览图保存到指定目录。有些版本还会支持--show_label把 json 里的类别名叠在图片上。跑完会发现normal 类就是干净的玻璃绝缘子dirty 类表面有明显黑色或深色的积污条带dust 类像盖了一层薄薄的灰。如果你发现某个类里混了明显不对的图趁早自己决定是删掉还是手工纠正否则后面训练出来的模型会把噪声当作特征。我一般会把预览图存下来贴在实验记录里之后每次分析 test set 错误样本时都回来对照。3. 用 CNN 跑通绝缘子四分类从 DataLoader 到训练闭环3.1 transforms 与输入尺寸512x512 怎么处理这批原始图片来源是 512x512 左右的图。CNN 分类任务里直接喂 512x512 也不是不行但显存占用大、收敛慢。常见做法是先缩放到 256x256再随机裁剪到 224x224这样等于顺手做了一点数据增强。也可以用 Resize 到 224。如果你打算后面接 YOLOv8 做检测那么保持大分辨率反而有价值这里先讨论分类。from torchvision import transforms train_transform transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomCrop(224), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness0.1, contrast0.1, saturation0.1), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) test_transform transforms.Compose([ transforms.Resize((256, 256)), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ])ColorJitter 的幅度要克制。这个数据集本质是模拟污染物本身灰度差异和颜色分布是关键特征。你把对比度拉得太多可能会把 normal 的图增强得像 dirty模型会特别困惑。而且原始图已经是 semTextura——无纹理的模拟图再叠加高斯模糊这类强增强会丢失仅剩的可分信息。所以我的建议是轻量增强可以重增强慎重。3.2 模型选型ResNet18 起步Transformer 先等等14400 张图在分类任务里算“小中型”数据集预训练 ResNet18 是一个性价比很高的起点。它只有 11M 参数单卡 V100 或 3060 上batch 32 跑一个 epoch 大约几十秒到一两分钟。相比 ResNet50ResNet50 精度可能高 1-2 个点但训练时间翻倍。我一般先拿 ResNet18 定基线和排错确认数据链路没问题后再升级到 ResNet50 或 EfficientNet。import torchvision.models as models import torch.nn as nn model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) num_ftrs model.fc.in_features model.fc nn.Linear(num_ftrs, 4)如果要用 Transformer比如 ViT-B/1614400 张肯定能训但需要更精细的增强、更长的 warmup 和 weight decay收益不一定比 CNN 大。只有当你要和检测网络共享 backbone或者你怀疑卷积的感受野不足以捕捉大面积积污条带时再试 ViT。起步阶段别跟风先把 ResNet18 这版跑通。3.3 训练参数与主循环batch、lr、loss 怎么配建议训练参数batch_size32显存不够就 16lr1e-4用 AdamWweight_decay0.01训练 30 个 epoch每隔 5 个 epoch 在 test 上评估一次保存最优准确率的权重。这个数据集类别数少用标准交叉熵即可。如果发现 dirty 和 dust 特别容易混淆可以给 loss 加类别权重但首先要排除边界问题——也就是图片本身在两者之间确实没有明显边界。import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader from torchvision.datasets import ImageFolder device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) train_ds ImageFolder(./dataset/train, transformtrain_transform) test_ds ImageFolder(./dataset/test, transformtest_transform) train_loader DataLoader(train_ds, batch_size32, shuffleTrue, num_workers4) test_loader DataLoader(test_ds, batch_size32, shuffleFalse, num_workers4) criterion nn.CrossEntropyLoss() optimizer optim.AdamW(model.parameters(), lr1e-4, weight_decay0.01) scheduler optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max30) best_acc 0.0 for epoch in range(30): model.train() total_loss 0.0 for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() * images.size(0) scheduler.step() print(fepoch {epoch1}, loss: {total_loss/len(train_ds):.4f})这段主循环省略了验证逻辑但核心结构没问题。值得提醒的是ImageFolder要求目录严格按类别排列如果 json 里类别顺序和目录不一致会出现上面提到的错位。还有一点num_workers在 Windows 下建议设为 0否则会报BrokenPipeErrorLinux 下可以开 4 或 8加快 IO。4. 可视化与结果验证不要只看准确率4.1 show 脚本的扩展用法把预测结果叠回预览图show 脚本不只可以用来展示原始数据。很多版本允许传入一个预测结果文件它会在原图上叠出预测类别和置信度。用法类似python show.py --data_dir ./dataset/test --csv predict_results.csv --mode overlay --sample_per_class 5这样你能最直观地看到哪些图被分错。特别是绝缘子模拟图里dirty 和 dust 的边界非常主观人眼看都不一定能分清。把预测叠在原图上比翻一堆数字有效得多。4.2 混淆矩阵错误都集中在哪里训练结束后在 test 集上做一次完整推理生成混淆矩阵和分类报告。这一步别省。准确率 90% 告诉你“还行”混淆矩阵告诉你“哪里不行”。from sklearn.metrics import confusion_matrix, classification_report import torch model.eval() all_preds, all_labels [], [] with torch.no_grad(): for images, labels in test_loader: images, labels images.to(device), labels.to(device) preds model(images).argmax(dim1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) cm confusion_matrix(all_labels, all_preds) print(cm) print(classification_report(all_labels, all_preds, target_namestrain_ds.classes))如果看到 normal 被错分成 dust大概率是增强过重或者积灰轻微的 normal 本来就和 dust 长得像。如果看到 dirty 被误分成 normal就要怀疑是光照问题——模拟图里有些脏污颜色很浅人工标注本身也存在主观性。这些结论需要回到 show 脚本的预览图里人工确认不能直接调 loss 权重掩盖数据本身的模糊性。4.3 Grad-CAM模型到底在看哪里绝缘子污染识别的关键是模型要看绝缘子表面而不是背景的天空、导线或者其他杂物。用 Grad-CAM 能快速验证这一点。PyTorch 里可以用 torchcam 这个库代码参考如下from torchcam.methods import GradCAM cam_extractor GradCAM(model, target_layerlayer4) model.eval() with torch.no_grad(): out model(img_tensor.unsqueeze(0)) act cam_extractor(0, out) cam act[0].squeeze(0).cpu().numpy()注意target_layer需要根据实际模型名称调整ResNet18 是layer4如果换成 EfficientNet 或 ViT层名完全不同。对 test 集抽几张图跑一下把热力图叠加到原图上。如果热力点集中在绝缘子伞裙边缘说明模型学到了结构特征如果大面积覆盖背景那你就要注意了——模型可能学到了“环境亮度”这类捷径。对于模拟图背景相对干净这个问题不严重但一旦将来换到真实巡检图片模型鲁棒性会明显下降。5. 避坑指南14400 张标注数据里最常翻车的五个位置5.1 json 类别顺序与文件夹排序不一致现象训练时 loss 正常下降但 test 准确率只有 20%40%重复几次都一样。原因json 里的类别 id 和 ImageFolder 按名称排序后的类别顺序对不上。比如 json 里 0dust、1dirty、2normal、3other而文件夹排序是 dirty、dust、normal、other于是所有标签都偏移了一位。解决训练前同时打印 json 里的 class_names 和ImageFolder自动生成的 class_to_idx人工对齐后再喂给模型。如果你自己写 Dataset干脆忽略 json 里的 id统一用文件夹名作为标签来源把 json 只作为类别注释。5.2 train 和 test 里类别集合不一致现象训练正常一跑 test 就报 KeyError 或准确率异常还可能 classes 列表长度不同。原因整理数据时test 里漏拷了一个类文件夹或者多了一个只有几张图的类。解决用os.listdir对比两个 split 的类别集合。多出来的类删掉或移入对应类缺失的类从 train 里按比例抽一部分补进去。这一步要趁早做否则后面做迁移学习或生成 report 时全是坑。5.3 文件名前缀一样就默认为同类现象展示预览图时每个类别文件夹里都看到同样的isolador_vidro_semTextura_512x_*.jpg前缀于是怀疑数据集是不是没分对。原因文件名是同一批原始图生成的编号和类别无关。这个前缀只是在生成模拟图时保留的原始命名不携带标签信息。解决不要靠文件名判断类别一切以文件夹和 json 为准。如果你有强迫症可以写脚本把训练集按类别统一改名但这不是必要的。5.4 迁移学习时分类头改错现象把 ResNet18 的 fc 层改成 4 后训练报维度不匹配的错或者拿 ResNet50 的代码改到 ResNet18 上报错。原因不同模型的分类头不一样ResNet 是fcEfficientNet 是classifierViT 是heads.head。总有人直接套模板。解决打印 model 最后一层再改。代码写成num_ftrs model.fc.in_features是通用做法但换模型就必须同步换属性名。我的习惯是封装一个小函数传入模型和类别数内部判断分类头类型避免每次都去翻模型结构。5.5 ColorJitter 把类别边界调没了现象加了 ColorJitter 后验证集准确率反而比不加低好几个点尤其 dirty 类掉得厉害。原因这个数据集靠灰度/颜色差异区分污染物。亮度对比度拉过头会把轻度积污的图“洗”成 normal或把 normal 增强成 dirty造成了矛盾的标签。解决把 brightness0.1、contrast0.1或者干脆只保留水平翻转和随机裁剪。对语义极其依赖低层颜色统计特征的场景增强策略要保守不要套用 ImageNet 上那套强增强。6. 进阶把四分类数据集用足再往前推一步6.1 把分类输出变成检测任务的“半自动标注”如果你下一步想做绝缘子目标检测这个分类数据集不能直接用于 YOLO但它可以帮你减少标注工作量。先用训练好的分类模型在真实巡检图上做滑窗推理滑窗尺寸可以用 256x256、步长 64把高置信度的绝缘子区域裁剪出来保存为伪标签。之后用标注工具人工修正边界框时你只需要改框不需要重新选类别。这套流程我实践下来能把一张图的标注时间从 5 分钟压到 2 分钟而且类别名称可以直接沿用这里的四分类。6.2 用 ViT / Swin 做一次对比实验等 ResNet18 基线稳定后值得花半天时间试一次 ViT-B/16 或 Swin-T。针对大块积污区域Transformer 的全局感受野有时会带来惊喜。微调时用 lr2e-5 到 5e-5、batch_size32、warmup_ratio0.1、weight_decay0.05训练 15-20 个 epoch 就够。对比时要固定相同的 train/test 划分并记录每次实验的混淆矩阵不要只比较 Accuracy。调这种模型时不要随手改增强变换了数据分布模型对比就没意义了。6.3 每次拿到数据集的三件套习惯从那以后我每次拿到这类带标注的视觉数据集都强制自己走一遍三件套先打印 json 和目录结构并存日志再跑 10 个 epoch 的 ResNet18 基线最后用 Grad-CAM 检查 3 张正确、3 张错误样本。这三个动作加起来不到一小时却能规避绝大多数“数据没搞清就训模型”的翻车。尤其这个绝缘子数据集看着注释很齐全但类别顺序、目录差异和增强过度这三个坑哪一个都能让你白跑一个下午。希望帮到你。本文还有配套的精品资源点击获取