ARTICLE DETAIL

资讯详情

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

输电线路巡检YOLO实战:3334张带标签数据集从XML转格式到部署

输电线路巡检YOLO实战:3334张带标签数据集从XML转格式到部署 简介面向输电线路智能巡检场景的YOLO目标检测数据集适合计算机视觉入门学习者、电力行业算法工程师及相关科研人员主要解决输电线路关键部件自动识别与状态监测问题。压缩包内共计2000个XML标注文件压缩包大小约为366.78MB每个XML文件都包含图像中目标的边界框坐标、类别标签以及可扩展的属性描述信息能够直接用于YOLOv5、YOLOv8等主流模型的训练与验证也可通过脚本快速转换为VOC或COCO标注格式。数据集涵盖导线、绝缘子、铁塔、间隔棒等典型部件并包含不同光照、角度和背景环境下的标注样本有助于提升检测算法在复杂巡检场景下的鲁棒性。目前已有508人学习下载该数据能够帮助读者完整体验从数据整理、标注解析到模型训练评估的流程也可用于绝缘子破损、线路异物等异常检测课题的算法研究为电力巡检自动化项目提供有效的数据支撑。1. 输电线路巡检跑YOLO3334张带标签图像数据集到底能做什么做输电线路巡检的人最难的不是飞无人机而是飞完回来面对几百G照片。靠人眼在显示器上找绝缘子破损、导线断股一上午看两三百张就花了。把 yolo算法 用在这类场景里等于把人工看图换成模型自动定位这套 输电线路数据集 包含3334张图像每张都带xml标签标注框直接指向电线、绝缘子、铁塔这些关键部件。对刚开始做电力视觉落地的工程师来说它的价值在于省掉了最累的标注环节拿到手可以直接做格式转换、训练和验证而不是花一个月请人画框。适合以下三类人准备用YOLO做电网巡检的算法工程师、给无人机或在线监测设备配视觉能力的嵌入式开发者以及正在做毕业设计但拿不到真实样本的学生。下面我按自己拆数据集的习惯从标注格式、转换脚本、训练排错到部署落地完整走一遍。2. 先读懂这批数据XML标注格式、文件命名规律和类别分布压缩包拿到手先别急着开训练很多人在这一步就开始翻车。数据集的标注形式决定了后续转YOLO格式时脚本怎么写也决定了哪些坑会在训练中爆出来。我习惯把“读懂数据”拆成三步确认文件完整、解析单个XML看字段、统计类别分布。2.1 解压核对数量文件名规律与完整性检查zip 包解压之后先确认两件事文件数量是不是真的到了3334以及XML和图像是否一一对应。常见做法是先列包内容再统计解压结果用两个命令就能做完。# 先不急着解压列一下 zip 包里到底有什么 unzip -l yolo算法-输电线路数据集-3334张图像带标签-.zip | head -20 # 解压到 raw 目录保留原始结构 unzip -q yolo算法-输电线路数据集-3334张图像带标签-.zip -d transmission_line/raw # 统计 xml 和 jpg/png 各自数量核对是否与描述一致 echo xml文件数$(find transmission_line/raw -name *.xml | wc -l) echo 图像文件数$(find transmission_line/raw -name *.jpg -o -name *.png | wc -l)unzip -l只列内容不解压适合先确认内部是散图还是套目录避免解出来一堆乱码文件名。-d指定解压目标目录比解压到当前目录再手动挪要干净。从文件名看img_0793_3236.xml这种带两组数字后缀的命名大概率是“拍摄点编号 帧序号”同类图像之间场景相关性强这一条后面划分训练集和验证集时要用上。如果解压中途报错或者数量对不上先检查是不是下载不完整重新下载一次比硬着头皮用残缺数据训练省时间。2.2 解析XML标注结构object节点里的name和bndbox才是关键这种数据集的标签通常是Pascal VOC格式外层有filename、size、object等多个节点object节点内部再套name和bndbox。真正决定YOLO标签的是name类别名和bndbox四个坐标。先用脚本把一个XML完整读出来结构就清楚了。import xml.etree.ElementTree as ET import glob # 挑一个样例XML解析并打印关键字段 xml_file transmission_line/raw/img_0793_3236.xml tree ET.parse(xml_file) root tree.getroot() print(图像文件名:, root.find(filename).text) print(图像尺寸:, root.find(size).find(width).text, x, root.find(size).find(height).text) # 遍历所有标注目标 for obj in root.findall(object): name obj.find(name).text bndbox obj.find(bndbox) xmin int(bndbox.find(xmin).text) ymin int(bndbox.find(ymin).text) xmax int(bndbox.find(xmax).text) ymax int(bndbox.find(ymax).text) print(f类别: {name}, 坐标: ({xmin},{ymin})-({xmax},{ymax}))ET.parse把XML转成树结构find是按节点路径取值的方法findall(object)返回图像里所有标注目标。一张图可能同时有多个目标每个object代表一个标注框。注意bndbox里是像素坐标原点在图像左上角x向右增大、y向下增大这个坐标系在后面转YOLO标签时要除以图像宽高归一化我见过有人忘记转浮点导致所有目标位置错乱的那个错非常隐蔽。2.3 统计类别分布提前确认类别不平衡风险读完一个XML只能算认识格式还要扫一遍全部3334个文件统计每个类别出现了多少次。类别不平衡在输电线路场景里特别常见绝缘子样本可能占七成耐张线夹只有零星几个。模型会把大头学得很好对稀缺类直接摆烂。import xml.etree.ElementTree as ET import glob from collections import Counter counter Counter() # 遍历所有XML统计每个类别的目标数量 for xml_path in glob.glob(transmission_line/raw/*.xml): tree ET.parse(xml_path) root tree.getroot() for obj in root.findall(object): counter[obj.find(name).text] 1 # 按数量从大到小打印 for cls, cnt in counter.most_common(): print(f{cls}: {cnt})Counter是Python内置的计数工具most_common()直接按数量排序输出。跑完这步你手里会有一张类别分布表。常见的情形是绝缘子、导线这类主部件样本充足某些金具或缺陷类别很少。如果某一类不足100个后面训练时得考虑针对性增强或者先只做主部件的二分类检测别指望一次把所有类别都训到满意。下表是我在类似数据集上看到过的情况供参考类别样本量对训练的影响绝缘子充足好训练mAP通常最高导线中等长条目标需注意标注框宽高比铁塔/金具稀缺容易欠拟合必须加增强缺陷类极少建议先合并到主类或单独用切图训练3. 把XML转成YOLO训练格式归一化坐标与转换脚本读懂数据之后进入实操YOLO不认XML只认txt标签。每张图像对应一个同名txt每行是“类别id cx cy w h”前两个是归一化中心点坐标后两个是归一化宽高。这一步写不好后面训练出来的模型就是玄学。3.1 划分训练集和验证集按拍摄点切分比随机切更可靠划分数据集看起来简单直接random.shuffle分百分之八十训练、百分之二十验证就行但输电线路图像之间相关性很强。前面提到文件名里带拍摄点和帧号同一个拍摄点的画面背景高度相似随机切分会让同源画面同时出现在训练和验证里验证指标虚高部署到新线路时才暴露出来。import os import random import shutil raw_dir transmission_line/raw img_dir transmission_line/images val_dir transmission_line/val_images # 收集图像文件挑出jpg或png imgs [f for f in os.listdir(raw_dir) if f.endswith((.jpg, .png))] # 按拍摄点前缀分组前缀取前两段数字 groups {} for img in imgs: prefix _.join(img.split(_)[:2]) groups.setdefault(prefix, []).append(img) # 每个组内按比例抽到验证集 random.seed(42) val_ratio 0.2 val_imgs [] for prefix, files in groups.items(): val_n max(1, int(len(files) * val_ratio)) val_imgs.extend(random.sample(files, val_n)) os.makedirs(img_dir, exist_okTrue) os.makedirs(val_dir, exist_okTrue) for img in imgs: dst_dir val_dir if img in val_imgs else img_dir shutil.copy(os.path.join(raw_dir, img), os.path.join(dst_dir, img))代码按文件名前缀分组的思路是前缀代表同一个拍摄点/采集批次验证集从每个组内按比例抽避免整个拍摄点的图像全跑进训练集或验证集。random.seed(42)固定随机种子保证复现。如果文件命名规律不同把split(_)[:2]改成你确认的前缀切分方式即可。还需要把同名XML也拷贝一份到对应目录用同名规则匹配就行脚本里可加一个以.xml替换后缀的拷贝步骤。3.2 核心转换脚本Pascal VOC到YOLO的归一化公式把XML转成YOLO txt标签核心就是归一化公式。设W 图像宽度、H 图像高度标注框像素坐标为xmin/ymin/xmax/ymax则中心点xcx ((xmin xmax) / 2) / W中心点ycy ((ymin ymax) / 2) / H框宽w (xmax - xmin) / W框高h (ymax - ymin) / H四个值都必须在0到1之间。我写转换脚本时习惯加一个坐标合法性检查框坐标小于0或大于图像尺寸的直接打印警告而不是静默跳过。import xml.etree.ElementTree as ET import os # 类别映射表按你的类别统计结果修改 CLASS_NAMES [insulator, conductor, tower, fitting] def voc_to_yolo(xml_path, img_w, img_h, out_path): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): name obj.find(name).text if name not in CLASS_NAMES: print(f未知类别: {name}, 文件: {xml_path}) continue cls_id CLASS_NAMES.index(name) bb obj.find(bndbox) xmin float(bb.find(xmin).text) ymin float(bb.find(ymin).text) xmax float(bb.find(xmax).text) ymax float(bb.find(ymax).text) # 合法性检查坐标不能出界 if xmin 0 or ymin 0 or xmax img_w or ymax img_h: print(f坐标越界: {xml_path} {name}) # 归一化注意用浮点除法 cx ((xmin xmax) / 2) / img_w cy ((ymin ymax) / 2) / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(out_path, w) as f: f.write(\n.join(lines)) # 示例批量转换一个目录 xml_dir transmission_line/raw label_dir transmission_line/labels os.makedirs(label_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue xml_path os.path.join(xml_dir, xml_file) # 图像宽高从XML的size节点读取 root ET.parse(xml_path).getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) out_name xml_file.replace(.xml, .txt) voc_to_yolo(xml_path, w, h, os.path.join(label_dir, out_name))CLASS_NAMES的顺序就是最终训练时的类别id顺序必须和训练配置里的nc数量保持一致。.index(name)把类别名转成整数id。{:.6f}保留六位小数足够YOLO使用也避免输出过长。图像宽高从XML的size节点读不要自己写死因为数据集中可能混有不同分辨率的图像。参数上最需要改的是CLASS_NAMES请用上一节类别统计的实际结果替换。如果你发现某个类别名有大小写或前后空格不一致先统一成一份干净清单再转否则会出现“一个类被拆成两个id”的隐形错误。3.3 可视化抽检用OpenCV画框看一眼避免训练完才后悔转换脚本写完我强烈建议先抽三十张图把归一化坐标还原成像素坐标并画框保存肉眼确认一遍。这个习惯帮我省过不止一次返工。坐标翻转、类别错位这类问题在可视化下暴露得很快。import cv2 # 从YOLO txt读标签并画框txt行格式: cls_id cx cy w h def draw_yolo_box(img_path, txt_path, out_path, class_names): img cv2.imread(img_path) h_img, w_img img.shape[:2] with open(txt_path) as f: for line in f.readlines(): parts line.strip().split() cls_id int(parts[0]) cx, cy, w, h map(float, parts[1:]) # 反归一化到像素坐标并转成左上角/右下角 x1 int((cx - w / 2) * w_img) y1 int((cy - h / 2) * h_img) x2 int((cx w / 2) * w_img) y2 int((cy h / 2) * h_img) # 越界区域裁剪到图像内避免画框超出画布 x1 max(0, x1); y1 max(0, y1) x2 min(w_img, x2); y2 min(h_img, y2) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, class_names[cls_id], (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) cv2.imwrite(out_path, img) # 抽批量检查 import glob for txt_path in glob.glob(transmission_line/labels/*.txt)[:30]: img_path txt_path.replace(.txt, .jpg) out_path check/ os.path.basename(txt_path).replace(.txt, .jpg) draw_yolo_box(img_path, txt_path, out_path, CLASS_NAMES)反归一化公式就是转换的逆运算中心点减去半宽得到x1加上半宽得到x2。cv2.rectangle需要的是整数坐标和左上角/右下角坐标所以我做了取整和越界裁剪。如果画出来的框明显偏在图像角落、大小异常或者类别名和实物对不上回去查转换脚本和类别映射表不要带着错误标签开训练。4. 训练与排查超参设置和五个高频翻车现场数据格式对了训练阶段反而简单。但训练不等于把命令敲完然后睡觉参数怎么设、指标怎么看、报错了怎么定位这节把最常见的经验和坑一起说掉。4.1 训练参数怎么定从YOLOv8n起步分辨率640比1280更划算3334张图像属于中小规模数据集我一般先用YOLOv8n或YOLOv5s这类轻量模型跑通流程再根据结果决定要不要换大模型。起步参数可以用下面的配置参数推荐值说明imgsz640大部分目标检测默认尺寸显存和速度均衡batch1624GB显存够用显存小降到8epochs1203334张图像没必要一上来训300轮lr00.01默认学习率不要乱调optimizerAdamW或SGD小数据集用SGD更稳AdamW收敛快但容易过拟合小类命令行示例yolo detect train datatransmission_line.yaml modelyolov8n.pt imgsz640 batch16 epochs120 lr00.01transmission_line.yaml里指定train和val的图片目录、类别名列表和nc数量。设成640的好处是显存占用小、训练速度快如果最终漏检集中在很小的目标再试1280分辨率但训练时间接近翻倍。先跑通再调优是我处理这类数据集的一贯顺序。4.2 怎么评估“能用了”mAP50、mAP50-95和PR曲线的实际含义训练完之后看测试集指标YOLO主输出会打印一堆指标重点看两个mAP50和mAP50-95。mAP50是IoU阈值0.5下的平均精度均值数值高说明目标大致位置找得准mAP50-95是0.5到0.95每隔0.05取一次IoU再取平均这个值越高说明框的贴合程度越好。对巡检场景我会优先保mAP50因为判断“有没有问题”比“框有多正”更关键但框太歪也会影响后续缺陷分析。再看PR曲线横轴召回率、纵轴精确率。巡检场景里漏检的代价比误报高——漏一个损坏绝缘子可能造成停电事故多报一个顶多让运维人员白跑一趟。所以调阈值时我一般偏向高召回宁可多框几个也不要漏。4.3 高频翻车现场与排查记录以下踩坑记录按“现象 → 原因 → 解决”写都是同场景下常见的坑。现象loss正常下降但mAP一直为零。原因标签txt被写到了和图片不同的目录或者data.yaml里train路径指向了空目录模型训练时读不到标注等于在训无监督。解决训练前先手动打印一张标签txt确认内容非空再用yolo detect train前跑一次yolo detect val快速验证数据链路。现象绝缘子这类主类召回率很高铁塔/金具完全检不出。原因类别不平衡稀缺类在3334张里可能只有几十个实例模型把它当噪声忽略。解决对稀缺类做mosaic和copy-paste增强或者把稀缺类图像单独多复制几份参与训练再不行就降低存在感先只训主类。现象验证集mAP不错换到无人机实拍画面漏检严重。原因验证集和训练集来自同一批拍摄点背景相似现场新的光照、角度和背景导致分布偏移。解决数据增强里把亮度、对比度、随机旋转幅度加大尤其加hsv增强和随机透视模拟不同姿态拍摄。现象训练时报CUDA out of memory。原因batch设太大或者开了过多worker显存被吃满。解决batch降到8或4imgsz降到640以下开启amp混合精度训练。现象zip解压后jpg损坏打不开训练时图像加载报错。原因下载中断或压缩包本身不完整个别图像损坏。解决解压后用PIL.Image.open全部扫一遍损坏图直接剔掉并同步删对应xml避免训练中断。5. 让它落地导出ONNX做实时推理无人机部署的三个提醒训练完拿到best.pt只是开始真正落地到无人机或在线监测设备还要把模型导出成ONNX或TensorRT格式然后接摄像头或视频流。导出和验证做了部署才算闭环。5.1 导出ONNX格式并跑一次推理验证yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640导出后可以在Python里用onnxruntime做个简单推理串联验证import onnxruntime as ort import cv2 import numpy as np sess ort.InferenceSession(best.onnx) img cv2.imread(test.jpg) img cv2.resize(img, (640, 640)) blob np.expand_dims(img.astype(np.float32) / 255.0, axis0).transpose(0, 3, 1, 2) # ONNX输出shape通常是 (1, num_anchors, 4nc)按需要后处理 outputs sess.run(None, {sess.get_inputs()[0].name: blob}) print(输出shape:, outputs[0].shape)blob转了NCHW格式并归一化到0~1这是ONNX模型的标准输入要求。输出shape里5 nc的前五个值对应中心和宽高四个坐标加一个目标置信度。5.2 部署中的三个经验提醒第一机载设备优先转TensorRT而不是跑ONNX。无人机上Jetson Orin这类设备TensorRT FP16推理速度比ONNX runtime快一半以上。第二输入分辨率别小于训练尺寸实测把640训练模型压到416推理绝缘子这类小目标明显掉点。第三白天强烈反光下漏检率高这是数据集里光照均匀和现场逆光差异导致的先加大hsv增强重训一版比在推理端调阈值更有用。从那以后我每次拿到新数据集都强制走一遍“解压清点→解析XML→统计类别→划分→转换→可视化抽检”的流程六个步骤做完才允许自己开训练。这套流程多花不到半小时但能拦住后面一整周的返工。希望帮到你。本文还有配套的精品资源点击获取
返回列表