ARTICLE DETAIL

资讯详情

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

VOC格式输电线路鸟巢目标检测数据集:从标注规范到训练实践全解析

VOC格式输电线路鸟巢目标检测数据集:从标注规范到训练实践全解析 简介面向输电线路巡检和电力设施视觉检测场景的VOC格式鸟巢目标检测数据集适合用于目标检测模型训练、迁移学习与算法评测。压缩包整体约814.44MB内含2461张输电线路现场jpg图像以及一一对应的2461个xml标注文件另附1个txt说明文件标注采用labelImg绘制矩形框类别统一为nest共给出2567个目标标注框。目前已有1079人学习推荐给电力视觉、无人机巡检以及深度学习目标检测方向的开发者使用。数据集已按Pascal VOC格式组织图片与标注一一对应可直接接入常见检测框架单类别标注和框数统计完整便于围绕模型精度、漏检率等指标进行针对性验证与对比。图像内容覆盖不同线路背景与目标尺度贴近真实巡检场景可作为算法落地前的重要验证数据。1. VOC格式输电线路鸟巢目标检测数据集到底能解决什么问题在输电线路巡检的实际项目里鸟巢检测并不是一个可以拿 COCO 预训练模型直接部署的通用任务。无人机拍摄的杆塔影像中鸟巢往往占据画面不到 2% 的面积又被角钢、绝缘子串和杂草遮挡人工排查一小时影像可能要花掉半天。VOC 格式的“输电线路鸟巢目标检测数据集”就是把这类原始影像和标注框以 Pascal VOC 的标准组织起来让“训练一套能用的目标检测模型”这件事有了一份可复用的起点。2461 张在自动驾驶或通用视觉里不算大但对工业巡检场景已经是能支撑迁移学习和预训练微调的中等规模数据。拿到这份数据习惯做法是先做格式校验与数据分布统计再决定增强策略和训练参数而不是直接扔进训练脚本。后续处理顺序按“VOC 标注格式 → 目标特性 → 数据规划 → 训练基线 → 排错技巧”推进适合正在把无人机巡检数据整理成目标检测数据集的工程师也适合准备用 YOLOv8 训练自己的数据集但还没有跑通全流程的读者。2. VOC格式的目录结构与标注规范从单张图像到XML的坐标表示2.1 VOC格式由四部分组成缺了labels目录并不代表数据有问题拿到的压缩包通常长这样dataset_root/ ├── JPEGImages/ │ ├── T123_20240301_001.jpg │ └── ... ├── Annotations/ │ ├── T123_20240301_001.xml │ └── ... ├── ImageSets/ │ └── Main/ │ ├── train.txt │ ├── val.txt │ └── test.txt └── labels/ ├── T123_20240301_001.txt └── ...目录存放内容在训练中的作用JPEGImages全部原始图像模型读取的图像输入Annotations与图像同名的一个 XML记录类别、坐标、尺寸等标注信息ImageSets/Main不带扩展名的文件名列表用于划分训练集、验证集与测试集labels转换后的 YOLO 格式 txt大多数训练脚本会自行生成并非 VOC 原始成分VOC 格式的标准定义只到 Annotations 为止labels 目录是转换产物。如果拿到手的数据包里没有 labels用脚本转换即可不用怀疑数据集结构不完整。需要留意的反而是 JPEGImages 里是否存在大小写混用的扩展名比如.JPG与.jpg这类细节会让第 2.3 节的文件匹配命令出现假阳性结果。2.2 XML 里一定要看明白的字段是 bndbox 与 size打开一个 Annotations 下的 XMLannotation folderJPEGImages/folder filenameT123_20240301_001.jpg/filename source databaseUnknown/database /source size width3840/width height2160/height depth3/depth /size segmented0/segmented object namebird_nest/name poseUnspecified/pose truncated1/truncated difficult0/difficult bndbox xmin2134/xmin ymin945/ymin xmax2396/xmax ymax1210/ymax /bndbox /object /annotation这段 XML 的作用是说明原始图像的名字、分辨率、物体类别以及目标在原始像素坐标系中的左上角与右下角坐标。xmin/ymin/xmax/ymax 都是相对整张原图的绝对像素值不是归一化值这一点在后续转成 YOLO 格式时特别容易出错。difficult为 1 表示该目标被判定为难例多数转换脚本会直接丢弃truncated为 1 表示目标在图像边缘被截断已标注部分仍然可以学习但截断框可能越界转换时需要做边界截断。完成基本阅读后可以用一段脚本快速知道数据集里有多少小目标这是决定输入分辨率的关键import xml.etree.ElementTree as ET import os def analyze_voc(annotations_dir): small_ratio 0 total 0 for fname in os.listdir(annotations_dir): if not fname.endswith(.xml): continue tree ET.parse(os.path.join(annotations_dir, fname)) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) for obj in root.findall(object): box obj.find(bndbox) x1 int(box.find(xmin).text) y1 int(box.find(ymin).text) x2 int(box.find(xmax).text) y2 int(box.find(ymax).text) area_ratio (x2 - x1) * (y2 - y1) / (img_w * img_h) total 1 if area_ratio 0.01: small_ratio 1 print(f目标总数: {total}, 面积占比小于1%的目标数: {small_ratio}) analyze_voc(Annotations)脚本先读取 XML 的 size 字段拿到图像宽高再遍历所有 object 的 bndbox 计算目标面积占比。area_ratio 0.01的含义是该框面积不足图像面积的 1%这里作为“小目标”的初筛阈值。如果这个占比超过 30%后续训练输入尺寸建议直接按 1280 规划而不是默认的 640。提示坐标统计脚本依赖 XML 中 size 字段使用前随机抽取 5 张图核对实际解码分辨率避免 XML 与实际图像宽高不一致导致统计失真。2.3 2461张未对齐的XML如何快速自查继续在现有 VOC 数据上补标时常见做法是用 labelImg 打开 JPEGImages 目录并指定 Annotations 输出目录labelImg JPEGImages/ Annotations/ --predefined_classes classes.txt--predefined_classes参数保证新建标注框时类别下拉列表固定避免手打类别名造成空格或大小写差异。后续转 YOLO 时bird_nest与birdnest会被识别成两类这类低级错误会直接影响类别数配置而且极难排查。标注原则对输电线路鸟巢比较特殊只框可见的巢体或草枝轮廓不框杆塔横担本身目标被角钢遮挡一半时按可见部分紧贴外接框但保留truncated1标记一个图像里有多个鸟巢时每个目标都要单独一个object节点。这样模型学到的位置是“实际鸟巢占位”而不是“鸟巢下面的整根横担”。图像与 XML 是否同名对应用下面两组命令比对ls JPEGImages/ | sed s/.jpg/.xml/ | sort /tmp/img.txt ls Annotations/ | sort /tmp/xml.txt comm -3 /tmp/img.txt /tmp/xml.txt这段命令把 JPEGImages 文件名批量改为同名 xml 再与 Annotations 列表比较comm -3输出只在其中一边出现的文件。如果图像里有.JPG后缀先把第一个ls的 sed 替换写成s/\.jpg/\.xml/gi否则会全部判为缺失。3. 输电线路鸟巢检测的目标特性为什么不能当通用目标来训练3.1 鸟巢检测任务与通用目标检测的三个本质差异场景特性具体表现对算法的影响目标尺度小全尺寸 3840×2160 图像中巢体仅占 0.5%-2% 面积默认 640 输入会造成严重下采样特征可能不足 4 像素背景干扰强角钢锈迹、绝缘子串、杂草与巢体纹理相似特征提取阶段产生大量误检需要更强的感受野遮挡与截断多鸟巢通常搭建在横担与塔身交界处被结构件部分遮挡回归框不稳定NMS 后容易遗漏遮挡目标如果直接按照通用目标检测流程在 640×640 输入下用小预训练模型训练 100 轮出现的问题往往是漏检率极高而不是精度不够高。因为模型不是分辨不出“鸟巢和绝缘子”而是特征已经被前处理压缩掉了。所以拿到数据后先做目标特性分析是必选项不是可选项。3.2 用统计脚本把“目标到底有多小”量化延续上一章的脚本再加一步统计目标尺寸分布和宽高比来判断锚框是否需要重新设计import xml.etree.ElementTree as ET import os import numpy as np annotations_dir Annotations # COCO 把小于 32x32 的目标定义为 small area_boxes [] aspect_ratios [] for f in os.listdir(annotations_dir): if not f.endswith(.xml): continue root ET.parse(os.path.join(annotations_dir, f)).getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) for obj in root.findall(object): b obj.find(bndbox) w int(b.find(xmax).text) - int(b.find(xmin).text) h int(b.find(ymax).text) - int(b.find(ymin).text) area_boxes.append(w * h) aspect_ratios.append(w / h) area_boxes np.array(area_boxes) aspect_ratios np.array(aspect_ratios) print(总目标数:, len(area_boxes)) print(面积小于32*32像素的目标占比:, np.mean(area_boxes 1024)) print(宽高比中位数:, np.median(aspect_ratios)) print(宽高比0.5~2占比:, np.mean((aspect_ratios 0.5) (aspect_ratios 2)))这段代码把“小目标”的定义从面积占比切换到绝对像素因为绝对像素直接决定了输入尺寸。假设 4K 原图上目标约 60×60 像素缩放到 640 后只有 10×10 像素在 YOLO 的 P3 特征图上勉强能覆盖一个网格如果目标更小特征图上的响应峰值就不明显。宽高比中位数用来判断锚框初始化方式鸟巢接近椭圆宽高比集中在 1 附近是常见情况说明默认锚框不需要大改。3.3 清理数据集的三个检查项顺序不能反先做文件名匹配再做边框合法性最后人工抽检漏标。顺序不能反因为后两步建立在文件已经对齐的基础上。文件名匹配见第 2.3 节的comm -3命令边框合法性判断需要关注负坐标以及xmax width这类越界情况越界坐标在转换公式里会出现负数或大于 1 的归一化坐标直接导致训练 loss 变成 NaN。输出越界记录的脚本import xml.etree.ElementTree as ET import os def check_bndbox(annotations_dir): errors [] for f in sorted(os.listdir(annotations_dir)): if not f.endswith(.xml): continue root ET.parse(os.path.join(annotations_dir, f)).getroot() w int(root.find(size/width).text) h int(root.find(size/height).text) for obj in root.findall(object): b obj.find(bndbox) vals [int(b.find(t).text) for t in [xmin, ymin, xmax, ymax]] if vals[0] 0 or vals[1] 0 or vals[2] w or vals[3] h: errors.append((f, obj.find(name).text, vals)) return errors for e in check_bndbox(Annotations): print(越界:, e)脚本把四个坐标与 XML 的 size 字段逐一比较越界记录会输出文件名、类别和具体坐标。这类越界常见于人工标注时边缘截断框没扣准。最后的人工抽检漏标常见操作是把 9 张图拼成一张大图快速浏览每 100 张里抽 5 张重点关注与背景颜色相近的鸟巢因为这些漏标在 loss 里几乎不体现但会让 recall 上限被卡死。4. 2461张样本目标检测数据规划划分、增强与迁移学习4.1 2461张到底是多大的数据量对比公开数据MS COCO 训练集约 11.8 万张PASCAL VOC 2007 与 2012 合并也有一万八千张以上。2461 张对目标检测来说属于中小规模如果没有合理策略模型会表现出对训练塔形的过拟合同一基塔在晴朗天气下识别良好换个角度或换个塔就漏检。常见的做法是不要再按“随机划分 80/10/10”而是按杆塔实体分组划分让验证集看到尽量多的新场景。4.2 按杆塔分组而不是按文件随机划分训练集验证集文件名一般有规律如T123_20240301_001.jpgT123 是塔号。先解析塔号再分组import os import random from collections import defaultdict imgs [f for f in os.listdir(JPEGImages) if f.endswith(.jpg)] tower_groups defaultdict(list) for img in imgs: tower_id img.split(_)[0] # 取文件名里的塔号 tower_groups[tower_id].append(img) towers list(tower_groups.keys()) random.seed(42) random.shuffle(towers) # 验证集取 16 个塔而不是固定 10% 比例 val_towers set(towers[:16]) train_imgs, val_imgs [], [] for tower, files in tower_groups.items(): if tower in val_towers: val_imgs.extend(files) else: train_imgs.extend(files) print(训练图像:, len(train_imgs), 验证图像:, len(val_imgs))塔号在文件名结构不同的数据集上需要换成自己的正则核心逻辑是确保同一杆塔的所有图像进入同一个集合。如果塔数量足够多可以固定验证塔数而不是固定比例如果塔太少退而求其次按拍摄航次分组也可以。分组后确认一下两个集合的类别数量是否平衡尤其是验证集中若恰好没有正样本鸟巢val 的 mAP 会完全没有参考意义。4.3 针对鸟巢场景的增强参数建议以 YOLO 系列常见的增强超参数为例推荐这样一组配置超参数建议值理由hsv_h0.015只给轻微色相扰动过大会把铁塔颜色带偏hsv_s0.7提升对不同天气下色彩饱和度的适应translate0.1模拟无人机悬停时的轻微偏移scale0.5覆盖不同飞行高度下的目标尺度变化fliplr0.5水平翻转不会改变鸟巢语义mosaic1.0四图拼接增加背景多样性小目标也能受益hsv_h控制色相通道的最大扰动比例0.015 表示每个样本的色相最多偏移 1.5%scale为 0.5 时输入图像最短边会在 0.5~1.5 倍之间随机缩放模拟无人机飞行高度变化。Mosaic 增强建议在训练前 2/3 轮次打开后 1/3 关闭因为最后精调阶段模型需要看到真实完整的图像分布而不是拼接过的样本。如果需要更多正样本可以做一个 cut-paste把标注中的鸟巢区域裁剪出来随机粘贴到不包含鸟巢的杆塔背景图上生成新的标注框。实施时注意对裁剪块做 0.8~1.2 的随机缩放和光照扰动否则生成样本过于干净反而让模型在真实遮挡场景下的鲁棒性下降。4.4 迁移学习与网络规模的选择2461 张的规模适合选中型网络起步。以 YOLOv5s 或 YOLOv8s 为初始结构不要直接上 x 系列大型网络参数量在几十个百万量级用这个数据量训练很容易过拟合。加载 COCO 预训练权重是必要的鸟巢与 COCO 中的鸟、枯枝没有语义上的延续但边缘、纹理、颜色泛化这些底层特征是能迁移的。注意冻结 backbone 阶段若发现 loss 完全不下降先确认是否所有标注都成功转换到了 labels 目录空标签文件会直接拉低召回上限。常见做法是分两个阶段先冻结 backbone 训练 50 轮让检测头收敛再解冻全部参数微调 100 轮。冻结阶段可以用较小输入快速迭代微调阶段再用完整分辨率。5. VOC转YOLO格式并跑通训练基线从XML到best.pt5.1 VOC绝对坐标转YOLO归一化坐标的代码YOLO 系列训练不读 XML需要将每个目标写成一行class cx cy w h坐标是相对图像宽高的 0~1 浮点数。转换代码import xml.etree.ElementTree as ET import os classes [bird_nest] def convert(xml_path, out_dir): root ET.parse(xml_path).getroot() filename root.find(filename).text w int(root.find(size/width).text) h int(root.find(size/height).text) lines [] for obj in root.findall(object): if obj.find(difficult).text 1: continue name obj.find(name).text if name not in classes: continue cls_id classes.index(name) 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.0, xmin); ymin max(0.0, ymin) xmax min(w, xmax); ymax min(h, ymax) box_w (xmax - xmin) / w box_h (ymax - ymin) / h cx (xmin xmax) / 2.0 / w cy (ymin ymax) / 2.0 / h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {box_w:.6f} {box_h:.6f}) out_path os.path.join(out_dir, os.path.splitext(filename)[0] .txt) with open(out_path, w) as f: f.write(\n.join(lines))这里做了两个关键处理丢弃 difficult 目标把越界坐标截回到图像尺寸内避免出现负数或大于 1 的归一化框。YOLO 标签文件与图像同名且无扩展名输出目录可以直接放在 dataset/labels 下。对所有 XML 跑完一遍后应检查生成的 txt 是否有空文件空文件表示图像中没有可用正样本训练时会作为纯背景被读到。5.2 data.yaml与最小训练命令数据集目录安排成images/train、images/val、labels/train、labels/val后data.yaml 内容如下path: ./dataset train: images/train val: images/val nc: 1 names: 0: bird_nest接着用 YOLOv5 的预训练权重开始训练python train.py --data data.yaml --weights yolov5s.pt \ --img 1280 --batch 16 --epochs 150 --hyp hyp.yaml--img 1280是本次数据集最大的调参点。从第 3 章的统计结果看如果小目标占比高640 输入会导致目标在下采样后只剩 2~3 个像素把输入提高到 1280 是提升 recall 最直接的手段代价是显存占用和训练时间接近翻倍。显存不够时先在 640 下跑通全流程再把--img 1280作为精调阶段的配置。--hyp hyp.yaml指向第 4.3 节改好的增强参数没有自定义超参数文件时可以省略。5.3 读懂训练过程中的四个指标训练结束后runs/目录下会出现 best.pt 和 last.pt。best 是验证集上 mAP 最高的权重last 是最后一个 epoch 的权重部署优先用 best。指标位置/计算方式对鸟巢检测的判断价值mAP0.5val 阶段输出IoU 阈值 0.5 下的平均精度用于粗看模型是否收敛mAP0.5:0.95从 0.5 到 0.95 的 IoU 平均对框的定位精准度敏感小目标在这个指标上通常偏低precision/recallconfusion_matrix.png 配合阅读调检测阈值时需要在漏检和复检成本之间取平衡小目标 APval.py 按尺寸分组输出判断是否要继续提升输入分辨率或补样本用 YOLOv5 做验证时加--img 1280和降低置信度阈值能更真实地反映模型能力python val.py --data data.yaml --weights best.pt --img 1280 --conf 0.001--conf 0.001会把几乎所有预测框都保留用于观察模型在低置信度区域内是否还有正确的检测而不是只看 0.25 默认阈值下的表现。若低置信区有大量正确目标说明训练目标没问题只是检测阶段阈值定高了。6. 用坏例反向调优VOC格式数据训练后mAP上不去的排障顺序6.1 先查标注再调参数模型训完一轮后若验证集 mAP 明显偏低第一反应不是立刻改锚框而是先看漏检图片的标注是否正确。抽查 50 张漏检图如果目标本身没有框或框有大面积偏移问题就在数据端。图像分辨率与 XML 尺寸不一致是最常见的隐性错误可以先统计解码后的图像宽高与 XML 不符的数量再做其他实验。6.2 调低置信度阈值接受更多低置信目标的代价通用目标检测通常把置信度阈值设为 0.25但输电线路巡视场景的代价结构不同误检的代价是人工复核多花几分钟漏检的代价是隐患被放过去。因此把阈值调低到 0.15 甚至 0.1 是合理的python detect.py --weights best.pt --img 1280 \ --source 待检测影像/ --conf 0.15 --iou 0.35--iou 0.35比默认的 0.45 略低两个目标靠得很近时不会只保留一个但重叠目标可能输出重复框。调低置信度会显著增加误检数需要配套一个后置过滤流程可以按目标预期像素面积和纵横比做规则过滤把明显不合理的框去掉。6.3 导出一份bad-case CSV作为下一轮迭代的输入检测结果不能只看最终指标要把 error case 落成文件。常见做法是写一个对比脚本遍历验证集 XML 的标注框与 detect 输出结果做 IoU 匹配IoU 低于 0.1 且预测置信度大于阈值的目标记为漏检输出文件名、坐标和类型字段import csv # ground_truth 与 predictions 已按图像名组装好 with open(badcase_round1.csv, w, newline) as f: writer csv.writer(f) writer.writerow([image, x1, y1, x2, y2, type]) for image_name, boxes in ground_truth.items(): preds predictions.get(image_name, []) for ann in boxes: matched max((iou(ann, p) for p in preds), default0) if matched 0.1: writer.writerow([image_name, *ann, miss])这里需要自己补一个iou函数计算每个真实框与所有预测框的最大 IoU。保存为badcase_round1.csv后下一轮再从这些位置截取局部图补充进训练集或针对性地做 hard negative mining。后续每轮迭代只对比 CSV 数量是否下降不做空泛的调参实验。本文还有配套的精品资源点击获取
返回列表