244张电塔图训练YOLOv8:从VOC转YOLO到遥感检测的完整避坑指南
2026/9/23 21:21:05 网站建设 项目流程

简介:面向遥感目标检测与电力设施巡检方向的开发者与研究者,这份数据集提供了244张电塔遥感影像及对应标注,统一采用Pascal VOC和YOLO两种格式,方便直接接入主流检测框架。包内共734个文件,包括244张jpg原图、244个xml标注文件、244个txt标注文件,以及若干辅助说明文件,压缩包整体78.4MB,体积适中,适合快速下载与迭代实验。数据集由labelImg工具画框标注,唯一类别dianta共包含504个真实框,既可用于电塔检测模型的训练与验证,也可作为算法评测的基准样本。目前已有280人学习下载,适用于入门目标检测或电力巡线场景的模型调优与精度对比。请注意,资源仅保证标注准确合理,不承诺模型精度,使用时应结合自身任务验证。

1. 244 张电塔图,能不能训 YOLO?

做电力巡检的算法工程师,大概率都收到过类似名字的压缩包:遥感图像电塔检测数据集VOC+YOLO格式244张1类别.7z。先别急着嫌 244 张太少,这个数据量对电塔这类目标其实是“能训,但要看手法”的临界点。电塔在遥感影像里形态明确、尺寸相对稳定,拿它先把完整流程跑通,再逐步扩数据,是这个数据集最常见的用法。

这篇笔记按拿到压缩包之后的真实动作来写:怎么拆包看格式,为什么要从 VOC 转 YOLO,怎么用 YOLOv8 把它练起来,以及 244 张小数据最容易踩的一串坑。核心就一句话:这个数据集不是给你直接上生产的,是给你把流程验证明白、把坑提前踩平的。

2. 拆开 .7z 先看清两套标注:VOC 和 YOLO 的真实结构

这种压缩包解压之后,最常见的交付方式是把图片和 VOC 标注放在两个目录里,图片是.jpg,标注是.xml;有些包会顺带给你一个labels目录,里面是已经转好的 YOLO 格式.txt。不管给没给,你都得先搞清楚两套格式各自描述了什么东西,否则后面训练报错都不知道去哪查。

第一步永远是解压后别急着跑训练,先看目录骨架。

2.1 解压后先看这层:jpg、xml、txt 三种文件各管什么

我拿到过的这类电塔数据,解压后一般长这样:

. ├── JPEGImages/ │ ├── tower_001.jpg │ ├── tower_002.jpg │ └── ... ├── Annotations/ │ ├── tower_001.xml │ ├── tower_002.xml │ └── ... └── labels/ # 有些包会预生成,有些没有 ├── tower_001.txt ├── tower_002.txt └── ...

JPEGImages放原始遥感图像,Annotations放 VOC 格式的 XML 标注,labels放 YOLO 格式的 TXT 标注。三者的文件名一一对应,靠主文件名关联。

为什么大多数数据集交付用 VOC 而不是直接用 YOLO 格式?因为 VOC 的 XML 可读性好,labelimg 等标注工具原生支持,且能携带difficulttruncated这类附加信息;而 YOLO 格式每张图只有一个小 TXT,信息量少,但训练读取速度快,不需要解析 XML 树。所以常见做法是交付用 VOC,训练前再转 YOLO。

2.2 VOC 的 XML 长什么样:一个电塔标注的逐字段拆解

用文本编辑器打开任意一个 XML,结构大致如下:

<annotation> <folder>JPEGImages</folder> <filename>tower_001.jpg</filename> <size> <width>1024</width> <height>768</height> <depth>3</depth> </size> <object> <name>tower</name> <bndbox> <xmin>412</xmin> <ymin>233</ymin> <xmax>687</xmax> <ymax>510</ymax> </bndbox> </object> </annotation>

<size>里的宽高是后面转换 YOLO 坐标的基准,务必以 XML 里的值为准,不要自己另猜。<object><name>是类别名,电塔数据一般只有tower一类;<bndbox>是目标的像素坐标边界框,左上角(xmin, ymin),右下角(xmax, ymax)

一个容易忽略的点:有些标注员会在同一张图里标多个电塔,<object>节点会出现多次,转换脚本要按循环处理,不能只取第一个。

2.3 YOLO 的 TXT 才是训练时要吃的格式:归一化坐标怎么算

YOLO 格式每一行只存五个数:class_id, x_center, y_center, width, height。注意,后面四个数全部归一化到 0~1 区间,单位不是像素,而是相对图像宽高的比例。

转换公式如下,设图像宽高为WH

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

对应上面那组 XML,计算结果是:

0 0.536621 0.483724 0.268555 0.360677

这里0是类别编号,因为只有tower一类,所以编号从 0 开始。归一化坐标的好处是模型不关心输入图像的分辨率,训练时无论是 640 还是 1280,框的相对位置不变。

选型理由也要说清楚:VOC 负责“给人看”,YOLO 负责“给模型快速读”。244 张小数据,转换这步做得干净,后面训练能省掉大量排错时间。

3. 把 VOC 转成 YOLO:转换脚本、目录重建与数据划分

这一章是全文最该抄作业的部分。拿到这种数据集,你至少要经过三步:整理目录、转换标注、划分数据。三步都做对,训练才不会被路径和格式问题拦在半路。

3.1 先立好目录骨架:images 与 labels 一一对应

不少人在这一步翻车:图片在JPEGImages/,标注在Annotations/,训练时 YOLO 按数据配置文件去找图片路径,结果标签目录对不上,直接报错。

我一般先把数据整理成统一的结构,图片归到images/,标签归到labels/,文件名保持一致:

data/ ├── images/ │ ├── tower_001.jpg │ └── ... └── labels/ ├── tower_001.txt └── ...

整理这一步看起来简单,实际上是在为后面所有步骤打底。目录混乱的代价是:训练时报错,你不知道是路径问题还是格式问题,排查成本非常高。

3.2 转换脚本:用 XML 生成 TXT,并处理类名不一致

下面这个脚本把Annotations/下所有 XML 转成 YOLO 格式的 TXT,输出到labels/

import os import xml.etree.ElementTree as ET # 类别列表顺序必须与后续 data.yaml 中的 names 完全一致 class_names = ["tower"] def voc_to_yolo(xml_path, out_dir): tree = ET.parse(xml_path) root = tree.getroot() size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) txt_name = os.path.basename(xml_path).replace(".xml", ".txt") out_path = os.path.join(out_dir, txt_name) with open(out_path, "w", encoding="utf-8") as f: # 一张图里可能有多个电塔,必须循环 object for obj in root.iter("object"): name = obj.find("name").text if name not in class_names: print(f"未知类别: {name}, 文件: {xml_path}") continue cls_id = class_names.index(name) box = obj.find("bndbox") xmin = float(box.find("xmin").text) ymin = float(box.find("ymin").text) xmax = float(box.find("xmax").text) ymax = float(box.find("ymax").text) # 坐标归一化,保留 6 位小数足够训练 x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h f.write(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n") if __name__ == "__main__": os.makedirs("labels", exist_ok=True) xml_dir = "Annotations" for xml_file in os.listdir(xml_dir): if xml_file.endswith(".xml"): voc_to_yolo(os.path.join(xml_dir, xml_file), "labels")

逻辑说明:脚本用ElementTree解析 XML,先读<size>拿到图像宽高,再遍历所有<object>节点,计算归一化坐标后写入 TXT。class_names列表的索引就是类别编号,顺序一旦确定,后面data.yaml里必须保持一致。

参数说明:x_center保留 6 位小数,YOLO 训练时读取浮点精度足够;遇到img_wimg_h为 0 这样的脏数据,建议直接打印出来人工检查,不要静默跳过。

一个容易漏的点:如果某张 XML 里没有任何<object>(背景图),转换脚本会生成一个空 TXT 文件,这是正确的,YOLO 训练允许空标签图,避免报错。

3.3 按 7:2:1 划分数据集:固定随机种子,避免同源样本泄漏

划分数据是 244 张图训练的重中之重。常见做法是训练集 70%、验证集 20%、测试集 10%,测试集尽量留作最后评估,不要参与调参。

import os import random random.seed(42) # 固定随机种子,保证多次跑结果可复现 image_dir = "images" label_dir = "labels" train_ratio = 0.7 val_ratio = 0.2 all_images = [f for f in os.listdir(image_dir) if f.endswith(".jpg")] all_images.sort() random.shuffle(all_images) n = len(all_images) n_train = int(n * train_ratio) n_val = int(n * val_ratio) split_map = { "train": all_images[:n_train], "val": all_images[n_train:n_train + n_val], "test": all_images[n_train + n_val:], } for split_name, file_list in split_map.items(): os.makedirs(f"data/{split_name}/images", exist_ok=True) os.makedirs(f"data/{split_name}/labels", exist_ok=True) with open(f"data/{split_name}.txt", "w") as f: for img_name in file_list: img_src = os.path.join(image_dir, img_name) label_name = img_name.replace(".jpg", ".txt") label_src = os.path.join(label_dir, label_name) os.rename(img_src, f"data/{split_name}/images/{img_name}") if os.path.exists(label_src): os.rename(label_src, f"data/{split_name}/labels/{label_name}") print(f"{split_name}: {len(file_list)} 张")

逻辑说明:脚本按比例随机切分图片,再把对应的标签文件一起搬进data/下的训练、验证、测试子目录,顺便生成一个按行存路径的.txt文件,供 YOLO 直接读取。

参数说明:random.seed(42)让实验可复现,别人跑你的代码能得到一样的划分结果。train_ratioval_ratio按 0.7 和 0.2 设定,剩下 10% 做测试集,确保模型没见过的遥感图能反映真实水平。

需要特别强调的是:遥感图像常有同一区域多帧重叠的情况。如果只是按文件随机切分,同一座电塔的不同帧可能同时落进训练集和验证集,让验证指标虚高。更严谨的做法是提前看文件名前缀,把同一场景的图分到同一边。244 张数据量不大,这一步人工检查十分钟,能省掉后面的误判。

3.4 生成 data.yaml:让 YOLO 知道去哪找图

训练前还需一个数据配置文件,YOLOv8 用 YAML 格式:

path: /full/path/to/data # 改成你的实际路径 train: train.txt val: val.txt names: 0: tower

trainval可以指向 3.3 生成的train.txtval.txt,也可以直接写成train/imagesval/images的目录路径,两种写法 YOLOv8 都认。names必须和转换脚本里class_names的顺序一致,否则类别标签会错乱。

到这里,数据准备工作就结束了。下一步是搭建环境,把 YOLOv8 训起来。

4. 在 YOLOv8 上练起来:环境、训练命令与 5 个必调参数

数据准备好之后,训练本身反而是最机械的一步。244 张图不算多,但如果你直接用默认参数跑,大概率会得到一份过拟合的模型。这一章把这些参数为什么这么调讲清楚。

4.1 环境配置:Anaconda 里跑通 YOLOv8 的最小流程

YOLOv8 的安装比早期版本省心很多,核心只需要ultralytics这一个包。用 Anaconda 新建环境,避免污染系统 Python:

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

安装完成后先跑一个最小推理命令验证环境,不要直接上训练:

yolo predict model=yolov8n.pt source=https://ultralytics.com/images/bus.jpg

能弹出检测结果,说明 CUDA、PyTorch 和 ultralytics 之间没有版本冲突。环境配置阶段最常见的报错是 PyTorch 的 CUDA 版本和显卡驱动不匹配,建议安装 ultralytics 前先用nvidia-smi看一眼驱动支持的 CUDA 版本,再决定装哪个 PyTorch 轮子。

4.2 5 个必调参数:imgsz、epochs、batch、patience、cache

244 张图的小数据集,不要照着检测比赛的大参数抄。下面这张表是我在这种体量数据上常用的推荐值:

参数默认值244 张推荐值理由
imgsz640640电塔在遥感图中尺寸偏大,640 够用;512 可加快训练但会损失细节
epochs100200小数据需要更多轮次收敛,配合早停控制过拟合
batch1616 或 32batch 太小会让 BN 统计不稳定,显存允许则用 32
patience5050val 指标连续 50 轮不提升就早停,省时间
cacheFalseTrue244 张图全部缓存到显存或内存,减少磁盘 IO 等待

cache=True在这里很划算:数据量小,缓存不会爆显存,但训练每轮的读取速度会明显提升。patience是 Early Stopping 的耐心值,验证集损失连续多少轮不降就停,小数据集上非常管用,能避免无效训练到 200 轮。

其他增强参数如hsv_hdegreesflipud先用默认值,不要一上来就乱调。电塔的朝向有物理约束,过度的旋转增强反而会让模型学到错误特征。

4.3 训练命令与 loss 曲线:先别盯 mAP,先看损失

执行训练:

yolo detect train data=data.yaml model=yolov8n.pt epochs=200 imgsz=640 batch=16 patience=50 cache=True device=0

这里model=yolov8n.pt表示用 YOLOv8 nano 的预训练权重做初始化,小数据集上比从零训练收敛快得多。训练结束后,重点不是只看最后的 mAP,而是打开runs/detect/train/results.png看三条损失曲线。

YOLOv8 的损失函数主要由三部分组成:box_loss是预测框与真实框的回归损失,cls_loss是分类损失,dfl_loss是分布焦点损失,管边界框的边线精度。小数据集上你常看到的现象是:

  • train/box_loss快速降到 0.02 附近后不再明显下降,正常;
  • train/cls_loss缓慢下降,说明类别特征还在学习;
  • val/cls_loss先降后升,而train/cls_loss还在降,说明过拟合开始了,这时要依赖早停或减少 epochs。

如果训练日志里出现 loss 变成nan,优先检查学习率是不是太大,或数据里有没有坏图。244 张图训到一半崩掉,先怀疑这几个点,不要急着改模型结构。

5. 电塔检测避坑实录:小样本训练常翻车的 5 个位置

这一章专门写小样本电塔检测里最容易踩的坑。每条都是实打实见过的问题,按“现象 → 原因 → 解决”来记。

5.1 BN 层在小数据上翻车:loss 掉一半直接发散

现象:训练前 20 轮 loss 正常下降,到第 40 轮左右 train loss 突然反弹,val 曲线剧烈抖动,严重时直接出现nan

原因:Batch Normalization 层的统计量在小 batch 上估计不稳定,244 张图还要切出验证集,训练样本更少,BN 的均值和方差波动大,导致特征分布漂移。

解决:优先把 batch 从 16 提到 32,显存不够就换更小的yolov8n模型;训练命令里加--lr0 0.001降低初始学习率;再不行就冻结 backbone 前几层,让 BN 只在检测头部分更新。简单说,模型越小、batch 越大、学习率越收敛保守,小数据越稳。

5.2 误检把电线杆当电塔:类别太宽 + 背景样本不够

现象:模型在测试图上把一排电线杆、路灯杆标成 tower,置信度还在 0.5 以上,看起来像模型真的“确信”了。

原因:电塔本质是杆状结构,和电线杆在视觉上高度相似,而 244 张数据里几乎没有负样本图——也就是没有电塔、只有类似物的背景图。模型没见过“这是错的”的样本,自然学不会区分。

解决:从现场巡线视频里截取一批不含电塔的遥感图,打成空标签放进训练集,让模型在验证时能接触到负样本;推理时把置信度门限从默认 0.25 往上提到 0.45 到 0.5。这在 yolo 检测里是调整置信度门限最典型的使用场景:数据里负样本不足,就用门限硬压误检率。注意门限抬高会损失召回,适合巡检场景宁可漏检再人工复核的诉求。

5.3 转换格式时少除了图像尺寸:框肉眼可见地偏

现象:转完 YOLO 格式,用可视化脚本把框画回原图,发现框整体偏移,位置和大小都不对。

原因:转换脚本里没有读 XML 的<size>字段,而是用了自己假设的固定宽高(比如默认 1024),或者把像素值当成浮点直接除以 1000,坐标全部错位。

解决:回到 3.2 的脚本,强制从<size>节点读取widthheight,并且全部转成float再计算。转完别急着训练,先随机抽 10 张图,把 TXT 的框画回原图看一眼,这一步一分钟的事,能省掉一小时的排错时间。

5.4 labelimg 打标完 YOLO 格式的标,训练却提示数据错误

现象:labelimg 标注完成后,训练时报错“image not found”或“invalid label format”,排查半天发现标注文件根本没生成在预期目录。

原因:labelimg 保存 YOLO 格式时,默认把 TXT 和图片放在一起,而不是放在labels/目录里;还有一类常见情况是 labelimg 里的类别文件和data.yaml的 names 顺序不一致,导致标注索引错乱。

解决:在 labelimg 打标前,先创建一个classes.txt,内容写tower,然后在界面里加载这个文件;保存时明确指定输出目录为labels/,不要用默认的与图片同目录。训练前用脚本统计一下labels/下的 TXT 数量和images/下的图片数量是否一致,不一致先补齐再说。

5.5 验证集指标虚高:切分时同源样本泄漏

现象:val 的 mAP50 能到 0.9 以上,模型看起来很强,换到一段全新的巡线视频上效果直接拉胯。

原因:数据切分时只按文件名随机打散,但遥感数据里同一座电塔往往有多帧,几乎一模一样的图同时进了训练集和验证集,模型相当于“背题”了,验证集根本不具备泛化评估能力。

解决:回到 3.3 的划分思路,按文件名前缀或按场景分组,把同一区域的所有图分到同一个集合里。244 张数据,宁可训练集少几张,也要保证验证集是从没见过的场景。

6. 遥感大图怎么测:切片推理与置信度调整技巧

训练好的模型不能直接塞给一张几千乘几千的遥感大图。遥感影像动辄 5000 像素边长,直接 reszie 成 640 会把电塔细节抹掉,小目标直接消失。这一章讲两个最实用的推理技巧。

6.1 滑窗切片推理:把大图拆开喂给模型

常见做法是滑窗裁剪:把大图按 640 的窗口、15% 到 25% 的重叠率切成小块,逐块送入模型检测,再把结果坐标映射回原图。重叠率不能省,否则电塔正好被切在边缘时,检测框会被截断,模型只看到半个塔,误检率直线上升。

切出来的小块独立推理后,同一座电塔可能被多个窗口重复检测到,需要用 NMS 合并。YOLO 推理命令里自带iou参数,默认 0.5,可以按场景微调,框重合度超过这个阈值的都合并成一个。

6.2 置信度门限与 NMS:把误检压到巡检可接受范围

推理时通过命令行参数直接调整门限:

yolo detect predict model=runs/detect/train/weights/best.pt source=test_big.tif imgsz=640 conf=0.45 iou=0.5

conf=0.45比默认值 0.25 高不少,在这个电塔数据集上是一个相对保守的起点。一个更稳的做法是:先把验证集所有预测结果按置信度从高到低排列,人工往前翻 100 个预测框,看有多少是误检。如果前 100 个里误检明显少于真检,门限就合适;如果误检占了三分之一,就继续上调,直到误检率降到项目能接受的水平。

在电力巡检这类场景里,漏检可以靠人工复核兜底,误检不进行为性能压力,所以门限往里调一档并不亏。

最后说一个我自己养成的习惯:拿到这种 244 张小数据集,第一遍永远用默认参数跑通,不作任何花哨改进,先看输出的结果缩略图里误检长什么样,再决定是调阈值、补数据还是换模型。这个流程走完,你会发现电塔检测最难的其实不是训练,而是数据划分和负样本设计这两件看着不起眼的事。希望帮到你。

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

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

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

立即咨询