简介:面向遥感目标检测与电力设施巡检场景,这份数据集以约1000张真实电力塔图像为基础,由LabelImg标注并同时提供VOC(xml)、COCO(json)、YOLO(txt)三种格式标签,存放在不同文件夹中,可直接接入YOLO系列模型训练,适合需要快速获取高质量电力设施样本的研究者或算法工程师。压缩包内共2000个文件,主体为1000个xml标注文件与990个txt标签文件,另有YOLO环境搭建与训练教程的html说明、数据集划分Python脚本和yaml配置文件,整包约177.44MB。目前已吸引265人学习下载。配套内容不仅涵盖Linux/Windows两种环境搭建与训练案例,还提供训练集、验证集、测试集划分脚本,可根据自有需求灵活生成数据划分,省去手工整理标签的时间,便于快速迭代。整体结构清晰,拿来即可用于电力塔目标检测模型的训练与验证。
1. 遥感电力塔目标检测数据集:1000 张图能解决什么
拿到「YOLO遥感电力塔目标检测数据集」这套东西,第一反应是省事。做遥感图像目标检测的人都知道,公开数据里带电力塔标签的本就不多,自己用标注工具从零画 1000 张遥感图,按一张图平均半小时算,一周就搭进去了,还得反复返工修框。这个包把 1000 张遥感图像连同 VOC、COCO、YOLO 三种格式标签、划分脚本和训练教程打包在一起,等于把「从裸图到能跑 YOLO 训练」这条路上最磨人的环节直接抹掉了。
它解决的是一个非常具体的诉求:电网巡检里「找塔」这个重复劳动能不能用目标检测替代。遥感影像里的电力塔尺度小、纹理碎、背景杂,很多团队第一个版本就翻车在数据格式不统一、标签和图像对不上这类低级问题上。有了现成数据,你可以把精力放在调参和修模型上,而不是耗在数据清洗里。
这东西适合三类人:刚入目标检测、想拿真实遥感数据练手的算法工程师;做电网数字化项目、需要快速出验证结果的实施团队;还有学校里要跑通完整检测流程的学生。接下来按我自己的实操顺序讲:先拆三种标签格式,再讲划分脚本怎么用,然后落到 YOLO 训练和排错,最后聊怎么验证模型真的能用。
2. 拆开数据集:VOC/COCO/YOLO 三种标签的坐标体系与真实差异
2.1 三种格式的存储结构与坐标体系
这套数据集里最值钱的东西其实是标签。VOC、COCO、YOLO 是目标检测领域最常见的三种标签格式,但三者的坐标体系完全不同,混用必炸。先分别说清楚它们长什么样。
VOC 格式源自 Pascal VOC 竞赛,结构是「每张图像配一个 XML 文件」。典型目录长这样:Annotations/放 XML,JPEGImages/放原图,ImageSets/Main/放 train.txt、val.txt 这类图像列表。单个 XML 内容大致是:
<annotation> <folder>JPEGImages</folder> <filename>0001.jpg</filename> <size> <width>1280</width> <height>960</height> <depth>3</depth> </size> <object> <name>power_tower</name> <bndbox> <xmin>102</xmin> <ymin>208</ymin> <xmax>261</xmax> <ymax>343</ymax> </bndbox> </object> </annotation>VOC 的坐标是像素坐标的左上角和右下角,即 (xmin, ymin, xmax, ymax)。注意它是终点坐标而不是宽高,宽需要自己算。这种格式直观,但坏处是图像尺寸一变,坐标全部失效。
YOLO 格式则完全相反。它每张图对应一个同名 TXT,每行一个目标:类别id x_center y_center width height,其中中心点和宽高都是归一化到 0~1 的比值,分母是图像的宽和高。比如上面那个框换算过来就是0 0.1418 0.2870 0.1242 0.1406。归一化意味着图像从 1280×960 缩放到任意尺寸,标签都不用动,这是 YOLO 训练首选它的原因。
COCO 格式是「全量标注塞进一个 JSON」。顶层有images、annotations、categories三个数组,每个框的 bbox 写成[x, y, width, height],单位是像素,另外还带area、iscrowd等字段。三者的核心区别我用表格收一下:
| 格式 | 文件形态 | 坐标表示 | 典型用途 |
|---|---|---|---|
| VOC | 每图一个 XML | xmin/ymin/xmax/ymax,像素 | LabelImg 标注、原版两阶段检测器 |
| YOLO | 每图一个 TXT | cx/cy/w/h,归一化 0~1 | YOLO 系列直接训练 |
| COCO | 全量一个 JSON | x/y/w/h,像素 | mmdetection、Detectron2 等框架 |
这套包给了三种格式,是因为生态不互通:你想用标注工具复核框,要 VOC;想换到 mmdetection 跑对比实验,要 COCO;想直接开训 YOLO,要 TXT。拿到包的第一件事,建议抽 5 张图对比同一个框在三种格式里的数值是否一致,这是最便宜的自检。
2.2 从 VOC 到 YOLO 的坐标换算:一条公式和一个脚本
三种格式之间的转换本质上就是坐标系的换算,最常用的是 VOC 转 YOLO。公式就两条:
x_center = (xmin + xmax) / 2 / W y_center = (ymin + ymax) / 2 / H w = (xmax - xmin) / W h = (ymax - ymin) / HW、H 是图像的宽和高,必须取原图的真实尺寸。这里最容易翻车的地方是:遥感图像不是正方形的,很多是 1280×960、4000×3000 这种比例,如果你在心里假设 W=H 或者把宽高写反,所有框都会偏。下面是一段可复用的转换脚本:
import xml.etree.ElementTree as ET import os CLASS_MAP = {'power_tower': 0} # 类别名到 id 的映射,多类时往里加 def voc_to_yolo(xml_path, W, H, out_path): tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.iter('object'): name = obj.find('name').text if name not in CLASS_MAP: continue # 跳过未映射的类,避免训练时报类别越界 bndbox = obj.find('bndbox') xmin = float(bndbox.find('xmin').text) ymin = float(bndbox.find('ymin').text) xmax = float(bndbox.find('xmax').text) ymax = float(bndbox.find('ymax').text) x_center = (xmin + xmax) / 2.0 / W y_center = (ymin + ymax) / 2.0 / H w = (xmax - xmin) / W h = (ymax - ymin) / H lines.append(f"{CLASS_MAP[name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") with open(out_path, 'w') as f: f.write('\n'.join(lines)) if __name__ == '__main__': os.makedirs('labels', exist_ok=True) for xml_name in os.listdir('Annotations'): stem = xml_name.replace('.xml', '') tree = ET.parse(os.path.join('Annotations', xml_name)) size = tree.getroot().find('size') W = float(size.find('width').text) H = float(size.find('height').text) # 宽高从 XML 读,不要手写,手写必错 voc_to_yolo(os.path.join('Annotations', xml_name), W, H, os.path.join('labels', stem + '.txt'))这段脚本的逻辑很简单:遍历Annotations目录,从每个 XML 的<size>节点读宽高,再遍历<object>节点取类别名和目标框。类别名通过CLASS_MAP映射成数字 id,这是 YOLO 格式的死要求——TXT 第一列只能是整数。输出保留 6 位小数,遥感图里塔的框常常只有几十像素,精度太低会导致训练时锚框匹配错乱。
参数说明:CLASS_MAP里如果出现 XML 中未定义的类别名,脚本会直接跳过,所以跑完后要核对生成的行数是否和 XML 里的<object>数量一致。另一个值得注意的点是W和H必须从size节点读,而不是拿图像文件名去猜,特别是当数据集里混着不同分辨率的图时,任何一处手写宽高都会埋雷。
2.3 COCO 格式为什么「重」,以及标注工具怎么选
COCO 是三兄弟里最重的那个。它把所有标注堆进一个 JSON,顶层必须有images、annotations、categories三个数组,annotations里每个目标除了 bbox 还要给image_id、category_id、area、iscrowd。很多框架加载时会校验这些字段,缺一个就报错。也正因如此,COCO 格式在遥感检测里主要的价值是兼容 mmdetection、Detectron2 这类框架——它们官方数据加载器只认 COCO。
对单类电力塔检测来说,COCO 确实有点杀鸡用牛刀,但好处是留了一条换框架的后路。你不可能永远只用 YOLO,后面要做对比实验、要上旋转框检测,COCO 转起来最顺手。我一般把 COCO 当「存档格式」,平时训练用 YOLO TXT,复核用 VOC。
再提一句标注工具。目标检测常用标注工具里,LabelImg 导出的是 VOC XML,labelme 和 X-AnyLabeling 导出的是 COCO 风格 JSON,makesense.ai 这类网页工具也支持导出 YOLO 格式。这套数据集把三种格式都备齐了,省掉的正是「标注完还要写一遍转换脚本」的重复劳动。如果你之后要自己扩数据,记住工具链:画框用 LabelImg,要转 COCO 就用现成脚本转,别在标注环节就把格式定死。
3. 划分脚本的正确打开方式:按图切分、防泄漏、查类别分布
3.1 划分脚本的核心逻辑:按图切,不按标注切
划分脚本是数据准备到训练之间的桥梁,但很多人不重视它,以为就是把文件名随机分三堆。这个环节做错了,后面所有指标都是假的。核心逻辑只有一条:按图像切,不按标注切,同一张图的 jpg 和 txt 必须进同一个子集。
为什么强调这一点?因为标注文件本身不携带图像内容信息,如果你只按标注文件名随机分,很可能出现某张图进了训练集、它的标注却进了验证集的错位情况。更隐蔽的问题是同源泄漏:遥感影像往往是从一条航带上一帧帧切出来的,相邻帧的重叠度很高,如果纯随机 8:2 划分,几乎必然有一批近重复图同时落在训练集和验证集,验证分数虚高得离谱。这个问题我在后面排查章节会细讲。
划分比例上,1000 张单类小数据集,惯例是 8:1:1,即 800 训练、100 验证、100 测试。如果你只做研究和调参,可以把测试集省掉改成 8:2,但要上线部署,测试集必须留。遥感小数据集还有一个额外讲究:划分要带随机种子(seed),否则每次跑结果不一样,你没法判断模型改动到底是真有效还是划分运气。
3.2 一份可复用的划分脚本:参数与执行说明
这套包里带的划分脚本,思路通常就是扫描图像、随机打乱、按比例切分、把图和标签成对复制到对应子目录。我自己常用的版本长这样:
import os import random import shutil random.seed(42) # 固定种子,保证每次划分结果一致 IMAGES = "images" # 原图目录 LABELS = "labels" # YOLO 标签目录 OUT = "split" # 输出根目录 RATIO = (0.8, 0.1, 0.1) # train / val / test EXT = ".jpg" image_names = [f for f in os.listdir(IMAGES) if f.lower().endswith(EXT)] label_names = set(os.listdir(LABELS)) def has_label(name): return name.rsplit('.', 1)[0] + '.txt' in label_names valid = [n for n in image_names if has_label(n)] # 只保留图、标签都在的样本 random.shuffle(valid) n_train = int(len(valid) * RATIO[0]) n_val = int(len(valid) * RATIO[1]) splits = { 'train': valid[:n_train], 'val': valid[n_train:n_train + n_val], 'test': valid[n_train + n_val:], } for split_name, items in splits.items(): os.makedirs(f"{OUT}/{split_name}/images", exist_ok=True) os.makedirs(f"{OUT}/{split_name}/labels", exist_ok=True) for name in items: stem = name.rsplit('.', 1)[0] # 用 copy 而不是 move,原始数据留一份后悔药 shutil.copy(os.path.join(IMAGES, name), f"{OUT}/{split_name}/images/{name}") shutil.copy(os.path.join(LABELS, stem + '.txt'), f"{OUT}/{split_name}/labels/{stem}.txt") print(split_name, len(items))这段脚本的逻辑是:先过滤出「图像和标签同时存在」的样本,避免把只有图没有标签的脏数据送进训练;再按比例切分,输出结构是split/train/images+split/train/labels,这正是 YOLO 训练期待的标准目录布局。用copy而不是move是刻意为之——划分本身就是实验性的,原始数据保留一份,后面想调整比例随时能重切。
参数说明:random.seed(42)里的数字可以随便换,但定下来就别动;RATIO是一个三元组,三者相加必须等于 1.0,如果你只想分训练和验证,就写(0.8, 0.2, 0.0);EXT要根据你的图像后缀改,遥感图里有 .jpg 也有 .png 的,漏了后缀会导致过滤后样本数骤减。
3.3 划分后用类别计数检查分布是否合理
切完别急着训练,先跑一段类别分布检查。很多人跳过这步,结果验证集里某个类只有两三个实例,指标波动大得像心电图。检查脚本很简单:
import os from collections import Counter for split_name in ['train', 'val', 'test']: counter = Counter() label_dir = f"split/{split_name}/labels" for txt in os.listdir(label_dir): with open(os.path.join(label_dir, txt)) as f: for line in f: cls_id = int(line.split()[0]) counter[cls_id] += 1 print(split_name, dict(counter))逻辑说明:遍历每个子集的标签目录,按行读取 TXT,统计每一类的目标实例总数。注意统计单位是「目标个数」而不是「图像张数」,一张图里可能有好几个塔,这个数字才是模型真正学习的样本量。
分布是否合理的经验值:单类 1000 张图、每张平均 1 到 3 个塔,训练集里应该有 800 到 2400 个实例,验证集 100 到 300 个。如果验证集只有十几个实例,说明划分比例或者原始标注分布有问题。多类场景更要看比例是否和整体一致——比如 A 类占 90%、B 类占 10%,划分后 train 里 B 类只剩几十个,那 B 类的 PR 曲线基本没有参考价值,这时候就需要做分层划分,按类别比例来抽。
4. 用 YOLOv8 训练电力塔检测器:yaml、预训练权重与超参数
4.1 数据集 YAML 的写法和三个路径暗坑
数据划分好之后,训练前的最后一步是写数据集 YAML。YOLO 系列训练时都靠这个文件定位数据,写法本身不难,但路径暗坑能卡住一半的人。
# data.yaml path: /home/user/power_tower/split # 数据集根目录 train: images/train val: images/val test: images/test nc: 1 names: 0: power_tower这里的train、val是相对path的目录路径。三个暗坑按踩坑频率排:第一,YOLOv5 和 YOLOv8 对path的解析基准不一样。YOLOv5 里path相对 YAML 文件所在目录解析,YOLOv8 里相对当前工作目录解析,所以最稳的做法是path写绝对路径,别赌相对路径。第二,Windows 下路径分隔符是反斜杠,YAML 里会被当成转义符,中文目录名还可能引发编码问题,我建议项目路径全部用英文、用正斜杠。第三,train指向目录时,YOLO 会默认在同级目录下找labels文件夹,也就是images/train配labels/train。如果你的目录结构是另起的名字,比如imgs和anns,训练会直接报找不到标签,必须改成标准命名。
4.2 预训练模型下载与训练命令的参数说明
1000 张图的数据量,从零训练不现实,正确做法是加载预训练权重做微调。YOLOv8 首次运行训练命令时会自动下载 yolo 预训练模型,比如yolov8s.pt会自动拉取到当前目录;离线环境就得手动把权重文件放到工作目录,别指望每次都自动下载。
模型规格选择上,n 是最轻的,s 是性价比默认项,m 以上适合显存充足的情况。电力塔是典型的遥感小目标,我用 s 起步,验证效果不够再升 m。完整训练命令:
yolo detect train \ model=yolov8s.pt \ data=data.yaml \ epochs=120 \ imgsz=640 \ batch=16 \ device=0 \ project=runs/power_tower \ name=exp1参数含义逐个说:epochs=120对 1000 张的小数据集足够,通常 80 轮左右 loss 就平稳了,多留 40 轮是为了让指标再稳一点;imgsz=640是训练输入尺寸,后面会讲为什么遥感场景可能要提到 960 或 1280;batch=16取决于显存,12GB 显存跑 s 模型、640 尺寸,batch=16 是安全值;project和name指定输出目录,方便多组实验对比。如果你用 YOLOv5,等价命令是python train.py --data data.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100。
训练过程中重点盯三条损失曲线:box_loss、cls_loss、dfl_loss。三者都应整体下降,任何一条突然抬升都要警惕,后面排查章节会展开。
4.3 imgsz、锚框与损失函数:遥感小目标的超参数取舍
电力塔检测和常规目标检测最大的区别在于目标尺度。在 512×512 的遥感切片里,一座塔可能只占 30×50 像素,放到 640 训练尺度下属于标准的小目标。imgsz是这里影响最大的超参数:从 640 提到 960,小目标召回率通常能涨 5 到 10 个点,代价是显存和训练时间涨一倍。显存不够时有个变通:训练用 640,验证时单独调高推理尺寸,YOLOv8 支持在验证命令里单独指定imgsz=1280。
锚框问题上,YOLOv5 默认开 autoanchor,会在训练前根据你的标注自动重算锚框尺寸,一般不用手动干预;YOLOv8 改成了 anchor-free 加 DFL 的架构,更不用手工调锚框。值得关注的是 yolo 损失函数里的dfl_loss,它负责预测框的边界分布,对细长的塔身比较友好。常见的错误是看到 loss 不降就去调损失权重,我建议先保持默认,把imgsz和epochs调好,损失权重是最后的手段。
另外一个小技巧:YOLOv8 支持close_mosaic=10,意思是最后 10 个 epoch 关闭马赛克增强,让模型在真实分布上稳定收敛。小目标训练时马赛克增强是好事,但一直开到最后一轮,验证分数会比实际水平虚高,所以我的习惯是保留这个默认行为,不改。
5. 训练翻车排查:电力塔检测常见的 5 个坑
5.1 标签坐标越界:宽高写反的归一化错乱
现象:训练刚开始 loss 正常,几轮后突然变成 inf 或 nan;或者用可视化脚本画框,发现有些框跑到图像外面去了。
原因:最常见的是 VOC 转 YOLO 时把宽高写反。遥感图像不少是非正方形的,比如 1280×960,如果转换时把 1280 当成高、960 当成宽,归一化坐标会整体偏移,部分框超出 0~1 范围。另一个来源是标注文件里存在xmax < xmin或ymax < ymin的脏框,标注工具偶尔会导出这种数据。
解决:训练前加一道扫描,把所有标签读出来检查是否越界:
def scan_labels(label_dir): bad = [] for txt in os.listdir(label_dir): with open(os.path.join(label_dir, txt)) as f: for line in f: parts = line.split() if len(parts) != 5: bad.append((txt, '字段数不对')) continue _, x, y, w, h = map(float, parts) if not (0 <= x <= 1 and 0 <= y <= 1): bad.append((txt, '中心点越界')) if w <= 0 or h <= 0 or w > 1 or h > 1: bad.append((txt, '宽高异常')) return bad逻辑说明:逐个标签文件检查中心点和宽高是否在合法区间。查出越界的标签后,回到转换脚本重新生成,或者直接把越界框 clamp 到边界内。我的经验是补救不如重转,越界往往意味着转换逻辑有系统性问题。
5.2 电力塔小目标全程漏检:imgsz 缩得太狠
现象:mAP50 已经到 0.85 以上,但在原始大图上推理时漏掉大量小塔;看检测结果图,漏检的塔在图上占比普遍小于 2%。
原因:imgsz=640时,一座 30×50 像素的塔经过 5 次下采样,特征图上只剩不到 2 个像素,模型根本没有足够的信息去识别它。加上训练图里大塔数量占多数,模型会偏向学好识别的大目标。
解决:把训练imgsz提到 960 或 1280,让塔在输入图像里的像素占比变大;显存不够就减小 batch,或者把原图切成 640 切片再训练,相当于把目标相对尺寸放大。这是遥感小目标检测最有效的两个手段,没有之一。
5.3 BN 崩溃与 loss 突然变 nan:batch 与学习率不匹配
现象:训练到第 40 轮左右,loss 从 0.05 直接跳到 nan,怎么重启都在差不多同样的位置崩掉;或者 val mAP 曲线先升后断崖式下跌。
原因:这是 yolo 训练中 bn 崩溃的典型表现。batch size 太小(1 或 2)时,BatchNorm 层的均值和方差在每步之间剧烈震荡,尤其是遥感图像背景差异大,BN 统计量根本稳不住。加上学习率偏大,梯度更新一步就冲进数值不稳定的区域。
解决:把 batch 提到至少 8,最好 16;如果显存实在不够,同步降低学习率,比如lr0=0.01降成0.005或0.001。另外检查标签目录里有没有 0 字节的空 TXT——空标签被当成纯背景样本,会干扰 loss 计算,遇到直接删掉或补标。YOLOv8 还可以打开梯度裁剪,但那是最后手段,先调 batch 和 lr。
5.4 混淆矩阵的数字对不上:先搞清归一化方式
现象:训练完打开confusion_matrix.png,发现行和列的数字加起来不符合直觉,网上的说法也各不相同,有的说每行应为 100%,有的说总和应为 1,怎么都对不上。
原因:YOLOv8 会输出原始混淆矩阵和归一化混淆矩阵两张图,归一化的基准是按真实类别行归一化还是按预测列归一化,两种方式数值意义不同。此外背景类会吸收所有置信度低于阈值的检测结果,导致行和列的和都不是 1。这不算 bug,是很多初学者理解错了矩阵的含义。
解决:看confusion_matrix_normalized.png,重点读真实类别那一行:真实电力塔中有多少比例被正确识别(主对角线),有多少落进了背景列(漏检)。单类模型只要主对角线占比超过 0.9,就说明检测器本身是靠谱的。别纠结行和列的求和,那是两类归一化口径造成的正常现象。
5.5 验证集虚高:同源图像泄漏进训练集
现象:train 和 val 的 mAP 都在 0.9 以上,但把模型拿到新采集的遥感图上测试,mAP 直接掉到 0.6 左右。
原因:这是最隐蔽的坑。遥感图像来自连续航拍条带,相邻帧高度相似,随机划分时这些近重复的帧会同时进入训练集和验证集。模型在验证集上等于「背题」,分数虚高,一遇到真正的新数据就现原形。
解决:先检查 train 和 val 的文件名是否共享前缀或连续编号,比如同一条航带的图像通常有相近的编号区间。如果确实重叠,按文件名编号分块切:把连续编号的图像整体划进同一个集合,而不是逐张随机。严格的做法是按拍摄时段或条带分组,保证任何一组只进一个 split。这个小数据集的划分脚本如果默认是纯随机,建议你改成按块切,评估结果才有说服力。
6. 从 mAP 到实际可用:切片推理与部署前的验证习惯
6.1 用混淆矩阵和 PR 曲线判断模型能不能上线
训练结束后,runs/power_tower/exp1/目录里会生成confusion_matrix_normalized.png、PR_curve.png、F1_curve.png等文件。判断模型能不能用,我不看训练日志里的最终 mAP,而是先开混淆矩阵看主对角线,再开 PR 曲线看置信度阈值怎么选。单类模型里,PR 曲线靠近右上角说明检测器质量好;如果曲线中段出现明显下凹,说明有一批置信度不高但真实的检测,这时候把置信度阈值调低比调高更合适。F1 曲线能帮你直接读出最优阈值——F1 最高点对应的置信度就是推理时要设的conf参数。
6.2 大图切片推理:部署前的最后一道检查
遥感实际场景里,输入图像往往有 4000×4000 甚至更大,直接整图喂给 YOLO 会爆显存,小目标也会被下采样抹掉。标准做法是切片推理,把大图切成 640 或 960 的滑窗分别检测,再把框坐标拼回原图坐标系:
def slide_infer(model, img, size=640, stride=320): h, w = img.shape[:2] boxes = [] for y in range(0, h - size + 1, stride): for x in range(0, w - size + 1, stride): crop = img[y:y + size, x:x + size] res = model(crop, imgsz=size)[0] for b in res.boxes: x1, y1, x2, y2 = b.xyxy[0].tolist() boxes.append([x + x1, y + y1, x + x2, y + y2, float(b.conf)]) return boxes逻辑说明:滑窗以stride步长遍历全图,每个切片单独推理,检测框坐标加上切片左上角的偏移量,还原到原图坐标系。参数上,size和训练尺寸保持一致效果最好,stride设为size的一半可以避免目标恰好被窗口边界切开。切片结果会有大量重复框,拼回后做一次全局 NMS 去重。最后说一个部署小习惯:推理时改imgsz不需要重训,比如训练用 960、部署用 640,mAP 会有小幅波动但一般能接受;真正要重训的是模型结构本身换了。我自己现在拿到任何数据集,第一件事都是先可视化 20 张图和标签叠在一起的效果,再进训练——这一步能挡掉后面 90% 的翻车。希望帮到你。
本文还有配套的精品资源,点击获取