ARTICLE DETAIL

资讯详情

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

光伏板航拍鸟粪缺陷检测:367张VOC+YOLO数据集训练全流程与避坑指南

光伏板航拍鸟粪缺陷检测:367张VOC+YOLO数据集训练全流程与避坑指南 简介本数据集面向光伏电站智能巡检与航拍图像缺陷检测方向提供367张光伏板航拍图片聚焦鸟粪这一单一类别缺陷的识别任务适合从事目标检测算法训练、电力巡检系统开发及计算机视觉课程实践的研究者与工程师使用。压缩包共1103个文件包含367张jpg原图、367个VOC格式xml标注文件、367个YOLO格式txt标注文件及少量说明文本整体约23.04MB两种标注格式可直接对接主流检测框架省去格式转换环节。全部标注由labelImg完成采用矩形框方式niaofen类别共标注1421个目标框平均每张图约3.9个实例标注密度较高。目前已有375人学习下载可作为光伏板缺陷检测模型训练与验证的实用数据来源帮助读者快速搭建实验流程、验证检测效果并积累航拍场景下的调参经验。1. 光伏板航拍鸟粪缺陷检测367 张 VOCYOLO 数据集到底能训出什么光伏电站运维里有个很尴尬的现实组件热斑、隐裂、二极管故障这些都能靠红外或 EL 图像抓出来但鸟粪这种遮挡型缺陷红外画面上往往只表现为一个温度略低的暗斑跟积灰、云影、低辐照下的正常温差混在一起靠阈值分割基本抓瞎。真正靠谱的做法是上可见光航拍让模型直接学会「这一坨白色/灰白色不规则斑块是鸟粪不是灰尘也不是反光」。这就是光伏板航拍鸟粪缺陷检测数据集 VOCYOLO 格式 367 张 1 类别要解决的事——它把无人机巡检拍到的组件画面裁成单块组件或组件串标注出鸟粪位置导出成 VOC 和 YOLO 两套标注让你能直接喂给 YOLO 系列做单类目标检测。367 张、1 个类别bird_drop 或类似命名这个规模放在通用检测里算小得可怜但放在「单一电站、单一缺陷、固定拍摄角度」的垂直场景里恰恰是能跑通的最小可用集。它适合两类人一是做光伏智能运维、想快速验证「可见光航拍能不能检出鸟粪」的算法工程师二是手里有无人机、想自己扩数据集但不知道标注规范和格式转换怎么做的现场人员。别指望它直接上生产它的价值是让你在半天内跑通一条从 VOC 到 YOLO、从训练到推理的完整链路然后拿这条链路去接你自己的电站数据。2. 拆开这个数据集VOC 与 YOLO 双格式的目录结构和字段含义2.1 解压后你会看到什么目录树与文件对应关系拿到一个.7z压缩包第一件事不是急着解压训练而是先看清里面到底装了什么。这类「VOCYOLO 格式」的数据集常见做法是把同一批图分别导出两套标注目录结构大致如下具体命名以你解压后为准但逻辑一致# 解压Linux 下需要 p7zipWindows 用 7-Zip 右键即可 7z x 光伏板航拍鸟粪缺陷检测数据集VOCYOLO格式367张1类别.7z -o./birddrop_dataset # 典型目录结构 birddrop_dataset/ ├── JPEGImages/ # 367 张原始 jpgVOC 和 YOLO 共用同一批图 │ ├── 000001.jpg │ └── ... ├── Annotations/ # VOC 格式 xml每张图一个 │ ├── 000001.xml │ └── ... ├── labels/ # YOLO 格式 txt每张图一个 │ ├── 000001.txt │ └── ... ├── ImageSets/ │ └── Main/ │ ├── train.txt # 训练集图片名不带扩展名 │ ├── val.txt │ └── trainval.txt └── classes.txt # 类别名列表通常只有一行 bird_drop这里有个容易翻车的点VOC 的Annotations和 YOLO 的labels是同一批图的两种标注表达不是两批不同的图。很多人第一次拿到双格式数据集以为图也是两套结果把JPEGImages复制了两遍白白占空间还搞乱了路径。2.2 VOC 的 xml 里哪些字段真正影响训练VOC 格式的核心是每张图对应一个 xml里面object节点记录每个目标。对单类别鸟粪检测来说你只需要盯住这几个字段字段含义训练时是否关键filename图片文件名关键必须和 JPEGImages 里一致sizewidth/height图像宽高关键坐标换算依赖它objectname类别名关键必须和 classes.txt 一致bndboxxmin/ymin/xmax/ymax左上右下像素坐标关键转 YOLO 就靠它difficult是否难样本可选YOLO 训练一般忽略pose/truncated姿态/截断单类别场景基本用不上VOC 坐标是绝对像素值且原点在左上角xmax/ymax是包含边界的。转 YOLO 时要先归一化再算中心点和宽高这一步的公式错一位框就会整体偏移。2.3 YOLO 的 txt 为什么只有 5 列以及类别索引从 0 还是 1 开始YOLO 格式每行是class_id x_center y_center width height全部归一化到 0~1。单类别时class_id恒为 0。这里最常见的坑是类别索引从 0 开始而 VOC 的name是字符串转换时必须建立映射表。如果你直接拿 VOC 里第一个类别当 1训练时类别数配成 1模型会报 index 越界或者把所有框都当背景。# 一个最小校验脚本检查 YOLO 标签是否合法 import os def check_yolo_labels(label_dir, num_classes1): bad [] for fn in os.listdir(label_dir): if not fn.endswith(.txt): continue with open(os.path.join(label_dir, fn)) as f: for i, line in enumerate(f): parts line.strip().split() if len(parts) ! 5: bad.append((fn, i, 列数不为5)) continue cid int(parts[0]) if cid 0 or cid num_classes: bad.append((fn, i, f类别索引越界: {cid})) for v in parts[1:]: val float(v) if val 0 or val 1: bad.append((fn, i, f归一化值越界: {val})) return bad issues check_yolo_labels(./birddrop_dataset/labels) print(f发现 {len(issues)} 处问题) for it in issues[:10]: print(it)这段脚本做三件事列数校验、类别索引范围校验、归一化值范围校验。参数num_classes要和你的classes.txt行数一致单类别就传 1。跑完如果输出 0 处问题说明标注基本干净如果有越界优先怀疑转换脚本把xmax当成了宽、或者忘了除以图像宽高。3. 从 VOC 到 YOLO转换脚本、划分策略与三个必调参数3.1 手写一个不依赖第三方库的 VOC→YOLO 转换脚本网上现成的转换脚本很多但大多依赖xml.etree之外还引入了一堆包出错了不好排查。我一般直接手写一个逻辑透明方便你按自己数据改import os import xml.etree.ElementTree as ET # 类别映射VOC 里的 name - YOLO 的 class_id CLASS_MAP {bird_drop: 0} def voc_to_yolo(xml_path, img_w, img_h): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): name obj.find(name).text if name not in CLASS_MAP: continue cid 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) # 归一化中心点与宽高 x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h # 裁剪到 [0,1]防止标注越界 x_center min(max(x_center, 0), 1) y_center min(max(y_center, 0), 1) w min(max(w, 0), 1) h min(max(h, 0), 1) lines.append(f{cid} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) return lines def convert_all(anno_dir, out_dir): os.makedirs(out_dir, exist_okTrue) for fn in os.listdir(anno_dir): if not fn.endswith(.xml): continue xml_path os.path.join(anno_dir, fn) tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines voc_to_yolo(xml_path, img_w, img_h) out_name fn.replace(.xml, .txt) with open(os.path.join(out_dir, out_name), w) as f: f.write(\n.join(lines)) convert_all(./birddrop_dataset/Annotations, ./birddrop_dataset/labels)关键参数说明CLASS_MAP必须和你的classes.txt严格对应多类别时按行号从 0 递增img_w/img_h从 xml 的size读不要用PIL再读一遍图避免图和标注尺寸不一致时出现静默错误最后的裁剪是为了兜住标注人员手抖画出边界的框YOLO 对越界值虽然有时能容忍但训练时容易产生异常梯度。3.2 367 张怎么划分train/val 比例与随机种子的选择367 张是个尴尬数字按 8:2 分是 293/74按 7:3 是 257/110。我的经验是单类别小数据集验证集不要低于 15%也不要高于 25%。低于 15% 时验证指标抖动大高于 25% 时训练样本不够模型欠拟合。推荐 8:2并且固定随机种子保证每次复现一致。import os import random random.seed(42) # 固定种子保证可复现 img_dir ./birddrop_dataset/JPEGImages names [os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.endswith(.jpg)] random.shuffle(names) split int(len(names) * 0.8) train_names names[:split] val_names names[split:] with open(./birddrop_dataset/ImageSets/Main/train.txt, w) as f: f.write(\n.join(train_names)) with open(./birddrop_dataset/ImageSets/Main/val.txt, w) as f: f.write(\n.join(val_names)) print(ftrain: {len(train_names)}, val: {len(val_names)})random.seed(42)不是玄学是为了让你和别人对齐结果。如果你换种子发现 mAP 波动超过 5 个点说明数据分布本身有问题比如某些角度或光照的图全被分到一边了这时候要做的是分层抽样而不是继续调种子。3.3 训练前必须确认的三个参数imgsz、batch、类别数YOLO 训练配置里跟这个数据集强相关的参数有三个参数建议值理由imgsz640航拍图里鸟粪目标通常占几十到几百像素640 能保留细节再大显存吃不消batch8 或 16367 张图batch 太大一个 epoch 只有十几步BN 统计不稳nc类别数1必须和 classes.txt 行数一致写错直接报错或全预测背景imgsz如果你显存够比如 V100 16G可以试 960但要注意放大输入尺寸后小目标召回可能提升但误检也会增加尤其是组件边框反光容易被当成鸟粪。batch在 367 张规模下8 是比较稳的选择配合workers4左右数据加载不会成为瓶颈。4. 用 YOLOv8 跑通第一条基线环境、命令与指标解读4.1 环境搭建一条命令装完依赖别踩 CUDA 版本坑YOLOv8 现在通过ultralytics包安装比早期 YOLOv5 的 clone 方式省事。但 CUDA 版本和 PyTorch 的对应关系仍然是翻车重灾区。我一般先确认驱动支持的 CUDA 上限再装对应 torch# 查看驱动支持的 CUDA 版本 nvidia-smi # 以 CUDA 11.8 为例先装 torch再装 ultralytics pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics # 验证 python -c import torch; print(torch.cuda.is_available(), torch.__version__)如果torch.cuda.is_available()返回 False先别怀疑代码九成是 torch 版本和驱动不匹配。这时候要么降 torch要么升驱动别在代码里找原因。ultralytics装完后yolo命令就可以直接用了。4.2 写一份针对单类别鸟粪检测的 data.yamlYOLOv8 训练靠data.yaml描述数据路径和类别单类别时最容易写错的是names的格式path: ./birddrop_dataset train: ImageSets/Main/train.txt val: ImageSets/Main/val.txt nc: 1 names: 0: bird_drop注意train和val指向的是图片名列表文件不是图片目录。YOLOv8 会去path下找JPEGImages里的图再去labels里找同名 txt。如果你的目录结构和上面不一样要么改 yaml要么改目录别硬凑。names用字典形式0: bird_drop比列表形式更不容易错位。4.3 启动训练与看懂 results.csv 里的关键列yolo detect train \ data./data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch8 \ workers4 \ project./runs \ namebirddrop_baselinemodelyolov8n.pt用的是 nano 预训练权重367 张图从零训容易过拟合用预训练权重做迁移是标准做法。epochs100对这个小数据集通常够但要看早停。训练完在runs/detect/birddrop_baseline/下会有results.csv重点看这几列metrics/mAP50(B)IoU0.5 时的 mAP单类别下就是该类 AP这是主指标。metrics/precision(B)/metrics/recall(B)精确率和召回率鸟粪检测里召回率比精确率更值钱漏检意味着运维人员要爬上去看。train/box_loss框回归损失如果它一直不降检查标注坐标是否归一化错了。如果 mAP50 在 0.6 以上说明这条链路是通的如果低于 0.3先别调模型回去查标注和划分。5. 避坑与排查367 张小数据集训练鸟粪检测的 5 个血泪教训5.1 现象训练 loss 正常下降但验证 mAP 始终为 0原因data.yaml里names的类别名和 YOLO 标签里的class_id对不上或者nc写成了 2 但标签里只有 0。YOLOv8 不会直接报错而是把所有预测都归到不存在的类别上验证时自然全错。解决先跑一遍第 2.3 节的校验脚本确认class_id最大值小于nc再检查names的键是否从 0 开始连续。改完重新训练mAP 会立刻有反应。5.2 现象模型把组件边框、反光点也框成鸟粪原因367 张里如果鸟粪样本大多出现在组件边缘模型会学到「边缘鸟粪」的伪相关。加上航拍图里组件边框本身有高亮反光和白色鸟粪在低分辨率下纹理接近。解决一是做负样本挖掘把明显没有鸟粪但含边框反光的图加进训练集标签为空 txt二是训练时开mosaic和hsv_h/hsv_s/hsv_v增强让模型对颜色和位置不那么敏感。YOLOv8 默认增强已经比较强但小数据集建议把hsv_v调到 0.5 左右模拟不同光照。5.3 现象验证集 mAP 波动大换个种子差 10 个点原因367 张里可能只有几十张包含鸟粪且分布不均。随机划分时某些验证图里的鸟粪角度在训练集里完全没出现过。解决改成按「拍摄批次」或「组件串」分层划分保证训练和验证覆盖相似的场景。如果数据里没有批次信息至少按图像亮度或拍摄角度做一次聚类再分。固定种子只是保证可复现不解决分布问题。5.4 现象推理时框的位置整体偏移或者框比实际大一圈原因VOC 转 YOLO 时xmax/ymax是否包含边界、是否用了img_w而不是xml里的width这两个细节任一出错都会导致系统性偏移。另外如果标注时用的是缩放后的图但 xml 里写的是原图尺寸也会偏。解决抽 5 张图用 OpenCV 把 YOLO 标签画回图上肉眼看框是否贴合。代码很简单import cv2 def draw_yolo(img_path, label_path): img cv2.imread(img_path) h, w img.shape[:2] with open(label_path) as f: for line in f: 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, 255, 0), 2) cv2.imwrite(check.jpg, img) draw_yolo(./birddrop_dataset/JPEGImages/000001.jpg, ./birddrop_dataset/labels/000001.txt)如果画出来框明显偏回去查转换脚本里的除法分母。5.5 现象训练到一半 BN 层报错或 loss 变 NaN原因batch 太小比如 2 或 4时BN 的统计量不稳定加上学习率没降容易梯度爆炸。367 张图如果 batch 设成 2一个 epoch 要 180 多步前期波动很大。解决把 batch 提到 8 或 16同时把lr0从默认 0.01 降到 0.005 左右。如果显存不够用accumulate做梯度累积等效放大 batch。YOLOv8 里没有直接的 accumulate 参数但可以通过减小batch同时调低学习率来缓解。6. 把 367 张用出 3670 张的效果离线增强与半自动标注的衔接技巧小数据集的终极出路不是调模型而是把数据规模做上去。367 张直接训mAP 天花板大概在 0.7 左右如果能扩到 1500 张以上同样的模型能到 0.85 以上。这里说两个我实际用过的衔接技巧。第一个是离线增强 难例回灌。不要只依赖训练时的在线增强因为在线增强每个 epoch 看到的还是那 367 张的变换。做法是先用基线模型对一批未标注的航拍图做推理把置信度在 0.3~0.6 之间的框导出来人工确认后转成 YOLO 标签加入训练集。这批「模型觉得像但不确定」的样本信息量最大。代码上就是把yolo detect predict的save_txtTrue打开然后写个脚本把 txt 里的框按置信度过滤yolo detect predict \ model./runs/detect/birddrop_baseline/weights/best.pt \ source./unlabeled_imgs \ conf0.3 \ save_txtTrue \ save_confTrue \ project./pseudosave_confTrue会在每行末尾多一列置信度方便你按阈值筛。筛完后人工过一遍把误检删掉就是一批高质量新标注。第二个是用类别平衡采样代替随机采样。367 张里如果含鸟粪的图只有 200 张另外 167 张是负样本训练时负样本会被反复抽到导致模型偏向预测背景。可以在data.yaml同级写一个采样权重文件或者在生成train.txt时对正样本重复列几次。YOLOv8 本身不直接支持按文件加权但你可以通过复制正样本图片名到train.txt里实现过采样。注意别复制太多正负比控制在 1:1 到 2:1 之间。最后一个习惯每次改完数据或参数先跑 10 个 epoch 看 loss 曲线别一上来就 100 epoch。小数据集上前 10 个 epoch 的验证 mAP 如果完全不涨后面大概率也不会涨早点停下来查数据比等结果划算。这套流程我踩过的坑基本都在第 5 章里了希望帮到你。本文还有配套的精品资源点击获取
返回列表