资讯动态

电塔鸟巢检测数据集1165张VOC+YOLO双格式使用指南与YOLO训练避坑

发布时间:2026/10/3 2:47:21 来源:尧图企业网站定制
简介这份资源是面向计算机视觉开发者与电力智能巡检方向研究者的目标检测数据集聚焦电塔上鸟巢的识别与定位任务可用于生态观测、电网安全预警等场景的模型训练与评估。压缩包共2000个文件以1165个VOC格式xml标注文件和835个YOLO格式txt标注文件为主另含对应jpg图片整体约87.32MBxml记录矩形框坐标与类别txt则适配YOLO系列算法直接读取。数据集仅含nest一个类别共标注1187个鸟巢框采用labelImg完成矩形框标注图片与标注一一对应部分图片含多个目标。目前已有202人学习下载适合直接投入检测模型训练、格式转换与算法对比实验省去自行采集与标注成本。1. 电塔鸟巢检测数据集1165 张 VOCYOLO 双格式到底怎么用电力巡检这几年最大的变化不是无人机飞得多稳而是后端算法开始真正替代人眼去盯绝缘子、销钉和塔顶的鸟巢。鸟巢这东西看着简单实际在航拍图里特别难缠枯枝颜色和塔材接近逆光下只剩一团黑小目标还经常被压缩到几十像素。所以当我拿到「目标检测电塔上鸟巢检测数据集1165张VOCYOLO格式.zip」这个标题时第一反应不是它有多大而是它同时给了 VOC 和 YOLO 两套标注——这意味着从传统检测框架到 YOLO 系训练链路都能直接接上省掉了最耗时的格式转换和标注校验。1165 张这个量级说大不大说小也绝不算玩具集刚好够做一次完整的迁移学习微调也够暴露小目标检测里那些玄学问题。这篇就按我实际处理这类电力巡检数据集的顺序把格式、划分、训练参数和踩过的坑讲清楚适合正在做电力视觉巡检、或者手上有类似鸟类目标检测需求的人照着走一遍。2. 先搞懂 VOC 和 YOLO 两套标注在鸟巢检测里的差别2.1 VOC 的 XML 结构决定了它能承载哪些信息VOC 格式每张图对应一个 XML 文件核心节点是folder、filename、size和若干个object。对鸟巢检测来说object里通常只有一个类别名比如nest或birdnest外加bndbox的四个坐标。VOC 的好处是信息冗余度高图片宽高、通道数、标注是否截断、是否难例都能写进去。电力巡检场景里塔顶鸟巢经常被塔材部分遮挡truncated和difficult这两个字段就有实际意义——你可以在训练时决定要不要把 difficult 样本的损失降权。但 VOC 的坐标是绝对像素值且原点在左上角不同标注工具导出的 XML 在xmin/ymin上偶尔会出现 1 像素的取整差异。1165 张里如果混了多个标注批次这种差异会累积成框偏移。我一般会先跑一遍统计脚本看所有 XML 的宽高分布和框面积分布确认没有异常值再往下走。import os import xml.etree.ElementTree as ET from collections import Counter def parse_voc_stats(xml_dir): widths, heights, areas, labels [], [], [], [] for f in os.listdir(xml_dir): if not f.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, f)) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) widths.append(w) heights.append(h) for obj in root.findall(object): name obj.find(name).text labels.append(name) 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) areas.append((xmax - xmin) * (ymax - ymin)) print(图片数量:, len(widths)) print(宽高范围:, min(widths), max(widths), min(heights), max(heights)) print(类别分布:, Counter(labels)) print(框面积中位数:, sorted(areas)[len(areas)//2]) print(最小框面积:, min(areas), 最大框面积:, max(areas)) parse_voc_stats(./VOC/Annotations)这段脚本输出四个关键信息图片总数、宽高范围、类别分布和框面积分布。如果类别分布里出现多个标签名比如nest和birdnest混用必须在转 YOLO 之前统一否则训练时会被当成两个类。框面积中位数如果小于 32×32说明小目标占比高后面选模型和调 anchor 都要针对性处理。2.2 YOLO 的 txt 格式为什么更适合直接训练YOLO 格式每张图对应一个 txt每行是class_id x_center y_center width height全部归一化到 0 到 1。它不存图片尺寸也不存 difficult 标记所以信息量比 VOC 少但训练时读取快、解析简单。1165 张的规模用 YOLO 格式直接喂给 ultralytics 系的训练脚本几乎不需要额外预处理。从 VOC 转 YOLO 的公式很直接x_center (xmin xmax) / 2 / img_wy_center (ymin ymax) / 2 / img_hwidth (xmax - xmin) / img_wheight (ymax - ymin) / img_h。但这里有个容易翻车的点如果 XML 里的size和实际图片尺寸不一致归一化就会整体偏移。我遇到过标注工具导出时把size写成固定 1920×1080而实际图片是 3840×2160结果所有框缩小了一半。所以转换前必须用 PIL 或 OpenCV 重新读一遍真实尺寸和 XML 里的size做校验。import os import xml.etree.ElementTree as ET from PIL import Image def voc_to_yolo(xml_dir, img_dir, out_dir, class_map): os.makedirs(out_dir, exist_okTrue) for f in os.listdir(xml_dir): if not f.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, f)) root tree.getroot() img_name root.find(filename).text img_path os.path.join(img_dir, img_name) with Image.open(img_path) as im: real_w, real_h im.size lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_map: continue cls_id class_map[name] 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) # 用真实图片尺寸归一化避免 XML size 字段错误 xc (xmin xmax) / 2.0 / real_w yc (ymin ymax) / 2.0 / real_h bw (xmax - xmin) / real_w bh (ymax - ymin) / real_h # 裁剪到 0-1防止标注越界 xc min(max(xc, 0), 1) yc min(max(yc, 0), 1) bw min(max(bw, 0), 1) bh min(max(bh, 0), 1) lines.append(f{cls_id} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) out_name os.path.splitext(f)[0] .txt with open(os.path.join(out_dir, out_name), w) as wf: wf.write(\n.join(lines)) voc_to_yolo(./VOC/Annotations, ./VOC/JPEGImages, ./labels, {nest: 0})这段代码里class_map把类别名映射成 0因为鸟巢检测通常单类。real_w和real_h从图片实际读取不信任 XML 的size。最后做了 0 到 1 的裁剪防止个别越界框导致训练时 loss 爆炸。转换完一定要抽查几张用可视化脚本把 YOLO 框画回原图确认框位置没偏。2.3 双格式并存时该以哪套为准如果压缩包里 VOC 和 YOLO 两套标注都全我的习惯是以 VOC 为原始真值YOLO 只作为转换产物。原因是 VOC 保留了difficult和truncated你可以在转 YOLO 时决定是否过滤掉 difficult 样本或者保留但记录索引。反过来如果只有 YOLO 没有 VOC那就没法恢复这些元信息遇到遮挡严重的鸟巢只能硬训。实际操作中我会先校验两套标注的一致性随机抽 50 张把 VOC 转出的 YOLO 和包里自带的 YOLO 逐行对比看class_id和四个归一化值是否在 1e-4 误差内一致。如果不一致说明其中一套有问题通常以 VOC 为准重新生成。这一步花不了十分钟但能避免后面训练完发现 mAP 异常却找不到原因。3. 1165 张怎么划分训练验证集才不浪费样本3.1 按场景分层比随机划分更稳1165 张如果纯随机 8:2 划分训练集 932 张、验证集 233 张。但电力巡检图有个特点不同塔型、不同光照、不同拍摄角度的分布很不均匀。如果随机划分可能出现验证集里全是逆光样本训练集里全是顺光导致验证 mAP 虚低。我一般按三个维度做分层光照条件顺光/逆光/阴天、拍摄距离近景/中景/远景、塔型如果标注里有的话。每个维度下按比例抽验证集保证验证集覆盖所有子场景。具体做法是给每张图打一个场景标签可以用文件夹名或者文件名前缀如果没有就人工快速过一遍1165 张大概半小时能标完。然后用sklearn的train_test_split带stratify参数按场景标签分层。import os import shutil from sklearn.model_selection import train_test_split def split_dataset(img_dir, label_dir, out_root, val_ratio0.2): imgs [f for f in os.listdir(img_dir) if f.endswith((.jpg, .png))] # 这里用文件名前缀模拟场景标签实际按你的命名规则改 scenes [f.split(_)[0] for f in imgs] train_imgs, val_imgs train_test_split( imgs, test_sizeval_ratio, stratifyscenes, random_state42 ) for subset, files in [(train, train_imgs), (val, val_imgs)]: img_out os.path.join(out_root, images, subset) lbl_out os.path.join(out_root, labels, subset) os.makedirs(img_out, exist_okTrue) os.makedirs(lbl_out, exist_okTrue) for f in files: shutil.copy(os.path.join(img_dir, f), os.path.join(img_out, f)) lbl os.path.splitext(f)[0] .txt src_lbl os.path.join(label_dir, lbl) if os.path.exists(src_lbl): shutil.copy(src_lbl, os.path.join(lbl_out, lbl)) print(训练集:, len(train_imgs), 验证集:, len(val_imgs)) split_dataset(./images, ./labels, ./dataset, val_ratio0.2)stratifyscenes保证每个场景在训练和验证里的比例一致。random_state42固定随机种子方便复现。划分完检查一下验证集里是否每个场景都有样本如果某个场景只有一两张考虑合并到相近场景或者直接放进训练集避免验证指标抖动。3.2 验证集不能只用来算 mAP很多人划分完验证集就只等训练结束看 mAP这浪费了验证集的一半价值。我的做法是训练前先用验证集跑一遍可视化把每张图的真值框画出来人工过一遍看有没有漏标、错标。鸟巢检测里最常见的标注问题是把塔材交叉处误标成鸟巢或者鸟巢只标了外圈没标内部。1165 张里如果有 5% 的错标训练时模型会学到错误特征mAP 卡在 0.6 上不去。另外验证集要留一部分做「困难样本」专项把逆光、远距离、遮挡严重的图单独拎出来组成一个子集训练过程中每隔若干 epoch 单独跑一次这个子集观察模型在困难场景下的召回率变化。如果整体 mAP 在涨但困难子集召回不涨说明模型在拟合简单样本需要加困难样本的损失权重或者做针对性增强。3.3 小目标占比高时划分要注意框面积分布鸟巢在远景图里可能只有 20×20 像素在近景图里能到 200×200。如果训练集里全是小框、验证集里全是大框模型学到的尺度先验就会偏。划分时除了场景分层还要看框面积分布。简单做法是统计每张图的平均框面积按面积分位数分成小、中、大三档再在每档里按比例抽验证集。import os import numpy as np def box_area_stats(label_dir): areas [] for f in os.listdir(label_dir): if not f.endswith(.txt): continue with open(os.path.join(label_dir, f)) as rf: for line in rf: parts line.strip().split() if len(parts) 5: _, _, _, w, h map(float, parts) areas.append(w * h) # 归一化面积 areas np.array(areas) print(归一化面积分位数:) for q in [10, 25, 50, 75, 90]: print(f {q}%: {np.percentile(areas, q):.6f}) print(小目标占比(0.01):, (areas 0.01).mean()) box_area_stats(./labels)归一化面积小于 0.01 基本对应小目标。如果小目标占比超过 40%训练时输入分辨率不能太低640 可能不够要考虑 960 或 1280。同时 anchor 的尺寸也要重新聚类不能直接用 COCO 的默认 anchor。4. 用 YOLO 训练鸟巢检测的必调参数与避坑记录4.1 输入分辨率和 batch size 的取舍1165 张的规模单卡 8G 显存的话640 分辨率下 batch size 可以到 16960 分辨率下大概 81280 下只能 4。鸟巢检测里小目标多我倾向优先保分辨率batch size 小一点用梯度累积补。比如 960 分辨率、batch 4、累积 4 步等效 batch 16。这样显存不爆小目标召回也能上来。但分辨率不是越高越好。如果原始图片本身是 1920×1080你拉到 1280 训练推理时也用 1280速度会明显下降。电力巡检如果要求实时得在精度和速度之间找平衡。我的经验是验证集上小目标召回率低于 0.7 就加分辨率高于 0.85 且推理速度不达标就降分辨率。4.2 anchor 聚类和损失函数里的小目标权重YOLO 默认 anchor 是基于 COCO 的鸟巢的宽高比和 COCO 里的目标差别很大。用 k-means 在自己的标注框上重新聚类 anchor通常能提升 2 到 5 个点 mAP。聚类时用归一化宽高k 取 9迭代 100 次。import numpy as np from sklearn.cluster import KMeans def cluster_anchors(label_dir, k9): wh [] for f in os.listdir(label_dir): if not f.endswith(.txt): continue with open(os.path.join(label_dir, f)) as rf: for line in rf: parts line.strip().split() if len(parts) 5: _, _, _, w, h map(float, parts) wh.append([w, h]) wh np.array(wh) kmeans KMeans(n_clustersk, random_state42, n_init10).fit(wh) anchors kmeans.cluster_centers_ anchors anchors[np.argsort(anchors[:, 0] * anchors[:, 1])] for a in anchors: print(f{a[0]:.4f} {a[1]:.4f}) cluster_anchors(./labels, k9)输出的 9 组宽高按面积从小到大排列替换模型配置里的 anchor。注意聚类前要确认标注里没有宽高为 0 的脏数据否则 k-means 会报错。损失函数方面如果小目标占比高可以适当提高 box loss 的权重或者用 CIoU 替代 GIoU。有些 YOLO 版本支持fl_gamma调节 focal loss鸟巢检测里正负样本极不平衡focal loss 能缓解简单负样本主导梯度的问题。但fl_gamma别设太大1.5 到 2.0 之间比较稳设到 3 以上容易训练不稳定。4.3 避坑记录鸟巢检测训练里最常见的 5 个翻车点现象一训练 loss 正常下降但验证 mAP 一直是 0。原因通常是类别映射错了。VOC 里类别名是nest转 YOLO 时class_map写成了{birdnest: 0}导致所有标注被跳过txt 全是空的。解决方法是转换后统计每个 txt 的行数如果大量为 0回去检查类别名。现象二训练到一半 loss 突然变成 NaN。最常见的原因是学习率太大或者标注框越界。先检查 txt 里有没有坐标大于 1 或小于 0 的行有就裁剪。然后看学习率1165 张微调一般从 0.001 开始如果 NaN 就降到 0.0005。另外 batch size 太小时 BN 层统计量不稳定也会导致 loss 爆炸可以改用 group norm 或者增大 batch。现象三验证集 mAP 很高但实际推理时漏检严重。这是典型的过拟合到验证集分布。检查验证集和实际推理图的场景差异如果推理图里有大量夜间红外图而验证集全是可见光模型没见过红外特征自然漏检。解决办法是在训练集里补充红外样本或者做可见光到红外的风格迁移增强。现象四同一张图里多个鸟巢只检出一个。NMS 的 IoU 阈值设太高了。鸟巢之间如果挨得近默认 0.45 的 NMS 会把相邻框抑制掉。把 NMS IoU 降到 0.3 到 0.4 之间试试或者用 soft-NMS。另外如果两个鸟巢确实重叠标注时要注意区分别标成一个框。现象五模型把塔材交叉处误检成鸟巢。这是负样本不足导致的。训练集里如果只有鸟巢图没有纯塔材图模型没见过「没有鸟巢的塔」长什么样。解决办法是加入一定比例的负样本图即没有鸟巢的塔材图标注为空 txt。负样本比例控制在 10% 到 20% 之间太多会拉低召回。5. 从 1165 张到可部署模型验证与进阶技巧训练完拿到权重只是第一步真正要部署到电力巡检流程里还得过验证和优化两关。我一般会做三件事第一用验证集跑一遍混淆矩阵看误检和漏检的具体分布如果误检集中在某个塔型就针对性补样本第二用测试集从训练集里再切一小部分训练时完全不碰跑一次端到端推理统计每张图的推理耗时和显存占用确认在目标硬件上能跑第三把模型导出成 ONNX 或 TensorRT对比导出前后的 mAP 差异如果掉点超过 1 个点检查导出时的输入尺寸和归一化参数是否一致。进阶技巧方面如果 1165 张训完 mAP 卡在瓶颈可以试试半监督或者自训练用当前模型对未标注的电力巡检图做推理把高置信度的预测框作为伪标签加入训练集迭代两到三轮。这个方法在鸟巢检测里效果不错因为鸟巢特征相对固定伪标签噪声可控。但要注意每轮加入的伪标签图不能太多控制在原始训练集的 30% 以内否则错误会累积。还有一个容易被忽略的点推理时的输入尺寸最好和训练时一致。训练用 960推理用 640小目标召回会掉得很明显。如果部署端算力有限宁可在训练时就用 640让模型适应低分辨率也不要训练高分辨率再降推理分辨率。我自己在这个数据集上踩过最深的坑是早期没做标注校验直接开训结果验证 mAP 一直在 0.5 左右晃查了两天才发现有一批 XML 的size字段是错的导致归一化坐标整体偏移。从那以后我养成了一个习惯任何数据集到手先跑统计脚本再看可视化最后才写训练配置。这个顺序看起来慢实际上省时间。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑