资讯动态

YOLO遥感油罐检测实战:VOC/COCO/TXT格式转换与训练避坑全指南

发布时间:2026/10/8 19:07:48 来源:尧图企业网站定制
简介面向遥感目标检测与YOLO系列模型训练需求这份资料提供真实场景下的油罐检测数据集共含1000张高质量图片使用LabelImg标注标注框质量高可满足日常教学或科研中的检测任务。压缩包内共2000个文件对应VOC、COCO、YOLO三种标注格式标签文件以xml和txt为主另含yaml配置与Python划分脚本同时内置html版环境搭建教程和训练案例教程覆盖Linux与Windows双系统便于在不同平台快速复现。教程从环境配置、数据集划分到模型训练均有说明三个划分脚本可自动生成训练集、验证集、测试集帮助用户省去手动整理图片与标签的烦琐步骤。当前已有247人学习下载适合遥感目标检测入门者及需要现成标注数据进行YOLO系列实验的研究人员。1. 拿到YOLO遥感油罐检测数据集先别急着解压我拿到标注好的数据集固定动作从来不是立刻解压扔进训练脚本而是先想清楚油罐这种密集排列的小目标和日常马路上的车、人是两类完全不同的图像分布。遥感影像里的油罐是俯视视角圆形、金属反光强、受阳光照射还拖一条长影通用目标检测数据集里很难见到这种形态学特征。你手头这个压缩包里有1000张油罐影像配了VOC、COCO、YOLO三套标签还附带了划分脚本和训练教程这其实已经替新手回答了最常见的两个问题标注怎么转成训练要的格式、数据怎么划分。但真正值钱的不是文件数量而是这1000张图里包含的边界情况——阴影遮挡、油罐并排紧贴、不同分辨率下的尺度差异。这篇笔记按「读懂三格式 → 跑通划分脚本 → 训练出模型 → 小目标改进与部署」的顺序展开新手可以照着一步一步做熟手直接跳到第4章和第6章看边界条件。2. 三种格式标签的内在逻辑VOC、COCO、YOLO各自怎么描述一个油罐2.1 从一张遥感图出发VOC的XML文件字段逐个拆解VOC格式对新手最友好因为它的组织方式非常直观一个XML文件对应一张图片里面把目标类别、外接矩形坐标、图片尺寸全部写清楚。但缺点也在这里——1000张图就有1000个XML文件复制、备份、传输时小文件特别多容易出现漏拷贝。打开一个典型的VOC标注文件你会看到这样的结构annotation folderJPEGImages/folder filenameoil_tank_001.jpg/filename size width1024/width height768/height depth3/depth /size object nameoil_tank/name bndbox xmin412/xmin ymin289/ymin xmax468/xmax ymax345/ymax /bndbox /object /annotation这段XML说的是图片oil_tank_001.jpg中有一个目标类别名为oil_tank外接矩形的左上角是(412, 289)右下角是(468, 345)。注意这个框是轴对齐的矩形不表达旋转角度。遥感影像里的油罐虽然是圆形但标注时统一用外接矩形包住YOLO类检测器默认也只输出矩形框所以这种表达足够训练使用。逻辑说明写XML时最容易出错的是filename字段与真实文件名不一致或文件夹层级写错。脚本改写到VOC时如果发现标注加载不出来十有八九是filename里带了绝对路径或者Windows风格的反斜杠。另一个问题是多目标时object块会有多个每个object必须单独写完整VOC的解析器按object块数量读取少一个就少一个目标。2.2 COCO的JSON结构什么时候用得上它COCO格式把整份标注聚合到一个JSON文件里images、annotations、categories三个数组各司其职。这种设计的好处是官方配套工具链多模型评估、可视化、Mask标注都能直接用pycocotools处理。你在YOLO训练时基本用不到它但想换成DETR、Mask2Former或者其他支持COCO输入格式的框架时这份JSON能省下大半天的时间。一个精简过的COCO JSON长这样{ images: [ {id: 1, file_name: oil_tank_001.jpg, width: 1024, height: 768} ], annotations: [ {id: 1, image_id: 1, category_id: 1, bbox: [412, 289, 56, 56], area: 3136, iscrowd: 0, segmentation: [[412,289,468,289,468,345,412,345]]} ], categories: [ {id: 1, name: oil_tank} ] }参数说明COCO的bbox是「左上角x、左上角y、宽度、高度」单位是像素不归一化segmentation是多边形顶点坐标我自己给油罐做分割标注时一般只给外接矩形的四个角点不画精细轮廓因为油罐边缘在遥感影像里受光照和阴影影响波动大多边形勾得太细反而把噪声教给了模型。area字段就是宽乘高pycocotools评估时会用它做面积分层的AP统计例如小目标AP、中等目标AP。COCO的坑是JSON里所有整数不能写成浮点数尤其是images宽度和高度一旦写成1024.0某些严格校验的框架会直接报类型错误。还有iscrowd字段默认0如果出现1评估时该目标会被当作人群处理特别影响mAP计算结果。2.3 YOLO的TXT格式训练脚本真正读的是什么这是压缩包里最重要的一份标签因为YOLOv5、YOLOv8在训练时读的是同名TXT不是XML也不是JSON。YOLO标签格式的核心是归一化的中心点坐标一行代表一个目标0 0.4297 0.4128 0.0547 0.0729按顺序解读这五个值类别id为0对应oil_tank、目标中心点的x在图片宽度42.97%的位置、中心点的y在图片高度41.28%的位置、框宽度占图片宽度5.47%、框高度占图片高度7.29%。所有值都在0到1之间宽度和高度严格大于0。这种相对坐标的表达精妙之处在于图片从1024x768换成2048x1536TXT里数值不需要改动模型在数据加载时按当前分辨率重新映射。但代价是理解门槛高了新人在转换时容易忘记除以宽高导致坐标值超过1训练过程大概率报错或框全在图片角落。我在第2.1节例子中的VOC框换算成YOLO格式是这样的x_center (412 468) / 2 / 1024 # 0.4297 y_center (289 345) / 2 / 768 # 0.4128 w (468 - 412) / 1024 # 0.0547 h (345 - 289) / 768 # 0.0729逻辑说明中心点坐标取(xminxmax)/2再除以图片宽度框宽是(xmax-xmin)除以图片宽度全部用浮点数运算除法不能取整。这些换算关系就是作者做格式转换脚本时最核心的逻辑你在复现或二次转换时同样绕不开这一步。2.4 三种格式互转的关键坐标换算一份对照表把三种格式放在同一张表里看边界框的表达方式一目了然格式坐标体系是否归一化文件组织典型用途VOC XMLxmin, ymin, xmax, ymax否每图一个XML标注工具导出、教学示例COCO JSONx, y, width, height否全量一个JSON框架迁移、pycocotools评估YOLO TXTx_center, y_center, width, height是每图一个TXTYOLOv5/v8训练直接读取平时做项目我一般以YOLO格式为主仓库训练时直接喂需要做评估或者对比实验时再从YOLO转回COCO这个转换是无损的。反过来的路径——从COCO转YOLO——同样是纯算术操作但要注意COCO的坐标x、y是左上角而YOLO需要的是中心点。油罐检测场景尤其要看宽度和高度的换算因为圆形目标的外接矩形近似正方形如果某个标注框的宽高比明显偏离1:1很可能是标注工具或转换脚本出了问题排查时先从这种异常值入手。提示判断一份标签是否转换正确最快的方法是把标注框画回原图直接肉眼检查。不要相信脚本不出错就等于结果正确坐标换算的错误经常是静默发生的。3. 划分脚本的正确用法保证图片和三种标签同步移动3.1 先盘点解压后的目录弄清三类标签的摆放层级解压rar之后第一步不是急着改代码而是把目录结构完整地看一遍。常见的一种布局是这样oil_tank_dataset/ ├── images/ │ ├── oil_tank_001.jpg │ └── ... ├── annotations_voc/ │ ├── oil_tank_001.xml │ └── ... ├── annotations_coco/ │ └── annotations.json └── labels_yolo/ ├── oil_tank_001.txt └── ...先把三类标签和图片的数量对起来再抽查文件名的前缀是否一致。我通常会写一个极短的Bash命令做校验for f in images/*.jpg; do base$(basename $f .jpg) [ -f annotations_voc/$base.xml ] || echo 缺少VOC: $base [ -f labels_yolo/$base.txt ] || echo 缺少YOLO: $base done逻辑说明遍历所有图片用去掉后缀的文件名去检查对应XML和TXT是否存在缺失时打印提示。压缩包标题写的是「对应」三种格式标签正常情况下三类数量应该完全一致如果发现缺失先别跑划分缺标签的图片在训练时会被静默跳过或报出警告不报错的黑匣子问题最麻烦。我建议也在这一步统计一下空标签的数量。YOLO的TXT文件允许是0字节的代表这张图没有任何油罐目标这类图是背景负样本训练时可以保留但数量需要控制在合理范围。3.2 按比例划分的Python脚本同步移动三类文件划分脚本是压缩包里最实用的小工具它做的事情本质上是把图片按比例分成train、val、test三份同时把对应的VOC XML、YOLO TXT移动到相同结构的目录下。下面是实际可用的版本import os import random import shutil src_img images src_voc annotations_voc src_yolo labels_yolo dst_root dataset split_ratio {train: 0.8, val: 0.1, test: 0.1} random.seed(42) imgs [f for f in os.listdir(src_img) if f.endswith(.jpg)] random.shuffle(imgs) for split, ratio in split_ratio.items(): n int(len(imgs) * ratio) part imgs[:n] imgs imgs[n:] for img in part: base os.path.splitext(img)[0] for sub_src, sub_dst in [ (src_img, images), (src_voc, annotations_voc), (src_yolo, labels_yolo)]: ext .jpg if sub_src src_voc: ext .xml elif sub_src src_yolo: ext .txt src os.path.join(sub_src, base ext) dst_dir os.path.join(dst_root, split, sub_dst) os.makedirs(dst_dir, exist_okTrue) shutil.copy(src, dst_dir)参数说明split_ratio字典控制划分比例默认8:1:1想改成7:2:1直接把字典里的数值换掉random.seed(42)保证每次运行得到完全一样的划分结果这个细节在对比实验时特别重要没有固定随机种子两次划分到train里的图片不同实验结论就不客观。用shutil.copy而不是move是因为原始文件只有一份move跑错一次目录就乱了留一份原始文件算是给自己留了后悔药。注意这里的取整逻辑每次从imgs列表头部取走n张剩下的继续给下一个集合所以最后三个集合的图片总数一定等于全部图片数不会出现同一张图出现在两个集合里的情况。提示划分完成后COCO的JSON也要跟着拆。因为一个JSON包含全量标注简单做法是读JSON后按image_id归属分别写train.json、val.json、test.json这个步骤常被忽略等到换框架训练时才发现验证集里混进了训练图片。3.3 划分后数据质量如何验证不可跳过的一步划分脚本跑完输出一个数字总让人心里不踏实因为复制操作是否完整、有没有丢文件脚本本身不会告诉你。我的习惯是立刻再跑一遍校验import glob for split in [train, val, test]: imgs glob.glob(fdataset/{split}/images/*.jpg) xmls glob.glob(fdataset/{split}/annotations_voc/*.xml) txts glob.glob(fdataset/{split}/labels_yolo/*.txt) print(split, imgs:, len(imgs), xml:, len(xmls), txt:, len(txts), OK if len(imgs) len(xmls) len(txts) else MISMATCH)逻辑说明glob按目录模式匹配出所有文件统计每个集合内图片、XML、TXT的数量。三个数必须相等只要有一个不等说明划分脚本里有路径或扩展名的疏漏不要带着这个隐患开始训练。数量校验通过之后还要抽查TXT内容里的数值范围。YOLO格式的x_center、y_center必须在0到1之间width、height必须大于0。如果有数值越界说明标签本身有损坏这类图片在训练时会导致loss异常升高。写一个快速的检查脚本扫一遍所有TXT看到异常值就打印文件名这个习惯能帮你避开很多莫名其妙的训练翻车。4. 遥感油罐数据集的5个避坑点格式转换与训练前检查4.1 坑一VOC转YOLO时把归一化坐标写成了整数现象用转换工具把XML转成TXT训练能正常启动但loss始终降不下去用可视化脚本画标注框时发现所有框都堆在图片左上角。原因VOC的xmin、ymin是绝对像素整数转YOLO的归一化坐标时有人直接用(xmin xmax) // 2再除以width或者转换脚本里把除法写成了整除结果中心坐标全部变成了0目标框自然全跑到左上角。解决转换时强制使用浮点数运算width和height加上非零保护x_center (xmin xmax) / 2.0 / img_width。转换完成后随机抽5张图把TXT中的归一化坐标乘以图片宽高反算回像素值和XML里的原始坐标核对误差不超过1个像素才算通过。4.2 坑二划分脚本没有固定随机种子现象两次训练代码一模一样但mAP忽高忽低你以为模型随机初始化造成的波动换了三个种子都在0.5到0.8之间乱跳。原因划分脚本没写random.seed每次运行把不同的图片分到了train和val验证集不稳定评估结果互相之间没有可比性。解决在脚本开头固定random.seed(42)同时保证Python的random、NumPy、PyTorch三者的种子全部设好。这样别人拿到你的脚本能复现同一个划分结果你的对比实验也才有意义。4.3 坑三空标注文件被当作负样本还是该删掉现象训练日志里出现大量「Image has no labels」警告训练本身不报错但val集mAP在0.3左右徘徊误检率特别高。原因数据集中存在一些没有油罐的纯背景图TXT为空。这类负样本如果占比超过10%模型会偏向于输出「没有目标」的预测漏检率上升如果完全删掉模型又容易把类似油罐的圆形物误检为正样本。解决先统计空TXT数量如果占比在3%以下直接连同对应图片一起移除占比在3%到10%之间保留但单独建一个负样本目录别和正样本混在同一个train集合里占比超过10%检查标注过程是否漏标了图片。4.4 坑四小目标漏检不是改个分辨率就能解决现象val集mAP50达到了0.8但把模型拿到大场景遥感图上推理远处的油罐全漏掉。原因输入分辨率640时一个小油罐在特征图上可能只占一个点YOLOv8的默认下采样倍数下小目标信息在深层特征图里已经丢失。调高imgsz到1024能改善但显存占用翻倍训练时间也成倍增加。解决先分析数据集中目标的像素尺寸分布统计宽度和面积的中位数。如果多数油罐小于32x32像素优先考虑在YOLOv8里添加P2检测层或者用NWD替换IoU损失函数这些属于结构性的改进单纯调imgsz是从数据端缓解不是从模型端根治。4.5 坑五密集排列的油罐导致NMS误抑制现象两个油罐紧挨着摆放模型检测时只输出一个框另一个被抑制掉了可视化时看着像漏检。原因遥感油罐经常成排密集布局真实目标的IoU本身就很高NMS算法认为它们是同一个目标把得分低的那个框抑制了。解决推理时把NMS的IoU阈值从默认的0.45调低到0.2或0.25允许两个高重叠框同时保留。这需要在验证集上重新测量mAP找到阈值调低后漏检率下降而误检率不激增的平衡点。另一种做法是训练时数据增强里加入Mosaic和Cutout的强度控制让模型对遮挡更鲁棒。5. 用YOLO训练油罐检测模型最小可跑通的训练流程5.1 数据配置文件把划分好的目录告诉YOLO训练前需要写一个描述数据集的YAML文件YOLOv5和YOLOv8的格式基本一致。以YOLOv8为例在数据集根目录创建oil_tank.yamlpath: /home/user/oil_tank_dataset train: train/images val: val/images test: test/images nc: 1 names: [oil_tank]参数说明path是绝对路径train和val填写的是相对path的目录指向划分脚本生成的train/images与val/images。nc是类别数量这里只有一个油罐类别所以是1。names列表里的顺序必须和TXT标签第一列的id对应id从0开始所以第一个名字就是油罐。YOLOv8在首次训练时会扫描所有图片和标签生成缓存文件以加速后续读取这个过程会打印扫描进度如果发现某个TXT文件名与图片对不上会在这一步直接报错。这个机制本身就是对划分结果的一次自动校验。5.2 训练命令与关键参数跑通一个最小可用的模型写好了YAML直接执行训练命令yolo detect train \ --data oil_tank.yaml \ --model yolov8s.pt \ --epochs 100 \ --imgsz 1024 \ --batch 16 \ --device 0参数说明model用yolov8s.pt作为预训练权重COCO数据集上预训练过的模型已经学会了通用的边缘、纹理特征迁移到油罐数据集上收敛速度远快于随机初始化。imgsz1024是为遥感小目标设置的第一次跑也可以先用640验证流程通不通通之后再调成1024正式训练。batch16在1024分辨率下需要大约12GB显存如果CUDA out of memory就降成8。device指定GPU编号多卡机器写成0,1,2,3即可。YOLOv5对应命令python train.py \ --data oil_tank.yaml \ --weights yolov5s.pt \ --img 1024 \ --batch 16 \ --epochs 100两者的本质是一样的选择时主要看团队里已有的代码基础设施和部署链路。我自己的经验是如果只想快速出结果YOLOv8的命令行更干净如果后续要改loss、改结构YOLOv5的源码组织更直观。5.3 训练日志怎么看loss下降和mAP上升的合理节奏训练过程中终端会实时打印每个epoch的box_loss、cls_loss、dfl_loss并在验证集上计算mAP50和mAP50-95。不要只盯着最终mAP节奏比终值更重要。box_loss应当稳定下降前20个epoch下降幅度大后面逐渐平缓如果box_loss降到一定值后反弹说明学习率过大或数据里有异常标签。cls_loss对于单类别数据集通常下降最快因为二分类比多分类简单如果分类损失一直不降检查TXT的类别id是否全部正确。mAP50在油罐检测任务上训练正常的话应该在0.85以上mAP50-95比mAP50低0.2到0.3是正常的因为小目标对IoU的微小变化非常敏感不要因为这个差距去怀疑模型有问题。训练到一半发现效果不对先停掉改参数不要等100个epoch跑完再调整。还有一个细节观察前5个epoch的mAP曲线如果从第一个epoch就开始上涨说明预训练权重和数据集对接顺利如果前5个epoch为0先检查标签是否加载成功而不是继续等下去。6. 从改进到部署油罐小目标的NWD与TensorRT加速6.1 NWD改进思路用归一化高斯距离替代IoU油罐检测场景和YOLO小目标优化是天然强相关的圆形目标密集、像素面积小、排列紧密IoU对小目标的位置偏差特别敏感偏移两三个像素IoU就可能从0.7掉到0.3梯度信号弱且震荡大。改进YOLO时一个常见的落点是换回归损失用归一化高斯距离替代IoU把边界框视为二维高斯分布通过Wasserstein距离度量两个分布的形态差异。def nwd_loss(pred, target, alpha2): # pred, target: [..., 4]格式为x_center, y_center, w, h center_dist (pred[..., 0] - target[..., 0]) ** 2 \ (pred[..., 1] - target[..., 1]) ** 2 wh_dist (pred[..., 2] - target[..., 2]) ** 2 \ (pred[..., 3] - target[..., 3]) ** 2 wd alpha * center_dist wh_dist nwd torch.exp(-torch.sqrt(wd) / 2) return (1 - nwd).mean()逻辑说明center_dist计算两个框中心点的距离平方wh_dist计算宽高差异平方alpha控制中心距离和尺度差异的权重比。整个表达式把Wasserstein距离归一化到0到1之间两个框完全不重叠时IoU为0导致梯度消失NWD仍然能提供微小的梯度信号让漏检的小目标在后续迭代里有的学。参数说明alpha的典型取值在2到4之间油罐这类形状规整的目标取2就够遥感场景下如果目标尺度变化大可以适当调大到3或4。NWD在油罐数据集上的效果通常能提升2到5个点的mAP50但收益集中在小目标部分如果你的目标普遍大于64x64像素这个改动的意义就不大。这也是我一直强调的模型改进要对着数据短板打而不是反过来套一个热门改进就期待涨点。6.2 导出ONNX与TensorRT小目标模型的部署落地训练收敛之后下一步是部署。先把PyTorch权重导出为ONNXyolo export modelbest.pt formatonnx opset12 simplifyTrue导出成功的ONNX可以在onnxruntime里直接推理验证。在英伟达平台上TensorRT通常比onnxruntime快一截转成engine文件trtexec --onnxbest.onnx --saveEnginebest.engine --fp16trtexec默认batch为1如果你计划做多路RTSP视频流并行推理需要在导出ONNX时就设置动态batch维度并用maxBatch参数指定最大batch数。推理时把多路视频的帧拼成一个batch统一送入engineGPU利用率会明显高于逐路推理。我经常被问到T4显卡能带多少路1080p视频这个问题的答案取决于预处理耗时、后处理耗时和batch大小单看engine推理时间没有意义必须把整条pipeline一起算。6.3 最后一个建议把数据清单当作项目的第一份产出这个压缩包给你的不只是1000张图和标签而是一个完整的产业起点。我每次拿到新数据集第一件事永远是建一张清单图片数量、每个类别的目标数量、标签格式、划分比例写成一个表格放在项目根目录。后续所有的实验记录、模型迭代都围绕这张表展开。数据本身的干净程度比模型算法重要十倍YOLO结构再怎么更新都抵不上你认真排查一遍训练集里有没有漏标目标、有没有坐标越界。我自己在这条路上踩过最大的坑就是拿到数据集后迫不及待地开训默认的640分辨率训了50个epoch才发现小目标全漏了回头一查才发现目标面积中位数只有十几个像素白白浪费了时间。希望这些排过的雷、试过的参数能帮你在拿到这个油罐数据集之后少走弯路快速跑通一个可用模型。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑