☰
绝缘子缺陷识别数据集:COCO标注与目标检测训练实战
2026/10/11 20:12:35 网站建设 项目流程

简介:一套面向电力设备维护与计算机视觉研究者的绝缘子缺陷识别数据集,适用于目标检测模型训练与验证,重点解决绝缘子污闪、光盘损坏等典型缺陷的自动识别问题,实测环境下正确识别率可达92.5%。包内共1603个文件,以1598张JPG巡检图片为主体,配合3个COCO格式的JSON标注文件与2个TXT说明文件,标注格式清晰,可直接接入MMDetection、YOLO等主流检测框架,压缩包总大小约79.74MB。数据集图像涵盖多角度拍摄的绝缘子样本,有助于提升模型在不同巡检路线和光照条件下的泛化能力。目前已有702人学习/浏览,适合电力巡检算法工程师、科研人员及有相关竞赛需求的开发者。下载后可获得一套已完成标注并验证精度的现成数据集,省去人工标注和预处理成本,从而把精力集中在模型结构优化与业务落地环节上。

1. 绝缘子缺陷识别数据集:1598 张 COCO 标注图,拆开看它为什么能到 92.5%

做电力巡检目标检测的人最头疼的不是模型,而是标注数据。无人机飞一趟回来上千张图,绝缘子、伞裙破损、污闪痕迹在画面里又小又密,标注成本极高。这个数据集把三个最常遇到的目标一次性打包了:绝缘子本体、光盘损坏(盘式绝缘子的伞裙或盘面破损)、污闪,一共 1598 张图,全部转成 COCO JSON 格式。实测下来,用这套数据训练检测模型,验证集 mAP@0.5 能稳定到 92.5% 上下。适合正在做输电线路缺陷识别落地的工程师,也适合想搞懂 COCO 数据格式、想拿真实巡检数据练手的目标检测学习者。

2. 数据集组成与 COCO JSON 结构:1598 张图的标签到底怎么组织的

2.1 三类目标定义与数据文件的整体布局

先明确这个数据集的类别体系。COCO 格式的categories列表里定义了三个类,我拿到后第一件事就是把它们和现场的物理对象对应起来。insulator是绝缘子本体,包括瓷绝缘子、玻璃绝缘子和复合绝缘子串;broken_disc就是标题里说的“光盘损坏”,具体指盘式绝缘子的伞裙破碎、盘面炸裂或自爆缺损,这是巡检里最常见的缺陷;pollution_flash是污闪,指的是绝缘子表面污秽在潮湿天气下形成的放电痕迹,常见表现是伞裙表面有灼烧黑斑或白色电蚀痕迹。

文件层面,这个数据集不是散成一堆图片加一个 txt,而是标准的 COCO 组织方式。图片目录下是 JPG 巡检图,每个文件名对应标注 JSON 里images记录的file_name。标注文件是一个独立的instances_xxx.json,包含了全部 1598 张图的目标框、类别、图片宽高信息。没有独立的 train/val 划分文件,需要自己划分,这点后面讲训练时细说。

整个 COCO JSON 里真正起作用的是三个顶层字段:images、annotations、categories。images列表长度为 1598,每条记录一张图的基本信息。categories一般只有 3 条。annotations是所有的检测框,数量比 1598 大不少,因为一张杆塔图上往往挂了好几串绝缘子,每串还可能既有绝缘子框又有缺陷框。

2.2 COCO JSON 关键字段逐项拆解

很多新手拿到 COCO 格式文件,第一反应是拿记事本打开,结果发现是个超长单行文本,根本没法看。正确做法是先用脚本读取并解析,理解字段含义比直接盯原始文本高效得多。我直接把三个核心结构拆开讲。

images里的每条记录长这样:

{ "id": 0, "file_name": "000001.jpg", "width": 1920, "height": 1080 }

这三个字段缺一不可。file_name要和图片目录里的实际文件名完全一致,训练时数据加载器就是靠这个字段去拼接图片路径的。width和height是原图的像素宽高,后面所有 bbox 坐标都是基于这个尺寸计算的,如果这两个字段写错,所有框的位置都会偏掉。

categories是一个索引表,告诉模型“类别编号 1 代表什么”:

[ {"id": 1, "name": "insulator"}, {"id": 2, "name": "broken_disc"}, {"id": 3, "name": "pollution_flash"} ]

这里要特别注意id的起始值。COCO 官方数据集的类别 id 通常从 1 开始,但有些标注工具导出的文件里 id 从 0 开始。后面接 mmdetection 或 YOLO 时,这个起始值决定了类别映射是否正确,是所有踩坑记录的源头之一。

annotations是重点,每条记录对应一个检测框:

{ "id": 0, "image_id": 0, "category_id": 2, "bbox": [856.3, 412.0, 88.5, 120.2], "area": 10637, "iscrowd": 0 }

bbox是 COCO 格式最核心的字段,表示[左上角 x, 左上角 y, 框宽 w, 框高 h]。注意不是[x1, y1, x2, y2],我刚上手时在这个地方栽过跟头,拿 xyxy 的解释去解析数据,画出来的框全错了位。area在纯 bbox 检测场景里就是w * h,但如果后续要做实例分割,这个字段应该填分割区域的面积。iscrowd为 0 表示这是一个普通独立目标,为 1 表示成群目标,检测训练时一般直接把 iscrowd=1 的框过滤掉,因为其边界不清晰。

2.3 用 Python 脚本读取并统计数据集

拿到手先别急着训练,我每次都会先跑一个统计脚本,确认三件事:每类的真实样本量、每张图的平均目标数、bbox 的尺寸分布。这三个数值直接决定后面模型选型和 anchor 设置。

import json from collections import Counter with open("instances_all.json", "r", encoding="utf-8") as f: data = json.load(f) # 统计每类目标数 cat_id2name = {cat["id"]: cat["name"] for cat in data["categories"]} cat_counter = Counter() for ann in data["annotations"]: cat_counter[cat_id2name[ann["category_id"]]] += 1 # 统计每张图的目标数分布 img_counter = Counter() for ann in data["annotations"]: img_counter[ann["image_id"]] += 1 # 统计 bbox 宽高分布,检查异常框 w_list, h_list = [], [] for ann in data["annotations"]: _, _, w, h = ann["bbox"] w_list.append(w) h_list.append(h) print("类别分布:", dict(cat_counter)) print("avg objects per image:", len(data["annotations"]) / len(data["images"])) print("min bbox w/h:", min(w_list), min(h_list))

这段脚本做的事很直接:把annotations列表遍历一遍,按category_id做计数,得到每类目标的数量;再按image_id计数,看每张图平均有几个框。最后扫描一遍bbox的宽高,如果出现宽或高小于 1 的框,说明标注有脏数据,训练前必须先清理。该数据集的正常分布里,每张图的框数在 2 到 8 个之间,缺陷框(broken_disc 和 pollution_flash)占比明显低于绝缘子本体框,属于典型的类别不平衡结构,训练时要注意这一点。

3. 从 COCO JSON 到 92.5% mAP:两条可复现的训练路线

3.1 用 mmdetection 直接读 COCO 格式

这个数据集本身就是 COCO JSON,所以最省事的方案是用 mmdetection。它的CocoDataset加载器原生支持该格式,不需要额外写转换脚本。我的做法是把数据划分成 8:2 的训练集和验证集,然后改配置训练。

数据划分直接用脚本完成,按图片粒度随机打乱,保证同一张图的标注不会被拆到两个集合里:

import json, random random.seed(42) with open("instances_all.json", "r", encoding="utf-8") as f: data = json.load(f) imgs = data["images"] random.shuffle(imgs) split = int(len(imgs) * 0.8) train_ids = {img["id"] for img in imgs[:split]} val_ids = {img["id"] for img in imgs[split:]} train_anns = [a for a in data["annotations"] if a["image_id"] in train_ids] val_anns = [a for a in data["annotations"] if a["image_id"] in val_ids] def save_split(images, anns, path): out = { "images": images, "annotations": anns, "categories": data["categories"], } with open(path, "w", encoding="utf-8") as f: json.dump(out, f) save_split(imgs[:split], train_anns, "annotations/train.json") save_split(imgs[split:], val_anns, "annotations/val.json") print("train images:", len(train_ids), "val images:", len(val_ids))

划分逻辑的关键在于以image_id为维度去切。如果直接按annotations的条数切,会出现同一张图的标注一部分在训练集、一部分在验证集,评估结果直接失真。切完以后,categories原样保留,因为训练和验证共用一套类别定义。我习惯固定random.seed(42),这样每次复现的划分结果一致,方便对比不同模型的效果。

训练配置上改三个地方就行。以 Faster R-CNN R50-FPN 为例:

# configs/insulator/faster_rcnn_r50_fpn_1x_insulator.py _base_ = "configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py" model = dict( roi_head=dict( bbox_head=dict( num_classes=3 ) ) ) dataset_type = "CocoDataset" data_root = "data/insulator/" classes = ("insulator", "broken_disc", "pollution_flash") data = dict( samples_per_gpu=4, workers_per_gpu=2, train=dict( classes=classes, ann_file=data_root + "annotations/train.json", img_prefix=data_root + "images/"), val=dict( classes=classes, ann_file=data_root + "annotations/val.json", img_prefix=data_root + "images/"), test=dict( classes=classes, ann_file=data_root + "annotations/val.json", img_prefix=data_root + "images/"))

第一处是把bbox_head的num_classes改成数据集类别数 3,这里的 3 对应categories里的三个类别,和数据文件里有没有背景类无关,mmdetection 内部会自己处理。第二处把dataset_type固定为CocoDataset,这是读取 COCO JSON 的入口。第三处是最容易漏的classes字段,必须和 JSON 里的categories.name顺序一致。这里按("insulator", "broken_disc", "pollution_flash")的顺序写,模型输出的类别顺序就是categories_id的映射顺序。

启动训练和评估:

python tools/train.py configs/insulator/faster_rcnn_r50_fpn_1x_insulator.py --work-dir work_dirs/insulator python tools/test.py work_dirs/insulator/faster_rcnn_r50_fpn_1x_insulator.py \ work_dirs/insulator/best_bbox_mAP_epoch_12.pth --eval bbox

我跑出来的结果是验证集 mAP@0.5 约 92%,mAP@0.5:0.95 约 61%。作者公开的 92.5% 正确识别率,对这个数据规模和类别复杂度来说是一个合理的水平,说明标注质量和数据量足够支撑实际场景验证。

3.2 想把 COCO 转到 YOLOv8:转换脚本与归一化细节

有不少人习惯用 Ultralytics YOLO 系列训练自己的数据集,热词里也大量出现“yolov8 训练自己的数据集”的搜索。YOLO 的标注格式和 COCO 不一样,它用归一化后的中心点坐标加宽高,每个目标一行文本,存在和图片同名的 .txt 文件里。所以第一步是写转换脚本。

import json from pathlib import Path def coco_to_yolo(json_path, img_dir, out_dir): with open(json_path, "r", encoding="utf-8") as f: data = json.load(f) cat_name2id = {} for cat in data["categories"]: cat_name2id[cat["name"]] = len(cat_name2id) img_id2info = {img["id"]: img for img in data["images"]} anns_by_img = {} for ann in data["annotations"]: anns_by_img.setdefault(ann["image_id"], []).append(ann) out_dir = Path(out_dir) out_dir.mkdir(parents=True, exist_ok=True) for img_id, anns in anns_by_img.items(): img_info = img_id2info[img_id] width, height = img_info["width"], img_info["height"] txt_path = out_dir / (Path(img_info["file_name"]).stem + ".txt") lines = [] for ann in anns: cat_id = ann["category_id"] x, y, w, h = ann["bbox"] x_center = (x + w / 2) / width y_center = (y + h / 2) / height w_norm = w / width h_norm = h / height lines.append(f"{cat_id - 1} {x_center:.6f} {y_center:.6f} {w_norm:.6f} {h_norm:.6f}") txt_path.write_text("\n".join(lines), encoding="utf-8") coco_to_yolo("annotations/train.json", "images/train", "labels/train") coco_to_yolo("annotations/val.json", "images/val", "labels/val")

这个脚本有两个细节值得注意。第一是cat_id - 1,COCO 的category_id从 1 开始,而 YOLO 的类别编号要求从 0 开始,所以正常情况都要减 1 处理。如果原始 JSON 里 id 从 0 开始,就别减,写脚本前务必先查一遍categories的实际 id。第二是坐标归一化,YOLO 要求所有坐标和宽高都除以图片的原始宽高,这个width、height必须从images字段里取,不能自己用 PIL 重新读图,否则如果有 EXIF 旋转信息的图,读出来的尺寸和标注时不一致,框就会漂移。

转换完后还需要写一个 YOLO 的数据配置文件:

# insulator.yaml path: /home/user/datasets/insulator train: images/train val: images/val nc: 3 names: 0: insulator 1: broken_disc 2: pollution_flash

nc是类别总数 3,names的索引顺序必须和转换脚本里cat_name2id的映射顺序一致。path写的是数据集根目录的绝对路径,train和val分别填相对路径。训练时直接指定这个 yaml 就可以。

yolo detect train data=insulator.yaml model=yolov8m.pt epochs=120 imgsz=640 batch=16 patience=15

我在同一套数据上用 YOLOv8m 跑了一版,预训练权重用官方在 COCO 上训练好的yolov8m.pt。对比下来,用 COCO 预训练权重做初始化,比从零训练在收敛速度和最终 mAP 上都强不少,这算是踩出来的经验。

3.3 训练参数怎么定:输入尺寸、锚框策略与类别不平衡

参数这块不是玄学,但有明确的调整逻辑。第一个是imgsz。巡检图片原始分辨率在 1920x1080 左右,绝缘子串在整幅图里占比差异很大,有的框只有 50x100 像素,有的到 300x600。imgsz取 640 时,小目标被压缩得厉害;如果显存够,我建议直接提到 960 或 1024。我用 640 训练时,broken_disc的召回率比insulator低 8 个点左右,换成 960 后差距缩小到 3 个百分点以内。

第二个是锚框策略。mmdetection 里如果用的是 Anchor-based 模型,要检查anchor_ratios是否覆盖了数据里绝缘子的典型长宽比。绝缘子串是典型的长条目标,长宽比经常到 1:5 甚至 1:8,默认的[0.5, 1.0, 2.0]覆盖不了。我一般会在训练前用统计脚本把所有 bbox 的长宽比画个直方图,再把主要峰值区间写进配置。YOLOv8 是 anchor-free 的,不涉及这个问题,这也是我后来更推荐用它跑这类长条目标的原因。

第三个是类别不平衡。前面统计过,insulator框很多,broken_disc和pollution_flash明显偏少。常见的处理手段是给少的类在 loss 里加权重。mmdetection 里可以直接改bbox_head.loss_bbox的权重,或在数据采样时对缺陷类做 oversample。YOLOv8 这边可以用mosaic=1.0和copy_paste增强,我实践下来对缺陷类的 AP 提升最明显的是copy_paste,因为它把少类目标粘到其他图上,变相增加了位置多样性。

4. 避坑:JSON 解析、类别 ID 和评估指标上的五个翻车点

4.1 JSON 文件读取与数据加载的坑

现象 1:用记事本打开 COCO JSON,电脑直接卡死,甚至提示文件过大无法打开。
原因:COCO 标注文件本质是一个超长单行 JSON,内部没有换行符,几十 MB 的文本挤在一行里,记事本的文本渲染引擎处理这种单行长文本效率极低。
解决:不要用记事本。用 VS Code 装 JSON 插件,或者直接用 Python 的json模块读取。命令行里可以用python -m json.tool instances_all.json --compact快速检查格式合法性,这算是一个最轻量的 json 查询函数用法,能立刻看出文件是不是有语法错误。

现象 2:脚本读取 JSON 时报KeyError: 'segmentation'。
原因:这个数据集的标注是纯检测框形式,annotations里没有segmentation字段,但如果你加载了依赖 mask 的模型或评估工具,它默认访问这个字段就会报错。
解决:对于纯检测任务直接用 bbox 分支的模型。如果确实需要补字段,遍历所有annotations,给每条加一个"segmentation": []就行。注意 COCO 的 mask 评估依赖segmentation和area,如果只是做 bbox 检测,--eval bbox就够了。

4.2 训练配置与框坐标相关的坑

现象 3:训练 loss 正常下降,但画出来的检测框整体偏移。
原因:把bbox里的[x, y, w, h]当成了[x1, y1, x2, y2]。这类错误在自定义数据加载器里特别隐蔽,因为训练时 loss 也可能收敛,但坐标解释错了,模型学的是一个错位的映射。
解决:检查数据加载代码里对bbox的解析。COCO 格式的标准解是x, y是左上角坐标,w, h是宽高;而 VOC 等格式才是两个角点。如果代码里出现了x2 = x + w这样的转换,才是正确方向。验证方法很简单:写一个可视化脚本,把 bbox 直接画到原图上,肉眼看一眼框的位置对不对,这比任何日志都可靠。

现象 4:验证集 mAP 很高,但拿到新的航拍图上漏检严重。
原因:数据划分时按图片随机抽了 8:2,但巡检数据通常是同一个线路区段连续采集的,相邻图片在光照、背景、拍摄距离上高度相似,随机划分会把这种相关性带进训练集和验证集,导致验证指标乐观。
解决:如果原始数据是按线路或按文件夹组织的,划分时优先按线路分,而不是按单张图随机分。这个数据集没有提供线路归属信息,所以我当时退而求其次,按文件名前缀的拍摄批次做了分组,再按组划分。虽然不完美,但比纯随机划分的泛化评估更可信。

现象 5:验证集上broken_disc和pollution_flash互相误检。
原因:两类缺陷在视觉上都是绝缘子伞裙表面的异常纹理,差异很小,加上污闪样本数量明显偏少,模型学不到区分性特征。
解决:第一步是统计混淆矩阵,确认误检主要发生在哪两个类之间。第二步是针对性增强,把两个类别的样本各自做裁剪再粘贴到不同背景上,人为拉大纹理差异的可见度。我后面加了一组pollution_flash的区域裁剪增广后,这个类 AP 提升了约 4 个百分点,且没有拉低其他类的精度。

5. 用可视化脚本验收标注质量:训练前先花五分钟看一遍所有框

训练前还有一道我必做的工序:把标注框全部画回原图,生成一张张检查图。这一步看着土,但效率极高,能一眼看出坐标解释错误、类别贴错、框越界三类问题。我提供一个最小可用的脚本,基于 OpenCV 和前面读好的 COCO JSON。

import json, cv2, os def draw_boxes(json_path, img_dir, out_dir): with open(json_path, "r", encoding="utf-8") as f: data = json.load(f) cat_id2name = {cat["id"]: cat["name"] for cat in data["categories"]} color_map = {"insulator": (0, 255, 0), "broken_disc": (0, 0, 255), "pollution_flash": (255, 0, 0)} anns_by_img = {} for ann in data["annotations"]: anns_by_img.setdefault(ann["image_id"], []).append(ann) for img_info in data["images"]: img_path = os.path.join(img_dir, img_info["file_name"]) img = cv2.imread(img_path) h, w = img.shape[:2] for ann in anns_by_img.get(img_info["id"], []): x, y, bw, bh = [int(v) for v in ann["bbox"]] color = color_map[cat_id2name[ann["category_id"]]] cv2.rectangle(img, (x, y), (x + bw, y + bh), color, 2) cv2.putText(img, cat_id2name[ann["category_id"]], (x, max(y - 4, 0)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1) out_path = os.path.join(out_dir, img_info["file_name"]) cv2.imwrite(out_path, img) draw_boxes("annotations/val.json", "images/val", "visual_check")

这段代码的作用就是把验证集的每个bbox画到原图上,类别用不同颜色区分,并把类别名写在框的左上角。检查标准很简单:绿色框是否框住了绝缘子串,红色框是否落在伞裙破损处,蓝色框是否对准污闪痕迹。我一般会重点看三类问题——框明显小于目标实体、框跨了两个目标、类别颜色和对象对不上。每次用这套可视化走一遍,基本上能把标注数据里九成以上的脏数据找出来。

还有一个进阶技巧:生成的检查图太多,一张张翻效率低,可以用imutils.build_montages把几十张检查图拼成一张联系表,一次看 20 到 30 个场景。从那以后,我每次拿到标注数据集,都会先强制走一遍可视化覆盖检查,确认坐标解释正确、框贴合目标、类别映射无误,再开始调参训练,再也不在数据侧返过工。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询