遥感油罐检测数据集全解析:YOLO三种标签格式与训练避坑指南
简介YOLO遥感油罐检测数据集面向计算机视觉目标检测学习者与研究人员收录1000张真实遥感场景下的油罐图片场景丰富经LabelImg精细标注框体质量高配套VOCxml、COCOjson及YOLOtxt三种格式标签分别存放可直接用于YOLO系列模型训练无需额外转换格式。压缩包共2000个文件除xml与txt标签外还包含6个html操作教程、3个Python数据集划分脚本及1个Yaml配置文件整体大小224.22MB。教程覆盖Linux与Windows两套环境下的YOLO环境搭建与训练流程并给出基于案例修改以适配自定义数据集的完整思路脚本支持将图片与标签按训练集、验证集、测试集自动划分写入新文件夹也可生成ImageSets文件。已有247人学习下载适合需要快速上手遥感目标检测或进行数据预处理的用户。1. 遥感油罐检测这个数据集为什么能让你少走三天弯路看到YOLO遥感油罐检测数据集(含1000张图片)对应voc、coco和yolo三种格式标签划分脚本训练教程.rar这个压缩包第一反应是终于不用自己画油罐了。遥感图像里的油罐是典型的小目标罐体是圆形顶面有阴影人工标注一千张图要消耗两三天还不算把LabelImg导出的VOC格式转成COCO或YOLO格式的功夫。这个数据集把图片、三种格式标签、划分脚本和训练教程打包在一起解决的不只是有没有数据的问题而是拿到手能不能直接开训的问题。对准的人有三类做遥感目标检测课题的学生想快速验证YOLO在圆形小目标上表现的算法工程师以及拿来做算法对比的基准数据需求者。后面几章我会按解压、核对、划分、训练、验证的路径讲一遍把容易翻车的地方提前点给你。2. 数据集里有什么1000张遥感油罐图的目录结构与标签格式2.1 压缩包解压后的目录布局图片、标签、脚本、文档各归其位拿到.rar文件别急着双击解压到桌面先把它放成一个独立的工程目录。常见做法是解压后得到images、Annotations、labels、annotations这几个顶层目录外加一个划分脚本可能叫split.py或divide.py和一份训练教程文档。我一般会用tree命令先扫一遍目录结构确认作者是不是按约定俗成的顺序摆的tree -L 2逻辑说明tree命令把目录层级打印成树状图-L 2只显示两层避免训练教程里嵌套的图片目录刷屏。如果你在Windows上用PowerShell的tree /F效果类似能同时看到文件名。这一步的核心目的是判断图片和标签是不是一一对应油罐数据集的命名通常是oil_tank_001.jpg和oil_tank_001.xml这样的同前缀结构也有直接用四位数字编号的。只要前缀能对上后面写路径匹配脚本就省事。接下来我会再用一条命令统计图片总数看看和标题里说的1000张是否一致find images -type f | wc -l逻辑说明递归查找images目录下所有文件并计数。如果结果不是1000不要慌先看看是不是有隐藏文件或者子目录里还放了别的图片。有些作者会把原始大图和切片图放在不同文件夹标题写的是切片后的数量。确认好数量之后再抽查几个文件名确认JPG和TXT的命名完全一致。一个容易踩的坑是有的图片带后缀_aug但标签还是原名这就导致标签对不上图。提示整个数据集目录一定放在纯英文路径下比如D:\oil_tank_dataset。ultralytics的YOLO实现读取训练路径时对中文兼容性差报错信息往往还是file not found排查了半天才发现是中文目录名的问题。这个坑至少能浪费你半小时。解压后还要看训练教程文档是哪种格式常见的可能是Markdown或PDF。不要跳过它先翻到环境要求那一段看看作者用的框架版本和你本机是否一致。我自己遇到过教程里写的是YOLOv5的依赖但实际压缩包里的划分脚本是用ultralytics风格写的版本对不上训练命令直接报参数错误。所以解压后第一件事永远是看文档、跑脚本、数文件顺序不能乱。2.2 VOC、COCO、YOLO三种标签格式的字段差异与换算关系三种格式是目标检测领域最常见的标签组织方式VOC是XML文件一个图片配一个XMLCOCO是单个JSON文件所有标注集中在一起YOLO是每张图片配一个TXT每行一个目标。它们描述同一个油罐目标但坐标体系完全不同拿到手后不能混用。下表帮你看清三者的差异格式文件组织框坐标表示类别表示适用框架VOC每图一个.xmlxmin, ymin, xmax, ymax绝对像素标签名字符串如oil_tankMMDetection、Faster R-CNN、SSDCOCO单文件.json[x, y, width, height]左上角原点绝对像素category_id一般从1开始Detectron2、MMDetectionYOLO每图一个.txtcx, cy, w, h全部归一化到0~1class_id从0开始YOLO系列这张表里藏着最典型的一个坑VOC和COCO是绝对像素坐标YOLO是归一化坐标。很多人会用LabelImg打标后导出VOC再转YOLO时忘了除以图片宽高导致所有的中心点和宽高都落在1.0以上训练时loss直接飞掉。坐标换算的常见公式只有两组YOLO的cx (xmin xmax) / 2 / width YOLO的cy (ymin ymax) / 2 / height YOLO的w (xmax - xmin) / width YOLO的h (ymax - ymin) / height反过来从YOLO转VOC时用图片宽高反乘回去注意四舍五入后的边界框不要出现xmin为0但xmax为1这种合法但不合理的极端值。COCO的[x, y, w, h]中x、y是左上角坐标不是中心点新手最容易在这里搞混把COCO当YOLO去画框框直接偏到右上角去了。注意COCO的category_id和YOLO的class_id起始值不同。COCO如果只有一个类经常从1开始而YOLO从0开始。转换时要统一偏移否则评测时mAP全部是0因为模型预测的类别编号和真实标注的类别编号完全错位。如果这个数据集三种格式都齐我拿到后会随机抽一张图把三种标签的框画上去做叠加对比VOC用绝对坐标画框COCO用坐标加宽高画框YOLO用中心点和宽高画框三个框应该完全重叠。差一个像素以内是正常的差几十个像素就说明转换过程有偏移。我一般会写一个快速脚本检查所有YOLO标签的坐标合法性因为TXT格式最容易被误改import glob import os for txt_path in glob.glob(labels/*.txt): if os.path.basename(txt_path) classes.txt: continue with open(txt_path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: print(f字段数错误: {txt_path} - {line.strip()}) continue cid int(parts[0]) cx float(parts[1]) cy float(parts[2]) w float(parts[3]) h float(parts[4]) if not (0 cx 1 and 0 cy 1 and 0 w 1 and 0 h 1): print(f坐标越界: {txt_path} - {line.strip()}) if w 0 or h 0: print(f宽高为负: {txt_path} - {line.strip()})逻辑说明遍历labels目录下所有txt文件跳过classes.txt每行按空格切分后判断字段数和坐标范围。YOLO标签的格式是类别 中心x 中心y 宽 高五个字段缺一不可宽高归一化后必须在0到1之间而且必须为正数。中心点也必须在0到1之间否则说明训练时这张图会提供无效回归目标。参数说明glob路径按实际目录改就行。if os.path.basename(txt_path) classes.txt这一行非常重要因为YOLO的labels目录里通常还放着一个classes.txt它里面只有类别名没有坐标不跳过的话会被误判成格式错误。运行后如果没有任何输出说明标签基本干净如果有输出按文件名找到对应图片用可视化工具看问题在哪。还要注意类别名的统一。VOC的XML里写的是oil_tankYOLO的classes.txt里写的也得是oil_tankCOCO的JSON里categories列表下的name字段也一样。如果哪个文件里写成了oil tank带空格或者写成了中文油罐YOLO训练时会把它当作两个不同的类最后分类结果全乱套。数据集作者通常不会犯这种错但你要在正式训练前检查一遍尤其是当你准备在这个数据集基础上再追加自己标注的图片时。3. 用划分脚本把数据切成训练/验证/测试集参数与输出3.1 划分脚本的工作流程从全量文件到三份清单划分脚本是整个数据集的灵魂。很多人拿到数据集后习惯手动建train和val文件夹把图片拖动分开标签也跟着拖这是最原始也最容易出错的做法。常见做法是划分脚本只生成三个文本清单train.txt、val.txt、test.txt每一行是一个图片路径。YOLO训练时直接读清单不会真的把图片挪动位置这样原来的目录结构保持不变后期调整比例只需要重新跑一遍脚本不需要搬文件。一个典型的最小划分脚本用Python写的话大概是这样import os import random data_root oil_tank_dataset images_dir os.path.join(data_root, images) # 收集所有图片路径 img_paths [os.path.join(images_dir, f) for f in os.listdir(images_dir) if f.lower().endswith((.jpg, .jpeg, .png))] # 固定随机种子保证划分结果可复现 random.seed(42) random.shuffle(img_paths) # 按比例切分test自动占剩余比例 train_ratio 0.8 val_ratio 0.1 n_train int(len(img_paths) * train_ratio) n_val int(len(img_paths) * val_ratio) n_test len(img_paths) - n_train - n_val splits { train: img_paths[:n_train], val: img_paths[n_train:n_train n_val], test: img_paths[n_train n_val:], } for split_name, paths in splits.items(): out_path os.path.join(data_root, f{split_name}.txt) with open(out_path, w) as f: for p in paths: f.write(p.replace(data_root os.sep, ) \n) print(f{split_name}: {len(paths)} 张)逻辑说明先把images目录下所有图片路径收集起来设置随机种子后打乱顺序然后按比例切成三段。切分时用列表切片train取前80%val取中间10%test取最后剩余部分。写入文本时路径用相对路径而不是绝对路径这样整个数据集目录移到别的机器上也能直接用只要在data.yaml里改一下根目录就行。参数说明train_ratio0.8和val_ratio0.1是常用配置测试集自动得到0.1也就是100张左右。如果你的数据总量只有几百张测试集比例不要低于0.1否则评估结果波动太大单张图的偶然漏检都会让mAP掉好几个点。random.seed(42)是固定随机种子的典型写法数值本身没有特殊含义关键是固定下来以后每次运行脚本得到的划分结果完全一致这样调参的时候才能排除数据划分带来的干扰。跑完脚本后一定要检查三个txt里的路径是否存在。我见过写完脚本后不检查直接拿去训练结果train.txt里有一张图片已经被移动走了YOLO训练时报image not found但不会中止只是默默跳过最后训练集少了一张图你根本察觉不到。检查办法很简单while read line; do test -f oil_tank_dataset/$line || echo 缺失: $line; done oil_tank_dataset/train.txt逻辑说明逐行读取train.txt检查oil_tank_dataset/加上该行路径是否是一个真实文件不存在则打印出来。如果最后没有任何输出说明所有路径有效。3.2 划分比例、随机种子与类别映射的参数设置划分比例怎么定取决于你的数据场景。油罐检测是单类小目标任务1000张图不算多我一般会用80/10/10或者70/15/15。如果这个数据集的图片是从同一张遥感大图上切出来的切片那么相邻切片的内容高度相似纯随机划分会让验证集和训练集几乎长得一样评估出来的mAP虚高在真实场景下一测就露馅。这种情况需要按来源分组先看文件名里有没有scene编号把同一个场景的切片全部放进同一个集合绝不让它横跨训练集和验证集。判断文件名是否带场景号可以先用一条Shell命令统计ls images | awk -F_ {print $1} | sort | uniq -c逻辑说明awk按下划线切分文件名取第一部分作为场景编号sort后uniq -c统计每个编号下的图片数量。假设文件名叫scene01_oil_0132.jpg取到的就是scene01。如果每个场景的图片数量很平均随机划分问题不大如果发现某个场景占了300张图那必须按场景划分否则验证集里会混进和训练集连续帧一样的切片。随机种子这一步我要多提醒一句种子只决定打乱顺序不改变图片内容。固定种子之后你调学习率、改batch size时看到的效果变化全部来自参数而不是数据划分这样才能准确判断哪个改动真正有效。我自己习惯用random.seed(2024)也会在脚本里把种子值写进输出文件名比如train_2024.txt这样即使后来改过种子也能知道手里的清单是哪个版本生成的。类别映射是另一个容易被忽略的点。YOLO的txt每行第一个数字是类别ID从0开始。如果这个数据集只有油罐一类那classes.txt只有一行oil_tank所有txt里的类别ID应该全是0。检查类别集合可以用一段很短的Pythonimport glob class_ids set() for f in glob.glob(labels/*.txt): with open(f) as fp: for line in fp: if line.strip(): class_ids.add(line.split()[0]) print(类别ID集合:, sorted(class_ids))逻辑说明遍历所有YOLO标签文件把每行的第一个字段加入集合最后打印出去重后的结果。正常情况下输出是类别ID集合: [0]。如果出现了[0,1]说明有某个标注文件把类别ID写成了1而classes.txt里根本没有第二个类这会导致训练时报类别数错误。参数说明class_ids.add(line.split()[0])中line.split()按空白字符切分第一个字段就是类别ID。加if line.strip()是为了跳过空行空行虽然不影响训练但统计时会被split解析成空列表触发IndexError。跑完这个脚本再结合前面2.2节的坐标校验标签这块基本就算把关过了。以后你自己用LabelImg打标再导YOLO格式时这两个检查脚本可以直接复用。4. 训练教程与常见问题排查五个让YOLO训练翻车的坑4.1 环境配置与目录准备YOLO的data.yaml怎么写才能不报错划分出train/val/test三个清单之后下一步是写YOLO的data.yaml。无论你用的是ultralytics的YOLOv8还是老一些的YOLOv5data.yaml的结构都差不多。我一般建议用Anaconda建一个独立环境Python版本3.9左右直接pip安装ultralytics然后准备一个这样的配置文件path: /home/user/oil_tank_dataset train: train.txt val: val.txt test: test.txt names: 0: oil_tank逻辑说明path是数据集根目录train/val/test写的是相对于根目录的文本清单文件名。YOLO会读取train.txt中每一行图片路径然后自动把路径中的images替换成labels找标签文件。所以你的目录结构里图片所在文件夹必须叫images标签文件夹必须叫labels这是一个硬编码映射关系不需要在yaml里指定标签目录。如果图片文件夹叫imgs标签文件夹叫labelsYOLO会找不到标签日志里出现WARNING: no labels found in xxx。参数说明path必须写绝对路径。Linux下写~开头虽然shell能识别但YOLO内部解析时不一定展开容易报路径找不到。Windows下尤其糟糕反斜杠方向错误、盘符带中文都会出问题。最省心的方法是在data.yaml里写纯英文绝对路径比如D:/dataset/oil_tank_dataset正斜杠和反斜杠的区别经常让人抓狂。names里的类别索引要和YOLO标签里的class_id严格对应只有一类就只写0: oil_tank。环境验证跑通这一步也很重要。不要一上来就训练先加载预训练权重做一次前向推理确认框架和CUDA正常yolo predict modelyolov8s.pt sourceoil_tank_dataset/images/oil_tank_001.jpg逻辑说明这是ultralytics的命令行预测入口会下载或读取yolov8s.pt对指定图片做一次推理并保存结果。如果这一步能跑通说明CUDA、PyTorch、ultralytics的链路没有问题接下来训练才会顺利。如果报错绝大多数情况是PyTorch版本和CUDA版本不匹配去查对应版本的安装命令不要硬着头皮往下走。4.2 训练命令与关键参数imgsz、batch、epochs怎么定环境就绪后训练命令可以写成这样yolo train dataoil_tank.yaml modelyolov8s.pt epochs200 imgsz640 batch16 patience30逻辑说明data指向yaml文件model指定预训练权重epochs设上限200配合patience早停。imgsz640是训练输入边长batch16是批次大小。油罐是圆形小目标yolov8s这个体量足够不一定非要上larger模型因为模型越大1000张图的训练集越容易过拟合。patience30表示连续30个epoch验证集mAP没有提升就自动停止省时间又不会错过最优权重。参数说明imgsz这个参数在遥感场景里要特别上心。油罐数据集里的原图可能有大几千像素如果你直接imgsz1280显存占用是640的四倍目标确实大了一点但batch只能降到4训练速度大幅下降还不见得能提升精度。更合理的路线是保持640或512配合第5章的切片推理策略。batch16对8G显存来说是安全值12G可以尝试32显存不够时优先减batch不要轻易动imgsz。epochs200只是理论上限实际1000张图往往在80到120个epoch就收敛了早停能帮你省下一半时间。训练过程中要看什么不要盯着训练loss曲线它下降不代表模型好用。重点关注验证集mAP50和mAP50-95这两个指标。mAP50是IoU阈值0.5下的平均精度油罐这种小目标更看重它mAP50-95更严格更适合对比算法性能。如果mAP50已经到0.9但mAP50-95还在0.6以下说明框虽然能框住油罐但位置和边界还不够准这时候可以试试调低置信度门限或者增加训练轮次。训练结束后会生成runs/detect/train/weights/best.pt和last.pt前者是验证集上表现最好的权重后者是最后一次epoch的权重。实际使用中一定用best.pt不要迷信last.pt。这里也有个玄学问题有时候best.pt的mAP比last.pt高不少但因为验证集只有100张best也可能是过拟合验证集的产物。稳妥的做法是最后再看test集的表现如果test集的mAP比val低很多说明泛化有问题需要回看是不是数据划分出了泄漏。4.3 五个必踩的坑现象、原因与解决办法坑一loss不下降训练曲线一路横盘。现象是前几个epoch的box_loss就有几十几个epoch过去纹丝不动。原因绝大多数是标签格式出了问题比如YOLO的归一化坐标没有除以图片宽高或者类别ID从1开始而names里只写了0。解决办法是把第2.2节那个坐标校验脚本跑一遍查一下坐标范围是否在0到1之间类别ID是否连续且从0开始也可以用可视化工具把txt标签画到原图上直接看框是否贴合油罐。坑二训练日志里出现大量no labels found in train set。现象是图片路径都有效但YOLO提示所有图片都没有标签。原因是图片目录名不是images或者标签目录里根本没有与图片同名的txt文件。解决办法是把目录命名调整为images和labels两兄弟确保oil_tank_001.jpg对应labels/oil_tank_001.txt。注意大小写YOLO在Linux下区分大小写Images不等于images。坑三验证集mAP很高但把整张遥感大图喂进去一个目标都检测不到。现象是训练时mAP50到了0.9推理大图时输出框为零。原因是训练和评估都用了小尺寸切片而推理时整图直接被resize到640油罐被压缩到十几个像素池化之后特征全没了。解决办法分两步第一步推理时把置信度门限从默认的0.25降到0.1看看是不是有低置信度的框第二步对大图做切片推理先用滑窗切成512或640的小块逐块检测后把结果映射回原图坐标再按NMS合并重叠框。这一步是遥感小目标落地的关键不能省。坑四训练中途报CUDA out of memory。现象是前几个epoch正常突然显存爆掉。原因可能是batch太大也可能是开了太多并行加载线程导致内存不足。解决办法是先把batch减半从16降到8再考虑把imgsz从640降到512。如果还不够用nvidia-smi看显存占用确认没有其他进程霸占显存。这里不要开着浏览器和视频软件就开训浏览器挂十几个标签页能吃掉几个G显存。坑五train/val/test三个清单之间有重复图片。现象是训练loss高但验证loss很低明显不合理。原因是划分脚本被运行了多次且没有清空旧输出或者原始列表本身有重复。解决办法是在划分脚本末尾加三段断言把三个集合两两做交集只要有重复就直接报错不要等到训练完才发现。train_set set(splits[train]) val_set set(splits[val]) test_set set(splits[test]) assert len(train_set val_set) 0, train和val有重复 assert len(train_set test_set) 0, train和test有重复 assert len(val_set test_set) 0, val和test有重复逻辑说明把三个列表都转成set用集合的交集运算符计算共同元素数量。正常情况应该是0一旦有重复就触发AssertionError脚本在写文件之前停下来避免带着脏数据去训练。注意这里需要把路径去重后再比较因为绝对路径和相对路径即使指向同一个文件字符串也不一样所以划分脚本里要统一用同一套路径表示法。参数说明断言语句不需要额外参数但如果你的数据集中有同一张图片复制成两个文件名的情况set比较不会发现因为文件名不同。这种情况更隐蔽只能通过计算图片内容的哈希值去重不过一般的数据集很少会出现这种问题。5. 训练完怎么验证成果用PR曲线和切片推理让结果真正落地训练结束拿到best.pt先用官方验证命令看一眼整体指标同时生成PR曲线和混淆矩阵yolo val dataoil_tank.yaml modelruns/detect/train/weights/best.pt conf0.25逻辑说明val子命令会在验证集上跑一遍输出mAP50、mAP50-95并保存PR曲线图。PR曲线的横轴是召回率纵轴是精确率曲线越靠近右上角说明模型越可靠。油罐检测这类单类任务PR曲线比mAP数字更能说明问题如果曲线在召回率0.8之前保持平直到0.8之后急剧下降说明有接近两成的油罐被漏掉这时候需要降低置信度门限来换召回率。参数说明conf0.25是推理时的置信度门限。调参技巧是如果业务场景更看重不能漏罐就把conf降到0.1代价是误检变多如果更看重报警就得准就调到0.4以上。没有绝对正确的值要看你的使用场景。我自己习惯把0.1到0.5每隔0.1测一轮挑一个精确率和召回率的平衡点再结合PR曲线决定。真正的遥感大图推理我强烈建议做切片。以一张5000×5000的原始影像为例先按512×512滑窗切成大约100个小块每块之间重叠64像素然后把每个小块分别送入模型检测出的框坐标加上窗口偏移映射回原图坐标最后用非极大值抑制把重叠区域的重复框合并。这个过程看起来简单但窗口大小、重叠率、边缘裁剪都会影响结果。油罐直径在切片上的占比至少要有20像素否则网络学到的特征不够用。如果切片后大多数油罐仍然小于32×32就得考虑把imgsz提高到1024或者换更擅长小目标的检测头。切片推理之外还有一个容易被忽略的验证动作统计测试集上每个油罐框的置信度分布。置信度普遍在0.9以上说明模型学得很确定置信度大多在0.5左右徘徊说明标注本身可能存在边缘不清的问题比如油罐有没有把阴影包进去不同标注者可能有不同理解。我现在的习惯是每次标注新一批数据时固定一个人统一规范罐体用外接矩形阴影不算目标这样模型学到的特征才不会前后矛盾。这个油罐数据集的方向值不值得投入取决于你的任务颗粒度。如果只是做算法验证和课程设计1000张图足够把YOLO的训练流程跑顺三种格式标签也省去了写转换脚本的时间完全够用。如果要做真实业务比如能源设施巡护或违规占地监测那1000张图明显偏少尤其是不同分辨率、不同季节、不同云层遮挡的情况模型会很快露馅。我的做法是把这个数据集当冷启动种子先跑通管线和基准指标之后再用半自动标注扩展新样本用训练好的模型打预标注人工只修边界效率能提升好几倍。还有一点是我的个人习惯每个训练项目都单独建一个目录把data.yaml、train.txt、best.pt和PR曲线放进同一个文件夹并写上备注用的是哪个种子、哪个划分脚本版本。因为训练时各种参数组合实在太多光靠模型文件名区分迟早会记混。导出模型的时候也记得看一下输入尺寸因为推理端如果用TensorRT或OpenVINO输入的imgsz必须跟导出时一致不然框的位置会整体偏移。这些小问题看起来不起眼却在部署时浪费了我最多时间。这个数据集本身就是帮你跳坑的把它用好再往后扩展自己的数据时就能少踩不少雷。希望帮到你。本文还有配套的精品资源点击获取