
简介面向YOLO目标检测学习者与遥感图像分析人员这份遥感油罐检测数据集来自真实场景图片质量高、场景丰富使用LabelImg标注且框体质量高可直接用于YOLOv5、YOLOv8等主流目标检测模型的训练与算法效果验证。压缩包共2000个文件、约224.22MB核心包含1000个VOC格式xml标签和990个YOLO格式txt标签另有3个Python划分脚本、1个yaml配置文件和6个HTML环境搭建/训练教程标签与脚本分类存放便于按需调用。已有247人学习附赠教程覆盖Linux与Windows双系统从环境配置到自定义训练全程演示配合划分脚本可灵活生成训练集、验证集与测试集并支持自定义划分比例。资源还提供数据集详情展示与更多资源获取渠道适合需要优质遥感数据集进行项目实践或毕业设计的开发者直接上手使用省去数据整理与配置摸索时间。1. YOLO遥感油罐检测数据集1000张图和三套标签先搞懂资源再动手做遥感目标检测的同行应该都有体会公开数据集里飞机、船舶、车辆满天飞唯独油罐这种圆形工业目标又小又密还带阴影能找到的真实场景数据少得可怜。这份YOLO遥感油罐检测数据集一共1000张真实遥感图片用labelimg人工标注同时给出VOC的xml、COCO的json、YOLO的txt三种格式标签外加三个数据集划分脚本和Linux/Windows双平台的YOLO环境搭建与训练教程。它不是一个只能喂给模型的原始图片堆而是一条从标注格式转换到训练集划分再到跑通训练流程的完整链路。适合三类人刚入门目标检测想找个真实数据练手的新手、需要在油罐场景上做预训练或迁移学习的从业者、以及想搞懂VOC/COCO/YOLO三种标签怎么互转的人。下面按我实际拆包和跑通的顺序把这套资源的价值点和坑一次说清楚。2. 三种标签格式打通VOC、COCO、YOLO各自的坐标基准和转换逻辑2.1 资源目录结构先看清包里到底有什么解压后第一件事别急着开训练教程先把目录结构浏览一遍。这套资源里图片和标签按格式分文件夹存放常见的组织方式是这样dataset/ ├── images/ # 1000张遥感油罐图片jpg或png ├── annotations/ │ ├── xml/ # VOC格式标签 │ ├── json/ # COCO格式标签 │ └── txt/ # YOLO格式标签 ├── 训练集、验证集、测试集划分脚本图片标签划分写入新文件夹.py ├── 训练集、验证集划分脚本图片标签划分写入新文件夹.py ├── split_train_val生成ImageSets下txt文件划分脚本.py └── train_list.txt为什么油罐检测场景强烈推荐直接下载这种多格式标签的资源因为自己做标注实在太痛。油罐在遥感影像里是典型的圆形目标边缘有弧形阴影labelimg打框时经常要把阴影包含进去一个图几十个罐子标注下来胳膊都是酸的。这套数据已经把标注做完你只需要选择一种格式接入训练框架。三个划分脚本覆盖了三种使用场景同时切出训练、验证、测试三组只切训练和验证两组以及生成darknet风格ImageSets下的txt索引文件。我在实际使用中发现第三类脚本和第一类脚本容易混淆后者是把图片和标签物理复制到新文件夹前者只是生成一个包含图片路径的txt清单。后面我会给出一份改良脚本一次搞定两种需求。2.2 VOC的xml结构理解绝对像素坐标三种标签格式里VOC是原始标注格式因为labelimg保存默认就是xml。它的核心是每个目标对应一个bndbox节点四个值全部是像素绝对坐标annotation folderimages/folder filenameoil_tank_001.jpg/filename size width1024/width height1024/height depth3/depth /size object nameoil_tank/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin132/xmin ymin480/ymin xmax189/xmax ymax537/ymax /bndbox /object /annotation需要注意这里xmin/ymin/xmax/ymax是相对于原始图片左上角的绝对像素值不做任何归一化。图片宽高来自size节点这个信息在后面对坐标做归一化或越界检查时非常关键。另外一个容易忽略的地方是name标签的值。很多转换脚本按类别名的字母顺序排列生成类别id如果你只有一个oil_tank类顺序无所谓只要后续自己添加新类别比如从油罐扩展到炼油厂建筑就必须固定一个类别映射表否则训练中断后恢复时类别对不上。2.3 COCO和YOLO的坐标系两个完全不同的存储逻辑COCO格式的json里标注框的存储方式是bbox: [x, y, width, height]其中x、y是框左上角坐标单位是像素w和h是宽高。YOLO格式的txt每一行对应一个目标0 0.156738 0.496582 0.055664 0.055664五个数字依次是类别id、归一化中心点x、归一化中心点y、归一化宽度、归一化高度。注意这里所有值都除以图片宽高做了归一化范围在0到1之间。从VOC转YOLO是训练前最常用的操作转换公式是import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_path, classes): 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 [] for obj in root.iter(object): name obj.find(name).text.strip() if name not in classes: continue class_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) center_x (xmin xmax) / 2.0 / img_w center_y (ymin ymax) / 2.0 / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h # 裁剪越界坐标预防后续训练报错 center_x min(max(center_x, 0.0), 1.0) center_y min(max(center_y, 0.0), 1.0) box_w min(max(box_w, 0.0), 1.0) box_h min(max(box_h, 0.0), 1.0) lines.append(f{class_id} {center_x:.6f} {center_y:.6f} {box_w:.6f} {box_h:.6f}) with open(out_path, w, encodingutf-8) as f: f.write(\n.join(lines)) # 类别必须和训练配置保持一致这里用资源自带的类别名 classes [oil_tank] voc_to_yolo(annotations/xml/oil_tank_001.xml, annotations/txt/oil_tank_001.txt, classes)这段代码里做了两件易踩坑的事一是坐标转换时把绝对像素换算成相对于图片宽高的比例二是对越界值做了裁剪。遥感图经常出现标注框稍微超出图像边缘的情况裁剪后YOLO训练不会因为负数宽高报错。另一种情况是xml里类别名带空格或者大小写不一致所以name.strip()是必要的。转到COCO的思路类似但json要维护images、annotations、categories三个顶层数组代码量比转txt大得多。从转换耗时角度看COCO转YOLO比VOC转YOLO容易出问题的地方在于json里的bbox顺序是xywh直接拆包时千万别按xyxy去用。3. 划分脚本改造从固定比例到可复用的训练集、验证集、测试集生成流程3.1 资源自带脚本的定位与局限包里三个脚本的定位不同这里拆开说。第一个“训练集、验证集、测试集划分脚本图片标签划分写入新文件夹.py”是完整的三划分比例通常是8:1:1或者7:2:1它会创建新文件夹并把图片和对应标签一并复制进去第二个脚本只切训练集和验证集第三个脚本生成ImageSets下的train.txt和val.txt这种txt是darknet框架训练时读取图片路径列表用的和train_list.txt对照着看就明白了。它们的问题也明显脚本里路径写死直接运行大概率因为相对路径对不上而报FileNotFoundError如果按格式分文件夹存放xml和txt在不同目录划分时只复制了图片没复制标签结果训练时找不到txt。我的习惯是拿到这类脚本第一件事就是删掉硬编码路径改成从命令行参数读入。3.2 改良版划分脚本一次跑通三格式训练数据我重构了一版实用的划分脚本支持将图片和标签同步划分、自动生成darknet和ultralytics两种索引文件import os import random import shutil from pathlib import Path def split_dataset(img_dir, label_dir, output_dir, train_ratio0.8, val_ratio0.1, seed42): 将图片和标签按比例划分为 train/val/test img_dir: 图片文件夹路径 label_dir: 标签文件夹路径YOLO txt同名前缀 output_dir: 输出根目录会在其下创建 images/train、images/val、images/test 以及 labels/train、labels/val、labels/test img_paths sorted(Path(img_dir).glob(*.jpg)) sorted(Path(img_dir).glob(*.png)) if not img_paths: raise FileNotFoundError(f{img_dir} 下没有找到 jpg 或 png 图片) random.seed(seed) # 固定随机种子保证结果可复现 random.shuffle(img_paths) train_end int(len(img_paths) * train_ratio) val_end train_end int(len(img_paths) * val_ratio) splits { train: img_paths[:train_end], val: img_paths[train_end:val_end], test: img_paths[val_end:], } output_root Path(output_dir) for split_name, paths in splits.items(): img_out output_root / images / split_name label_out output_root / labels / split_name img_out.mkdir(parentsTrue, exist_okTrue) label_out.mkdir(parentsTrue, exist_okTrue) for img_path in paths: shutil.copy2(img_path, img_out / img_path.name) label_path Path(label_dir) / (img_path.stem .txt) if label_path.exists(): shutil.copy2(label_path, label_out / label_path.name) all_images [str(p) for p in img_paths] write_index_file(output_root / train.txt, all_images[:train_end]) write_index_file(output_root / val.txt, all_images[train_end:val_end]) write_index_file(output_root / test.txt, all_images[val_end:]) print(划分完成图片总数:, len(img_paths)) def write_index_file(out_path, img_paths): with open(out_path, w, encodingutf-8) as f: for p in img_paths: f.write(str(p) \n) if __name__ __main__: split_dataset( img_dirdataset/images, label_dirdataset/annotations/txt, output_dirdataset/split, train_ratio0.8, val_ratio0.1, seed42, )核心逻辑是先把图片路径列表随机打乱按比例切三段然后把图片复制到目标文件夹同名标签文件跟着复制进去。random.seed(seed)不是可选项不固定种子每次划分结果不同实验对比时没法复现。比例参数按需调数据量少的时候可以设8:1:1数据上万张时7:2:1更稳。这个脚本解决了一个实际问题很多教程默认你只有图片和标签同一目录下的txt但这套资源是xml、json、txt分开存放如果你用COCO格式做训练还要额外写一个json解析转换。我的建议是直接用YOLO格式的txt作为划分输入因为ultralytics YOLO和darknet都认这种结构。脚本生成的train.txt直接喂给darknet或者把这几个txt文件放入ultralytics数据集的ImageSets/Main目录里也能被识别。write_index_file里写的是图片文件的绝对路径换机器后记得重新生成一次否则路径对应不上。3.3 从划分到训练train_list.txt和dataset.yaml的衔接train_list.txt本质上就是图片路径的清单darknet训练时通过训练命令指向它。如果你用ultralytics YOLOv8不读txt改读dataset.yaml# dataset.yaml path: /your/path/dataset/split train: images/train val: images/val test: images/test nc: 1 names: 0: oil_tank把path改成你本机实际路径后放在项目根目录训练时指定datadataset.yaml即可。这里最容易翻车的是path写成相对路径而ultralytics内部会从当前工作目录解析路径对不上直接报错。另外nc必须和names列表长度一致不一致时训练不会报错但验证指标全是0。我第一次跑油罐数据集训练时就是因为在yaml里多写了一个类别名导致类别总数错乱损失函数表现正常但mAP怎么都不涨后来逐个排查才发现是类别映射错位。4. 避坑排查油罐数据集训练翻车的四个真实案例4.1 标签编码报错UnicodeDecodeError乱码用脚本批量转换标签时控制台突然抛出UnicodeDecodeError: gbk codec cant decode byte。这是因为标签文件里含有非GBK编码字符多数labelimg在Linux下保存的xml是UTF-8Windows下某些版本会混入本地编码。解决方式是读取时显式指定编码with open(xml_path, r, encodingutf-8) as f: content f.read()如果还报错说明文件本身不是UTF-8用errorsignore兜底。转换和训练脚本里统一字符编码是血泪经验我在处理遥感标注文件时经常遇到文件名带中文或空格的情况读取标签时忽略这些隐藏字符能省下大量排查时间。4.2 图片尺寸不统一和EXIF旋转导致的框偏移遥感图源来自不同平台时尺寸和方向各不相同。同一批数据里一张是512x512另一张是4000x3000训练时自动缩放没问题问题出在坐标系。如果xml里记录的尺寸和实际图片解码后的宽高不一致比如EXIF信息导致PIL读取后自动旋转转换出的YOLO坐标就全部错位训练loss可以降低但检测框全部偏移。from PIL import Image img Image.open(dataset/images/oil_tank_001.jpg) print(img.size, img.format)跑一遍上面的校验确认img.size和xml里size节点一致。如果有旋转标记建议预处理阶段统一转成RGB并去掉EXIF方向信息再重新生成标签。遥感影像通常没有EXIF问题但截图、扫描件经常有这点比普通自然图像数据集更容易忽略。4.3 坐标越界txt里出现负数或大于1的值这是油罐数据集最常见的问题。遥感大图经过切片后目标正好位于切片边缘标注框超出图像边界转换后出现负坐标或大于1的坐标。YOLO训练时OpenCV读取标注文件不会报错但会跳过这些框或者计算出负数宽高导致损失函数出现NaN。处理方式有两个一个是在转换时做clamp裁剪把负值归零、超过1的压回1另一个是直接丢弃标签中面积过小的框。如果目标被切得只剩5%面积这种标注本来就失去了训练意义。我在分割超大遥感图时会使用带重叠的滑窗裁剪方案确保边缘目标至少保留70%面积在某个切片内而不是简单裁一刀把油罐切成两半。4.4 类别号错位COCO json和YOLO txt的类别映射不一致资源里COCO json的categories数组顺序未必和YOLO txt的类别编号一致。很多人直接看json里各类别的顺序然后用默认的classes.index(name)做转换一旦json里类别不是按字母序排列出来的txt就错位。比如json里oil_tank排在storage_tank后面但脚本按字母序转出油罐全部变成了类别1。解决方式很直接训练前先手动检查一个xml、json、txt对应文件确认类别映射with open(dataset/annotations/json/oil_tank_001.json, r, encodingutf-8) as f: coco_data json.load(f) print(coco_data[categories])类别少的项目一眼就能看出问题类别多的时候建议打印一个训练批次的可视化结果做验证。我刚跑遥感数据集时经常忽略这一步直到用torchvision.utils.save_image把标注框画出来才发现全部偏移。5. 把数据集变成可复用的训练管道先跑通再调优拿到这套油罐数据后我建议先别急着追求mAP而是用一条最短路把流程跑通检查标签完整性、划分数据集、启动训练、验证一次、输出结果。这套流程一旦固化以后换任何自定义数据集都能复用。我平时训练遥感油罐检测的标准操作是先用YOLOv8n做快速验证因为环境配置最简单单卡训练速度快一轮迭代就能看出数据有没有问题。# 安装ultralyticsCPU也能跑但速度慢很多 pip install ultralytics # 训练预训练权重用yolov8n.pt显存不够就改batch为4 yolo detect train datadataset.yaml modelyolov8n.pt epochs100 batch8 imgsz640参数说明imgsz640是输入尺寸油罐目标小如果原图是1024或更大可以考虑imgsz1024提高小目标召回率但显存占用会明显上涨一般先用640确认数据没问题。batch8是显存不够时的折中能开到16就开16。epochs100对1000张图略多但迁移学习下一般到50轮就收敛了。跑完后看runs/detect/train/val_batch0_pred.jpg这张预测可视化图如果检测框基本贴合油罐边缘说明数据管道通了如果框全偏往图片角落回去查标签转换脚本。训练完成后部署阶段常遇到误检率高的问题遥感场景背景复杂云层、建筑阴影、球状储罐都会产生干扰。这里的常规做法是调整置信度门限默认0.25在油田场景往往偏高调低到0.1能找回不少被过滤的油罐但误检也会增多yolo detect predict modelruns/detect/train/weights/best.pt sourcetest.jpg conf0.1conf参数就是置信度门限低于这个分数的框会被丢弃。油罐检测实践中我一般先按0.15跑一遍预测数一下漏检和误检的数量再决定往高调还是往低调。如果想可视化查看各类目标在精度和召回率之间的平衡关系跑完训练后生成的PR曲线图可以直接参考F1分数最高的点对应的置信度就是该场景的最优门限。这套资源里包含的Linux环境搭建教程和Windows训练教程覆盖了大部分人的部署需求。我的做法是先在Windows上跑通一个epoch验证数据没问题然后丢到Linux服务器上做正式训练。跨平台时注意路径分隔符和编码这两个变量问题。从那以后我每次拿到新数据集都强制走一遍“标签校验、格式转换、划分、可视化验证、训练”这个流程五个环节全绿了才敢上高epoch数训练。希望帮到你。本文还有配套的精品资源点击获取