简介:本数据集面向光伏电站智能巡检与航拍图像缺陷检测方向,提供367张光伏板航拍图片,聚焦鸟粪这一单一类别的目标检测任务,适合从事无人机巡检、新能源运维及计算机视觉算法验证的开发者与研究人员使用。包内共1103个文件,包含367张jpg原图、367个Pascal VOC格式xml标注文件以及369个YOLO格式txt标注文件,压缩包约23.04MB,两种标注格式可直接对接主流检测框架,省去格式转换环节。全部标注由labelImg完成,采用矩形框方式,niaofen类别共标注1421个目标框,标注数量与图片一一对应,分布较为充分。目前已有375人学习下载,可作为光伏板鸟粪缺陷识别模型训练与评估的基础数据,帮助读者快速搭建检测流程、验证算法效果并开展对比实验。
1. 光伏板航拍鸟粪缺陷检测:367 张 VOC+YOLO 数据集到底能干什么
光伏电站的运维人员最怕两件事:热斑和遮挡。鸟粪落在组件表面,局部遮挡会让该串电池片从发电单元变成耗电负载,轻则形成热斑烧穿背板,重则整串失效。传统人工巡检靠人扛着红外枪在方阵里走,一个 50MW 电站走完要几天,而无人机航拍一次能覆盖全场,问题就变成了:拍回来的几千张图,谁来标、怎么标、标完怎么训。
这个标题里的「光伏板航拍鸟粪缺陷检测数据集 VOC+YOLO 格式 367 张 1 类别」,本质是一份已经标注好的小样本目标检测数据集,专门针对鸟粪这一类缺陷。367 张不算多,但胜在类别单一、标注格式双份(VOC 的 XML 和 YOLO 的 TXT),拿来跑通 YOLO 训练链路、验证航拍小目标检测的可行性是够用的。它适合两类人:一类是想入门缺陷检测但找不到干净数据的算法工程师,另一类是手里有电站航拍图、想先拿公开数据试水再自己标注的运维技术员。下面从数据本身讲到训练、验证和踩坑,把这条路走一遍。
2. 拆开这个数据集:VOC 与 YOLO 双格式的目录结构和字段含义
2.1 367 张图、1 个类别,标注文件里到底存了什么
拿到压缩包解压后,常见的目录组织是这样:一个JPEGImages放原图,一个Annotations放 VOC 格式的 XML,一个labels放 YOLO 格式的 TXT,外加ImageSets/Main下的训练验证划分文件。367 张图对应 367 个 XML 和 367 个 TXT,一一对应,文件名主干一致,只是扩展名不同。
VOC 的 XML 是「绝对坐标 + 语义信息」的结构,核心字段是<size>里的宽高和<object>里的<name>、<bndbox>。<name>就是类别名,这份数据只有一类,通常写成bird_drop或guano之类。<bndbox>里是xmin/ymin/xmax/ymax,单位是像素,左上角为原点。
YOLO 的 TXT 是「归一化中心点 + 宽高」的结构,每行一个目标,格式是class_id cx cy w h,五个值全部除以图像宽高归一化到 0~1。因为只有一类,class_id恒为 0。这两种格式描述的是同一批框,只是坐标系和表达方式不同,转换时最容易出错的就是归一化和取整。
提示:先确认类别名。如果 XML 里写的是中文或带空格,转 YOLO 时映射表要对上,否则训练时类别全乱。
2.2 为什么航拍鸟粪检测偏爱 YOLO 而不是两阶段检测器
鸟粪在航拍图里属于典型的小目标,几十像素见方,且和组件表面的污渍、灰尘、反光容易混淆。两阶段检测器(如 Faster R-CNN)精度不差,但推理速度在边缘设备上吃紧,电站巡检往往要现场出结果或快速回传,YOLO 系列的单阶段结构更合适。加上这份数据只有 367 张,YOLO 的轻量 backbone 在小样本上过拟合风险相对可控,配合预训练权重迁移学习,收敛比从头训快得多。
选 YOLO 还有一个现实原因:生态成熟。标注工具、格式转换脚本、训练框架、部署工具链都齐全,从 VOC 转 YOLO 有现成脚本,训练有 ultralytics 这类库,导出 ONNX 或 TensorRT 也顺。对只有几百张图的项目来说,把时间花在数据增强和调参上,比花在搭框架上划算。
2.3 用脚本把 VOC 的 XML 转成 YOLO 的 TXT
虽然数据集号称双格式,但实际拿到的往往只有 XML,或者 TXT 的类别映射不对,自己转一遍最稳妥。下面这段脚本读Annotations下的 XML,输出到labels,同时生成classes.txt。
import os import xml.etree.ElementTree as ET # 类别映射,按实际 XML 里的 name 改 classes = ["bird_drop"] ann_dir = "Annotations" img_dir = "JPEGImages" out_dir = "labels" os.makedirs(out_dir, exist_ok=True) for xml_file in os.listdir(ann_dir): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(ann_dir, xml_file)) root = tree.getroot() size = root.find("size") w = int(size.find("width").text) h = int(size.find("height").text) lines = [] for obj in root.iter("object"): name = obj.find("name").text if name not in classes: continue cls_id = classes.index(name) 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) # 归一化中心点和宽高,注意裁剪到 [0,1] cx = min(max((xmin + xmax) / 2.0 / w, 0.0), 1.0) cy = min(max((ymin + ymax) / 2.0 / h, 0.0), 1.0) bw = min(max((xmax - xmin) / w, 0.0), 1.0) bh = min(max((ymax - ymin) / h, 0.0), 1.0) lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") txt_name = os.path.splitext(xml_file)[0] + ".txt" with open(os.path.join(out_dir, txt_name), "w") as f: f.write("\n".join(lines)) with open("classes.txt", "w") as f: f.write("\n".join(classes))逻辑说明:先解析 XML 拿到图像宽高,再对每个 object 取框坐标,转成中心点加宽高后除以宽高归一化。min/max裁剪是防止个别标注框超出图像边界导致归一化值大于 1,YOLO 训练时遇到这种值会报错或产生异常梯度。classes.txt的顺序必须和训练配置里的names一致,否则类别索引对不上。
参数说明:classes列表按实际类别名填,这份数据只有一类所以长度为 1;ann_dir、img_dir、out_dir按解压后的实际路径改;输出保留 6 位小数足够,YOLO 读取时按浮点解析。
2.4 划分训练集和验证集时别用随机划分
367 张图如果随机划分,同一块组件、同一飞行架次的图可能同时进训练和验证,验证指标会虚高。航拍数据往往按架次或区域成批出现,正确做法是按拍摄批次或区域划分,让验证集来自训练时没见过的组件和光照条件。如果数据里没有批次信息,至少按文件名前缀或时间分组后再划分,别用random.shuffle一刀切。
# 假设图片按架次命名,如 flight01_0001.jpg ls JPEGImages | cut -d'_' -f1 | sort -u # 手动挑几个架次作为验证集,其余训练这一步没有标准脚本,因为划分逻辑依赖数据来源。核心原则是验证集要能反映真实部署时的分布差异,而不是和训练集同分布。
3. 用 YOLOv8 在 367 张图上跑通训练:配置、命令与参数
3.1 环境准备和数据集 YAML 的写法
训练前先装好 ultralytics,建议用虚拟环境隔离。GPU 方面,367 张图在 8GB 显存的卡上跑 YOLOv8n 或 YOLOv8s 完全够,batch 可以设到 16。CPU 也能跑,就是慢,适合先验证流程。
数据集配置文件pv_bird.yaml长这样:
path: /data/pv_bird train: images/train val: images/val nc: 1 names: ["bird_drop"]path是数据集根目录,train和val是相对路径,指向存放图片的文件夹。YOLO 会自动在同级找labels目录下的同名 TXT。nc是类别数,这份数据是 1。names顺序必须和转换脚本里的classes一致。
注意:图片和标签的目录结构要对应,
images/train/xxx.jpg对应labels/train/xxx.txt,少一个都会在训练启动时报找不到标签。
3.2 训练命令和关键参数怎么设
启动训练的命令很直接:
yolo detect train \ data=pv_bird.yaml \ model=yolov8s.pt \ epochs=150 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=30 \ project=runs/pv \ name=exp1逐项说明:model=yolov8s.pt用官方预训练权重做迁移学习,小样本下比从头训稳定得多;epochs=150对 367 张图偏多,但配合patience=30早停,实际可能在 80~120 轮就停;imgsz=640是默认输入尺寸,如果鸟粪目标普遍小于 32 像素,可以提到 1024,但显存和速度要权衡;lr0=0.01是初始学习率,小数据集上如果 loss 震荡可以降到 0.005;batch=16按显存调,显存不够就减半并同步调小学习率。
训练过程中重点看三个指标:box_loss是否稳定下降、mAP50是否在验证集上持续上升、cls_loss有没有异常波动。如果box_loss降但mAP50不涨,多半是过拟合或验证集分布不对。
3.3 小样本下数据增强的取舍
367 张图直接训容易过拟合,YOLO 默认开启 mosaic、HSV 抖动、随机翻转等增强。航拍场景下,水平翻转和垂直翻转通常安全,因为组件朝向不固定;但大角度旋转要谨慎,鸟粪的形状没有方向性,旋转问题不大,可组件边框的纹理方向会被破坏,可能让模型学到错误特征。
颜色抖动(HSV)对航拍图有用,因为不同时段光照色温差异大,适度抖动能提升泛化。mosaic 增强在小样本上效果好,但要注意它会把四张图拼一起,如果鸟粪目标本身很小,拼接后目标更小,可能低于检测下限。可以先用默认增强跑一版,看验证集表现再决定是否关闭 mosaic。
# 在 yaml 里或命令行覆盖增强参数 hsv_h: 0.015 hsv_s: 0.7 hsv_v: 0.4 flipud: 0.5 fliplr: 0.5 mosaic: 1.0这些值不是拍脑袋,是 ultralytics 的默认值,小样本上可以先照用,再根据验证曲线微调。如果发现模型对某类光照条件特别差,针对性增加对应增强。
3.4 训练完怎么看结果:混淆矩阵和 PR 曲线
训练结束后runs/pv/exp1下会生成confusion_matrix.png、PR_curve.png、results.csv等。混淆矩阵只有一类时看着简单,但要看背景被误判为鸟粪的比例(假阳性)和鸟粪被漏检的比例(假阴性)。光伏巡检里漏检比误检更危险,漏检意味着热斑风险没被发现,所以调阈值时宁可稍微放宽召回。
PR 曲线看的是不同置信度阈值下的精度和召回权衡。如果曲线下面积大但高召回区精度掉得快,说明模型在低置信度时误检多,部署时可以把置信度阈值设高一点。results.csv里记录了每轮的 loss 和 mAP,用 pandas 画个曲线就能看出有没有过拟合。
import pandas as pd df = pd.read_csv("runs/pv/exp1/results.csv") df.columns = df.columns.str.strip() df[["train/box_loss", "val/box_loss"]].plot() df[["metrics/mAP50", "metrics/mAP50-95"]].plot()这两行代码分别看训练和验证的 box loss 是否分叉,以及 mAP 是否还在涨。分叉明显就是过拟合,该早停或加增强。
4. 避坑与排查:367 张小样本训练鸟粪检测最容易翻车的 5 个地方
4.1 现象:训练启动就报「No labels found」
原因:YOLO 按images/train的路径去推labels/train,如果目录名不是labels或者 TXT 文件名和图片名主干不一致,就找不到。另一个常见原因是 TXT 是空的,转换脚本因为类别名不匹配跳过了所有 object。
解决:先确认目录结构,再抽查几个 TXT 是否有内容。用wc -l labels/train/*.txt看行数,全零就是转换失败。回到转换脚本检查classes列表和 XML 里的<name>是否完全一致,包括大小写和空格。
4.2 现象:mAP 一直是 0 或者极低
原因:归一化坐标算错,比如忘了除以宽高,或者把 xmin/ymin 当成了中心点。另一种可能是类别索引错位,TXT 里写的class_id超出了nc范围。
解决:随机抽一张图,用脚本把 TXT 里的框还原成像素坐标画出来,和原图对比。如果框的位置完全不对,就是坐标转换的问题。还原公式是xmin = (cx - w/2) * img_w,以此类推。
4.3 现象:验证集指标很好,实际航拍图上一塌糊涂
原因:训练集和验证集同分布,但和真实航拍图分布差异大。常见差异包括拍摄高度不同导致目标尺度变化、光照条件不同、背景里组件排列方式不同。367 张图如果都来自同一电站同一时段,模型学到的就是那个特定场景。
解决:验证集按架次或区域划分,确保覆盖不同条件。如果条件允许,留一部分完全不同电站的图做测试集。数据增强里增加尺度抖动和亮度抖动,模拟不同高度和光照。
4.4 现象:训练到一半 loss 突然变 NaN
原因:学习率太大、batch 太小导致梯度不稳,或者数据里有异常标注(坐标超出图像范围、宽高为负)。YOLO 对异常标注的容忍度有限,个别坏样本能带崩整个训练。
解决:先把学习率降到 0.001 试一轮,如果还 NaN 就查数据。写个脚本遍历所有 TXT,检查每个值是否在 [0,1] 内、宽高是否大于 0。把异常样本挑出来修正或剔除。另外 batch 太小时(比如 2)BN 层统计量不稳,也会引发 NaN,尽量保持 batch 不小于 8。
4.5 现象:推理时同一张图框忽多忽少
原因:置信度阈值和 NMS 的 IoU 阈值没调好。鸟粪目标小且密集时,NMS 的 IoU 阈值设太高会保留大量重叠框,设太低会把相邻的真实目标合并掉。
解决:部署前在验证集上扫一遍置信度阈值和 IoU 阈值,画 F1 曲线找最优点。YOLO 推理时可以用conf和iou参数控制:
yolo detect predict model=runs/pv/exp1/weights/best.pt source=test_imgs conf=0.35 iou=0.5conf=0.35表示只保留置信度高于 0.35 的框,iou=0.5是 NMS 的重叠阈值。这两个值没有万能解,按实际漏检和误检的容忍度调。
5. 从 367 张到可用模型:小样本缺陷检测的进阶技巧
367 张图训出来的模型,直接上电站巡检大概率不够用,但作为基线完全合格。真正要落地,得在这个基线上做两件事:扩充数据和提升小目标检测能力。
扩充数据最实际的办法是用训好的模型去跑未标注的航拍图,把高置信度的预测框导出来,人工修正后加入训练集。这叫半自动标注,能把标注效率提升几倍。YOLO 支持把预测结果保存成 TXT:
yolo detect predict model=best.pt source=raw_imgs save_txt=True save_conf=Truesave_txt=True会在runs/detect/predict/labels下生成 TXT,save_conf=True会带上置信度。挑置信度高于 0.6 的框,人工过一遍,确认无误就并入训练集。迭代两三轮,数据量能从 367 涨到上千,模型泛化会明显改善。
提升小目标检测有几个方向。一是提高输入分辨率,imgsz=1024或1280,代价是显存和推理时间;二是在模型结构上做文章,比如加 P2 检测层,YOLOv8 默认从 P3 开始,P2 能捕捉更小的目标,但计算量增加;三是切片推理,把大图切成小块分别检测再合并,适合目标极小的场景。这几种方法可以组合,但每加一层都要重新评估速度和精度的平衡。
验证模型是否真的可用,别只看 mAP。找一批真实电站的航拍图,人工标出所有鸟粪,跑模型推理,统计漏检率和误检率。漏检的图单独看,分析是目标太小、对比度太低还是被遮挡。误检的图也看,多半是灰尘、水渍或反光被误判。把这些困难样本加进训练集,针对性增强,比盲目加数据有效。
我自己的习惯是每训一版模型都留一个「后悔药」:把权重、配置文件、训练命令和当时的验证结果存一个文件夹,命名带日期。小样本训练随机性大,同一份数据不同随机种子结果可能差几个点,没有记录根本复现不了。另外别迷信单次训练的最优结果,多跑几个种子取平均,或者用交叉验证看稳定性,心里才有底。
这套流程走下来,367 张图的价值不在于它本身能训出多强的模型,而在于它让你用最低成本跑通从数据转换、训练、评估到迭代的完整链路。链路通了,换成自己的数据只是替换路径和类别名的事。希望帮到你。
本文还有配套的精品资源,点击获取