输电线路数据集训练YOLO:从3334张图到可靠模型的避坑指南

发布时间:2026/10/5 4:59:23
输电线路数据集训练YOLO:从3334张图到可靠模型的避坑指南
简介面向电力巡检与计算机视觉学习者的YOLO输电线路数据集聚焦绝缘子、铁塔、导线等关键部件检测可用于训练实时目标检测模型辅助线路异常监测与故障预警。压缩包共2000个XML标注文件整体约366.78MB每个XML文件记录目标的边界框坐标与类别信息可直接转换为YOLO格式使用省去人工标注环节。已有511人加入学习下载适合电力行业算法研发人员、科研学生及目标检测爱好者作为训练与验证数据。该数据集在YOLO框架下能帮助快速构建输电线路巡检模型提升对设备老化、部件损坏等场景的识别能力同时也可用于不同光照与复杂背景下的算法效果对比为实际部署提供可靠支撑。1. 输电线路数据集到手别急着训3334张标注图离“能训练YOLO”还差四步这份“yolo算法-输电线路数据集-3334张图像带标签-.zip”文件名写得像开箱即用解压、喂给YOLO、跑训练一条龙。但真落到输电线路巡检这个场景里3334张标注图只是个起点。这种包大多是无人机巡检拍回来的绝缘子、防震锤、线夹和导线标注格式可能是VOC的xml、COCO的json或YOLO的txt包内还可能暗藏文件名错位、空标签、坏图。拿到手直接训练十有八九在DataLoader阶段就翻车。适合谁做电力巡检视觉检测的算法工程师、做目标检测课题的研究生以及想拿公开数据集评测YOLO版本的开发者。这篇按我平时处理同类数据集的路径从拆包、验标签、转格式到跑通第一条val命令一步步说清楚。2. 拆包看结构三条命令判断标签格式VOC、COCO还是YOLO一眼分清2.1 解压前的完整性检查先看列表、再测损坏、最后对账文件数很多人拿到zip第一件事就是双击解压我建议先执行两条命令避免解压到一半报“CRC错误”或者解出来缺文件。zip这层协议本身不难难的是包内文件一致性图片和标签的文件名能否对上、两个目录数量是否匹配。# 只列压缩包内容不实际解压前30行足够看出目录结构 unzip -l 输电线路数据集.zip | head -30 # 逐个文件做CRC校验损坏文件会在这里暴露 unzip -T 输电线路数据集.zip # 解压到独立目录避免和现有工程文件混在一起 unzip -q 输电线路数据集.zip -d transmission_line_dataunzip -l输出里的文件路径能直接告诉你目录层级是images/和labels/平级还是所有图片和标注混在一起。-T比-t更严格逐文件做checksum测试源文件在打包前如果已经损坏这一步会明确报错。解压后立刻做一次对账cd transmission_line_data echo 图片数量: $(find . -name *.jpg | wc -l) echo xml数量: $(find . -name *.xml | wc -l) echo txt数量: $(find . -name *.txt | wc -l) echo json数量: $(find . -name *.json | wc -l)标题说3334张图带标签如果图片数和xml/txt数对不上先别怀疑数据集质量先查是不是标签文件用了*.JPG大写后缀或者标签被放在子目录里。注意macOS压缩的zip经常带__MACOSX目录和._开头的元数据文件这些文件不影响校验但会干扰后面find的统计建议解压后直接删掉。2.2 标签格式三选一按后缀和首行内容快速定性输电线路数据集在市面上流通标注格式基本逃不出三种VOC的xml、COCO的json、YOLO的txt。判断方法很简单不用写代码看后缀和文件头就行。# 看标签目录下都有什么后缀 ls -la labels/ | head -10 # 如果是txt看第一个文件的前3行 head -3 labels/$(ls labels/ | head -1)VOC格式每个图片对应一个xml关键字段是filename、size里的width/height以及bndbox里的xmin/ymin/xmax/ymax是绝对像素坐标。COCO格式整个数据集只有一两个json文件annotations数组里每条记录包含image_id、category_id和bbox四个值是x、y、width、height。YOLO格式每个图片对应一个txt每行5个数字类别id cx cy w h其中cx、cy是中心点坐标w、h是宽高全部归一化到0到1。这三种格式对应完全不同的处理路径。如果是YOLO的txt打开一个文件看看坐标是否在0到1之间、类别id是否连续从0开始通常能直接用。如果是xml或json就得走第三章的转换脚本。还有一种少见情况txt里第一行是filename,xmin,ymin,xmax,ymax这种带表头的CSV那是某些标注平台导出格式需要单独解析。2.3 类别分布粗统计先看类别不平衡再决定训练策略输电线路目标检测的典型类别包括绝缘子、防震锤、线夹、导线、塔基缺陷类还分破损、缺失、闪络等。这类数据集有个通病正常部件样本占比极高缺陷样本可能只有几十张。不做统计直接开训模型会对少样本类别完全无视——loss被大类淹没val的mAP看起来还行实际部署时缺陷一个都检不出来。# 对所有YOLO格式txt做类别统计第一列就是类别id awk {print $1} labels/*.txt | sort | uniq -c | sort -rn # 如果是VOC格式统计xml里的object name grep -h name labels/*.xml | sort | uniq -c | sort -rn在输电线路场景里类别不平衡不只是“少”而是“极度少”。3334张图里绝缘子标注可能上万而“绝缘子破损”这个缺陷类别可能只有一两百个实例。统计结果出来后如果少数类实例数小于500后面训练必须配cls_loss权重或者做过采样否则这个类基本等于废的。这一步的结论会直接影响第四章的data.yaml配置别跳过。3. 统一成YOLO格式VOC/COCO转换脚本与三个必查项3.1 VOC转YOLO脚本框架xml解析、坐标归一化、类别映射顺序多数输电线路数据集的原始标注来自LabelImg或某平台导出格式是VOC xml。YOLO训练器不认识xml必须先转成txt。下面是我常用的转换脚本按实际使用场景简化过核心就三步读xml里的size拿图像宽高、读bndbox拿绝对坐标、换算成中心点加宽高的归一化值。import xml.etree.ElementTree as ET from pathlib import Path # 类别映射表顺序就是YOLO的class id一个字都不能乱 classes [insulator, damper, clamp, wire, tower] def voc2yolo(xml_path: Path, out_dir: Path): tree ET.parse(xml_path) root tree.getroot() # 图片原始宽高来自xml的size字段 img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in classes: print(f{xml_path.name}: 跳过未定义类别 {cls_name}) continue 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) # 中心点坐标 宽高全部除以原图宽高做归一化 cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h # 越界检查VOC里偶尔出现xmax比原图宽还大的脏标注 if cx 1.0 or cy 1.0 or w 1.0 or h 1.0: print(f{xml_path.name}: 坐标越界 {cls_name} {cx:.3f} {cy:.3f} {w:.3f} {h:.3f}) continue cls_id classes.index(cls_name) lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) if lines: out_path out_dir / (xml_path.stem .txt) out_path.write_text(\n.join(lines) \n) print(f{xml_path.name}: 转换出 {len(lines)} 个目标)这段脚本有两个关键点。第一classes列表的顺序就是YOLO的类别编号必须和后面data.yaml里的names完全一致。常见翻车是把类别按字母序排结果xml里的name顺序和列表对不上虽然转换没报错但训练出来的模型所有框的类别都是错的。第二脚本末尾的越界检查不是多余的。无人机航拍输电线路时绝缘子经常贴着图像边缘标注人员稍微多点一下xmax就超出宽度了。这类脏数据直接转进YOLO轻则训练警告重则loss变成nan。3.2 COCO转YOLO脚本框架按image_id聚合annotation再做换算有些平台导出的是COCO json。COCO的核心结构是三个数组images、annotations、categories。转换思路和VOC不同因为json是一个大文件得先按image_id把所有标注聚合到对应图片下再从categories里拿类别名。import json from pathlib import Path def coco2yolo(ann_file: Path, img_dir: Path, out_dir: Path): with open(ann_file, r, encodingutf-8) as f: coco json.load(f) # COCO的category_id不一定是连续的先把id映射成连续编号 cat_id_map {} for idx, cat in enumerate(coco[categories]): cat_id_map[cat[id]] idx # 注意这里idx的顺序取决于json里categories数组的顺序 # 如果想自定义顺序按名字排序后再建映射 # image_id - 文件名索引 img_id_to_name {img[id]: img[file_name] for img in coco[images]} # 按image_id聚合所有annotations anns_by_img {} for ann in coco[annotations]: img_id ann[image_id] anns_by_img.setdefault(img_id, []).append(ann) for img_id, anns in anns_by_img.items(): file_name img_id_to_name[img_id] img_path img_dir / file_name if not img_path.exists(): print(f警告: 图片不存在 {img_path}) continue # 读取真实宽高避免拿json里的宽高代替 lines [] for ann in anns: cls_id cat_id_map[ann[category_id]] x, y, w, h ann[bbox] # COCO的bbox是[x, y, width, height] # COCO的坐标是绝对像素值转归一化时要用真实图片宽高 cx (x w / 2.0) / img_w cy (y h / 2.0) / img_h nw w / img_w nh h / img_h if cx 1.0 or cy 1.0 or nw 1.0 or nh 1.0: print(f{file_name}: 越界bbox {ann[bbox]}) continue lines.append(f{cls_id} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) if lines: out_path out_dir / (Path(file_name).stem .txt) out_path.write_text(\n.join(lines) \n)COCO转YOLO最容易踩的坑是直接用json里images字段的width/height做归一化不重新读图片。某些标注工具在json里写的尺寸和实际图片不一致一旦不一致整个数据集的所有框都偏。我在脚本里坚持读取实际图片尺寸。另一个坑是COCO的category_id是从1开始的而YOLO从0开始上面代码用enumerate重新编号就是为了处理这个错位。如果你发现转换后的txt第一列有数字大于nc-1说明映射写错了。3.3 转换后三查文件名对得上、txt非空、坐标不出界转换脚本跑完不等于数据就能用。我见过太多人跑完脚本直接开训然后浪费一下午排查“为什么类别对不上”。转换后必须做三项校验都是命令行就能解决的。# 第一查每张jpg必须有同名txt反向也要对得上 for img in images/*.jpg; do base$(basename $img .jpg) if [ ! -f labels/$base.txt ]; then echo 缺标签: $img fi done # 第二查空标签文件必须处理掉或回补 find labels -name *.txt -size 0 | head -20 echo 空txt数量: $(find labels -name *.txt -size 0 | wc -l) # 第三查坐标越界任何w或h大于1都说明归一化漏了 awk {if($31 || $41 || $51 || $61) print FILENAME: $0} labels/*.txt | head -20第三查的awk条件里$3是cx、$4是cy、$5是w、$6是h。实际项目里我发现坐标越界最常见的原因不是转换脚本写错而是源xml里本身就存在xmax大于图片宽度的坏标注。这种情况在输电线路数据集中特别多因为绝缘子串经常延伸到图像边缘标注员画框时手一抖就出去了。处理方式不是删除而是裁剪到边界值再归一化毕竟一个框越界1%还是能用的删掉反而亏样本。提示以上三条命令全部通过后再进入数据划分。这一步数据是脏的后面每一步都会放大问题。4. 划分训练集并配置data.yaml同塔图片泄漏是val虚高的头号原因4.1 按塔位批次划分random.shuffle的指标好看但上线就崩输电线路数据集有一个和通用目标检测数据集截然不同的特性同一基塔的航拍图背景高度重复。无人机围绕一基塔拍摄可能连续拍下几十张不同角度的照片这些照片共享同一套背景、同一个绝缘子。如果直接用random.shuffle划分train和val同一基塔的图片会被拆散放进两边模型在val上看到的“新图片”其实和训练数据高度重合mAP会虚高到不真实。更稳妥的做法是按文件名里的塔位编号分组。输电线路巡检图片的命名通常带塔位信息比如T123_001.jpg、T123_002.jpg代表同一基塔的不同角度。划分时以塔位为单位整个塔位的图片只能进train或val不能拆散。这里给出一个按前缀分组的划分思路import random from pathlib import Path import shutil img_dir Path(images) train_dir Path(split/train/images) val_dir Path(split/val/images) train_dir.mkdir(parentsTrue, exist_okTrue) val_dir.mkdir(parentsTrue, exist_okTrue) # 提取每个文件名的塔位前缀比如 T123_001.jpg - T123 img_paths list(img_dir.glob(*.jpg)) groups {} for p in img_paths: prefix p.stem.split(_)[0] # 按实际命名规则调整 groups.setdefault(prefix, []).append(p) all_prefixes list(groups.keys()) random.seed(42) random.shuffle(all_prefixes) # 按前缀分组数量切分而不是按图片数量切分 split_idx int(len(all_prefixes) * 0.8) train_prefixes set(all_prefixes[:split_idx]) val_prefixes set(all_prefixes[split_idx:]) for prefix in train_prefixes: for p in groups[prefix]: shutil.copy2(p, train_dir / p.name) label img_dir.parent / labels / (p.stem .txt) if label.exists(): shutil.copy2(label, train_dir.parent / labels / label.name)划分比例建议3334张图按塔位分组后如果是8:2val大约有六七百张够用了。关键指标是塔位不重叠而不是图片数恰好八二分。如果文件名里没有塔位编号退一步按拍摄时间间隔划分也行同一天的巡检任务划进同一集合效果接近。这一步直接决定你后面提交的模型指标是否可信值得认真对待。4.2 data.yaml配置path、train、val、nc、names五个字段别写错划分完目录后写data.yaml是训练前最后一道配置。YOLO的data.yaml结构很简单但五个字段里每个都有坑。# 这个yaml放在数据集根目录下 path: . # 相对路径基准写.代表yaml所在目录 train: split/train/images val: split/val/images nc: 5 # 类别数量必须和names列表长度一致 names: 0: insulator 1: damper 2: clamp 3: wire 4: towerpath字段的坑在于它决定了后面train和val是相对谁解析的。如果yaml放在项目工程目录而数据集在别的路径path要写成数据集根目录的绝对路径。nc必须和names列表长度一致多一个少一个都会在训练启动时直接报错或产生诡异的类别映射。names的索引顺序必须和第三章转换脚本里的classes顺序完全一致这一点怎么强调都不为过。很多新手会问要不要写test字段。3334张图的数据集本来就不大我建议在划分时直接切成train/val/test三份哪怕test只有100张也值得留出来。这样做的好处是最后模型训练完有一个完全没碰过的集合做最终验证避免在val上调参调太久导致过拟合val。4.3 训练参数初设imgsz 640起步、anchor交给自适应机制data.yaml写好后训练命令本身也要设置参数。输电线路目标检测有个显著特点小目标占比极高。绝缘子在4K航拍图里可能只有几十个像素宽防震锤更小。但这不意味着你应该直接把imgsz拉到1280显存和训练时间会成倍增加而且小目标也不是单纯靠增大输入分辨率就能解决的。yolo train modelyolov8s.pt datadata.yaml epochs100 imgsz640 batch16imgsz640是起步值先跑通流程确认loss正常下降、val的mAP不是0再回头试imgsz960或1280。batch取决于显卡显存16G显存跑yolov8s用16没问题8G显存降到8。网上关于yolo算法讲解ppt的教程一大把但真正到这个落地的环节最关键的不是anchor尺寸或者backbone结构而是数据质量。YOLOv8以后anchor已经改成自适应训练时会自动从数据里统计框的尺寸分布不需要手工kmeans聚类。别把时间浪费在调anchor上先把数据划分和标注质量搞定收益高得多。如果类别不平衡严重比如缺陷类只有几百个实例训练命令里要加类别权重yolo train modelyolov8s.pt datadata.yaml epochs100 imgsz640 batch16 \ cls0.7 # 默认0.5提高对少样本类别的惩罚权重cls参数的原理是对分类loss加权类别越少权重越大。但这只是缓解手段不是根治方案。真正有效的是后续对少样本类别做数据增强或采集补样本会把精确率和召回率拉上一个台阶。5. 训练前避坑清单五个翻车点从现象到解法5.1 文件名大小写不一致导致标签匹配率为零现象训练启动时YOLO打印“WARNING: labels not found in image”或训练正常但val的mAP全是0。原因xml里的filename字段写的是IMG_20210305.JPG而实际图片文件叫img_20210305.jpg。Linux文件系统区分大小写Windows上解压出来的数据还好一旦放到Linux服务器训练全部标签匹配失败。解决建立索引时不要读xml或json里的filename字段直接用文件系统的实际文件名作为基准。如果已经转换完执行一个统一的改名脚本把所有图片和标签文件名都转成小写。for f in images/*.JPG; do mv $f ${f%.JPG}.jpg base$(basename $f .JPG) if [ -f labels/$base.txt ]; then mv labels/$base.txt labels/$(basename $base).txt fi done5.2 空标签文件导致训练直接崩溃现象训练几分钟后报错AssertionError: labels not in dataset或者某个epoch直接中断。原因转换脚本遇到源xml里没有object的图片生成了0字节txt。YOLO训练器读到空标签文件会认为数据异常。解决先统计空文件数量再决定是删除对应图片还是回补标注。对于输电线路数据集纯背景图没有绝缘子、没有塔材其实是有价值的负样本但YOLO的标签格式不支持空txt和图片共存。我的做法是空标签对应的图片单独放一个no_objects目录用它们做训练时的负样本增强而不是直接混进数据集。5.3 类别映射顺序错位模型把所有绝缘子都识别成防震锤现象训练loss正常下降val的mAP整体看也还行但单独看每个类别的mAP发现全乱了。原因第三章转换脚本里的classes列表顺序和data.yaml里的names顺序不一致。VOC xml里的name是insulator转换时classes.index(insulator)得到0但data.yaml里0对应的却是damper模型学到的类别编号就全歪了。这类错误最难排查因为训练过程完全不报错。解决转换脚本的classes列表和data.yaml的names必须从同一个配置源生成不要各写一份。最稳的做法是转换脚本从data.yaml里读names保证单一真源顺序永远一致。5.4 压缩包里的坏图和隐藏文件让cv2读取崩溃现象训练跑到一半报cv2.error: can not open or read file或者某个epoch的loss突然变成nan。原因解压后的目录里混入了macOS的._开头元数据文件、缩略图目录Thumbs.db甚至有几张jpg后缀但实际是损坏文件。这类文件占位不多但YOLO遍历目录时会把它们当成有效图片读入。解决训练前用脚本全量检查图片可读性顺便把隐藏目录清掉。# 删除macOS和Windows的隐藏目录 rm -rf __MACOSX find . -name ._* -delete find . -name Thumbs.db -delete再用Python做一次图片完整性检查from pathlib import Path from PIL import Image bad_images [] for p in Path(images).glob(*.jpg): try: img Image.open(p) img.load() except Exception: bad_images.append(p) print(f坏图数量: {len(bad_images)}) for p in bad_images: print(p)坏图直接删除或移出数据集目录因为后续任何一步都可能因为这张图导致训练中断。5.5 归一化坐标和绝对坐标混用一个txt里w大于1现象训练不报错但val的mAP在某一类上异常低打开标签可视化时框全画偏了。原因数据集不是单一来源部分图片的标签是VOC转过来的归一化坐标另一部分是有人手工用标注工具导出时勾了“绝对坐标”选项像素值直接写入txt。两类数据合并时没有统一。典型特征是一个txt文件里出现w1.2或者cx0.8这种明显越界的值。解决第三章的awk校验命令在合并前跑一遍把所有异常文件找出来单独重新归一化。处理完再进入训练流程这是唯一靠谱的解法没有捷径。6. 先用一条yolo val命令验证数据集再考虑要不要动模型结构6.1 用yolo val看per-class的mAP50数据质量的第一份体检报告很多人拿到数据集第一步直接train这是成本最高的做法。我的习惯是先用预训练模型在数据集上做一次val不训练只看推理结果。这一步只需要一条命令yolo val modelyolov8s.pt datadata.yaml imgsz640这条命令会用yolov8s在COCO上的预训练权重跑一遍你的数据集输出每类目标的mAP50和mAP50-95。这一步的价值在于预训练模型没在你的数据上训练过它的表现完全取决于你的标签质量和目标可辨识度。如果某一类的mAP50只有个位数先别怀疑模型回查那一类的标签——大概率是框没贴住目标、类别标错或者坐标越界。如果所有类都是0那几乎可以断定是类别映射错位或者标签路径没接对。第一次val建议加plotsTrue参数让YOLO输出混淆矩阵和预测样例图用眼睛直接看哪个类被认成了哪个类。比看数字直观得多。6.2 可视化bbox回查标注质量画完框一眼看出脏数据数值校验能查出物理层面的问题空文件、越界但查不出标注质量问题框偏了、框太大、目标漏标。这一步我会写一个极简的可视化脚本把标签框画在原图上抽几十张图肉眼扫一遍。import cv2 from pathlib import Path images_dir Path(split/train/images) labels_dir Path(split/train/labels) out_dir Path(check_vis) out_dir.mkdir(exist_okTrue) # 颜色表按类别id对应 colors [(0, 255, 0), (0, 0, 255), (255, 0, 0), (0, 255, 255), (255, 255, 0)] for txt_path in list(labels_dir.glob(*.txt))[:50]: # 先抽50张看 img_path images_dir / (txt_path.stem .jpg) if not img_path.exists(): continue img cv2.imread(str(img_path)) h, w img.shape[:2] for line in txt_path.read_text().strip().splitlines(): parts line.split() cls_id int(parts[0]) cx, cy, bw, bh map(float, parts[1:]) # 还原成像素坐标 x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), colors[cls_id], 2) cv2.putText(img, str(cls_id), (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, colors[cls_id], 1) cv2.imwrite(str(out_dir / txt_path.name), img)画完图你会立刻看到哪些框的边框离目标边缘有几十像素的空白。输电线路数据集的绝缘子标注最常见的质量问题是框过大——标注员把绝缘子串两端的金具也框了进来。这种框不算错但会让模型学到的目标范围偏大部署时对遮挡场景的检测能力明显下降。如果抽检50张里超过10张有明显标注问题我的建议是暂时放弃直接训练先花时间修正标签。脏标签训练出来的模型指标再好看也是空中楼阁。6.3 一个延续到现在的习惯先验数据、再训模型、最后调参做这个方案我踩过最大的一次坑是把一个“看着没问题”的输电线路数据集直接训了一版yolov8m训练三天val的mAP50报0.72当时觉得不错。结果拿到现场测试检测结果一塌糊涂查了两天才发现数据划分时没有按塔位分组val里全是训练时见过的同塔背景。从那以后我拿到任何标注数据集第一件事永远是先花两小时把标签和图片对一遍而不是着急写训练代码。后来所有项目都沿用了这个习惯教训就是数据集的坑越早发现修复成本越低。真正上线回查的时候省下来的时间一定是当初那两小时的十倍以上。希望帮到你。本文还有配套的精品资源点击获取