ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

电塔鸟巢检测双格式数据集:VOC与YOLO目标检测实战解析

电塔鸟巢检测双格式数据集:VOC与YOLO目标检测实战解析 简介面向电塔巡检与生态研究场景这份鸟巢目标检测数据集以1165张jpg图片为基础标注类别为单一“nest”共含1187个鸟巢矩形框可直接用于智慧电网安全预警、鸟类栖息监测以及目标检测模型训练等任务。数据同时提供Pascal VOC格式的xml标注与YOLO格式的txt标注压缩包共2000个文件包体约87.32MB图片与两种标注文件配套存放省去格式转换环节适合需要快速启动YOLO、SSD、Faster R-CNN等常见检测框架的开发者使用。全部标注由labelImg完成矩形框定位准确xml文件记录对象类别与边界框坐标txt文件则提供适合YOLO系列读取的归一化坐标数据整体标注规则统一、类别信息明确便于批量训练与评估。生态学家可借此分析鸟类活动规律电网运维团队也可用于电塔安全隐患排查与智能化巡检。当前已有202人浏览学习可作为电网巡检智能化改造或目标检测课程实践的参考数据。1. 电塔上的鸟巢检测1165张双格式数据集能解决什么无人机巡线拍回来的电塔照片很多算法工程师第一次拿到的就是“一堆没标注的jpg”。但想让目标检测模型自动框出塔上的鸟巢没有标注数据就没法训练。1165张电塔鸟巢检测数据集的价值在于它同时给了VOC和YOLO两种标注格式解压后不用转换就能直接喂给主流训练框架。它解决的是电力巡检里一个很现实的痛点——鸟巢威胁线路安全人工看片效率太低而自建数据动辄以周为周期。这套数据适合刚入目标检测的新人验证完整流程也适合电力行业算法工程师基于它微调YOLO模型。读完你会知道这份数据怎么组织、怎么校验、怎么训练以及哪些坑我反复踩过。2. 一套双格式电塔鸟巢检测数据集VOC与YOLO的组织方式与异同先说我拿到这类双格式数据集后的第一个习惯不要着急建环境跑训练先把目录结构和标注内容摸一遍。VOC格式是目标检测领域的老牌标准XML里写像素坐标YOLO格式是训练脚本直接消费的文件TXT里写归一化坐标。一份数据同时保留两种格式是为了让不同习惯的人都能上手。电塔鸟巢这个场景比较特殊标注质量比标注数量更影响结果所以前二十分钟花在校验上非常值得。2.1 电塔上的鸟巢为什么是目标检测难点电塔上的鸟巢和常规目标检测数据集里的猫狗车人完全不在一个难度等级上。首先电塔结构本身极复杂——角钢、横担、绝缘子串、导线、防鸟刺互相遮挡鸟巢常常嵌在这些结构中间和背景融为一体。其次无人机拍摄距离变化大塔顶的鸟巢在画面里可能只有二三十个像素属于典型的小目标检测而有些近景图里鸟巢占了大半个画面导致同一份数据集里目标尺度差异极大。第三鸟巢的形态变化也很大有枯枝堆叠的、有带杂草的、有正在筑巢的稀疏状态不同季节拍出来的颜色纹理完全不同。这三条叠加在一起决定了直接拿一份通用目标检测模型去跑或者随便标注一份数据拿来训练效果都会很差。这也是为什么“电塔鸟巢检测”这类专项数据集的标注颗粒度很重要框要紧贴目标别把大段塔材圈进框里否则模型学的就不是鸟巢而是角钢纹理。记住这一点后面所有坑都与此相关。2.2 VOC标注与YOLO标注的目录和坐标体系解压后常见目录是这样组织的dataset/ ├── JPEGImages/ # 原始图片 │ ├── tower_01_001.jpg │ ├── tower_01_002.jpg ├── Annotations/ # VOC格式标注 │ ├── tower_01_001.xml │ ├── tower_01_002.xml ├── ImageSets/ │ └── Main/ │ ├── train.txt │ └── val.txt ├── images/ # YOLO训练用的图片目录 │ ├── tower_01_001.jpg ├── labels/ # YOLO格式标注 │ ├── tower_01_001.txtJPEGImages和Annotations是VOC体系的标志性目录images和labels是YOLO训练脚本默认读取的目录。有些数据包会省略images直接用JPEGImages充当图片源有的没有ImageSets需要自己划分训练验证集。这都正常关键是把两种格式的坐标体系搞清楚。VOC的XML里每个object节点下是bndbox记录xmin、ymin、xmax、ymax单位是像素绝对值。YOLO的TXT每行是五个数类别id、归一化中心点x、归一化中心点y、归一化宽、归一化高。转换关系用这张表就能说清维度VOC (XML)YOLO (TXT)坐标含义左上角右下角像素坐标中心点宽高是否归一化否原始像素值是除以图像宽高类别表示object节点下的name文本行首整数id读取方labelimg等标注工具yolo训练脚本直接读取所以拿到数据集后第一件事是确认XML里object name是什么。如果是中文“鸟巢”或者“bird_nest”要先把names映射表定下来比如 bird_nest - 0。YOLO训练时TXT里只认整数id如果names和id对不上模型会把鸟巢学到别的类里训练不崩但推理结果完全没法看。2.3 写一个脚本校验xml标签与txt标签两条腿是否对齐双格式数据集最大的风险不是格式本身而是两种标注不一致。常见情况是标注人员用labelimg打完标签后存成VOC之后用第三方脚本批量转YOLO结果坐标偏移、类别id错位、甚至有几张图转换失败。我每拿到一份双格式数据都会先跑一遍校验脚本。import glob import xml.etree.ElementTree as ET def xml_to_yolo_lines(xml_path): 把VOC xml解析成yolo格式的标注行便于和txt比对 tree ET.parse(xml_path) root tree.getroot() size root.find(size) w float(size.find(width).text) h float(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text.strip() # 类名到id的映射需要根据实际数据调整 cls_id {bird_nest: 0, 鸟巢: 0}.get(name, -1) 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) x_center ((xmin xmax) / 2) / w y_center ((ymin ymax) / 2) / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}) return lines # 抽查前20个xml与同名txt逐行比对 xml_files sorted(glob.glob(Annotations/*.xml))[:20] for xml_path in xml_files: base xml_path.split(/)[-1].replace(.xml, ) txt_path flabels/{base}.txt xml_lines xml_to_yolo_lines(xml_path) with open(txt_path, r) as f: txt_lines [line.strip() for line in f if line.strip()] if len(xml_lines) ! len(txt_lines): print(f[数量不一致] {base}: xml{len(xml_lines)} txt{len(txt_lines)})这段脚本的逻辑是解析XML得到标准YOLO格式行再和同名TXT逐行比对。数量不一致说明转换时丢了目标如果数量一致但坐标对不上可以再按行解析比较浮点差。参数说明里最需要注意的是类名映射字典我临时用get方法兜底遇到未知名会返回-1跑完看输出有没有-1就能发现标注类名不规范的地方。我还习惯顺手统计一下目标尺寸分布import os small 0 total 0 for txt in glob.glob(labels/*.txt): with open(txt) as f: for line in f: parts line.strip().split() if len(parts) 5: continue w float(parts[3]) h float(parts[4]) total 1 if w * h 0.01: # 归一化面积小于1% small 1 print(f小目标占比: {small}/{total} {small/total:.2%})提示这套校验脚本跑完如果数量和坐标都对得上再花十分钟用labelimg随便抽查二十张图确认框贴边程度。数据集的坑大部分在标注这部分返工比训练完再调参划算得多。3. 搭建训练环境并跑通第一个电塔鸟巢检测模型最小命令流目录确认没问题接下来就是把它喂给模型。我默认以yolov8训练自己的数据集为例因为这是目前社区里资料最全、环境依赖最少的一条路。下面这组最小命令流已经把电塔鸟巢场景的参数选择放在里面了。3.1 用conda快速搭建yolov8训练环境训练环境越干净越好我习惯用conda隔离避免把系统Python搞乱。conda create -n bird_nest python3.10 -y conda activate bird_nest pip install ultralytics逐条说明conda create创建独立环境python3.10是目前ultralytics各版本兼容性最好的解释器版本最后一个pip install会同时装上torch、torchvision、opencv等依赖。如果你有NVIDIA显卡装完先确认CUDA是否可用python -c import torch; print(torch.cuda.is_available())输出True就说明GPU环境OK。如果输出False说明安装的torch是CPU版需要到PyTorch官网按自己机器的CUDA版本重装torch。我的经验是1165张图、单类别、分辨率640的情况下CPU也能训练但一个epoch可能要跑十几分钟一只几十小时的训练拉满谁用谁知道。所以能用GPU尽量用GPU哪怕是6G显存的入门卡也好过CPU硬扛。3.2 把VOCYOLO双格式整理成训练目录与data.yaml很多双格式数据集没有预先划分train和val需要自己做。注意这里的坑不要用随机划分就完事后面第五章会专门讲同塔图片污染验证集的问题。先看最基本的目录整理操作mkdir -p datasets/bird_nest/images/train mkdir -p datasets/bird_nest/images/val mkdir -p datasets/bird_nest/labels/train mkdir -p datasets/bird_nest/labels/val然后写个小脚本按8:2划分import os import random import shutil random.seed(0) imgs [f for f in os.listdir(images) if f.endswith(.jpg)] random.shuffle(imgs) split_idx int(len(imgs) * 0.8) for f in imgs[:split_idx]: shutil.copy(fimages/{f}, datasets/bird_nest/images/train/) shutil.copy(flabels/{f.replace(.jpg, .txt)}, datasets/bird_nest/labels/train/) for f in imgs[split_idx:]: shutil.copy(fimages/{f}, datasets/bird_nest/images/val/) shutil.copy(flabels/{f.replace(.jpg, .txt)}, datasets/bird_nest/labels/val/)这里random.seed(0)是为了让每次划分结果一致方便复现shutil.copy而不是move是为了保留原始目录不动后面发现问题还能重分。接下来写data.yamlpath: /absolute/path/to/datasets/bird_nest train: images/train val: images/val nc: 1 names: [bird_nest]参数说明path一定要写绝对路径写相对路径在某些版本的ultralytics里会找不到数据nc是类别数这里只有鸟巢一类所以是1names是类别名列表顺序必须和TXT标注里的id对应id0对应names[0]也就是bird_nest。如果你在2.3节校验时发现某些TXT第一列是1那这里names就得改成[background, bird_nest]否则训练会漏掉一类。3.3 训练命令、核心参数和第一次训练的输出目录就绪后训练命令很简单yolo detect train datadata.yaml modelyolov8s.pt epochs120 imgsz640 batch16 patience30 device0我解释一下这几个参数为什么这么设。modelyolov8s.pt是下载COCO预训练权重作为起点迁移学习比从零训练收敛快得多s是small版本参数量适中1165张单类数据完全够用没必要上yolov8m或l只会更慢更容易过拟合。imgsz640是默认值但如果2.1节统计的小目标占比高可以提到1024代价是显存占用翻倍。batch16在8G显存上跑yolov8s640是安全的如果显存不够优先把batch降到8而不是降imgsz——batch小只是慢imgsz小会导致小目标直接消失。patience30表示验证集指标连续30个epoch不提升就早停这个参数能帮你省掉大量空跑的算力。训练启动后终端会打印模型结构、参数数量和每个epoch的loss。第一次跑的时候重点看两处一是类别数是不是1如果显示nc80说明data.yaml没被正确加载二是训练到第十个epoch左右box_loss和cls_loss是不是在稳步下降。如果loss完全不动或者跑了几个epoch就变成nan大概率是数据问题先回去跑2.3的校验脚本。4. 训练效果评估从损失曲线到mAP再到三个必调参数很多人训练完只看一个mAP就下结论这在电塔鸟巢场景里很危险。鸟巢检测的最终目的是别漏宁可误报让人工多看一眼也不能漏掉一个真鸟巢。所以这章把评估链路完整过一遍。4.1 先看损失曲线训练是否正常的快速判断训练结束后ultralytics会在runs/detect/train目录下生成results.png和results.csv。我的习惯是只看曲线前先自己画一遍因为默认图信息密度不够import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/detect/train/results.csv) df.columns [c.strip() for c in df.columns] # 列名首尾有空格去掉 plt.plot(df[epoch], df[train/box_loss], labeltrain_box) plt.plot(df[epoch], df[val/box_loss], labelval_box) plt.xlabel(epoch) plt.ylabel(loss) plt.legend() plt.savefig(loss_check.png, dpi200)这段代码的作用是把训练集和验证集的box_loss画到同一张图里方便判断过拟合。逻辑说明train/box_loss下降、val/box_loss也下降说明模型正常学习train持续下降但val在某个epoch后开始回升说明过拟合处理办法是加大mosaic增强比例或减少epochs如果两条曲线都在高位震荡先怀疑数据标注不干净比如类别id混乱、框错位。yolov8的损失由三部分组成box_loss是框回归损失、cls_loss是分类损失、dfl_loss是分布焦点损失。电塔鸟巢任务里我优先看cls_loss和box_loss这两个直接对应“框得准不准”和“判得对不对”。dfl_loss只有几十数值小但影响边界框精度别因为看着小就忽略。4.2 mAP50与mAP50-95鸟巢场景怎样看指标训练完成后终端或results.csv里会给出mAP50和mAP50-95两组指标。它们的区别要搞清楚指标计算方式电塔鸟巢场景参考mAP50预测框与真值框IoU0.5即算命中主要关注项0.8以上说明模型基本可用mAP50-95IoU从0.5到0.95逐步提高后取平均严格指标小鸟巢框标注本身有主观性偏低正常precision预测为正的样本里真正例占比误报严重时该值下降recall真标注里被模型找回来的占比漏检严重时该值下降鸟巢场景的关键指标电塔鸟巢的框不像行人检测那样边界清晰枯枝边缘和塔材界限模糊不同标注员框出来的大小可能差个10%。所以我的判断标准是mAP50在0.8以上就能进测试环节mAP50-95低一些不用慌。最危险的场景是mAP50和mAP50-95都很高但recall偏低——这说明模型只学会了最典型的鸟巢换个角度拍就认不出来。这种“高分低能”模型上线后会让巡检人员产生虚假安全感。4.3 推理阶段必调置信度门限与NMS重叠阈值训练出的best.pt只是中间产物推理时还有两个参数对结果影响极大但很多人在训练完就忘了调。yolo detect predict modelruns/detect/train/weights/best.pt sourcetest_images/ conf0.25 iou0.7 save_txtTrueconf就是置信度门限它的语义是“置信度低于0.25的框全部丢掉”。这个值怎么设要看业务对漏检和误检的容忍度。电力巡检场景里漏掉一个鸟巢可能意味着一次线路跳闸所以我会把conf调到0.15到0.2之间宁可多出几个误报框让审核人员去排除也不能漏。如果你是在做自动化告警且没有人工复核环节那conf需要回到0.4以上压误报。iou是NMS去重时的重叠阈值。默认0.7对大多数场景够用但电塔上一个横担上经常挨着两三个鸟巢如果它们重叠度高NMS会把其中一个当成另一个的重复框删掉。遇到这种情况可以适当调低到0.5让靠得近的独立目标保留下来。调参没有金标准我的做法是对着验证集里的密集鸟巢图反复跑几遍看哪个组合能同时保住召回率和框数量。5. 电塔鸟巢检测踩坑排查四个最容易翻车的边界这一章可以说是我做类似项目时血泪经验的浓缩。每一条都真实影响过训练结果按“现象→原因→解决”写方便你直接对照。5.1 类别数没对齐损失直接变成nan现象从COCO预训练权重开始微调训练到第几个epoch时loss突然变成nan或者训练全程正常但推理输出全是“person”“bicycle”这种COCO类别。原因data.yaml里的nc和names与预训练模型输出头对不上。ultralytics新版框架会在加载预训练权重时自动适配类别数但如果你用的是旧版本或自己改过模型结构输出头的维度还是COCO的80类梯度回传时就崩了。另一种常见情况是TXT标注里的类别id写的是1而yaml里nc1、names[bird_nest]这样id1就越界了。解决先跑下面的命令看真实类别分布awk {print $1} labels/train/*.txt | sort | uniq -c输出结果会显示每一行第一个数字的出现次数。如果是0和1混在一起说明有人把背景也标了id如果只有0那data.yaml里nc1就是对的。确认yaml、TXT、XML三处的类别定义一致后再重新训练。5.2 小目标漏检imgsz设置是主因现象验证集mAP不低但拿无人机拍的高清原图直接推理塔上稍远一点的鸟巢完全没反应放大图片再跑又正常。原因yolov8的下采样倍率是32imgsz640时特征图只有20×20画面里30×30像素的小鸟巢下采样后连一个特征点都占不满目标信息直接消失。这不是模型问题是输入分辨率掐死了小目标的活路。解决优先把imgsz从640提到1024batch相应减半显存不够低于8G就用滑动窗口切图推理。切图的思路是把大图按1024×1024切成有重叠的块分别跑模型再把结果映射回原图坐标。ultralytics生态里有人用sahi做这件事我自己有时也直接写个双层循环加numpy切片实现逻辑不复杂。记住一个原则小鸟巢宁可让模型多看一会儿也别让它压根看不见。5.3 验证集划分不当mAP虚高现象训练完mAP50到了0.9以上模型看起来完美部署到新的线路区段后表现断崖式下跌。原因A场景里无人机对着同一座塔拍了上百张连续照片背景几乎一样。随机划分时同一座塔的照片一部分进了训练集一部分进了验证集模型等于“背过”验证集的答案验证分数自然虚高。解决按塔编号分组划分数据集。比如文件名是tower_01_001.jpg、tower_01_002.jpg划分前先按tower编号聚合整个编号的图片要么全在train要么全在val。代码上只要把3.2节里的random.shuffle(imgs)改成先按前缀分组再shuffle分组from collections import defaultdict groups defaultdict(list) for f in imgs: tower_id f.split(_)[0] # 假设前缀是塔编号 groups[tower_id].append(f) tower_ids list(groups.keys()) random.shuffle(tower_ids) split_idx int(len(tower_ids) * 0.8) train_towers set(tower_ids[:split_idx]) # 只有塔编号在train_towers里的图片才进训练集这样划分出来的验证集才是真没见过的新塔型指标才有参考价值。5.4 塔身背景被误检成鸟巢标注颗粒度是根源现象模型对绝缘子串根部、横担连接处的角钢结构输出高置信度鸟巢框每张图上十几个误报现场人员很快对模型失去信任。原因标注时框给大了。一个鸟巢最宽也就半米上下如果标注时把周围塔材一起圈进去模型学到的特征就混杂了角钢边缘和绝缘子纹理。鸟巢和这些背景在颜色、纹理上高度相似时模型只能靠“像不像那个框里的东西”来猜。解决手动挑几十张误报严重的图把标注框重新贴边精修。另一个有效手段是加负样本从没有鸟巢的电塔图上裁出绝缘子、横担区域放到数据集里但不生成任何标注空TXT文件。这样训练时这些区域作为背景被强制学习模型就会明白“角钢不是鸟巢”。负样本数量不需要多两三百张就够但一定要覆盖模型当前最容易混淆的几种结构。6. 把1165张双格式数据用出价值半自动标注、分辨率与鲁棒性验证数据只有1165张直接训练完就交付有点浪费。最后聊几个我用这套数据时的经验也算是项目收尾习惯。6.1 小模型先探路把时间花在刀刃上拿到新数据我第一轮永远先跑yolov8n imgsz512 epochs50。这个组合在消费级显卡上半小时内就能跑完目的是验证数据格式、类别划分和训练流程有没有问题。如果小模型loss曲线正常、val图里能看到像模像样的框再换成yolov8s imgsz1024跑正式训练。一步到位跑大模型万一中途发现数据有问题浪费的是几小时电费和耐心。6.2 半自动标注扩展数据一条自增强循环1165张图覆盖的塔型有限。我会用训练好的best.pt对新采集的未标注图片做预测推理时加save_txtTrue得到机器预标注的TXT。然后把这些结果导入labelimg人工只做修正——删掉错框、拉紧松框、补漏框。这样标注速度能提升三四倍积累到一定程度后混入原数据集重训精度还能再涨一截。注意一个细节只有置信度大于0.5的预测框才值得导入人工修正低置信度框噪声太多人工改起来不如直接标。6.3 上线前的鲁棒性验证难样本比平均精度更诚实交付前我不会只看best.pt在val上的整体指标而是把val里“最难”的几十张图单独拎出来看阴天逆光的、鸟巢被导线遮挡的、塔身在画面边缘的。这些图如果预测框稳定才算真正有把握。此外我还会抽几段无人机视频做抽帧测试因为视频里的图远比静态验证集多样能暴露模型的真实水平也能统计出实际漏检率。我把这部分结果当作选型的最终依据而不是训练日志里的mAP数。做这类巡检项目久了我养成了一个习惯给现场交付时把conf阈值人为调低一档同时要求告警附上原图截图供人工复核。宁可被骂误报多也绝不能让一个真鸟巢从眼皮底下溜走。这一行漏检的代价比误报高一个量级。希望帮到你。本文还有配套的精品资源点击获取
返回列表