光伏板鸟粪检测数据集:VOC+YOLO双格式367张图实战指南
发布时间:2026/9/24 22:43:48来源:尧图网络
简介一套面向光伏板航拍场景的鸟粪缺陷检测数据集包含三百六十七张原始航拍图片统一采用矩形框标注共标记出一千四百二十一个鸟粪目标类别名称使用鸟粪的拼音表示适合光伏组件污损识别、无人机巡检等目标检测任务。每张图片均同步提供Pascal VOC格式的XML标注和YOLO格式的TXT标注标注文件与图片一一对应可直接用于主流检测模型的训练与验证省去数据格式转换环节。压缩包共包含一千一百零三个文件其中图片三百六十七张、XML标注三百六十七个、TXT标注三百六十九个整体体积约二十三兆字节传输和存储都非常方便。资源由LabelImg工具逐张绘制边界框与目标贴合且只有单一类别便于专注评估模型基础性能、调整超参数或进行消融实验。目前已有三百七十五人浏览学习对需要快速获取标注数据开展算法验证的开发者而言是一份移步即用的训练基础。1. 光伏板航拍鸟粪缺陷检测数据集VOCYOLO格式367张小样本怎么撬动光伏巡检问题光伏电站巡检里鸟粪是被低估的高频缺陷。一块组件上落几坨鸟粪遮挡区域就会形成热斑日积月累轻则功率衰减重则把电池片烧穿。而做检测的人往往卡在第一步没有干净的、带标注的真实数据。这份光伏板航拍鸟粪缺陷检测数据集VOCYOLO格式正好补上这个缺口——367张航拍图统一命名为firc_gfb_编号.jpg每张图配套一个VOC格式的xml和一个YOLO格式的txt标注类别只有一类“niaofen”共1421个矩形框。这个规模打不了比赛但足够把一条YOLO训练、评估、推理的完整流水线跑通。数据集的定位也很直接只保证标注准确合理对最终模型精度不作任何背书。适合刚接触工业缺陷检测、想验证自己pipeline又不想四处拼图的人也适合光伏运维团队用来做热斑隐患检测的前期验证。2. 数据集结构拆解VOC与YOLO双标注的目录布局和文件对应关系2.1 压缩包内文件与命名规律解压这个.7z压缩包后按摘要描述应包含三份对等文件jpg原图367张、VOC格式的xml文件367份、YOLO格式的txt文件367份三者一一对应。没有额外的训练集、验证集划分文件也没有分割路径的txt。目录具体叫什么名字以你解压出来的实际结果为准但通常会按images、labels、annotations这类约定组织。我建议拿到后先不要改目录名直接复制一份再操作。文件类型数量命名示例作用JPG原图367firc_gfb_7.jpg航拍原图训练输入VOC标注xml367firc_gfb_7.xml矩形框像素坐标labelImg生成YOLO标注txt367firc_gfb_7.txt归一化坐标YOLO训练实际读取文件名的数字段并不连续从firc_gfb_1.jpg到firc_gfb_352.jpg中间缺了很多编号说明这些图来自多架次采集后统一重命名。编号顺序不代表拍摄时序的严格连续这一点在后面做数据集划分时要注意如果直接按编号区间切分可能把不同光照、不同高度下的图片分别塞进训练集和验证集导致分布偏斜训练出来的模型在验证集上虚高、在真实场景里翻车。2.2 VOC格式xml逐字段解析VOC格式的xml是labelImg的默认输出格式也是很多标注工具通用的交接格式。打开firc_gfb_7.xml核心字段如下annotation folderfirc_gfb/folder filenamefirc_gfb_7.jpg/filename path/some/path/firc_gfb_7.jpg/path size width1920/width height1080/height depth3/depth /size object nameniaofen/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin326/xmin ymin198/ymin xmax412/xmax ymax273/ymax /bndbox /object /annotation先说size节点width和height是原图的像素宽高这是所有坐标换算的基础。这份数据集的航拍图尺寸大概率是统一的但写转换脚本时不能假设所有图都一样大必须逐张读取xml里的size。path字段存的是标注时图片的绝对路径可能指向标注员机器的某个目录对训练没有意义可以直接忽略。再说object节点name固定是niaofen表示鸟粪truncated和difficult基本都是0表示目标没有超出图像边界、也不属于难例。bndbox里的xmin/ymin/xmax/ymax是框的左上角和右下角像素坐标。注意这个坐标系以左上角为原点x向右增大y向下增大和数学坐标系的方向不同转换时不要习惯性地把y方向反过来。这份数据集比较干净的地方在于只有一个类别、没有多标签重叠的情况367份xml里的object结构完全一致很适合拿来做格式解析和转换脚本的基准数据。如果你后续上手的是多类别VOC数据集解析逻辑会复杂不少但字段结构是同一个套路。2.3 YOLO格式txt与坐标归一化换算YOLO的txt标注是每一行一个目标格式为class_id x_center y_center width height。五个值全是浮点数class_id固定为0因为只有niaofen一类后四个值是归一化坐标范围落在0到1之间。和VOC的像素坐标不同归一化坐标不依赖原图分辨率同一份标注在1920x1080和960x540的图上都能直接用。从VOC转YOLO的换算公式很直观# convert_voc_to_yolo.py 核心逻辑 img_w 1920 # 来自 xml/size/width img_h 1080 # 来自 xml/size/height xmin, ymin, xmax, ymax 326, 198, 412, 273 x_center ((xmin xmax) / 2) / img_w y_center ((ymin ymax) / 2) / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h # 输出: class_id x_center y_center width height print(f0 {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f})这里的换算分两步走先求出中心点的像素坐标再除以对应方向的像素数。宽和高分别除以img_w和img_h不能共用一个除数否则框的纵横比会失真画回图上就变成拉宽或压扁的矩形。一个容易踩的坑是精度问题。txt里如果只保留3位小数对于宽1920的图中心坐标会产生约1个像素的误差单个框不明显但训练时多个框的累计误差会引入噪声。我一般生成txt时保留6位小数labelImg导出时也是这个精度。你可以打开任意一个txt文件观察数值的有效位数如果发现都是3位左右建议用xml重新生成一遍不要直接沿用。2.4 双格式的真正价值备份与转COCO的桥梁txt丢失或损坏时xml就是后悔药。每一份xml都完整保留了像素坐标和类别信息只要原图还在随时可以重新生成txt。反过来不行txt是归一化坐标缺了原图宽高就无法还原成像素框。所以我处理这类数据集时有个原则把VOC标注当母版YOLO txt只当作供训练使用的派生文件。另外如果你以后想换到mmdetection或Detectron2COCO格式是绕不开的。COCO的bbox字段定义为[x, y, width, height]像素坐标正好可以由VOC的bndbox直接换算# voc_to_coco.py 片段 # 假设已解析出 xmin, ymin, xmax, ymax coco_bbox [ xmin, ymin, xmax - xmin, ymax - ymin ]这个转换在VOC侧更顺手因为VOC本身就是像素坐标不需要像YOLO txt那样先知道图像尺寸才能反推。这也是我判断一份数据集是否好用的标准能不能方便地转成其他格式而不是绑定在某一个框架上。3. 把数据集跑进YOLOv8训练环境配置、数据YAML与训练命令3.1 环境准备YOLOv8安装与显存判断367张图、1421个框的数据量不大yolov8n在8G显存的消费级显卡上就能训练yolov8s建议16G显存。我个人的习惯是先装好ultralytics用nano模型跑通全流程再换更大的模型看精度是否提升。环境安装命令如下conda create -n yolo python3.10 -y conda activate yolo pip install ultralytics pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118第一步是新建conda环境Python版本3.10是ultralytics比较稳妥的选择。第二步装ultralytics它会自动拉取依赖。第三步是装GPU版PyTorch指定CUDA版本为11.8这条命令只对NVIDIA显卡有效。如果你的驱动版本低于对应的CUDA要求导入torch时会报错此时需要改装CPU版或降低CUDA版本。验证安装是否成功运行下面这条命令输出True就说明GPU可用python -c import torch; print(torch.cuda.is_available())如果输出False多半是torch装成了CPU版卸载后重新走一遍带cu118的安装命令。这一步花不了十分钟但很多人卡在这因为ultralytics和torch是两个独立包前者不会自动捆绑GPU版torch。3.2 训练目录搭建与data.yaml配置YOLOv8对数据目录有硬性要求images/train和labels/train配对images/val和labels/val配对标签与图片同名但扩展名不同。先建目录结构cd firc_gfb_dataset mkdir -p images/train images/val labels/train labels/val然后把划分好的图片和txt分别放进去。划分脚本我放在第4章讲这里强调命名匹配规则YOLOv8找标签时是把图片路径里的images替换成labels、jpg替换成txt来定位的。目录名必须叫images和labels不能换成imgs或label_txt否则训练时会提示找不到标签。data.yaml放在数据集根目录内容如下# data.yaml path: /home/user/firc_gfb_dataset train: images/train val: images/val nc: 1 names: 0: niaofen字段值说明path数据集根目录绝对路径相对路径容易受当前工作目录影响建议写绝对路径trainimages/train训练图片目录相对于pathvalimages/val验证图片目录相对于pathnc1类别数只有niaofennames0: niaofenclass_id到类别名的映射names的写法有两种[niaofen]或{0: niaofen}效果等价。重点在顺序txt里的class_id是0对应names里的第一个名字。如果你自己扩充数据新增类别的class_id按0、1、2递增names顺序必须一致不要用类别名的首字母排序。提示改完data.yaml后先跑一次yolo val modelyolov8n.pt datadata.yaml它会提前暴露目录或标签格式问题比直接train少等很长时间。3.3 训练命令与关键参数说明目录和yaml都准备好后训练命令如下yolo train modelyolov8n.pt datadata.yaml \ epochs150 imgsz640 batch16 device0 patience30逐项说明参数含义modelyolov8n.pt用COCO预训练权重初始化网络。367张图的数据量不足以训练一个从零开始的深度网络迁移学习在这里是必须的。imgsz640训练输入分辨率。默认640如果你的鸟粪在图中很小建议提到960代价是显存占用大约变成原来的2.25倍。batch16批大小。8G显存跑yolov8n建议8到16训练中显存不足优先降这个值。epochs150训练轮数。单类小目标在这个量级上通常100到200轮收敛设太多容易过拟合。patience30验证集指标连续30轮不提升就早停避免无效计算。训练结束后best.pt是验证集上指标最好的权重last.pt是最后一轮权重。用best.pt单独跑一次验证yolo val modelruns/detect/train/weights/best.pt datadata.yaml输出里重点看mAP50和mAP50-95。对单类小目标mAP50达到0.7以上说明流程已经走通mAP50-95偏低属于正常现象小目标对IoU阈值比大目标敏感得多。3.4 训练日志与输出文件的读法很多人训练完只看最后一行mAP我建议多看三个文件results.png、confusion_matrix_normalized.png和val_batch0_pred.jpg。results.png里有box_loss、cls_loss和mAP曲线loss曲线一路下降后走平说明模型在收敛如果loss还在降但mAP不涨基本是过拟合该回去调整增强参数或提前早停。val_batch0_pred.jpg是验证集第一个batch的预测结果框叠加在原图上。这个图能直观看出预测框是不是紧贴目标、有没有把背景当目标。对小数据集来说这几个图的信号价值远大于loss数字本身毕竟367张图训练出来的模型评估指标波动本来就比大数据集大不少。4. 数据校验与可视化训练前必须做掉的四个检查4.1 用统计脚本核查框数与框尺寸拿到数据集的第一件事不是train而是统计。我写了一个简短脚本输出总框数、含框图片数和极小框占比# check_stats.py from pathlib import Path label_dir Path(labels) total_boxes 0 img_with_boxes 0 tiny_boxes 0 for txt in sorted(label_dir.glob(*.txt)): lines [l for l in txt.read_text().strip().splitlines() if l.strip()] if lines: img_with_boxes 1 total_boxes len(lines) for line in lines: cid, xc, yc, w, h map(float, line.split()) if w 0.05 or h 0.05: tiny_boxes 1 print(f总框数: {total_boxes}) print(f含框图片数: {img_with_boxes} / 367) print(f极小框占比: {tiny_boxes / total_boxes:.1%})脚本逻辑很简单遍历所有txt累加文件行数为总框数记录至少有一个框的图片数量再把宽或高小于原图5%的框记为极小框。这个数据集的期望值是总框数1421、含框图片数不超过367、极小框占比取决于航拍高度和鸟粪实际大小。如果统计结果和1421对不上先检查是不是目录没复制全如果含框图片数只有300张左右说明有六七十张空图。空图在训练阶段没有loss贡献但在验证阶段会稀释mAP的可信度因为模型预测出任何框都可能被算作假阳性。极小框占比超过30%就该考虑把imgsz提到960。4.2 把txt坐标画回原图做可视化核验统计只能暴露数量问题坐标是否画在鸟粪上还得靠人眼。下面的脚本从txt读取归一化坐标换算成像素后直接在原图上画红色矩形# draw_yolo.py import cv2 from pathlib import Path def draw_yolo_boxes(img_path: str, txt_path: str, out_path: str): img cv2.imread(img_path) h, w img.shape[:2] for line in Path(txt_path).read_text().strip().splitlines(): cid, 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, 0, 255), 3) cv2.imwrite(out_path, img) draw_yolo_boxes(images/firc_gfb_7.jpg, labels/firc_gfb_7.txt, check_firc_gfb_7.jpg)逻辑说明xc和yc是归一化中心bw和bh是归一化宽高。中心坐标减去半个宽高就是左上角加上半个宽高就是右下角再乘以原图宽高还原像素坐标。这里的乘法必须用浮点数最后用int()取整一次不要在中间过程取整否则小框会偏移好几个像素。画完随机抽20张重点看两类问题。第一类是框把大片光伏板边缘也圈了进来而不是紧贴鸟粪说明标注时框画大了模型学到的目标范围偏大定位精度上不去。第二类是框完全落在光伏板深色区域里看不到白色鸟粪说明坐标和图片错位多半是txt与jpg文件名没有一一对应。4.3 确定性划分用固定随机种子切分训练验证集因为原始数据没有划分文件你需要自己处理。我不建议用文件名排序后按比例切那会把不同架次的数据按编号顺序拆开导致分布偏斜。更稳妥的做法是随机打乱后按比例划分并且固定随机种子保证可复现# split_train_val.py import random from pathlib import Path import shutil random.seed(42) image_files sorted(Path(images).glob(*.jpg)) random.shuffle(image_files) val_count int(len(image_files) * 0.1) val_set set(image_files[:val_count]) for img in image_files: sub val if img in val_set else train stem img.stem shutil.copy2(img, Path(images) / sub / img.name) txt Path(labels) / f{stem}.txt shutil.copy2(txt, Path(labels) / sub / txt.name)这里用shutil.copy2保留原始目录的文件避免移动后想重新划分却找不到原图。10%是考虑到数据集本身只有367张验证集再小评估指标的方差会很大如果你对稳定性要求更高可以改为20%即74张进验证集训练集变成293张仍然够用。划分完必须检查两个数images/train里的jpg数量与labels/train里的txt数量相等验证集同理。任何一边对不上都要回到上一步排查。4.4 训练前的最后一道把关做完统计、可视化和划分我还会再做一次交叉验证随机抽10张验证集图片跑一个5个epoch的快速训练确认loss能降、能在图上画出框。这一步起冒烟测试的作用能在半小时内暴露大部分配置问题。5个epoch的loss不下降不代表模型有问题可能只是学习率太低但5个epoch就loss飞掉或直接NaN那一定是数据或配置层面的问题。数据校验做到位训练参数即使全用默认值通常也能得到合理结果这比盲目调参靠谱得多。5. 避坑与排查在367张小数据集上踩过的5个具体问题这里记录的5个问题都来自实际操作每一条按现象、原因、解决的顺序写方便直接对照排查。5.1 训练时报“No labels found”且直接中断现象yolo train执行后一两秒就退出日志里出现train: No labels found in /path/to/labels/train之类的提示。原因最常见的是data.yaml里的path写成了相对路径或者labels/train目录里没有与图片同名的txt。YOLOv8通过把图片路径中的images替换成labels来找标签如果目录名写成了label少写复数s或者标签放在Annotations目录下都会定位失败。解决先把path改成绝对路径确认目录结构是images/train和labels/train配对。接着用ls images/train | wc -l和ls labels/train | wc -l比对数两边的个数必须一致。改完之后先用yolo val加载一次配置它会比train更早暴露路径问题。5.2 VOC的xml与jpg文件名对不上导致框错位现象可视化时某张图上出现了明显不属于它的框或者框整体偏移核对后发现firc_gfb_7.xml里记录的filename不是当前图片的名字。原因labelImg保存xml时filename字段记录的是打开图片时的文件名。如果你在实际操作中批量重命名过图片xml不会跟着更新后续按新文件名匹配时就对不上号。这个问题的隐蔽性在于它不会报错只会让模型学得莫名其妙。解决写脚本遍历所有xml以filename为key建立映射再以当前实际存在的jpg文件名为准把xml里的filename反写回去。改完再跑一遍第4.2节的可视化脚本确认文件名、xml文件名和内部filename三者一致。这个操作趁早做越往后积累的错位越多。5.3 从VOC转YOLO时坐标换算错误现象画框检查时部分框明显偏向右下方还有一些框超出了图像边界但框的形状和大小看起来是对的。原因换算公式写错最常见的是中心坐标没除以2把xminxmax直接当成中心坐标另一种错误是宽高统一除以同一个数导致横向或纵向变形。解决在转换脚本里增加越界断言任何一个归一化值不在[0,1]范围内就抛出异常# assert_coords.py assert 0 x_center 1, fx_center out of range: {x_center} assert 0 y_center 1, fy_center out of range: {y_center} assert 0 width 1 and 0 height 1, size out of range再加上随机抽20张图做可视化复核基本能把这类问题全部拦下。坐标换算的技术含量不高但它是所有后续工作的地基地基偏了一点点模型精度就只能靠玄学。5.4 验证mAP不错新图漏检却很多现象验证集mAP50到了0.75模型部署到新的航拍图上细小鸟粪漏掉一多半误检倒是很少。原因验证集和训练集来自同一批数据场景分布、拍摄角度和光照条件高度一致评估指标天然偏乐观。而新图上的目标可能更小、对比度更低模型训练时没见过足够多的这类难例。解决优先把imgsz从640提到960输入分辨率提升对小目标的收益最大。然后从新图中挑几十张漏检图用labelImg补齐标注后加入训练集做增量训练。部署阶段的置信度阈值也可以从0.25降到0.15多出的误报用面积过滤或位置过滤的规则处理。这四件事按顺序做不要一上来就换大模型换模型的收益往往不如数据增量。5.5 验证曲线锯齿大、mAP震荡现象训练loss平滑下降但验证集的mAP曲线波动很大相邻两个epoch之间能差出0.1以上。原因验证集只有37张图的时候每张图对整体指标的影响太大如果某张图框数特别多那张图的预测结果就会显著拉高或拉低mAP。这是小数据集评估方差大的典型表现不是模型结构出了问题。解决把验证集比例从10%提高到20%让验证集包含更多图和更多框指标会更平滑。同时把patience适当调大到40避免在验证集震荡期间被早停打断。评估时以mAP最高的那一次为准而不是平均表现这是处理小数据集评估噪声的常用办法。6. 模型验收用mAP曲线、混淆矩阵和滑窗推理三关验证训练结束不代表模型能用在光伏缺陷检测这种场景里误报和漏检都会直接变成运维成本。我给自己定了一个三关验收流程。第一关是验证集指标。看mAP50和mAP50-95两个数mAP50对单类小目标有实际参考意义mAP50-95偏低不用太紧张小目标的定位精度对IoU阈值本就敏感。真正要警惕的是两者差距过大说明模型框得不够准原因多半出在标注框没有紧贴目标而不是网络结构不够深。如果mAP50连0.6都到不了回头查数据分布和标注质量比继续加训练轮数有效得多。第二关是看混淆矩阵和PR曲线。只有一类目标时混淆矩阵主要看背景被误判成鸟粪的比例这个值高误报率就压不住。解决办法是把推理置信度阈值从默认的0.25提到0.35或0.4误报会明显下降代价是漏检轻微增加。阈值的具体取值需要结合应用场景里两类错误的成本来定没有标准答案。PR曲线如果在中段出现断崖说明有一批样本特别难检把它们挑出来单独看往往能发现共性的标注或光照问题。第三关是滑窗推理。光伏航拍原图分辨率高直接把整张图喂给模型小目标信息会被缩放到几乎不可见。我的做法是把新图切成640x640的patch重叠率0.2逐patch推理后把检测框坐标加回原图偏移量再合并重叠框。重叠率0.2是我在多组测试里比较平衡的值太大会产生大量重复框太小会在patch边缘漏目标。合并重叠框用NMSIoU阈值设0.45比较合适。从那以后我拿到任何数据集都强制走一遍“统计-可视化-划分校验-训练-三关验收”这套动作已经变成肌肉记忆。367张图的数据集没有太多试错余地每一步省下来的检查时间最终都会变成训练失败后重新调数据的成本。希望这篇笔记里的脚本和参数能帮你把这段流水线跑顺少走我走过的弯路。本文还有配套的精品资源点击获取
网站建设高端定制企业官网