无人机航拍绝缘瓷瓶数据集:VOC/COCO/YOLO格式转换与训练全链路

📅 发布时间:2026/9/28 7:19:33
无人机航拍绝缘瓷瓶数据集:VOC/COCO/YOLO格式转换与训练全链路
简介这份资源面向无人机巡检与电力线路智能检测方向的学习者和开发者提供真实航拍场景下的输电线路绝缘瓷瓶目标检测数据集可用于YOLO系列模型的训练与验证。数据均经labelimg精细标注标注框质量较高并同时提供vocxml、cocojson和yolotxt三种格式标签分别存放于不同文件夹便于直接接入不同检测框架。压缩包共1281个文件以421张jpg图像、421个xml标注、428个txt标签为主另含json、yaml配置及少量py脚本与html说明整体约941.93MB。资源还附赠数据集划分脚本可按需生成训练集、验证集与测试集并配有YOLO环境搭建与训练案例教程覆盖Windows与Linux两种环境帮助读者快速跑通从环境配置到模型训练、再到自定义数据集适配的完整流程。目前已有334人学习下载适合电力巡检、航拍目标检测等场景的课程实践与项目复现。1. 无人机航拍绝缘瓷瓶数据集从一份 .rar 到能跑通训练的完整链路输电线路巡检这几年最大的变化是无人机航拍从拍回来人工翻图变成了拍回来直接喂模型。但真正动手做过的人都知道卡住你的从来不是 YOLO 本身而是数据。你手上可能有一批无人机航拍图里面是杆塔、绝缘瓷瓶、金具、导线背景是天空、山林、农田光照从正午硬光到阴天散射全都有。想把它们训成一个能用的检测模型第一步不是调参而是把标注格式、目录结构、划分脚本这三件事理顺。这份YOLO 无人机航拍输电线路绝缘瓷瓶数据集打包了 VOC、COCO、YOLO 三种标签格式外加划分脚本和训练教程本质上解决的就是拿到一批原始标注后怎么无痛切换格式、怎么科学划分、怎么把 YOLO 跑起来这条链路。它适合两类人一类是电力巡检方向刚接手检测任务、手里有数据但不知道怎么组织的工程师另一类是已经会跑 YOLO、但每次换数据集都要重新写转换脚本的老手。下面我按自己实际落地的顺序把这条链路拆开讲清楚。2. 三种标签格式到底差在哪VOC、COCO、YOLO 的坐标与目录对照2.1 同一张图三种格式的坐标写法完全不同很多人以为格式转换只是改个后缀结果一训就发现框全偏了。根子在于三种格式对坐标的定义根本不是一回事。VOC 用的是绝对像素坐标存在 XML 里一个目标一个objectxmin/ymin/xmax/ymax是左上角和右下角的像素值。它的坐标系原点在图片左上角x 向右、y 向下这个和图像本身一致所以人读起来最直观。COCO 用的是绝对像素坐标但组织方式是单个 JSON 文件所有图片的images、annotations、categories分开存。标注里的bbox是[x, y, width, height]注意是左上角坐标加宽高不是右下角。这个[x,y,w,h]和 VOC 的[xmin,ymin,xmax,ymax]差一个减法转换时最容易在这里翻车。YOLO 用的是归一化后的中心点坐标一个目标一行 txtclass_id cx cy w h五个值全部除以图片宽高落在 0 到 1 之间。它没有单独的类别文件类别靠一个classes.txt或data.yaml里的names列表按索引对应。归一化带来的好处是模型输入尺寸可以随便改坏处是你肉眼没法直接看出框在哪调试时得反算回像素。把这三者的关系记成一句话VOC 是角点绝对COCO 是角点绝对但拆成宽高YOLO 是中心点归一化。任何转换脚本本质就是在这三种表示之间做算术。2.2 目录结构决定你能不能直接开训格式对了目录不对YOLO 照样报错。我见过太多人把图片和标签混在一个文件夹里训练时No labels found直接卡死。三种格式对应的标准目录是这样的格式图片位置标签位置类别定义VOCJPEGImages/Annotations/XMLclasses.txt或脚本内写死COCOimages/单个annotations.jsonJSON 内categoriesYOLOimages/train、images/vallabels/train、labels/valdata.yaml的namesYOLO 的目录有个硬性约定图片和标签的相对路径必须镜像对应。也就是说images/train/001.jpg对应的标签必须是labels/train/001.txt文件名主干一致只是扩展名和父目录不同。这个约定不满足训练脚本就找不到标签。VOC 和 COCO 没有这个强制镜像要求但转换到 YOLO 时必须建立这个映射。提示如果你的数据集里图片是.JPG大写扩展名而标签是.txt转换脚本里做文件名匹配时一定要统一大小写否则会出现图片有、标签找不到的假象。2.3 用脚本把 VOC 批量转成 YOLO 的最小实现假设你手上是 VOC 格式想转成 YOLO下面这段脚本是我常用的最小实现逻辑清晰、好改import os import xml.etree.ElementTree as ET from PIL import Image # 类别列表顺序即 class_id必须和后续 data.yaml 的 names 一致 CLASSES [insulator, tower, fitting] def voc_to_yolo(xml_path, img_path, out_txt_path): # 读图片真实宽高归一化必须用真实尺寸不能用假设值 with Image.open(img_path) as im: w, h im.size tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in CLASSES: continue # 不在类别表里的目标直接丢弃避免 class_id 越界 cls_id CLASSES.index(name) bnd obj.find(bndbox) xmin float(bnd.find(xmin).text) ymin float(bnd.find(ymin).text) xmax float(bnd.find(xmax).text) ymax float(bnd.find(ymax).text) # VOC 角点 - YOLO 中心点归一化 cx (xmin xmax) / 2.0 / w cy (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h # 裁剪到 [0,1]防止标注越界导致训练时 loss 异常 cx, cy min(max(cx, 0), 1), min(max(cy, 0), 1) bw, bh min(max(bw, 0), 1), min(max(bh, 0), 1) lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(out_txt_path, w) as f: f.write(\n.join(lines))这段代码有三个关键点。第一CLASSES的顺序就是最终的class_id一旦定了就不能改改了所有标签的类别索引全错。第二归一化用的是Image.open读出的真实宽高不是从 XML 里猜的因为有些标注工具写的size字段和实际图片对不上。第三最后做了[0,1]裁剪这是血泪经验——航拍图里偶尔有标注框超出图片边界的不裁的话训练时会出现负的宽高loss 直接变 NaN。参数上cx/cy/bw/bh保留 6 位小数足够YOLO 内部会再处理写太多位反而让标签文件变大。类别名建议用英文小写下划线别用中文data.yaml读中文在某些环境下会乱码。3. 数据集划分脚本为什么随机 split 会让你的验证集虚高3.1 航拍数据的划分不能纯随机拿到数据集第一件事是划分 train/val。很多人直接random.shuffle然后 8:2 切训完发现验证集 mAP 高得离谱一上真实场景就崩。原因在航拍数据的特殊性同一基杆塔、同一段线路会被无人机连续拍很多张这些图高度相似。纯随机划分会把同一杆塔的相邻帧分到训练集和验证集两边等于验证集里全是训练集见过的场景指标自然虚高。正确的做法是按场景单元划分。常见做法是如果数据里有杆塔编号或拍摄批次信息按编号分组整组进训练或整组进验证如果没有就按图片文件名前缀或拍摄时间聚类同一簇不拆开。我一般会先看一眼文件名规律很多巡检数据集的命名里带了塔号比如T023_001.jpg、T023_002.jpg那就按T023这个前缀分组。3.2 一个带分组逻辑的划分脚本下面这个脚本在随机的基础上加了按前缀分组的能力避免同源图片泄漏import os import random import shutil from collections import defaultdict IMG_DIR images_all LBL_DIR labels_all OUT_DIR dataset VAL_RATIO 0.2 SEED 42 def group_key(filename): # 按文件名前缀分组例如 T023_001.jpg - T023 # 如果你的数据没有这种规律改成返回固定值即退化为纯随机 stem os.path.splitext(filename)[0] return stem.split(_)[0] def split(): random.seed(SEED) imgs [f for f in os.listdir(IMG_DIR) if f.lower().endswith((.jpg, .png))] groups defaultdict(list) for f in imgs: groups[group_key(f)].append(f) group_names list(groups.keys()) random.shuffle(group_names) val_count int(len(group_names) * VAL_RATIO) val_groups set(group_names[:val_count]) for split_name in [train, val]: os.makedirs(f{OUT_DIR}/images/{split_name}, exist_okTrue) os.makedirs(f{OUT_DIR}/labels/{split_name}, exist_okTrue) for g, files in groups.items(): split_name val if g in val_groups else train for f in files: stem os.path.splitext(f)[0] shutil.copy(f{IMG_DIR}/{f}, f{OUT_DIR}/images/{split_name}/{f}) lbl f{stem}.txt if os.path.exists(f{LBL_DIR}/{lbl}): shutil.copy(f{LBL_DIR}/{lbl}, f{OUT_DIR}/labels/{split_name}/{lbl}) if __name__ __main__: split()逻辑说明group_key是划分的灵魂它决定什么算同一个场景。SEED固定保证可复现团队协作时大家划分结果一致。VAL_RATIO按组数算而不是按图片数算所以验证集图片数量会有波动这是正常的别为了凑比例把同组拆开。参数上VAL_RATIO一般 0.15 到 0.2。如果数据量少于 500 张建议用 5 折交叉验证而不是固定切分否则验证集太小指标抖动大。SEED换一个值就能得到不同划分做多次实验取平均能更稳。注意划分完一定要抽查几个验证集样本确认里面没有和训练集同塔号的图片。这一步花两分钟能省掉后面几天的困惑。3.3 划分后必须做的三项校验划分脚本跑完不代表没事了我每次都会做三个校验缺一个都可能埋雷。第一数量校验。训练集图片数应等于训练集标签数假设每张图都有标注验证集同理。差一个都说明有图片没配上标签训练时会被静默跳过。第二类别分布校验。统计训练集和验证集里每个类别的框数量如果某个类别在验证集里一个都没有那这个类别的 mAP 就是 0不是模型不行是划分的锅。绝缘瓷瓶数据集里如果还有破损瓷瓶这种小类尤其要检查。第三坐标合法性校验。遍历所有 YOLO 标签确认每行的 5 个值里class_id是整数且在类别范围内后四个是 0 到 1 的浮点数。这一步能提前抓出转换脚本的 bug。def validate_labels(label_dir, num_classes): for f in os.listdir(label_dir): if not f.endswith(.txt): continue with open(os.path.join(label_dir, f)) as fp: for i, line in enumerate(fp): parts line.strip().split() if len(parts) ! 5: print(f{f} 第{i}行字段数不对: {line}) continue cid int(parts[0]) vals [float(v) for v in parts[1:]] if cid 0 or cid num_classes: print(f{f} 第{i}行 class_id 越界: {cid}) if any(v 0 or v 1 for v in vals): print(f{f} 第{i}行坐标越界: {vals})这个校验函数跑一遍输出为空才说明标签干净。别嫌麻烦我见过因为一行坐标写成1.2导致整个训练 loss 爆炸的情况排查了两小时才发现是标注工具导出的锅。4. 把 YOLO 训练跑起来环境、配置与三个必调参数4.1 环境配置与预训练权重选择环境这块CUDA 版本和 PyTorch 版本要对上这是老生常谈但每次换机器都要踩。我一般用 conda 建独立环境避免和系统 Python 打架conda create -n yolo_insulator python3.10 -y conda activate yolo_insulator # 按你的 CUDA 版本装对应 torch下面以 CUDA 11.8 为例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralyticsultralytics装完yolo命令就能用了。预训练权重方面航拍绝缘瓷瓶属于小目标密集场景我一般从yolov8s或yolov8m起步n太小对小目标召回不够l/x在数据量不到几千张时容易过拟合。权重文件下载后放在项目根目录训练时用相对路径引用即可。4.2 data.yaml 怎么写才不出错data.yaml是 YOLO 训练的入口配置写错一个字段就报错。标准写法path: /abs/path/to/dataset # 数据集根目录建议绝对路径 train: images/train val: images/val nc: 3 names: 0: insulator 1: tower 2: fitting几个容易错的点path用绝对路径最稳相对路径在不同工作目录下会失效train和val是相对path的路径不是绝对路径nc必须等于names的条目数多一个少一个都会在训练启动时崩names的顺序必须和标签里的class_id严格对应这个在转换阶段就定死了这里只是复述。4.3 三个必调参数imgsz、batch、lr0训练命令本身很简单yolo detect train \ datadata.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ projectruns/insulator \ nameexp1但参数怎么设直接决定你能不能训出可用的模型。下面这三个是我每次都会重点调的。imgsz输入尺寸。航拍图里绝缘瓷瓶往往只占几十个像素imgsz640时小目标可能只剩几个像素召回上不去。如果显存够我一般上到1024甚至1280小目标召回能明显改善。代价是显存和训练时间成平方增长。显存不够时的折中做法是imgsz640但把batch调小或者用切片推理SAHI在推理阶段补。batch批大小。它和imgsz一起决定显存占用。经验值是 8G 显存跑imgsz640用batch16跑imgsz1024就得降到batch4或8。batch太小会让 BN 层统计不稳训练 loss 抖动大这就是热词里说的yolo 训练中 bn 崩溃的常见诱因之一。如果显存实在小可以用梯度累积模拟大 batch。lr0初始学习率。YOLO 默认0.01对从头训或微调都算合理。但如果你用的是小数据集几百张0.01可能太大导致早期 loss 震荡我会降到0.001并配合cos_lr余弦退火。反过来数据量大、训得久0.01起步没问题。参数小数据集1000 张中等数据集大数据集imgsz640~1024640640batch8~161632lr00.0010.010.01epochs100~200100300提示训练前先用yolo detect train ... epochs1跑一轮确认数据能正常加载、loss 能下降再开长训练。这一步能提前暴露 90% 的配置错误。4.4 训练过程看什么指标训练启动后终端会打印每个 epoch 的 box_loss、cls_loss、dfl_loss 和 mAP。我重点看三个信号。第一cls_loss是否持续下降。如果它一直不降甚至上升多半是类别标签有问题回去查class_id和names是否对应。第二mAP50的爬升曲线。正常情况前 10 个 epoch 快速上升之后放缓。如果 50 个 epoch 还在 0.1 以下要么数据有问题要么lr0太大把模型训飞了。第三验证集 loss 和训练集 loss 的差距。验证 loss 早早开始上升而训练 loss 还在降就是过拟合该加数据增强或减模型规模了。5. 避坑与排查绝缘瓷瓶数据集训练中最容易翻车的五件事5.1 现象训练启动就报 No labels found原因YOLO 找不到标签文件绝大多数是目录结构不满足镜像约定。比如图片在images/train标签却放在了labels/train之外的目录或者标签文件名和图片名主干不一致图片001.jpg标签001_insulator.txt。解决确认labels/train/001.txt和images/train/001.jpg严格对应。写个脚本遍历图片目录检查每个图片是否有同名 txt缺的列出来补上。别靠肉眼几百张图看不完。5.2 现象mAP 一直是 0但 loss 在降原因类别索引错位。转换时CLASSES的顺序和data.yaml里names的顺序不一致模型学到的类别和评估时对不上。或者标签里出现了class_id超出nc的值被静默忽略。解决跑一遍 3.3 里的坐标合法性校验重点看class_id范围。再核对转换脚本的CLASSES和data.yaml的names是否逐字一致包括大小写。5.3 现象小目标绝缘瓷瓶召回率极低原因imgsz太小小目标在特征图上只剩一两个像素模型根本学不到。或者数据里小目标样本占比太低被大目标主导了 loss。解决把imgsz提到 1024 以上开启 Mosaic 和 Copy-Paste 增强人为增加小目标密度如果还不行考虑在推理阶段用切片推理把大图切成小块分别检测再合并。5.4 现象训练中途 loss 突然变 NaN原因标签里有非法值最常见的是坐标超出[0,1]或宽高为负。航拍图标注时框拖出边界就会产生这种脏数据。另一个可能是lr0太大梯度爆炸。解决先跑坐标校验脚本清理脏标签。确认标签干净后把lr0降到0.001并开启梯度裁剪YOLO 默认有但可以显式设clip_grad。如果还 NaN检查输入图片是否有损坏文件。5.5 现象验证集指标很好实际部署一塌糊涂原因划分时同源图片泄漏验证集和训练集有大量相似帧。这是航拍数据最隐蔽的坑指标虚高但模型没学到泛化能力。解决回到 3.2 的分组划分按塔号或拍摄批次分组。划分后抽查验证集确认没有和训练集同塔号的图片。宁可验证集小一点也要保证它干净。6. 从能训到能用用混淆矩阵和切片推理把模型推到落地训练跑通只是起点真正决定这个绝缘瓷瓶检测方案值不值得投入的是它能不能在真实巡检图上稳定工作。我一般用两个手段做最后验证。第一个是混淆矩阵。YOLO 训练完会在runs/下生成confusion_matrix.png它告诉你每个类别被分对、分错、漏检的比例。绝缘瓷瓶场景里我最关注的是insulator 被误判成 background这一格也就是漏检率。如果漏检集中在某一类背景比如天空、树林说明模型对这类背景下的目标不敏感需要针对性补这类样本。热词里提到yolo 混淆矩阵总合不唯一这通常是因为多类别时矩阵按预测和真实分别归一化看的时候要明确看的是哪一列别被数字绕晕。第二个是切片推理。航拍图分辨率动辄 4000×3000直接缩到 640 喂模型小目标全糊了。切片推理的思路是把大图切成有重叠的小块每块单独检测再把结果映射回原图做 NMS 合并。下面是一个最小实现from ultralytics import YOLO import cv2 import numpy as np model YOLO(runs/insulator/exp1/weights/best.pt) def sliced_inference(img_path, slice_size1024, overlap200, conf0.25): img cv2.imread(img_path) h, w img.shape[:2] step slice_size - overlap boxes [] for y in range(0, h, step): for x in range(0, w, step): patch img[y:yslice_size, x:xslice_size] if patch.shape[0] 32 or patch.shape[1] 32: continue res model.predict(patch, confconf, verboseFalse)[0] for b in res.boxes: x1, y1, x2, y2 b.xyxy[0].tolist() # 把切片坐标映射回原图 boxes.append([x1x, y1y, x2x, y2y, float(b.conf), int(b.cls)]) # 用 OpenCV NMS 合并跨切片的重复框 if not boxes: return [] arr np.array([b[:4] for b in boxes]) scores np.array([b[4] for b in boxes]) idx cv2.dnn.NMSBoxes(arr.tolist(), scores.tolist(), conf, 0.5) return [boxes[i] for i in idx]这段代码的关键参数是slice_size和overlap。slice_size一般设成训练时的imgsz保证推理和训练尺度一致。overlap要大于最大目标的尺寸否则目标正好落在切片边界会被切碎我一般设 200 像素。conf是置信度阈值切片推理会产生更多候选框阈值可以比常规推理略高一点减少误检。实测下来切片推理对绝缘瓷瓶这种小目标的召回提升很明显代价是推理时间成倍增加。所以我的习惯是离线巡检报告用切片推理保证召回实时视频流用常规推理保证速度两套配置按场景切换。最后说个我自己的教训。早期做这类项目我总想着一步到位把模型调到最好结果在数据格式和划分上偷懒训出来的模型指标漂亮但一上真实线路就漏检。后来才明白绝缘瓷瓶检测的瓶颈从来不在模型结构而在数据组织得够不够干净、划分得够不够科学、推理尺度和训练尺度对不对得上。把这三件事做扎实yolov8s这种中等模型就能给出能交差的结果。希望帮到你。本文还有配套的精品资源点击获取