电力场景遥感电杆塔检测数据集VOC+YOLO双格式小样本实践指南

发布时间:2026/10/10 13:55:59
电力场景遥感电杆塔检测数据集VOC+YOLO双格式小样本实践指南
简介面向电力巡检与遥感目标检测场景这份7z压缩包提供一套电杆塔检测数据集图片与标注均来自真实电力场景可直接用于训练YOLO系列或Faster R-CNN等目标检测模型。数据集中共400幅JPG图像每幅均配有Pascal VOC格式XML标注与YOLO格式TXT标注标注类别为单一的“sdgt”电杆塔总共包含680个矩形标注框由labelImg工具画框完成标注位置准确、格式规范可节省人工标注时间。7z压缩包内共1202个文件包括400个XML标注、400个TXT标注、400个JPG原图并附有2个辅助txt文件整体大小约136.93MB。目前已有605人学习浏览。对于需要构建电力线杆塔检测训练集的算法工程师或研究人员可直接基于该数据开展模型训练、验证与算法对比省去从零标注的繁琐过程。1. 拿到“电力场景遥感数电杆塔检测数据集VOCYOLO格式400张1类别.7z”先想清楚它能干什么做电力巡检的人基本都受过电杆塔检测的折腾。无人机和卫星拍回来的遥感图里杆塔又小、背景又乱想在几十上百米的高空视角下把它稳定框出来拿通用目标检测数据集来训几乎没法直接用。这个数据集就是专门把“电力场景遥感图”和“电杆塔”这一件事做成的小样本样本集400张图、1个类别同时给了Pascal VOC的xml和YOLO的txt两套标注再用7z压缩封装。对刚入门YOLO的开发者来说它是把训练全流程跑通的低成本起点对做电力智能巡检的工程团队它是验证算法路线和做预训练微调的地基数据。下面从解压、体检、格式互转、训练排查一路讲清楚最后说怎么验证这份数据值不值得继续投入。2. 为什么要用垂直场景小数据集电杆塔检测的两道门槛2.1 电杆塔检测为什么不能用通用目标检测数据集直接替代先说一个反直觉的事实COCO、CrowdHuman这些数据集的“通用性”是针对室内和城市街景的换到电力场景的遥感视角绝大多数样本直接失效。原因拆开有三层。第一层是目标尺度的变化。遥感图像里的电杆塔通常只有几十到一两百像素在4000×3000的大图上只占一个小角COCO里最常见的框能占图像面积的几十分之一两者尺度分布差了一个数量级。用跨域数据训练模型会天然把“小目标”的语义权重放得很低导致电杆塔漏检率居高不下。这也是YOLO系列在遥感任务里频繁要调anchor、调imgsz的根因不是模型不行是数据分布就不支持。第二层是背景纹理和遮挡模式的差异。电力场景的遥感图里杆塔周边往往是农田、山地、城市建筑目标本身还可能被树冠、阴影、绝缘子串和导线遮挡。通用数据集里同类物体边缘清晰、遮挡干净模型从那里学到的特征在电力数据上可迁移性很差。做巡检的团队可能都有这种体验在COCO上能跑到40mAP的模型部署到输电线路影像上直接“翻车”错检全在田埂、道路边缘和建筑物转角上。第三层是语义口径不一致。标题里的“1类别”是把输电铁塔、电线杆、拉线塔合并成一个语义整体来处理的。如果你拿一个40类的通用模型直接冻结主干网络抽特征“杆塔vs非杆塔”的二分类边界和原来40类之间的区分边界是错位的。这也是为什么做电力巡检的团队通常会备一份像这样的小样本垂直数据集而不是把通用数据集拿来直接训。和CCPD车牌、BDD100自动驾驶数据集一个道理垂直数据集的价值在于“同一类目标、同一类视角、同一套部署条件”而不是单纯的数量堆叠。2.2 VOC与YOLO双格式标注一份数据两套坐标不只是为了方便标题里写的VOC全称是Pascal VOC用的是XML文件。每个XML对应一张图框是绝对的像素坐标包含xmin、ymin、xmax、ymax文件头还带图像尺寸。YOLO系的标注则把坐标归一化到0~1之间以txt文本存储每行五列依次是类别编号、中心点x、中心点y、宽、高。两套格式的转换公式其实就两组center_x (xmin xmax) / 2 / width center_y (ymin ymax) / 2 / height w (xmax - xmin) / width h (ymax - ymin) / height反过来从YOLO五列恢复像素框就是把中心坐标乘以宽高再加减半个宽高。实际动手时这四行公式足够应付绝大多数转换需求。数据集里同时放两套格式常见做法是生成一份之后脚本自动转出另一份。它最大的价值不是冗余而是“对账”。训练前你用脚本统计XML里的坐标最大值是否超过图像宽高同时检查txt里的数值是否落在0~1之间任何一边不对对照检查就知道是源标注错了还是转换丢了。400张图、1个类别的数据量人工检查每一个框都不费劲前提是你能判断坐标到底准不准。两套格式的差异先看下面这张表对比点Pascal VOCXMLYOLOTXT坐标形式像素绝对值归一化相对值存储单位每个XML独立记录每个txt五列是否含图像尺寸文件内带有width/height不含训练时另读图片尺寸可读性人眼可读便于审核数值抽象便于训练直接加载常见标注工具导出LabelImg等LabelImg、Ultralytics等还有一个容易被忽略的点YOLO训练真正消费的是txt这套标注VOC更多是作为中间交换格式存在。开源社区里大量数据集以VOC格式托管先转成YOLO再开训是标准操作。所以“VOCYOLO格式”这个标题暗示的不是两套冗余而是一套可供转换的原始格式加一套训练就绪的目标格式。拿到手后只选一边作为真源另一侧全用脚本重新生成不要两边同时手改否则坐标错位你会查疯。先做一步最简单的数量核对for d in JPEGImages Annotations labels; do echo $d: $(ls $d | wc -l) done三行数字应该接近一致。如果不一致后面训练一定出问题这就是下一章要做的体检。3. 拿到手第一步解开7z压缩包再做数据体检3.1 Linux与Windows解压.7z命令行和图形化两条路.7z是高压缩比归档格式通常比zip再小20%~30%所以很多数据集发行版本用它打包。首先在Linux下解压最常见的是p7zip工具。Debian/Ubuntu系统这样装sudo apt update sudo apt install -y p7zip-full 7z x 电力场景遥感数电杆塔检测数据集VOCYOLO格式400张1类别.7z -o./power_pole_data参数说明x表示解压-o指定输出目录。注意-o后面没有空格这是7z命令行工具最容易踩的反直觉点写-o ./xxx会把目录名解析成“./xxx”的前缀解压位置全乱。如果你的包名是中文且终端显示乱码先执行7z l 包名.7z看压缩包内部文件列表再用-mcp4096参数指定UTF-8代码页解压或者干脆把文件重命名为英文再操作能省掉一大半编码烦恼。Windows下则是直接右键选“7-Zip——Extract to”在7-Zip设置里打开侧边栏的压缩文件树边看目录结构边核对标注文件很方便。解压后常见的目录结构长这样power_pole_data/ ├── JPEGImages/ # 原始遥感图 ├── Annotations/ # Pascal VOC的xml标注 ├── labels/ # YOLO格式的txt标注 ├── train.txt # 训练集文件列表 ├── val.txt # 验证集文件列表 └── classes.txt # 类别清单不同的发布版本可能略有差别比如labels目录可能叫yolo_labelstrain/val.txt可能没有。拿到手后先对照这个通用结构检查一遍。这里有个经验不要急着开训先用10分钟看清楚目录布局后面能省下按小时计的调试时间。3.2 数据体检图片尺寸、目标数量与XML/TXT的对账脚本解压完成后第一件事不是写训练配置而是跑一个体检脚本把图片、xml、txt三者的对应关系摸清楚。下面这个脚本我每次拿到新数据集都会先跑一遍import os, glob from PIL import Image import xml.etree.ElementTree as ET img_dir JPEGImages xml_dir Annotations label_dir labels imgs sorted(glob.glob(os.path.join(img_dir, *))) xmls sorted(glob.glob(os.path.join(xml_dir, *.xml))) txts sorted(glob.glob(os.path.join(label_dir, *.txt))) print(fimage{len(imgs)} xml{len(xmls)} txt{len(txts)}) # 1. 统计图片尺寸分布 size_stats {} for img_path in imgs: try: w, h Image.open(img_path).size size_stats.setdefault((w, h), 0) size_stats[(w, h)] 1 except Exception as e: print(cannot open, img_path, e) print(top5 size:, sorted(size_stats.items(), keylambda kv: -kv[1])[:5]) # 2. 检查XML里坐标是否越界、目标类别是否统一 cls_counter {} problem_count 0 for xf in xmls: root ET.parse(xf).getroot() size root.find(size) W int(size.find(width).text) H int(size.find(height).text) for obj in root.findall(object): name obj.find(name).text cls_counter[name] cls_counter.get(name, 0) 1 b obj.find(bndbox) xmin float(b.find(xmin).text) ymax float(b.find(ymax).text) if xmin 0 or ymax H: problem_count 1 print(class counter:, cls_counter) print(xml coordinate problems:, problem_count) # 3. 检查YOLO txt数值是否落在0~1 bad_txt 0 for tf in txts: for line in open(tf): parts line.strip().split() if len(parts) ! 5: print(bad line in, tf) bad_txt 1 break x, y, w, h map(float, parts[1:]) if not (0 x 1 and 0 y 1 and 0 w 1 and 0 h 1): print(value out of range in, tf) bad_txt 1 break print(bad txt count:, bad_txt)逻辑说明第一步统计图片尺寸是为了确认数据源是否统一。如果400张图有十几种分辨率说明数据来自多个拍摄平台训练时的resize策略和anchor设计要特殊处理。第二步读取每个XML里的size字段和object统计类别数量顺带检查坐标有没有小于0或超出图高的位置——这类越界标注在遥感数据里不算罕见尤其是半自动标注工具生成的边界框。第三步检查txt数值是否在0~1之间这一步看似简单但能过滤掉一批转换脚本写错坐标的脏数据。参数说明脚本里的glob模式指定了图片目录下的所有文件没有限制后缀这样既能覆盖jpg也能覆盖pngImage.open不会真正解码图片只是读文件头拿宽高速度很快。如果跑完发现xml和txt数量对不上基本可以断定转换流程有断点先不要训练回到第4章的转换脚本重新生成一侧标注。4. 把VOC转成YOLO的落地脚本转换公式与四个边界坑4.1 从XML到TXT一个能直接跑的转换函数大多数YOLO训练流程只消费txt标注所以拿到VOC格式后第一步是批量转换。下面这个脚本可以直接放在数据集根目录跑import os, glob import xml.etree.ElementTree as ET def voc_to_yolo(in_xml, out_txt, class_namesNone): root ET.parse(in_xml).getroot() size root.find(size) W float(size.find(width).text) H float(size.find(height).text) if W 0 or H 0: return False lines [] for obj in root.findall(object): name obj.find(name).text if class_names is not None and name not in class_names: continue cls_id class_names.index(name) if class_names else 0 b obj.find(bndbox) xmin float(b.find(xmin).text) ymin float(b.find(ymin).text) xmax float(b.find(xmax).text) ymax float(b.find(ymax).text) # 边界截断防止负坐标和超出图幅的框 xmin max(0, min(xmin, W)) xmax max(0, min(xmax, W)) ymin max(0, min(ymin, H)) ymax max(0, min(ymax, H)) if xmax - xmin 0 or ymax - ymin 0: continue cx (xmin xmax) / 2 / W cy (ymin ymax) / 2 / H w (xmax - xmin) / W h (ymax - ymin) / H lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(out_txt, w) as f: f.write(\n.join(lines)) return True if __name__ __main__: os.makedirs(labels, exist_okTrue) for xml_path in glob.glob(Annotations/*.xml): out labels/ os.path.basename(xml_path).replace(.xml, .txt) voc_to_yolo(xml_path, out, [power_pole])逻辑说明函数入口的class_names参数用来过滤类别。本数据集虽然只有一个类别但有些发布版本会在XML里混入人工复核时标错的类名比如把“电线”也标进去了。传一个白名单列表转换时自动跳过不认识的类别这样得到的labels目录更干净。坐标截断的四行min/max操作不是可有可无——半自动标注工具生成的目标框偶尔会超出图幅边界如果不截断归一化后w或h会大于1YOLO训练时这类样本的损失会异常放大。参数说明class_names.index(name)把类别名称映射成编号编号顺序必须和后续训练时yaml文件里的类别顺序保持一致否则就会出现“模型把铁塔识别成电线杆”的错位。建议转换完成后输出一份classes.txt内容是类别名称按编号排序训练时直接引用。4.2 反向转换YOLO转VOC以及最容易翻车的四个边界坑训练完成后如果要用官方mAP评估脚本或者要把预测结果交给标注平台复核需要把YOLO的txt转回VOC。反向转换的核心是恢复像素坐标def yolo_to_voc(txt_path, img_w, img_h): boxes [] for line in open(txt_path): parts line.strip().split() if len(parts) ! 5: continue cls_id, cx, cy, w, h map(float, parts) cx * img_w; cy * img_h; w * img_w; h * img_h xmin int(cx - w / 2) xmax int(cx w / 2) ymin int(cy - h / 2) ymax int(cy h / 2) boxes.append((int(cls_id), xmin, ymin, xmax, ymax)) return boxes这段代码本身不难但真实项目里转换环节最容易翻车的是下面四个边界坑坑一空目标文件。有些图片里确实没有杆塔对应的txt是0字节转换脚本如果没做空文件判断会把空文件写成一条空行下游读取时报IndexError。解决方法是转换时判断if len(line.strip()) 0: continue同时训练阶段要把空标注文件正常保留不能删除否则图片和标注的对应关系断裂。坑二坐标越界没有截断。YOLO的归一化坐标理论上都在0~1之间但模型预测时可能输出负值或大于1的值尤其在小目标密集的场景。反向转换时要做和第4.1节一样的钳制不然画出来的框会飘出图片边界评估时IoU计算全部异常。坑三类别编号错位。YOLO只存编号不存类名如果你用A顺序训练的模型去转B顺序的标注所有框都会变成错误类别。这个坑在“1类别”数据集里影响不大但只要将来扩成多类别就一定会在某个环节踩到。做法是永远从classes.txt读类别列表不要手写。坑四XML编码混用。部分标注文件是GBK编码ElementTree默认按UTF-8解析直接抛UnicodeDecodeError。如果在Windows上编辑过XML再打包这类问题尤其常见。解决方式是读文件时指定编码ET.parse(in_xml, parserET.XMLParser(encodingutf-8))不确定时先用file命令查看编码再统一处理。5. 从解压到训练全流程排查五个先知道早止损的坑5.1 解压与标注对不上账三个高发坑坑一中文文件名或乱码导致图片与XML失联。现象是7z解压后图片文件名变成乱码XML里引用的图片路径和实际文件名对不上训练时大量图片无法加载。原因是Windows下压缩时文件名编码和解压终端的代码页不一致。解决方法是先执行7z l 包名.7z查看包内文件列表如果看到乱码用-mcp4096参数重新解压如果已经解压坏了直接重新解压比逐个改名快得多。坑二train.txt和val.txt里保存的是绝对路径。现象是训练启动时能加载图片但换一台机器就报FileNotFoundError。原因是生成文件列表时用了os.path.abspath()把别人的机器路径写死在了文件里。解决方法是打开文件看看第一行是不是以/开头的绝对路径如果是用相对路径重新生成文件列表。养成习惯数据集里的文件清单永远用相对路径存放训练脚本启动时动态拼接。坑三XML和TXT数量对不上。现象是图片有400张XML有400个TXT只有397个。原因是转换脚本在处理某几张图时抛了异常比如坐标缺失或空对象程序默认跳过没有报错。解决方法是先跑第3.2节的体检脚本找出缺失的是哪几张单独对这几张图重新跑转换不要整批重来浪费时间。5.2 训练阶段两个必须提前设好的参数坑四400张图的小样本还从零随机初始化训练。现象是loss在几十个epoch里都降不到合理水平检测头几乎不输出框。原因是这个样本量不足以让backbone从头学习纹理特征尤其遥感图里的杆塔特征和自然图像差异巨大。解决方法是加载预训练权重做微调YOLOv8里直接指定--weights yolov8n.pt或者用同领域训练的权重做初始化。如果显存紧张可以先冻结backbone只训练检测头运行几个epoch看损失下降趋势稳定后再解冻全部层。坑五训练和推理时的resize策略不一致。现象是验证集mAP0.5还行部署到无人机上错检率暴涨。原因是训练时固定imgsz640推理时如果原图是4000×3000直接压缩到640会把小目标压成几个像素。解决方法是训练和推理统一使用同一个imgsz遥感场景建议设到1280以上显存不够时用切图或tiling推理把大图切成512×512的块分别检测再合并结果。还要注意YOLO默认做letterbox填充推理时如果关闭了letterbox矩形图像的检测精度会更差这个参数在Ultralytics推理脚本里叫rect需要保持和训练一致。6. 用可视化与基线训练验证这份数据值不值得投6.1 抽图回画标注10分钟肉眼质检训练前把标注画回图上是验证数据集质量性价比最高的动作。随机抽10张图画框可视化import os, random from PIL import Image, ImageDraw import xml.etree.ElementTree as ET xmls sorted(os.listdir(Annotations)) random.seed(42) for name in random.sample(xmls, 10): root ET.parse(os.path.join(Annotations, name)).getroot() img_path os.path.join(JPEGImages, root.find(filename).text) img Image.open(img_path).convert(RGB) draw ImageDraw.Draw(img) for obj in root.findall(object): b obj.find(bndbox) xmin int(float(b.find(xmin).text)); ymin int(float(b.find(ymin).text)) xmax int(float(b.find(xmax).text)); ymax int(float(b.find(ymax).text)) draw.rectangle([xmin, ymin, xmax, ymax], outlinered, width3) img.save(fcheck_{os.path.basename(name).replace(.xml, .jpg)})跑完打开图片扫一遍重点看框是否贴合杆塔轮廓、有没有把导线和绝缘子串框进去、有没有漏掉远处的目标。这一步能发现坐标对不上账发现不了的语义问题。6.2 框尺度直方图决定要不要切图训练统计400张图里所有目标框的像素尺寸如果大多数框小于32×32像素说明这是一个典型的小目标数据集。处理方法有两种一是把训练imgsz提高到1280以上让网络“看得更多”二是提前切图把大图切成若干个512×512的patch再训练推理时再用同样的切图策略。后者通常比前者效果好但会带来重叠区域的重复检测需要用NMS做后处理合并。6.3 快速基线训练以损失曲线判断数据健康度用YOLOv8n跑20个epoch不调任何超参数观察损失变化趋势。如果训练损失稳步下降、验证损失同步下降说明数据标注基本干净值得为它扩展更多样本如果训练损失降了但验证损失反弹说明过拟合已经出现400张数据只够验证路线不够部署标准如果两类损失一开始就上下乱跳大概率是标注坐标存在噪声回到第3.2节的体检脚本查数据。我在最早一批电力巡检项目里跳过体检直接开训结果有几张图的xml和txt对不上账白白花了两天排查才发现是转换脚本漏了空文件真正该做的可视化质检反而没做。现在拿到任何数据集第一件事永远是先跑体检脚本再决定要不要动训练希望你拿这份数据时不用再走这个弯路希望帮到你。本文还有配套的精品资源点击获取