简介面向输电线运维与目标检测算法开发场景的数据集资源覆盖气球、风筝、鸟巢、垃圾四类常见异物共1300张图片的标注数据已按Pascal VOC与YOLO两种格式整理可直接用于YOLO系列等主流检测框架的训练与验证。压缩包共2000个文件以VOC格式XML标注文件和YOLO格式TXT标注文件为主1300个XML、700个TXT包体约831.71MB并附使用前必读等说明文本。数据由人工使用LabelImg以矩形框方式标注总标注框数1319个其中鸟巢框数最多871、垃圾框数最少107约15%的样本经过数据增强处理目录结构清晰便于按类别筛选样例。适合已掌握目标检测基础、需要输电线外破隐患样本进行练手或迁移学习的开发者使用。目前已有967人学习下载。1. 输电线异物检测数据集先说清这1300张图能帮你省下什么无人机巡检拍回来的照片里导线上的风筝、鸟巢、塑料布经常要盯很久才能发现。没有现成数据的话从航拍原图里截目标、打框、标类别一个4类的异物识别数据集至少要磨两周还不算返工清洗的时间。“输电线异物检测数据集VOCYOLO格式1300张4类别.7z”解决的就是这个前置环节1300张真实场景图VOC的XML和YOLO的txt两套标注都在类别固定解压后按目录结构放好就能喂进训练管线。适合正在做电力巡检目标检测、手上没有标注数据、想先把一条链路跑通的工程师。2. 打开.7z先看目录结构VOC和YOLO两套标注到底存了什么拿到这种打包好的数据集我的习惯不是急着解压训练而是先看目录结构再把两类标注文件各打开一个确认坐标系和类别顺序。这一步花不了十分钟但能躲掉后面大半的“训练不起来”。2.1 解压后常见的目录结构images、labels、Annotations带“VOCYOLO格式”的数据包一般会按两套独立目录组织方便使用者直接选一条路走。常见做法是dataset/ ├── classes.txt # 4个类别名一行一个 ├── VOC/ │ ├── JPEGImages/ # 1300张jpg原图文件名与标注一一对应 │ └── Annotations/ # 1300个xml标注VOC格式 └── YOLO/ ├── images/ │ ├── train/ # 训练图按8:2或9:1已划分 │ └── val/ # 验证图 └── labels/ ├── train/ # 训练标注每张图一个txt └── val/ # 验证标注如果你拿到手的包没有预划分train/val只有全量images和labels就需要自己按比例切分。先看classes.txt里面有且只有4行每一行的行号就是YOLO格式里那条标注的class_id。这个顺序决定了后面所有事情先记住它。判断这个数据集能不能直接用的指标也简单原图是否清晰、标注是否贴合目标、4个类别的样本是否都有足够的数量。这三点过关训练才有意义否则后面都是白做。2.2 VOC的XML里到底存了什么object节点与bndboxVOC格式的标注文件是XML一张图对应一个同名xml。节点不算多关键就三个filename原图文件名、size图像宽高、object每个目标一个。object里面又有name类别名、difficult是否难例、bndbox左上和右下像素坐标。一个典型的片段长这样annotation filenameIMG_20231012_104533.jpg/filename size width1920/width height1080/height depth3/depth /size object namekite/name difficult0/difficult bndbox xmin830/xmin ymin240/ymin xmax1015/xmax ymax520/ymax /bndbox /object /annotation注意bndbox是绝对像素坐标xmin/xmax对应图片宽度方向ymin/ymax对应高度方向。如果一张图里有多个异物就会出现多个object节点每个都有自己的name和bndbox。VOC格式的好处是坐标是“人眼可读”的绝对值调试时对着原图看不会晕坏处是换一个分辨率这些坐标就全废了。如果你在标注之后对图片做过resize而没同步更新size字段后面转换出来的框一定是偏的这一点在第5章有对应的踩坑记录。2.3 YOLO的txt里存的是一组归一化坐标YOLO格式的标注是一行一个目标每行5个数字空格分隔0 0.480469 0.351852 0.096354 0.259259从左到右分别是class_id、x_center、y_center、width、height。后四者全部是归一化值也就是相对于图片宽高的比例取值范围是0到1。上面这行表达的是类别0的目标中心点在图片48.05%宽度和35.19%高度处框宽占图片宽度的9.64%框高占图片高度的25.93%。把这个txt当成像素坐标去算是90%的转换脚本bug的来源。归一化坐标的好处是图片从1080P换到4K标注不用改模型内部也是按网格相对位置做预测的。坏处是它完全脱离了对齐信息的校验一个坐标写错光看txt根本发现不了必须画框回原图检查。2.4 两套格式怎么选训练认txt人工复检认XMLYOLO系列训练管线读的就是txt你直接拿VOC的XML喂给ultralytics是喂不进去的。反过来复核某一张有争议的标注或者拿标注工具重新改框XML和jpg的组合比txt直观得多。所以两套都要保留在不同环节用不同格式。对比项VOC格式YOLO格式文件后缀.xml.txt坐标表示bndbox绝对像素中心点宽高的归一化值每个文件的目标数多个object节点多行每行一个目标可读性人眼可读机器友好人眼难读对图像分辨率的依赖依赖换分辨率要改标注不依赖典型标注工具labelImg默认输出转换脚本或ultralytics直接生成还要注意YOLO目录下images和labels的一一对应关系文件名相同后缀不同。这个对应关系是训练管线能跑起来的前提改图片名就同步改txt名这个习惯从第一天就要养成。4个类别的具体命名各包不一样不用猜以包内classes.txt实际内容为准后面训练前把它抄进data.yaml的names列表保持顺序一致。3. 把VOC转成YOLO最小转换脚本与画框校验两个步骤训练管线只认txt你手上如果只有VOC的XML第一件事就是做格式转换。这一步不难但坐标归一化计算和resize坑是主要翻车点。我一般用下面这个最小脚本不依赖任何检测框架只要Python能跑就行。3.1 转换脚本读bndbox、算中心点、写txtimport cv2 import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, img_width, img_height, class_names, out_txt): 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: continue cls_id class_names.index(name) bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) x_center (xmin xmax) / 2 / img_width y_center (ymin ymax) / 2 / img_height box_w (xmax - xmin) / img_width box_h (ymax - ymin) / img_height lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) with open(out_txt, w) as f: f.write(\n.join(lines)) # 示例类别名改成你包内classes.txt的实际内容顺序别动 class_names [kite, bird_nest, plastic, crane] xml_dir VOC/Annotations img_dir VOC/JPEGImages out_dir YOLO/labels os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): xml_path os.path.join(xml_dir, xml_file) img_path os.path.join(img_dir, xml_file.replace(.xml, .jpg)) img cv2.imread(img_path) h, w img.shape[:2] out_txt os.path.join(out_dir, xml_file.replace(.xml, .txt)) voc_to_yolo(xml_path, w, h, class_names, out_txt)逻辑说明一个XML里有几个object就生成几行txt类别名通过class_names列表的下标映射成id四个框坐标先算中心点和宽高再除以图片实际宽高完成归一化。这里关键一点是img_width和img_height用的是cv2读出的实际尺寸而不是XML里size节点的值。xml里的size在图片被改过尺寸后就不可信了用实际尺寸至少能保证框和当前图片是同一坐标系。参数说明class_names必须和classes.txt逐行一致顺序一变所有cls_id全错f-string保留6位小数对训练来说精度足够如果习惯用PIL换成Image.open一样能拿尺寸但cv2读取更快批量几百张图无感。3.2 转换完先画框肉眼过一遍再训练转换脚本跑完不要直接进训练。我用画框脚本做校验把txt的归一化坐标还原成像素坐标画到原图上人工扫一遍框的位置和大小是否贴合异物。import cv2 import os img_dir VOC/JPEGImages label_dir YOLO/labels check_dir check_output os.makedirs(check_dir, exist_okTrue) for txt_file in os.listdir(label_dir): img_name txt_file.replace(.txt, .jpg) img_path os.path.join(img_dir, img_name) if not os.path.exists(img_path): continue img cv2.imread(img_path) h, w img.shape[:2] with open(os.path.join(label_dir, txt_file)) as f: for line in f: cls_id, xc, yc, bw, bh map(float, line.split()) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(os.path.join(check_dir, txt_file.replace(.txt, _check.jpg)), img)逻辑说明归一化坐标乘回图片宽高就还原成像素坐标系上的框。x1/y1是左上角x2/y2是右下角int()取整只影响绘制显示不影响训练精度。把输出的check图随机抽十张看一遍重点看两类问题框有没有明显套偏目标、有没有一张图里出现特别大的框把整张图包住。特别大的框往往是标注时把背景也框进去了这种样本留着会让模型学着去框导线和杆塔本身。这一步只花几分钟但能拦住训练一个下午之后才发现“数据有问题”的返工。1300张图跑一次转换只要几秒抽十张图人工看一遍也只要五分钟。3.3 转换里最容易漏掉的两个边界情况第一个是类别名对不上。XML的name字段写的是kiteclasses.txt里第一行也是kite没问题如果XML里写的是Kite或者flying_kite脚本里name not in class_names会直接跳过这个目标结果就是某个类的目标在txt里消失了而你没有收到任何报错。所以跑完转换之后统计每个txt文件的行数之和和XML里object节点总数比对不一致就说明有目标被静默丢弃。这个比对写成一个循环几行就够不要偷懒。第二个是目标跨出了图片边界。异物检测里常有异物在图片边缘只露出一半的情况XML里的xmax可能比图片宽度还大直接除以width后得到大于1的坐标。YOLO训练时对这类超界坐标会告警或自动裁剪但为保险起见在转换脚本里加一个clip操作把算出来的四个值限制在0到0.999之间避免训练时出现奇怪的loss波动。4. 用YOLOv8训练自己的数据集1300张4类别的划分与三个必调参数数据格式没问题接下来就是把数据集跑进YOLO。这一章按实际调过的流程写覆盖数据划分、data.yaml和训练命令。1300张不算多但做异物检测足够训练一个能用的初版模型重点是别在配置上翻车。4.1 划分train/val别随机切按类别分层抽样先处理没有预划分目录的情况。1300张图直接random.shuffle切9:1看起来随机但鸟巢样本可能大部分被切进验证集训练集里这个类就只有几十张。更稳的做法是按类别做分层划分统计每张图包含哪些类别按“这张图里出现次数最少的类别”做分组再按组划分。import os import random from collections import Counter, defaultdict txt_dir YOLO/labels files os.listdir(txt_dir) # 第一遍统计每个类别的出现次数并记录每张图含哪些类别 cls_count Counter() file_classes {} for txt in files: with open(os.path.join(txt_dir, txt)) as f: classes set() for line in f: parts line.split() if len(parts) 5: classes.add(int(parts[0])) file_classes[txt] classes for c in classes: cls_count[c] 1 # 第二遍把每张图归到“该图包含的类别中出现次数最少”的那一类 groups defaultdict(list) for txt, classes in file_classes.items(): if not classes: continue rare_key min(classes, keylambda c: cls_count[c]) groups[rare_key].append(txt) # 第三遍按组85%/15%划分 train_files, val_files [], [] for key, group in groups.items(): random.shuffle(group) split int(len(group) * 0.85) train_files.extend(group[:split]) val_files.extend(group[split:])逻辑说明一张图如果同时含稀有类和常见类它会被分进稀有类那一组这样稀有类在train和val两边都有样本。如果某个类只出现在训练集而验证集里没有mAP曲线会严重失真分层抽样就是为了避免这种情况。划分完成后把train_files和val_files对应的图片、txt分别拷进YOLO的images和labels目录即可。参数说明0.85/0.15是中等数据集常用比例1300张图大概对应1105张训练、195张验证。验证集低于130张时mAP的波动会大到没法判断模型好坏所以别把验证集切得太小。划分完可以顺手统计一下每个类别在train里的数量for f in YOLO/labels/train/*.txt; do cat $f; done | awk {print $1} | sort | uniq -c左边是类别id右边是出现次数。如果某个类只有几十次这个类的AP会明显偏低后续要考虑针对它做数据增强。4.2 data.yaml与yolo detect train最小命令先确认你的yolo环境能正常执行yolo命令再写data.yaml。把classes.txt按顺序抄进names路径建议直接用绝对路径省得解析出错。path: /home/user/dataset # 数据集根目录改成你自己的绝对路径 train: YOLO/images/train # 训练图片目录 val: YOLO/images/val # 验证图片目录 nc: 4 # 类别数 names: [kite, bird_nest, plastic, crane] # 与classes.txt顺序一致yolo detect train \ datadataset.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ device0参数说明nc4和names列表这两处是关键顺序错误会出现“训练完了但预测时类别全错”的现象而且loss曲线看不出任何异常。modelyolov8s.pt是small版本1300张的规模用nano可能欠拟合medium以上在单卡上收益不明显s是速度和精度的平衡点。epochs100对这个数据量够用训练时配合早停一般在第60到80轮mAP就收敛了。batch16在8GB显存上是安全值显存不够就降到8最好别动imgsz。提示如果训练时命令提示需要联网下载yolov8s.pt预训练权重先手动把权重文件放好避免训练命令因为网络波动反复中断。这个权重后续会被复用放好后一劳永逸。YOLOv8的损失函数由box_loss、cls_loss和dfl_loss三部分加权组成默认权重对大多数检测任务都适用。不建议一上来就调loss权重。很多新手觉得“模型不收敛就调loss”其实绝大多数情况是数据或学习率的问题单独调loss权重收益很小。真觉得有问题先去看train_batch*.jpg里的标注框是否正常再谈loss。4.3 三个必调参数imgsz、batch、epochs外加一条学习率经验参数建议值说明imgsz640异物在图上特别小时可以提到768或896显存和推理时间会涨batch168GB显存安全值太小会让BN统计量不稳定epochs80~120配合早停1300张图再多的轮次只会过拟合lr00.01降0.001小数据集先降学习率比调loss权重见效快imgsz640是默认值也是推荐值除非异物在图上只有二三十像素再考虑提到768。batch优先满足显存上限然后取8的倍数batch2或4这种极小值会让BN层的统计量剧烈抖动后面那章要讲的BN崩溃多半就是这么来的。epochs设200以上纯属浪费时间小数据集很早就开始过拟合val_loss回升之后继续训练只会让mAP越来越差。关于学习率ultralytics默认lr0是0.01配合AdamW优化器。如果数据集很小或者发现前10轮loss反复震荡常见的做法是把lr0降到0.001再训一轮对比。改这一个参数比调任何loss权重都见效快。5. 输电线异物检测训练避坑标注错位、BN崩溃与混淆矩阵排查记录训练目标检测数据集数据本身的问题占比最大。像1300张这种中小规模数据集出了问题很多时候是数据的问题跟模型改动没多大关系——这句话是我踩了三次坑之后才信的。5.1 转换出来的框整体偏左或者画出来比异物大一圈现象画框校验时发现每一张图的框都没对准目标有的整体偏左上有的框比目标大30%。训练时loss能降但mAP很低。原因最常见的是XML里的坐标和当前图片不是同一个坐标系。比如原始图片是1920x1080数据包里的jpg被统一压缩到了1280x720而Annotations里的坐标还是1920宽下的绝对值。另一个常见情况是XML的size节点写的是原始尺寸转换脚本用了size里的值做归一化但实际图片已经被缩放过了。解决不要用XML的size字段做归一化分母直接用cv2.imread读出来的shape算。改转换脚本里的img_width和img_height来源重新转换再跑一遍画框校验。归一化之后坐标系就统一到相对比例上缩放前后都能对齐。5.2 txt文件里出现全零行或者坐标大于1现象训练时加载某些图片报错说label超出边界打开txt文件看到一行全是0或者1.35这种大于1的坐标。原因XML里存在空的目标节点name为空字符串或者bndbox四个值全为0还有一种情况是目标跨图片边界xmax比width还大。YOLO训练对坐标范围很敏感必须在转换时就截断。解决转换脚本里加一道校验凡是name为空、或者xmax小于等于xmin、ymax小于等于ymin的object直接跳过并在控制台打印文件名。对算出来的x_center、y_center、box_w、box_h统一做clip(0, 0.999)确保任何一个值都不超出合法区间。这样训练时不会因为一行脏数据崩掉。5.3 训练到一半loss变成nan或者出现BN崩溃现象训练前面几轮loss正常到第20轮左右loss突然变成nan或者报错提示BatchNorm的running stats出问题train_batch里的图全变成黑色或雪花噪点。原因这个现象在小数据集上出现频率特别高。核心原因一般是batch太小BN层在统计均值方差时样本不够统计量剧烈抖动另一个常见诱因是学习率太大梯度更新一步迈过头loss直接飞掉。还有一种少见但确实会发生的情况是某张图片本身损坏读进来是空数组前向传播收到nan输入。解决先检查图片完整性把读出来shape为0的图片找出来删掉然后把batch调大到8或16lr0调低到0.001重新从断点训练。如果还是nan把模型从yolov8s换成yolov8n再试排除是模型容量和学习率不匹配的问题。5.4 验证集mAP挺高但实际巡检视频里大量漏检现象yolo detect val跑出来的mAP50有0.85但拿无人机拍的视频直接测风筝漏检、鸟巢漏检只有大目标能框住。这个落差有一种说不出的玄学感。原因mAP高有一部分原因是验证集和训练集同源背景非常相似模型学到了“在这个背景里找异物”的捷径而不是真正理解异物的形状。另一部分是置信度门限设太高推理时conf默认0.25对置信度普遍偏低的稀有类别很多正确预测被当成背景滤掉了。解决不能只看mAP就收工。把验证集里的漏检图拼成一张大图观察漏检目标是不是都是小目标、遮挡目标或远景目标。如果是把imgsz从640提到768、对稀有类做针对性增强如果只是置信度偏低把conf降到0.1左右再测一轮。这类问题是迭代修正不是改一个参数就能好的。5.5 混淆矩阵总和不是1类别格子里的数字对不上现象训练结束打印混淆矩阵发现每行的概率总和不是1有的行加起来0.75有的行0.99。运行过YOLO的人会看到矩阵里的总合不唯一看起来是“脏”的。原因混淆矩阵的行是真实类别列是预测类别。如果验证集里某些真实类别样本没有GT框比如某张图实际有风筝但没标模型预测出风筝会在矩阵对应位置多出计数但行和不会增加因为那一行没有对应真实样本。反过来某类只出现几次行和自然会偏小。所以总合不唯一通常意味着验证集标注不完整。解决拿混淆矩阵反查验证集。找到分错最多的类别和场景把验证集里这些图片重新检查一遍GT标注。很多漏标补上之后矩阵的行和会明显归位mAP也会上涨。这一步看起来很笨但对小数据集来说清洗验证集是提升可信度性价比最高的动作。6. 验证模型与调置信度门限从PR曲线到边缘部署前的一步6.1 用验证集画PR曲线再决定置信度门限训练完成后yolo detect val会输出P、R、mAP50这些指标但真正决定部署效果的是PR曲线和F1曲线。把val的results.json导出来按score从0.05到0.95步长扫一遍对每个阈值重新算precision和recall画F1曲线。F1最高的阈值就是该用的conf这个值不一定是默认的0.25。yolo detect val modelruns/detect/train/weights/best.pt datadataset.yaml save_jsonTrue对输电线异物检测这种场景误报是有成本的后台要人工复核每一条告警。所以我会选F1曲线偏右、precision更高的点宁可不报也不误报。我习惯在脚本里多输出一个conf_at_max_f1字段省得每次部署都重新翻验证结果。6.2 导出ONNX并在边缘设备上验证软件层面调完部署前导出ONNX确认推理框架能跑yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640导出后在边缘设备上用onnxruntime跑一遍重点看两件事一是单张推理延迟电力巡检一般要求单张在100ms以内超过就要考虑半精度或换更小模型二是输出的4个类别顺序ONNX输出的类别顺序和data.yaml里names顺序一致这一点在写后处理代码时最容易搞错。数据、训练、验证这条链路走通之后我个人的经验是先别急着扩数据。1300张能做出可用的初版但异物场景多且杂真实部署时还会遇到雨雾、逆光、积雪这类背景变化。把初版模型拿去做一轮真实视频测试记录漏检场景再决定下一批数据补什么类别、补什么角度比盲目再标2000张图有效得多。希望这些记录能帮你少走一遍我走过的弯路也希望帮到你。本文还有配套的精品资源点击获取