简介:面向光伏发电板红外巡检与目标检测场景,这份数据集收录了173张真实红外图像,并针对鸟粪这一典型遮挡物提供了完整标注。图像统一为jpg格式,配套Pascal VOC标准的xml标注文件和YOLO标准的txt标注文件,可直接用于Faster R-CNN、YOLO系列等主流检测模型的训练与评估,省去自行采集数据和标注的环节,也可作为VOC与YOLO格式对照学习的样例。数据集中仅设guano一个类别,共574个矩形框,全部统一使用labelImg工具绘制,标注边界清晰、格式规范,便于开展算法对比和迁移学习。压缩包采用7z压缩,共521个文件,含173张jpg图片、173个xml标注、173个txt标注等,整体大小仅7.92MB,下载和存储都很轻量。目前已有446人学习使用,适合光伏运维智能化、计算机视觉方向的研究者与学生用于遮挡目标检测、数据格式转换及标注质量分析等实践。
1. 光伏板红外图像鸟粪检测:173张图能训练出一个能用的模型吗
光伏电站巡检拍到一张红外图,组件上有几块鸟粪,温度异常区域看着刺眼,但人眼盯屏幕找太慢,想用目标检测模型自动找。手头这份数据集只有173张图、1个类别,还是VOC和YOLO双格式的压缩包,第一反应往往是“这么点数据能训出什么”。实际做过就知道,红外场景下能用、敢用的模型,不一定需要几千张图,173张认真处理的小样本足够跑通完整流程,关键在格式转换不出错、训练策略不踩坑。这篇文章就把这套数据集从解压到训练到验证的完整路径拆开讲,适合手上有一批电力红外图像、正准备用YOLO起步的从业者。
2. VOC与YOLO双格式拆解:173张图的标注信息到底长什么样
拿到.7z压缩包,第一步不是急着训练,而是先把两种格式的目录结构和标注内容摸清楚。VOC和YOLO是目标检测领域最常用的两种标注格式,前者是XML描述边界框,后者是纯文本归一化坐标,同一份数据同时给两种格式,等于把标注信息重复存放了一份,方便切换不同的训练框架。
2.1 VOC格式的目录与XML字段:原始标注长什么样
解压后常见做法是看到Annotations、JPEGImages、ImageSets三个目录,这是VOC系列数据集沿袭下来的目录约定。JPEGImages放红外图像,Annotations放同名XML标注文件,ImageSets/Main下的txt文件记录哪些图划分到train、val或test。
打开一个XML文件,重点看这几个字段:
<annotation> <filename>IR_0001.jpg</filename> <size> <width>640</width> <height>512</height> <depth>3</depth> </size> <object> <name>bird_dropping</name> <bndbox> <xmin>120</xmin> <ymin>88</ymin> <xmax>156</xmax> <ymax>124</ymax> </bndbox> </object> </annotation>这里要注意size里的depth字段。很多红外数据集虽然是灰度图,但保存成JPG时被转成了三通道,depth写3也正常。后面转YOLO格式时要以实际读图结果为准,不能只看XML头。
bndbox四个值是像素坐标,xmin和ymin是左上角,xmax和ymax是右下角。一个XML里可能有多个object节点,对应多块鸟粪区域。做这个数据集标注时通常会遵循“只要遮挡引起的温度异常区域都框”的原则,所以边界框之间允许有重叠,训练时模型也能学到多目标同时出现的场景。
2.2 YOLO格式的txt与类别映射:转换后每一行代表什么
YOLO格式的标签文件与图像同名,扩展名换成.txt,每行描述一个目标,字段依次是class_id x_center y_center width height。注意后四个值全部做了归一化,除以图像的宽和高,所以取值范围在0到1之间。类别只有1个,class_id固定为0,navmapping文件里通常写:
0: bird_droppingYOLO格式之所以流行,是训练时直接按行读取归一化坐标,不用每次去解析XML,IO效率高很多。但代价是如果原图尺寸不固定,归一化之后的小目标坐标很容易丢精度,这一点在4.4节再展开。
有几点容易忽略:VOC标注越界的框(边界坐标超出图像宽高)在YOLO格式里必须裁剪,否则训练会报错;反向转换时还要从归一化坐标乘以原图宽高,取整得到像素框。
2.3 VOC转YOLO的最小脚本:拷贝即用的转换与检查
我一般会在项目里放一个voc2yolo.py,一次性完成XML解析、坐标归一化、txt写盘和空标注检查。为了不依赖第三方库,只用了Python标准库xml.etree.ElementTree,在任何装了Python的环境里都能跑。
import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_path, output_dir, class_map): tree = ET.parse(xml_path) root = tree.getroot() # 读取图像实际尺寸,来自XML的size节点 size = root.find('size') img_w = int(size.find('width').text) img_h = int(size.find('height').text) lines = [] for obj in root.findall('object'): name = obj.find('name').text if name not in class_map: continue # 只保留需要的类别 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) # 坐标越界裁剪,防止负数或超出宽高 xmin = max(0.0, min(xmin, img_w)) xmax = max(0.0, min(xmax, img_w)) ymin = max(0.0, min(ymin, img_h)) ymax = max(0.0, min(ymax, img_h)) # 防止标注退化成一个点 if xmax - xmin < 1 or ymax - ymin < 1: continue x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{class_map[name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") output_path = Path(output_dir) / (Path(xml_path).stem + ".txt") output_path.write_text("\n".join(lines), encoding="utf-8") return len(lines)调用时只需要准备好class_map和目录:
class_map = {"bird_dropping": 0} xml_dir = Path("Annotations") out_dir = Path("labels") out_dir.mkdir(exist_ok=True) empty_count = 0 for xml_file in xml_dir.glob("*.xml"): count = voc_to_yolo(xml_file, out_dir, class_map) if count == 0: empty_count += 1 print(f"警告: {xml_file.stem} 没有有效目标") print(f"转换完成,共处理 {len(list(xml_dir.glob('*.xml')))} 个xml,空标注 {empty_count} 个")这段脚本的逻辑不复杂,但有三个参数值得说明。class_map决定类别ID,目前只有1类,写死{"bird_dropping": 0}即可;坐标裁剪能避免训练时报“bbox坐标越界”的错;保留6位小数是给后续训练精度留余量。转换完一定要跑一遍空标注统计,因为173张图里大概率存在没有鸟粪的负样本图,负样本在目标检测里同样重要,不能因为空txt而把它删掉。
3. 用YOLO在本地训练鸟粪检测模型:环境、命令与参数设置
格式转换之后,最焦虑的一步就是“跑通训练”。其实YOLO的训练流程已经被工具链封装得很简单,常见做法是选YOLOv8或YOLOv5系列,以YOLOv8为例,整个流程只需要三步:建环境、写data.yaml、敲一条train命令。
3.1 环境准备:在Anaconda里装好训练依赖
用Anaconda建独立环境是避免依赖冲突最省心的方式。我习惯建一个Python 3.10的环境,然后安装ultralytics包,它会把训练、验证、导出一次打包好。
conda create -n yolo python=3.10 -y conda activate yolo pip install ultralytics如果机器有NVIDIA显卡,先确认PyTorch是否拿到CUDA版本。pip install ultralytics默认会装一个CPU或通用版PyTorch,训练速度差别很大。检查方式是在Python里执行:
import torch print(torch.cuda.is_available())输出True再继续,否则去PyTorch官网选对应CUDA版本的安装命令。这个环境配置要求不算高,1080Ti级别的显卡训173张图绰绰有余,只有CPU也能跑,只是慢一些。
3.2 整理目录与生成data.yaml:训练前的最后一步
训练前要把数据组织成YOLO约定的目录结构,并准备一个data.yaml描述路径和类别信息。目录结构一般是:
datasets/ bird_dropping/ images/ train/ val/ labels/ train/ val/ data.yaml划分比例建议按9:1来做,173张图里约156张训练、17张验证。因为只有1个类别且目标稀疏,不要按5:5切,验证集太少会让指标波动很大,这一点第4章还会展开。
data.yaml内容如下:
path: datasets/bird_dropping train: images/train val: images/val names: 0: bird_dropping这里path建议写绝对路径,避免YOLO在相对路径解析上出幺蛾子。类别顺序必须与class_map一致,不能改。names是字典形式,YOLOv8要求写一个list或dict,如果只写names: [bird_dropping]也能跑,但配合key写更清晰。
3.3 启动训练:命令与参数说明
核心训练命令如下:
yolo detect train \ data=datasets/bird_dropping/data.yaml \ model=yolov8n.pt \ epochs=200 \ imgsz=640 \ batch=16 \ patience=30 \ optimizer=AdamW \ seed=42 \ project=runs/detect \ name=bird_dropping_trial参数说明写在下面这张表里。
| 参数 | 推荐值 | 作用与说明 |
|---|---|---|
| model | yolov8n.pt | 预训练权重,小样本迁移学习的起点,n是轻量版训练快、显存低 |
| epochs | 200 | 配合patience早停,实际跑不到200轮 |
| imgsz | 640 | 与红外原图分辨率一致最好,但显存紧张可降到512 |
| batch | 16 | 受显存限制,单卡16较稳,小于8时注意BN崩溃风险 |
| patience | 30 | 验证集指标连续30轮不提升就停止 |
| optimizer | AdamW | 小数据集上比SGD收敛更稳,不容易出现loss震荡 |
| seed | 42 | 固定随机种子,保证结果可复现 |
model=yolov8n.pt会第一次运行时自动下载预训练权重,这是正常的,下载的是COCO预训练权重,迁移到红外场景能明显提升早期收敛速度。如果网络受限,也可以先手动下载权重文件放到项目目录,再把model参数改成权重文件的路径。
3.4 训练输出物:哪些文件值得看,哪些不用管
训练结束后,runs/detect/bird_dropping_trial目录下会生成一堆文件。值得关注的是:
weights/best.pt:验证集指标最优的权重,后续推理、部署都用它。weights/last.pt:最后一轮权重,有时best过拟合而last泛化更好,可以两个都试。confusion_matrix.png:预测类别与真实类别的混淆矩阵。results.png:训练损失、验证mAP、precision、recall随epoch变化的曲线。val_batch_pred.jpg:验证集预测可视化,快速判断模型效果。
剩余一堆labels_*.jpg、results.csv之类的文件属于过程产物,初次跑通时不用每个都研究,先看val_batch_pred.jpg和results.png有没有异常,再决定是否调参。
4. 红外图像鸟粪检测避坑:五条必须提前知道的血泪经验
小样本红外检测的坑比常规可见光目标检测多得多。这五条经验来自实测中使用这份数据集或类似电力红外数据集的常见问题,每一条都按“现象、原因、解决”写清楚。
4.1 黑白红外图被当成三通道:数据增强全部失效
现象是训练时results.png里训练损失能正常下降,但验证mAP始终在低位徘徊,而且跑完看val_batch_pred.jpg,发现检测框和图像内容对不上。根源在于红外图本质是单通道灰度,保存成JPG时如果被强行转成三通道,三个通道内容完全相同,模型等于在看冗余输入,而YOLO默认的随机颜色扰动、HSV增强会把这种“伪三通道”搅得面目全非。
解决方法是训练前统一图像通道策略。如果原图是灰度,就保持单通道读取,并在推理时也走同样的预处理。用OpenCV读取时用cv2.imread(path, cv2.IMREAD_GRAYSCALE),再扩展成三通道复制,避免颜色增强破坏温度信息。这个处理最好写成一个预处理脚本,把173张图全部过一遍,确认像素值分布正常后再进训练。
4.2 盲元被学习成鸟粪:先做盲元检测与替换
现象是模型在干净的无鸟粪光伏板上频繁输出假阳性,而且这些假阳性位置在每张图里几乎相同。原因是红外探测器存在盲元,表现为固定位置的亮点或暗点,人眼能分辨,但模型会把它们当作小目标学进去。尤其鸟粪区域在红外下也是亮点,盲元和鸟粪的灰度特征高度相似。
解决思路是做盲元检测与替换。如果机芯支持两点校正,在采集端先跑校正流程,生成两点校正参数后再出图,这是最理想的。如果数据集已经形成,无法回溯采集端,就在预处理阶段记录全图像素值长期不变的固定点,对每个盲元用周围3×3邻域的中值替换。这个操作不影响鸟粪标注框,又能显著降误检。
4.3 batch太小导致BN崩溃:loss变成nan
现象是训练到第几个epoch时loss突然跳到nan,之后指标全部归零,怎么调学习率都没用。原因是173张图本身数据量小,如果batch设成4或更小,BatchNorm层的均值方差统计量在少量样本上极不稳定,出现数值溢出。
解决方法是把batch至少开到16,如果显存放不下就用梯度累积,YOLOv8里没有直接暴露累积参数,但可以通过batch=8加上更大的imgsz间接模拟。另一个更稳妥的思路是前20轮冻结骨干网络,只训练检测头。预训练权重已经在COCO上学到了基础特征,冻结骨干相当于用小样本只学“鸟粪长什么样”的差异部分,能大幅减少BN统计量的漂移。
4.4 小目标归一化坐标精度不足:标签退化
现象是训练结束后,用val_batch_pred.jpg看小目标检测结果,发现预测框总比真实框偏几个像素,尤其针对远处光伏板上很小的鸟粪区域。原因是YOLO标签用归一化坐标保存,一个4×4像素的鸟粪在640×512图像里,宽度只占0.006,txt里保留6位小数已经接近精度上限,反复训练迭代后边界更容易偏移。
解决方式有两条路。一是提高输入分辨率,把imgsz从640调到1024再看效果;二是在不牺牲分辨率的前提下,对红外大图做切图训练,把每张图切成带重叠的图块,相当于变相放大目标尺度。后一种做法会在第6章展开,对这张只有173张的小数据集提升明显。
4.5 验证集不足20张:指标飘得离谱
现象是每次训练完重新划分验证集,mAP50在0.3到0.85之间反复横跳,无法判断模型到底有没有进步。原因不复杂:173张里切出17张验证,验证集里有没有难样本直接决定了指标高低,样本量决定了方差。
解决方法是做K折交叉验证。把173张图随机分成5份,每份约35张,训练5次每次换一份做验证,最后取mAP的均值和标准差。虽然训练时间变成原来的5倍,但得到的指标才真正接近模型能力。另外一个折中办法是固定随机种子并锁死划分文件,保证每次对比实验用同一批验证集,至少能看到相对变化。
5. 模型验收与置信度门限:单类别模型怎么看懂验证结果
训练跑完后不能只看一个mAP数字就收工。单类别检测的指标解读和可见光多类别场景不太一样,重点在于确认模型到底漏了多少鸟粪、误报集中在那些区域。
5.1 单类别模型优先看recall和mAP50
对鸟粪检测来说,漏检的代价远高于误检。漏检意味着热斑继续发展,可能损伤光伏板;误检最多让巡检人员多看一眼。所以评估时要优先看recall(召回率)和mAP50,mAP50-95反而不是最关键的,因为鸟粪边界本来就不清晰,标注框在红外图上存在较大主观性,追求高IoU下的精度意义不大。
YOLOv8训练结束会打印指标表,直接看all类那一行,R是recall,mAP50是IoU阈值0.5下的平均精度。如果recall低于0.7,说明模型漏掉了一批鸟粪,不要急着加数据,先查看漏检样本是目标尺寸太小还是对比度太低。
5.2 混淆矩阵读出误检来源:总合不唯一不奇怪
训练目录下confusion_matrix.png看起来直观,但很多人对着图发蒙:矩阵数字加起来对不上。原因在于混淆矩阵默认按归一化计数,负样本(背景)被模型预测成目标的样本数量来自整张图,统计口径和正样本不同,总合不唯一是正常的,不用怀疑代码写错了。
对单类别模型,重点看两个格子:bird_dropping这一行里被分成background的比例,代表漏检率;background这一行里被预测成bird_dropping的比例,代表误检率。如果误检率偏高,去val_batch_pred.jpg里看误检位置,如果集中在光伏板边框或组件缝隙,就要考虑增加负样本或调整温度对比度归一化。
5.3 推理时调整置信度门限:从默认0.25到0.1
训练权重导出后,推理默认置信度门限是0.25。这个门限对常规可见光目标合适,但对红外鸟粪检测偏保守。鸟粪区域红外特征有时很弱,置信度算出来只有0.1到0.2,但真实存在。直接把门限从0.25降到0.1,能明显提升召回,代价是误检增加。
实际操作中,先跑一遍验证集的验证命令,把不同置信度门限下的precision和recall打印出来,选一个recall不低于0.85、误检可接受的门限值。YOLO验证命令支持传入conf参数:
yolo detect val \ model=runs/detect/bird_dropping_trial/weights/best.pt \ data=datasets/bird_dropping/data.yaml \ conf=0.1观察输出指标里precision和recall的变化,如果precision掉到0.5以下,说明误报太多,需要配合后处理过滤而不是继续降门限。
6. 进阶:把单帧检测扩展成场站巡检的自动化流程
单帧检测模型只是起点,真实场站巡检里更多是视频流或大图巡检。把这条链路串起来,比继续刷模型指标更有工程价值。
6.1 用切图推理拾回小目标
光伏板红外图通常分辨率不低,鸟粪目标只有几十个像素,直接塞进模型等于把目标缩成几个像素。常见做法用SAHI库做切图推理,把大图切成带重叠的图块,每个图块单独检测,再把结果合并成原图坐标,小目标召回率能提升一个档次。
pip install sahi切图推理的切片尺寸、重叠率需要根据目标尺度来调,对鸟粪这种小目标,切片256到512、重叠0.2是合理的起点。合并时如果同一个目标被多个切块检出,用NMS按IoU过滤,输出的是原图坐标框。
6.2 多帧时序确认降低误报
单张图的误报难以完全消除,但相机连续拍摄时,真实鸟粪位置稳定,盲元位置固定,而随机噪声产生的误检帧间不连续。常见实践是连续3帧都在同一区域检出目标,才触发告警,这会去掉一大半随机误报。帧间做简单的框匹配,中心点距离小于目标框宽度就视为同一目标。
6.3 把温度信息作为后处理过滤条件
红外图像如果同时带温度矩阵,可以在检测框内统计最高温度与周边平均温度差。鸟粪遮挡区域与正常光伏板的温差通常有几度,而误检区域温差往往不明显。引入温差阈值做二次判断,比单纯堆模型参数更稳定。
我自己养成的一个习惯是:每次跑完一个新版本,先对着验证集的每张图看一遍误报和漏检,把检错的位置截下来贴进台账,连续看两周才会放心让模型参与巡检。模型迭代到后期瓶颈往往不在算法,而在对红外设备特性和现场环境的理解。希望帮到你。
本文还有配套的精品资源,点击获取