ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

电线杆数据集实战:YOLO与VOC双格式解析及YOLOv8训练避坑指南

电线杆数据集实战:YOLO与VOC双格式解析及YOLOv8训练避坑指南 简介本资源为面向目标检测学习者的电线杆识别数据集适用于电力巡检、基础设施监测等场景下的算法训练与验证适合具备一定深度学习基础、正在做目标检测项目或课程实践的开发者使用。压缩包共约2000个文件以1999个xml标注文件和1个说明txt为主同时包含对应图片与YOLO格式txt标签整体约128.26MBxml与txt分别对应VOC与YOLO两种标注格式便于直接接入不同训练框架。数据集共2127张清晰图片标注单一类别telegraph pole矩形框总数达2700个未做数据增强标注准确合理。已有216人学习下载读者可借此快速搭建电线杆检测实验省去自行采集与标注成本用于模型训练、格式转换与效果对比等环节。1. 电线杆数据集到底解决什么问题2127 张图背后的目标检测需求电力巡检、城乡规划、自动驾驶高精地图这几类场景里电线杆是一个绕不开的目标。它细长、背景杂乱、经常被树枝和广告牌遮挡而且不同地区的杆型差异极大——水泥杆、木杆、金属塔、带变压器的、带绝缘子的形态五花八门。你拿 COCO 或 VOC 通用数据集去训一个检测器电线杆这个类别的召回率通常惨不忍睹因为通用数据集里根本没有足够的电线杆样本。这个数据集的价值就在这里2127 张标注好的电线杆图像同时提供 YOLO 格式和 VOC 格式意味着你不需要自己从零标注拿到手就能直接喂给 YOLOv5/v8/v11 或者 Faster R-CNN 这类框架开训。2127 张不算大但对于一个单类别或少量类别的检测任务来说够用了——尤其是配合预训练模型做微调的时候。适合谁用三类人一是做电力巡检自动化的工程师需要快速验证电线杆检测的可行性二是学生或刚入门目标检测的开发者想找一个真实场景的数据集练手而不是拿 MNIST 或猫狗分类凑数三是做数据增强、半监督学习研究的人需要一个中等规模、标注质量可控的基准。下面从数据集的格式结构讲起一路说到怎么训、怎么调、怎么避坑。2. YOLO 与 VOC 双格式拆解目录结构、标注差异与转换逻辑2.1 两种格式的本质区别VOC 格式的核心是 XML 文件每张图对应一个 XML里面记录了图像的宽高、通道数以及每个目标的类别名和边界框坐标xmin, ymin, xmax, ymax。YOLO 格式则是一个 txt 文件对应一张图每行一个目标格式是class_id x_center y_center width height所有坐标都归一化到 0~1 之间。这两种格式的差异不只是文件类型不同背后是两套标注哲学。VOC 保留绝对像素坐标方便你做可视化、做坐标变换、做多尺度训练时的重新映射YOLO 的归一化坐标直接喂给网络省去了训练时的归一化步骤但你想还原到原图坐标就得乘回宽高。拿到这个数据集后第一件事是确认目录结构。常见的组织方式有两种# 方式一VOC 为主YOLO 为转换产物 dataset/ ├── VOCdevkit/ │ └── VOC2007/ │ ├── Annotations/ # XML 文件 │ ├── JPEGImages/ # 原图 │ ├── ImageSets/ │ │ └── Main/ # train.txt, val.txt │ └── SegmentationClass/ └── labels/ # YOLO 格式 txt # 方式二YOLO 为主VOC 单独存放 dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── VOC_Annotations/ # XML 备份不管哪种结构你都需要先跑一遍完整性检查。下面这段脚本做三件事统计图片数量、检查每张图是否有对应的标注文件、检查标注文件是否为空。import os from pathlib import Path img_dir Path(dataset/images) lbl_dir Path(dataset/labels) img_files sorted(img_dir.glob(*.jpg)) sorted(img_dir.glob(*.png)) print(f图片总数: {len(img_files)}) missing_label [] empty_label [] for img_path in img_files: lbl_path lbl_dir / (img_path.stem .txt) if not lbl_path.exists(): missing_label.append(img_path.name) elif lbl_path.stat().st_size 0: empty_label.append(img_path.name) print(f缺失标注: {len(missing_label)} 张) print(f空标注文件: {len(empty_label)} 张) if missing_label: print(前10个缺失:, missing_label[:10]) if empty_label: print(前10个空标注:, empty_label[:10])这段代码的逻辑很直白遍历所有图片按文件名去 labels 目录找同名 txt。缺失标注的图片如果直接拿去训练YOLO 会把它当成负样本背景如果这张图里其实有电线杆就会引入噪声。空标注文件同理要么是漏标了要么是真的没有目标——你需要人工抽查确认。参数方面img_dir和lbl_dir根据你的实际目录改。如果你的图片是.jpeg后缀glob 模式也要跟着改。统计结果里如果缺失超过 5%建议先补齐再训。2.2 VOC 转 YOLO转换脚本与四个边界坑数据集虽然同时提供了两种格式但你很可能遇到一种情况VOC 的 XML 是全的YOLO 的 txt 只有部分或者你想自己重新生成一遍确保对齐。下面这个转换脚本处理了四个容易翻车的地方。import xml.etree.ElementTree as ET from pathlib import Path from PIL import Image # 类别映射根据你的数据集实际类别修改 CLASSES [pole] # 如果有多类按字母序排列 class_to_id {c: i for i, c in enumerate(CLASSES)} def voc_to_yolo(xml_path, img_path, output_dir): tree ET.parse(xml_path) root tree.getroot() # 坑1用 PIL 读实际尺寸不用 XML 里的 size # XML 里的 width/height 有时和真实图片不一致 img Image.open(img_path) img_w, img_h img.size lines [] for obj in root.iter(object): cls_name obj.find(name).text.strip() if cls_name not in class_to_id: continue # 坑2跳过不在类别表里的目标 bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 坑3坐标裁剪到图像范围内 xmin max(0, min(xmin, img_w)) ymin max(0, min(ymin, img_h)) xmax max(0, min(xmax, img_w)) ymax max(0, min(ymax, img_h)) # 坑4过滤掉宽高为0的无效框 if xmax xmin or ymax ymin: continue 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 lines.append(f{class_to_id[cls_name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) out_path Path(output_dir) / (Path(xml_path).stem .txt) out_path.write_text(\n.join(lines)) # 批量转换 xml_dir Path(VOCdevkit/VOC2007/Annotations) img_dir Path(VOCdevkit/VOC2007/JPEGImages) out_dir Path(labels_converted) out_dir.mkdir(exist_okTrue) for xml_file in xml_dir.glob(*.xml): img_file img_dir / (xml_file.stem .jpg) if img_file.exists(): voc_to_yolo(xml_file, img_file, out_dir)四个坑逐个说清楚。坑1XML 里的size字段记录的是标注时图像的尺寸但如果你中途做过缩放或裁剪这个值可能和当前图片不一致用 PIL 读实际尺寸最保险。坑2有些 XML 里会出现拼写错误或多余空格比如pole 或Pole直接做字符串匹配会漏掉建议先统一 strip 和 lower。坑3标注框超出图像边界的情况在人工标注里很常见不裁剪的话归一化坐标会超过 1YOLO 训练时虽然不会报错但会影响损失计算。坑4宽或高为 0 的框是无效标注留着只会让模型学歪。转换完之后建议随机抽 20 张做可视化验证把 YOLO 格式的框画回原图上肉眼确认没有偏移。3. 用 YOLOv8 跑通训练从环境配置到第一个 baseline3.1 环境搭建与数据配置文件YOLOv8 是目前上手最快的选择Ultralytics 的 API 封装得很干净。环境配置不复杂但版本兼容性有几个注意点。# 创建虚拟环境 python -m venv yolo_env source yolo_env/bin/activate # Windows 用 yolo_env\Scripts\activate # 安装 ultralytics pip install ultralytics8.2.0 # 验证安装 yolo checksyolo checks会输出当前环境的 PyTorch 版本、CUDA 是否可用、GPU 型号等信息。如果你用的是 V100 或更高型号的卡确认 CUDA 版本和 PyTorch 版本匹配。V100 跑 YOLOv8n 或 YOLOv8s 绰绰有余2127 张图在 V100 上大概 10~15 分钟就能跑完 100 个 epoch。接下来创建数据配置文件pole_dataset.yamlpath: /absolute/path/to/dataset train: images/train val: images/val nc: 1 names: 0: polepath必须写绝对路径YOLOv8 对相对路径的处理有时候会出玄学问题。nc是类别数这个数据集如果只有电线杆一个类就写 1。names里的顺序要和转换脚本里的CLASSES列表一致否则类别 ID 会错位。划分训练集和验证集的比例2127 张图建议 8:2也就是大约 1700 张训练、427 张验证。如果数据集本身已经划分好了直接用它的划分。没有划分的话用下面这段脚本按比例随机拆分import random from pathlib import Path import shutil random.seed(42) # 固定随机种子保证可复现 img_dir Path(dataset/images) all_imgs sorted(img_dir.glob(*.jpg)) random.shuffle(all_imgs) split_idx int(len(all_imgs) * 0.8) train_imgs all_imgs[:split_idx] val_imgs all_imgs[split_idx:] for subset, imgs in [(train, train_imgs), (val, val_imgs)]: out_img Path(fdataset/images/{subset}) out_lbl Path(fdataset/labels/{subset}) out_img.mkdir(parentsTrue, exist_okTrue) out_lbl.mkdir(parentsTrue, exist_okTrue) for img_path in imgs: shutil.copy(img_path, out_img / img_path.name) lbl_path Path(dataset/labels) / (img_path.stem .txt) if lbl_path.exists(): shutil.copy(lbl_path, out_lbl / lbl_path.name) print(f训练集: {len(train_imgs)} 张, 验证集: {len(val_imgs)} 张)random.seed(42)这行别省不然每次跑出来的划分不一样实验没法对比。复制而不是移动保留原始文件作为备份这是血泪经验——有一次我移动完发现划分比例搞反了原始文件已经散落在两个目录里恢复起来很麻烦。3.2 训练命令与关键参数解读数据准备好之后训练命令本身很简单yolo detect train \ datapole_dataset.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/pole_det \ namebaseline逐个参数说。modelyolov8s.pt用的是 COCO 预训练权重这是微调的关键——2127 张图从零训会严重过拟合用预训练权重能把收敛速度提升好几倍。imgsz640是输入分辨率电线杆这种细长目标如果图片本身分辨率很高640 可能会把杆子缩得太细可以试试 1024但显存占用会翻倍。batch16在 V100 上跑 640 分辨率没问题如果显存不够就降到 8。lr00.01是初始学习率微调场景下这个值偏大可以降到 0.001但 YOLOv8 自带学习率预热和余弦退火0.01 也能跑。patience20是早停耐心值20 个 epoch 验证集指标不提升就停。训练过程中重点看三个指标box_loss是否稳定下降、mAP50是否在涨、cls_loss有没有异常波动。如果box_loss降到很低但mAP50不涨大概率是过拟合了需要加数据增强或减模型复杂度。3.3 训练结果解读与推理验证训练完成后runs/pole_det/baseline/目录下会有权重文件、混淆矩阵、PR 曲线等。先看混淆矩阵如果电线杆的召回率低于 0.7说明漏检严重可能是标注框太松或图片里小目标太多。再看results.png里的损失曲线正常情况是训练损失和验证损失同步下降如果验证损失先降后升就是过拟合的信号。推理验证用一行命令yolo detect predict \ modelruns/pole_det/baseline/weights/best.pt \ sourcedataset/images/val \ conf0.25 \ saveTrueconf0.25是置信度阈值低于这个值的检测框不输出。电线杆检测建议先用 0.25 看召回如果误检太多再往上调。推理结果会保存在runs/detect/predict/下肉眼抽查 20 张重点看密集排列的电线杆有没有漏检、远处的细杆有没有被忽略。4. 避坑与排查电线杆检测训练中最容易翻车的 5 个点4.1 现象mAP 从第 10 个 epoch 开始震荡不涨原因通常是学习率太大或者 batch size 太小导致梯度噪声大。2127 张图如果 batch 只设 4 或 8每个 batch 的样本差异大梯度方向不稳定。解决方法是把lr0降到 0.001同时把batch提到 16 或 32。如果显存不够用梯度累积模拟大 batch。4.2 现象验证集 loss 正常但推理时框全偏了这是典型的归一化坐标和绝对坐标搞混了。检查你的 YOLO 格式 txt 里坐标是不是都在 0~1 之间。如果出现了大于 1 的值说明转换脚本里忘了除以宽高。另一个可能是推理时imgsz和训练时不一致YOLOv8 会自动 resize但如果你手动改过 letterbox 参数就会出问题。4.3 现象训练到一半 loss 突然变成 NaNBN 层崩溃的典型表现。原因可能是某张图里有一个宽高极小的标注框比如 1 像素宽归一化后接近 0反向传播时梯度爆炸。解决办法是在转换脚本里加一个最小宽高过滤比如if w 0.001 or h 0.001: continue。另外检查学习率lr00.01在某些数据集上确实会炸降到 0.001 试试。4.4 现象混淆矩阵里电线杆被大量分到背景漏检严重。三个排查方向一是标注框太紧只框了杆子的一部分模型学到的特征不完整二是图片里电线杆太小640 分辨率下不足 10 个像素需要提高imgsz到 1024 或 1280三是正样本太少2127 张图里如果只有 500 张有电线杆其余都是背景模型会偏向预测背景。解决方法是过采样有目标的图片或者用 focal loss 加大难样本权重。4.5 现象训练速度极慢GPU 利用率不到 30%大概率是数据加载成了瓶颈。检查workers参数默认是 8如果 CPU 核心数少就降到 4。另外确认图片没有存在机械硬盘上2127 张图如果每张都是 4K 分辨率IO 会成为瓶颈。可以先把图片统一缩放到 1280 宽再训速度会快很多。还有一个容易忽略的点如果cacheTrue没开每个 epoch 都要重新读图开了之后第一次读入内存后续 epoch 直接从内存取。5. 从 baseline 到可用模型提升电线杆检测精度的三个进阶技巧5.1 用 SAHI 切片推理抓小目标电线杆在整张图里往往只占很小一块区域尤其是航拍或远景巡检图。直接把整图缩到 640 喂给模型杆子可能只剩几个像素。SAHISlicing Aided Hyper Inference的思路是把大图切成有重叠的小块每块单独推理最后把结果合并。对电线杆这种细长目标切片高度可以设大一点比如 512×1024保证杆子的完整性。from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction detection_model AutoDetectionModel.from_pretrained( model_typeyolov8, model_pathruns/pole_det/baseline/weights/best.pt, confidence_threshold0.3, devicecuda:0 ) result get_sliced_prediction( test_image.jpg, detection_model, slice_height1024, slice_width1024, overlap_height_ratio0.2, overlap_width_ratio0.2 ) result.export_visuals(export_dirsahi_output/)overlap_height_ratio0.2表示相邻切片有 20% 的重叠区域防止杆子在切片边界被切断。切片推理的代价是速度慢一张 4K 图可能要跑十几秒但召回率能提升 10~15 个百分点。适合对精度要求高、对速度不敏感的巡检场景。5.2 针对电线杆的锚框调整与数据增强YOLOv8 是无锚框的但如果你用的是 YOLOv5 或 Faster R-CNN锚框尺寸需要重新聚类。电线杆的宽高比通常在 1:5 到 1:15 之间默认锚框通常是 1:1、1:2、2:1 这类完全不匹配。用 k-means 对训练集的标注框做聚类得到适合电线杆的锚框尺寸。数据增强方面Mosaic 和 MixUp 对电线杆检测有帮助但要注意Mosaic 会把四张图拼在一起如果拼完之后电线杆被截断反而引入噪声。建议把mosaic的概率从默认的 1.0 降到 0.5同时开启copy_paste把电线杆实例复制粘贴到其他背景上增加正样本多样性。5.3 用验证集错误分析反推标注问题训完一个 baseline 之后别急着调参。先把验证集里所有漏检和误检的图挑出来按错误类型分类是标注框太松是杆子被遮挡是背景里有类似杆子的物体比如路灯、旗杆被误检我一般会写一个脚本把 FP 和 FN 的图分别存到两个文件夹然后花半小时一张张看。这个习惯帮我发现过一个隐蔽问题数据集里有一部分电线杆的标注框只框了杆身没框顶部的绝缘子导致模型学到的特征不完整遇到带绝缘子的杆子就漏检。重新标注这批图之后mAP 直接涨了 6 个点。标注质量决定模型上限调参只能逼近这个上限永远突破不了。希望帮到你。本文还有配套的精品资源点击获取
返回列表