尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

光伏板积灰检测数据集:VOC转YOLO、去重与YOLOv8训练

发布时间:2026/9/10 13:41:06

资讯中心
01
ARTICLE

光伏板积灰检测数据集:VOC转YOLO、去重与YOLOv8训练

光伏板积灰检测数据集:VOC转YOLO、去重与YOLOv8训练
简介面向太阳能光伏板积灰与灰尘检测场景的目标检测数据集包含1463张JPEG图像及对应的VOC格式XML与YOLO格式TXT标注文件标注类别为Dirt单一类共6822个矩形框。压缩包约75.98MB共2000个文件以XML与TXT标注文件为主目录结构清晰可直接接入YOLO、MMDetection等主流检测框架训练。数据集使用labelImg人工画框标注类别规则统一适合光伏运维巡检、新能源视觉检测方向的学习者快速搭建基线模型也可用于熟悉VOC与YOLO格式转换流程。由于图片重复度较高且价格定位偏低更适合初学者练手或作为数据增强素材。已有536人学习下载是一份实用且易上手的入门级光伏积灰检测数据资源。1. 光伏板积灰检测先处理数据再谈模型巡检无人机传回来的光伏板照片里真正影响发电量的往往不是热斑和隐裂而是组件表面积灰。这套太阳能光伏板积灰灰尘检测数据集提供了1463张jpg图片标注类别只有Dirt总框数6822同时给出labelImg导出的VOC xml和转换好的YOLO txt不需要再做格式迁移就能接入yolo目标检测流程。适合两类人一类是刚跑通yolov5或yolov8、想用真实脏污样本把训练流程走完整的初学者另一类是准备研究数据增强和重复样本去重策略的算法工程师。需要先说清楚数据集说明里明确写了图片重复度很高如果你不管这个特点直接随机划分train/valmAP会虚高后面所有指标判断都不可信。所以拿到压缩包之后第一件事不是训练是清洗和复核标注。2. 数据集结构与VOC/YOLO标注格式的转换细节2.1 解压后先核对三类同名文件压缩包解压后是平铺结构jpg、xml、txt三个后缀同名共处一个目录文件名类似firc_solarpanel_1345.jpg、firc_solarpanel_1345.xml、firc_solarpanel_1345.txt另外附带一个使用前必读.txt说明文件。这里的txt是YOLO检测格式每行对应一个目标框不包含分割掩码路径和分割任务用的txt不是一回事。unzip firc_solarpanel_1463.zip -d solar_panel_dirt cd solar_panel_dirt find . -name *.jpg | wc -l find . -name *.xml | wc -l find . -name *.txt ! -name 使用前必读.txt | wc -lunzip -d指定解压目录后面三条find命令分别统计jpg、xml、txt数量! -name把说明文件排除在统计之外。我这里实际跑完得到三个1463和摘要描述一致。如果某个数字偏少优先检查是不是有空的xml文件或者文件名后缀大小写不一致。这三种文件的对应关系如下表文件类型数量作用jpg1463原始光伏板图像xml1463VOC格式标注包含目标类别与绝对坐标txt1463YOLO格式标注包含类别id与归一化坐标总框数6822除以1463张图平均每张约4.66个框说明Dirt目标不是每张只框一个大区域而是分散在组件表面多个位置。这个分布特点会影响后面训练时anchor和imgsz的选择。2.2 VOC的xml与YOLO的txt字段差异VOC格式的核心是object节点一个目标对应一个object坐标用左上角和右下角的绝对像素值表示annotation filenamefirc_solarpanel_1345.jpg/filename size width1280/width height960/height depth3/depth /size object nameDirt/name bndbox xmin100/xmin ymin80/ymin xmax300/xmax ymax240/ymax /bndbox /object /annotation上面这段是示意结构实际坐标值以压缩包内xml为准。YOLO txt里每一行是类别id 中心点x 中心点y 宽度 高度且全部除以图片宽高做了归一化。两种格式的差异直接决定了转换逻辑对比项VOC xmlYOLO txt类别标识字符串名称如Dirt整数索引如0坐标形式xmin、ymin、xmax、ymaxx_center、y_center、width、height坐标范围像素绝对值0到1之间的归一化值是否需要图片尺寸是xml里自带size节点否训练时从图像本身获取转换时容易踩的坑是坐标顺序。VOC里先给左上角再给右下角YOLO里是先给中心点再给宽高两个顺序很容易换错。另外类别索引必须从0开始Dirt对应0如果后续自己加了一类这个索引就会整体错位。2.3 用Python做VOC到YOLO的批量转换labelImg默认导出VOC xml而yolov5和yolov8训练需要YOLO txt。转换脚本关键点在于把绝对坐标换算成归一化中心坐标import os import xml.etree.ElementTree as ET source_xml_dir annotations target_txt_dir labels os.makedirs(target_txt_dir, exist_okTrue) class_names [Dirt] def convert(size, box): dw 1.0 / size[0] dh 1.0 / size[1] x (box[0] box[1]) / 2.0 y (box[2] box[3]) / 2.0 w box[1] - box[0] h box[3] - box[2] return x * dw, y * dh, w * dw, h * dh for xml_name in os.listdir(source_xml_dir): if not xml_name.endswith(.xml): continue root ET.parse(os.path.join(source_xml_dir, xml_name)).getroot() size root.find(size) width int(size.find(width).text) height int(size.find(height).text) lines [] for obj in root.findall(object): cls_id class_names.index(obj.find(name).text) bndbox obj.find(bndbox) box [ float(bndbox.find(xmin).text), float(bndbox.find(ymin).text), float(bndbox.find(xmax).text), float(bndbox.find(ymax).text), ] cx, cy, bw, bh convert((width, height), box) lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) out_path os.path.join(target_txt_dir, xml_name.replace(.xml, .txt)) with open(out_path, w) as f: f.write(\n.join(lines))脚本读取xml里的width和height把左上角和右下角的坐标换算成中心点加宽高的归一化形式。class_names.index负责把字符串类别映射成int索引所以class_names列表的顺序必须和后续训练配置里的names顺序完全一致。{:.6f}保留6位小数对640或960分辨率的图片足够不需要担心精度损失。注意xml里如果缺少size节点这段脚本会直接报错。处理办法是打开对应图片用PIL读取宽高补进去而不是跳过该文件。3. 高重复度图片的清洗与标注复核3.1 为什么重复图片会让训练指标失真数据集的重复度高这是影响使用方式的最关键属性。所谓重复不一定是完全相同的文件也可能是同一块光伏板在相近角度、相近光照下拍摄的多张图片。这种近似重复如果同时落入训练集和验证集模型等于提前见过验证集答案mAP会异常高但部署到新场景立刻打回原形。常规做法是先用感知哈希把近似重复图片分到同一组然后按组为单位划分数据集保证同组图片要么全在train、要么全在val。另一个容易被忽略的动作是核对标注总数1463个xml和1463个txt应该都包含6822个框如果txt转换时漏了某个object最终训练框数会偏少。3.2 用感知哈希找出近似重复感知哈希里最常用的是dhash和phashdhash对亮度变化敏感phash对JPEG重压缩更稳。当前这个场景可以用phash配合hash_size8把每张图压缩成64位指纹指纹完全相同的图片视为同一组。from PIL import Image import imagehash import os img_dir images hash_to_imgs {} for img_name in sorted(os.listdir(img_dir)): if not img_name.endswith(.jpg): continue img_path os.path.join(img_dir, img_name) img_hash imagehash.phash(Image.open(img_path), hash_size8) hash_to_imgs.setdefault(img_hash, []).append(img_name) duplicate_groups [v for v in hash_to_imgs.values() if len(v) 1] print(f重复组数: {len(duplicate_groups)}) for group in duplicate_groups[:5]: print(group)hash_size8生成8x8的灰度指纹最终得到一个64位哈希值。setdefault把相同哈希的图片名聚合到同一个列表里。hash_size越大指纹越敏感过大的hash_size会把同一场景的光照差异也判定为不同图片。对光伏板这种背景单调的图像8已经能区分“同一块板子换了个角度”的情况。几种查重方式的选择可以按下面这个表格判断方法敏感度适合场景md5最高完全相同的文件dhash中亮度变化较小的近似重复phash中低JPEG重压缩、缩放后的重复图3.3 按哈希分组重新划分train和val找到重复组之后不要直接按单张图片随机划分要按组划分。单张随机切会让同一场景的相似图同时出现在训练集和验证集造成信息泄漏。import os import shutil import random groups list(hash_to_imgs.values()) random.Random(42).shuffle(groups) split_point int(len(groups) * 0.85) train_groups groups[:split_point] val_groups groups[split_point:] def copy_group(group, split_name): out_img_dir fdataset/images/{split_name} out_txt_dir fdataset/labels/{split_name} os.makedirs(out_img_dir, exist_okTrue) os.makedirs(out_txt_dir, exist_okTrue) for img_name in group: shutil.copy( os.path.join(img_dir, img_name), os.path.join(out_img_dir, img_name), ) txt_name img_name.replace(.jpg, .txt) txt_path os.path.join(labels, txt_name) if os.path.exists(txt_path): shutil.copy(txt_path, os.path.join(out_txt_dir, txt_name)) for group in train_groups: copy_group(group, train) for group in val_groups: copy_group(group, val)random.Random(42)固定随机种子保证多次运行划分结果一致便于复现。copy_group内部的os.makedirs会递归创建目录exist_okTrue避免重复报错。这里有个细节只有txt存在时才复制因为个别xml可能没有对应标注这种情况在labelImg里经常出现。3.4 统计txt框数并检查越界坐标划分完成后还要验证一下总框数。YOLO txt格式里类别id必须是整数中心点和宽高必须是0到1之间的浮点数宽高必须大于0。编写一个快速校验脚本import os total_boxes 0 bad_files [] for txt_name in sorted(os.listdir(labels)): if not txt_name.endswith(.txt): continue txt_path os.path.join(labels, txt_name) with open(txt_path) as f: lines [line.strip() for line in f if line.strip()] for line in lines: parts line.split() if len(parts) ! 5: bad_files.append((txt_name, 字段数异常)) continue cls_id, x, y, w, h parts if not cls_id.isdigit(): bad_files.append((txt_name, 类别id不是整数)) if not (0 float(x) 1 and 0 float(y) 1): bad_files.append((txt_name, 中心点越界)) if float(w) 0 or float(h) 0: bad_files.append((txt_name, 宽高异常)) total_boxes len(lines) print(f总框数: {total_boxes}) print(f异常记录: {bad_files[:10]}) print(f异常文件数: {len(bad_files)})总框数如果等于6822说明VOC转YOLO过程没有丢目标。len(parts) ! 5用于捕获空行或多余空格导致的问题cls_id.isdigit()判断类别索引是否符合要求。越界检查对后续yolov8训练尤其重要因为训练时数据加载器会把越界框裁剪到图像边缘少量越界不报错但会干扰损失计算。4. YOLOv8训练自己的Dirt数据集4.1 准备data.yaml目录配置yolov8训练过程中使用data.yaml描述数据集路径和类别名称。这里的关键是path、train、val相对于yaml文件的位置以及names索引必须与txt里的类别id对应。path: ./solar_panel_dirt train: images/train val: images/val names: 0: Dirtpath是数据集根目录train和val写相对路径即可yolov8会自动拼接成./solar_panel_dirt/images/train。names字典的key从0开始当前数据集只有一个Dirt类别所以只有0一项。如果你自己增加了类别顺序必须和生成txt时使用的class_names列表保持一致。4.2 训练超参数与命令积灰目标通常比较小且光伏板表面反光会导致同一块脏污在不同图片里亮度差异很大。训练前需要把imgsz、batch、数据增强这些参数确定下来。下面是我在这个数据集上常用的参数组合参数建议值说明imgsz640或960960对小目标更友好但显存占用明显增加batch168GB显存建议改成8epochs120单类别任务不需要过大epochoptimizerAdamW比SGD收敛快适合小数据集mosaic0.8保留mosaic增强但不要全程开启lr00.001预训练模型沿用这个初始学习率比较稳对应的训练命令为yolo detect train \ datasolar_panel_dirt.yaml \ modelyolov8s.pt \ epochs120 \ imgsz640 \ batch16 \ optimizerAdamW \ lr00.001 \ mosaic0.8modelyolov8s.pt表示使用yolov8s的COCO预训练权重作为起点迁移学习在小数据集上效果远好于随机初始化。mosaic0.8在ultralytics中会自动按epoch衰减不需要自己实现mosaic关闭逻辑。如果验证集mAP一直不稳定先把mosaic关掉再观察。如果你的显卡显存只有6GB把model换成yolov8n.pt并把batch改成8。4.3 从训练曲线判断积灰模型是否在泛化训练完成后ultralytics会在runs/detect/train/目录下生成results.csv里面记录了每个epoch的box loss、cls loss、mAP50、mAP50-95等指标。画出box loss和mAP50的变化趋势能快速判断模型是否过拟合到重复图片上。import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/detect/train/results.csv) fig, ax1 plt.subplots() ax1.plot(df[train/box_loss], colortab:red, labeltrain box loss) ax2 ax1.twinx() ax2.plot(df[metrics/mAP50(B)], colortab:blue, labelval mAP50) fig.legend() plt.savefig(training_curve.png)train/box_loss对应YOLOv8的CIoU边界框回归损失下降说明预测框逐步贴近真实框。metrics/mAP50(B)是验证集IoU阈值0.5下的平均精度。如果box loss持续下降但mAP50在某个epoch后不再上涨说明模型开始记忆训练集中的重复模式应当用第3章的去重结果重新划分或者提前早停。对于积灰这种边缘模糊的目标mAP50和mAP50-95之间的差距通常比较大这是正常的不必因为mAP50-95偏低就怀疑标注质量。5. 验证集中残留重复图的检查与TTA推理5.1 训练前检查train和val之间是否存在图片泄漏数据划分之后、训练开始之前最好再做一次跨集合重复检查。即便第3章按hash分组划分phash指纹完全相同的情况也可能漏掉少量“同一块板子不同曝光”的图片。检查逻辑很简单分别收集train和val目录下所有图片的phash求交集。from PIL import Image import imagehash import os def collect_hashes(root): hash_to_name {} for img_name in os.listdir(root): if not img_name.endswith(.jpg): continue img_path os.path.join(root, img_name) h imagehash.phash(Image.open(img_path), hash_size8) hash_to_name[h] img_name return hash_to_name train_hashes collect_hashes(dataset/images/train) val_hashes collect_hashes(dataset/images/val) leaks set(train_hashes.keys()) set(val_hashes.keys()) print(ftrain/val潜在重复数: {len(leaks)}) for h in list(leaks)[:5]: print(train_hashes[h], -, val_hashes[h])这段脚本把train和val中phash完全一致的图片视为潜在泄漏。如果potential_leak_count大于0说明之前的划分还不够严格需要回到第3章改用更大的hash_size或者把这些重复组整体挪到训练集一侧。注意这里用的是集合交集每个hash只保留了一张图片名所以输出只能用来定位问题不能完整列出所有重复对。5.2 用augment推理评估模型鲁棒性最后一个小技巧是用yolo推理的augment参数做Test-Time Augmentation。训练完的权重直接推理会看到较低或较高的mAP但TTA能把水平翻转、缩放、颜色抖动后的预测融合起来对积灰这种边缘模糊目标更稳定。命令如下yolo detect predict \ modelruns/detect/train/weights/best.pt \ sourcedataset/images/test \ imgsz640 \ augmentTrueaugmentTrue会启用水平翻转、多尺度等推理增强ultralytics会把多次预测结果融合后再输出最终框。这个参数默认是False部署时不要打开因为推理耗时大约会增加到原来的2到3倍。在验证集上比较augmentTrue和普通模式的mAP如果TTA明显更高说明模型对光照变化敏感后续可以增加亮度扰动数据增强而不是继续堆epoch。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。