☰
电力遥感电杆塔检测数据集:VOC+YOLO双格式400张训练与优化指南
2026/10/2 14:50:27 网站建设 项目流程

简介:本数据集面向电力巡检、遥感图像分析与目标检测方向的开发者与研究者,聚焦输电线路电杆塔的自动识别任务。资源采用Pascal VOC与YOLO双格式标注,包含400张jpg图像、400个VOC格式xml文件与400个YOLO格式txt文件,标注类别为sdgt,共680个矩形框,全部由labelImg工具完成画框标注,可直接用于主流检测框架的训练与验证。压缩包为7z格式,共1202个文件,除图像与标注外还附带说明类txt,整体约136.93MB,目录结构简洁,便于快速接入现有数据管线。目前已有605人学习下载,适合作为电力场景目标检测的入门练手或小规模实验数据,帮助读者省去自行采集与标注的成本,快速验证模型在电杆塔识别上的表现。

1. 电力场景遥感电杆塔检测数据集:400 张 VOC+YOLO 双格式到底能训出什么

电力巡检的遥感图像里,电杆塔是个很尴尬的目标:它细长、密集、背景杂乱,还经常被树冠、山体阴影和输电线路切割得七零八落。很多团队第一次做电杆塔检测,卡住的地方不是模型选型,而是手里没有一份标注干净、格式对得上、能直接喂进 YOLO 的小数据集。这份「电力场景遥感电杆塔检测数据集 VOC+YOLO 格式 400 张 1 类别」解决的正是这个起步问题——400 张遥感图,单一类别(电杆塔),同时提供 VOC 和 YOLO 两套标注,省掉格式转换那一步。它适合两类人:一是想快速验证电杆塔检测可行性的算法工程师,二是需要一个小规模基准来对比模型改进效果的研究者。400 张不算多,但作为冷启动和 pipeline 打通的第一块砖,够用。

2. 电杆塔检测数据集的结构拆解:VOC 与 YOLO 两套标注差在哪

拿到一个压缩包,第一件事不是解压完就开训,而是先搞清楚里面标注的组织方式。VOC 和 YOLO 虽然描述的是同一批图,但坐标表达、文件组织、类别编码完全不同,搞混了就是训练 loss 不降、mAP 恒为零的血泪经验。

2.1 VOC 格式的目录约定与 XML 字段含义

VOC 格式的核心是每张图对应一个 XML 文件,目录通常长这样:

dataset/ ├── JPEGImages/ # 原始图像,jpg/png ├── Annotations/ # 对应的 XML 标注 └── ImageSets/ └── Main/ # train.txt / val.txt 等划分文件

一个电杆塔的 XML 标注关键字段如下:

<annotation> <filename>tower_0001.jpg</filename> <size> <width>1024</width> <height>1024</height> <depth>3</depth> </size> <object> <name>tower</name> <!-- 类别名,本数据集只有 1 类 --> <pose>Unspecified</pose> <truncated>0</truncated> <!-- 是否被截断 --> <difficult>0</difficult> <!-- 是否难样本 --> <bndbox> <xmin>312</xmin> <ymin>208</ymin> <xmax>356</xmax> <ymax>402</ymax> </bndbox> </object> </annotation>

这里要盯紧三个点:name必须和你的类别表一致;bndbox是绝对像素坐标,且xmin<xmax、ymin<ymax;difficult字段在评估时是否忽略,取决于你用的框架,YOLO 系列默认不读这个字段。电杆塔目标细长,标注框经常贴着目标边缘,如果标注时框太松,会引入大量背景,训练时模型学到的是「一大片区域里有杆塔」而不是「杆塔本身」。

2.2 YOLO 格式的归一化坐标与类别索引

YOLO 格式每张图对应一个 txt,每行一个目标:

<class_id> <x_center> <y_center> <width> <height>

全部是相对图像宽高的归一化值,范围 0~1。单类别时class_id恒为 0。目录一般是:

dataset/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/

images和labels下的文件名必须一一对应,只是扩展名不同。YOLO 不存图像尺寸,所以归一化时用的宽高必须和实际图像严格一致,否则框会整体偏移。电杆塔这种细长目标,width往往只有 0.02~0.05,height能到 0.3 以上,归一化后数值很小,用文本编辑器肉眼看不出对错,必须靠可视化验证。

2.3 两套格式的对应关系与转换校验

VOC 转 YOLO 的公式很直接:

x_center = (xmin + xmax) / 2 / W y_center = (ymin + ymax) / 2 / H width = (xmax - xmin) / W height = (ymax - ymin) / H

但真正容易翻车的是类别映射和文件名匹配。VOC 的name是字符串,YOLO 要的是整数索引,单类别时映射成 0 即可,但如果你后面要扩类,这个映射表必须固定下来。另外 VOC 的JPEGImages里可能有.png,而 YOLO 的images里如果统一转成.jpg,文件名对不上就会导致「图片找不到标签」的静默失败——训练照跑,但那个样本的 loss 是 0,等于白喂。

提示:拿到双格式数据集后,先各抽 5 张做可视化叠加,确认 VOC 和 YOLO 的框在同一位置,再开始训练。这一步花 10 分钟,能省掉后面几小时的排查。

3. 用这份数据集跑通 YOLOv8 训练:从环境到第一个 mAP

数据集结构清楚了,接下来就是把它喂进模型。这里以 YOLOv8 为例,因为它的数据配置最简洁,社区资料也最全。整个流程分环境准备、数据配置、启动训练、看结果四步。

3.1 环境准备与目录重组

先建一个干净的环境,避免和已有项目冲突:

conda create -n tower python=3.10 -y conda activate tower pip install ultralytics

装完后验证:

yolo checks

这一步会打印 PyTorch 版本、CUDA 是否可用。如果CUDA显示不可用,训练会退回 CPU,400 张图虽然能跑,但速度慢到怀疑人生。确认 GPU 可用后,把数据集整理成 YOLO 训练要求的目录:

mkdir -p datasets/tower/images/train datasets/tower/images/val mkdir -p datasets/tower/labels/train datasets/tower/labels/val # 假设原始 YOLO 格式已按 8:2 划分好,直接拷贝 cp -r raw_yolo/images/train/* datasets/tower/images/train/ cp -r raw_yolo/labels/train/* datasets/tower/labels/train/ cp -r raw_yolo/images/val/* datasets/tower/images/val/ cp -r raw_yolo/labels/val/* datasets/tower/labels/val/

如果拿到的是 VOC 格式,需要先转换。下面这个脚本处理单类别电杆塔,同时做文件名对齐:

import os import xml.etree.ElementTree as ET from PIL import Image VOC_IMG_DIR = "VOC/JPEGImages" VOC_XML_DIR = "VOC/Annotations" OUT_IMG_DIR = "datasets/tower/images/train" OUT_LBL_DIR = "datasets/tower/labels/train" CLASS_MAP = {"tower": 0} # 单类别,扩类时在这里加 os.makedirs(OUT_IMG_DIR, exist_ok=True) os.makedirs(OUT_LBL_DIR, exist_ok=True) for xml_file in os.listdir(VOC_XML_DIR): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(VOC_XML_DIR, xml_file)) root = tree.getroot() filename = root.find("filename").text img_path = os.path.join(VOC_IMG_DIR, filename) if not os.path.exists(img_path): print(f"missing image: {filename}") continue W, H = Image.open(img_path).size lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in CLASS_MAP: continue 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) # 裁剪到图像边界,防止越界标注 xmin, ymin = max(0, xmin), max(0, ymin) xmax, ymax = min(W, xmax), min(H, ymax) xc = (xmin + xmax) / 2 / W yc = (ymin + ymax) / 2 / H bw = (xmax - xmin) / W bh = (ymax - ymin) / H lines.append(f"{CLASS_MAP[name]} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}") # 图片和标签同名,只换扩展名 stem = os.path.splitext(filename)[0] Image.open(img_path).convert("RGB").save( os.path.join(OUT_IMG_DIR, stem + ".jpg")) with open(os.path.join(OUT_LBL_DIR, stem + ".txt"), "w") as f: f.write("\n".join(lines))

这段脚本的关键处理有三处:一是CLASS_MAP把类别名映射成索引,单类别固定为 0;二是坐标裁剪到图像边界,遥感标注里偶尔会出现框超出图像的情况,不裁剪会导致归一化值大于 1,YOLO 会直接报错或静默丢弃;三是图片统一转成 RGB 的 jpg,避免灰度图或 RGBA 图在训练时通道数不匹配。转换完记得抽查几个 txt,确认每行 5 个值、坐标都在 0~1 之间。

3.2 data.yaml 配置与关键参数

YOLOv8 靠一个 yaml 文件描述数据集:

path: /abs/path/datasets/tower train: images/train val: images/val nc: 1 names: 0: tower

path用绝对路径最稳,相对路径在不同工作目录下启动训练时容易找不到。nc是类别数,必须和names的条目数一致,单类别就是 1。names的键是类别索引,和 txt 里的class_id对应。

启动训练:

yolo detect train \ data=datasets/tower/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/tower \ name=exp1

参数逐个说:model=yolov8n.pt用 nano 版,400 张图从零训容易过拟合,用预训练权重迁移学习更稳;imgsz=640是输入分辨率,遥感图如果原始尺寸很大(比如 1024 或 2048),缩到 640 后电杆塔可能只剩十几个像素,这时要么提高imgsz到 1024,要么先切图;batch=16看显存,V100 上可以拉到 32 甚至 64;lr0=0.01是初始学习率,小数据集上如果 loss 震荡,降到 0.001;patience=20表示 20 轮没提升就早停,防止无效训练。

3.3 训练过程监控与 mAP 解读

训练启动后,终端会打印每轮的 box_loss、cls_loss、mAP50、mAP50-95。电杆塔检测里,box_loss反映定位精度,cls_loss反映分类,单类别时cls_loss通常很快降到接近 0,如果它一直不降,八成是标签里的class_id不是 0 或者标签文件根本没被读到。

mAP50是 IoU 阈值 0.5 时的平均精度,电杆塔这种细长目标,IoU 对框的偏移非常敏感,mAP50能到 0.7 以上就算不错。mAP50-95更严格,通常会低不少。如果训练到 50 轮mAP50还在 0.1 以下,先别怀疑模型,去查标签:用下面的脚本把预测框和真实框画在一起看。

from ultralytics import YOLO import cv2 model = YOLO("runs/tower/exp1/weights/best.pt") results = model.predict("datasets/tower/images/val", conf=0.25, save=True) # 预测结果默认存到 runs/detect/predict

打开保存的图,如果真实框位置对但预测框乱飞,是训练不充分;如果真实框本身就画错了,那就是标注或转换的问题。这一步是排查的后悔药,别跳过。

4. 小数据集训电杆塔的避坑清单:从标签静默失败到过拟合

400 张图、单类别,看起来简单,但实际踩的坑一点不少。下面 5 条是我在类似规模数据集上反复遇到的,按「现象 → 原因 → 解决」写清楚。

4.1 训练 loss 正常但 mAP 恒为 0

现象:训练能跑完,box_loss 在降,但验证集 mAP50 一直是 0。

原因:最常见的是标签路径没对上。YOLO 找标签的规则是把images替换成labels、扩展名换成.txt,如果目录结构不是标准的images/train+labels/train,或者文件名大小写不一致,标签就静默读不到,模型学的是「所有图都没有目标」。

解决:训练前用脚本统计标签数量,确认每张图都有对应 txt,且 txt 非空。另外检查data.yaml里的train/val路径是相对于path的,别写成绝对路径又叠加了path。

4.2 电杆塔框大量重叠导致 NMS 后只剩一个

现象:推理时一张图里明明有多个电杆塔,结果只输出一个框。

原因:电杆塔密集排列时,标注框之间 IoU 可能超过默认 NMS 阈值 0.7,被当成重复框抑制掉了。

解决:推理时调高iou参数,比如model.predict(..., iou=0.9),或者训练时就用较大的iou阈值。如果目标确实重叠严重,考虑换用 soft-NMS,但 YOLOv8 默认不支持,需要改后处理。

4.3 小目标在 640 分辨率下消失

现象:原始遥感图里电杆塔只有几十像素,缩到 640 后 mAP 极低。

原因:下采样把目标缩没了,特征图上只剩几个像素,卷积核根本提取不到有效特征。

解决:提高imgsz到 1024 或 1280,或者先对原图切块(比如 512×512 带重叠),把电杆塔变成中等目标再训。切图时注意重叠区域的目标要完整保留在一个块里,否则标注会被切断。

4.4 过拟合:训练 mAP 0.95,验证 mAP 0.3

现象:训练集指标很高,验证集惨不忍睹。

原因:400 张图对 YOLOv8 来说偏少,模型容量相对数据量过大,记住了训练样本。

解决:用更强的数据增强(mosaic、mixup、随机旋转),YOLOv8 默认已开 mosaic,可以再加degrees、translate;或者换更小的模型(nano 已经很小了,再小就是 tiny 级别);还可以冻结 backbone 前几层,只训 head。另外早停patience设小一点,别让它一直训到过拟合。

4.5 验证集划分不合理导致指标虚高

现象:mAP 看起来不错,但换一批新图就崩。

原因:验证集和训练集来自同一区域、同一批采集,分布几乎一样,模型只是记住了这个场景。

解决:划分时按区域或按采集批次分,让验证集包含训练集没见过的背景。如果数据本身来自多个区域,用分层抽样;如果只有一个区域,至少按时间或拍摄角度分。400 张的规模,验证集留 80~100 张比较合适,太少指标波动大。

5. 把 400 张用出 4000 张的效果:切图、增强与迁移的实操组合

小数据集的出路不是硬训,而是把每一张图的价值榨干。我一般会按「切图扩样本 → 增强提泛化 → 迁移省数据」这个顺序做,实测能把有效样本量放大好几倍。

先说切图。遥感图往往是大场景,电杆塔只占一小块,直接缩放会丢细节。用滑动窗口切图,窗口 640×640、重叠 128 像素,一张 2048×2048 的图能切出 9~16 张子图,目标在子图里变成中等尺寸,检测难度大幅下降。切图时用下面这段逻辑保证标注跟着走:

import os from PIL import Image def slice_image(img_path, lbl_path, out_dir, size=640, stride=512): img = Image.open(img_path) W, H = img.size stem = os.path.splitext(os.path.basename(img_path))[0] # 读取原始 YOLO 标签并还原成绝对坐标 boxes = [] with open(lbl_path) as f: for line in f: c, xc, yc, bw, bh = map(float, line.split()) boxes.append((c, xc * W, yc * H, bw * W, bh * H)) idx = 0 for y in range(0, H, stride): for x in range(0, W, stride): x2, y2 = min(x + size, W), min(y + size, H) x1, y1 = max(0, x2 - size), max(0, y2 - size) crop = img.crop((x1, y1, x2, y2)) new_boxes = [] for c, xc, yc, bw, bh in boxes: # 目标中心落在窗口内才保留 if x1 <= xc < x2 and y1 <= yc < y2: nxc = (xc - x1) / size nyc = (yc - y1) / size nbw = bw / size nbh = bh / size # 裁剪到 0~1,防止越界 nxc, nyc = min(max(nxc, 0), 1), min(max(nyc, 0), 1) nbw, nbh = min(nbw, 1), min(nbh, 1) new_boxes.append(f"{int(c)} {nxc:.6f} {nyc:.6f} {nbw:.6f} {nbh:.6f}") if new_boxes: crop.save(f"{out_dir}/images/{stem}_{idx}.jpg") with open(f"{out_dir}/labels/{stem}_{idx}.txt", "w") as f: f.write("\n".join(new_boxes)) idx += 1

这段代码的核心判断是「目标中心落在窗口内才保留」,避免一个目标被切成两半后两边都标不完整。重叠 stride 设成 size 的 0.8 倍左右,保证边缘目标至少完整出现在一个窗口里。切完图,400 张能变成 3000~5000 张子图,训练时 batch 里的样本多样性明显提升。

再说增强。YOLOv8 内置的 mosaic 会把 4 张图拼成一张,对电杆塔这种细长目标,拼接后可能出现杆塔被截断的情况,但整体上利大于弊。额外可以加degrees=10(小角度旋转,遥感图方向不固定)、translate=0.1、scale=0.5。注意别开flipud(上下翻转),遥感图有固定的拍摄方向,上下翻转会造出不存在的场景,反而害了模型。

最后是迁移学习。yolov8n.pt是在 COCO 上训的,虽然 COCO 没有电杆塔这个类,但底层卷积特征(边缘、纹理、形状)是通用的。冻结 backbone 前 10 层,只训 head 和后面的层,在 400 张图上收敛更快,也不容易过拟合。等 mAP 稳定后,再解冻全部层做微调,学习率降到 0.001。

验证这套组合是否有效,别只看 mAP。我会额外做两件事:一是把验证集按「训练中见过的区域」和「没见过的区域」分开统计,看泛化差距;二是用conf=0.1的低阈值跑一遍,看漏检和误检的分布,如果误检集中在树冠和阴影上,说明模型还没学会区分杆塔和相似背景,需要补这类负样本。

这套流程我在几个类似规模的数据集上跑过,400 张起步、切图加增强后,电杆塔检测的 mAP50 能从 0.5 出头提到 0.75 左右,代价是训练时间翻倍。值不值得,取决于你的场景对漏检的容忍度——电力巡检里漏一个杆塔可能意味着一条线路的盲区,我倾向于把数据榨干再谈模型改进。希望帮到你。

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

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

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

立即咨询