卫星遥感舰船检测双格式数据集:VOC转YOLO与训练全流程

📅 发布时间:2026/9/24 0:31:21
卫星遥感舰船检测双格式数据集:VOC转YOLO与训练全流程
简介一份卫星遥感舰船检测数据集面向目标检测算法开发、遥感影像智能解译等应用场景也适合计算机视觉初学者作为多类别目标检测练习数据。数据涵盖17个舰船类别包括航空母舰、驱逐舰、潜艇、货船、油轮等典型目标类别划分细致基于两千余张遥感影像构建并提供Pascal VOC与YOLO两种主流标注格式可直接接入YOLO、Faster R-CNN、SSD等常见检测框架训练。资源包采用7z压缩格式解压后总大小约109.32MB共2000个文件文件以VOC格式的XML标注为主另附YOLO标注支持及使用说明目录清晰、便于批量读取与格式转换也方便按需筛选与二次开发。该数据集已吸引834人学习下载特别适合需要多类舰船标注样本的科研人员、算法工程师和竞赛团队使用能够显著节省数据采集与人工标注时间为高精度舰船识别模型提供可靠的数据基础也可用于教学演示与开源项目复现覆盖多尺度目标检测场景。1. 卫星遥感舰船检测数据集VOCYOLO双格式、2238张、17类直接绕开标注转换这一步拿到这包“卫星遥感舰船检测数据集”第一反应是确认它能不能直接喂给YOLO。解压之后是三组文件2238张jpg、2238个VOC风格的xml、2238个YOLO风格的txt类别17类覆盖航母、驱逐舰、护卫舰、油轮还包括Dock、Landing这类港口设施。这份资源的价值不在图片多而在双格式并存——xml负责可追溯、可修正txt负责直接训练两边是同一套标注内容等于给你留了后悔药。适合三类人做遥感目标检测的研究生、拿公开数据验证YOLOv8流程的工程新手、以及需要快速迭代舰船类目标的算法团队。接下来把从解压到跑通训练前的全部准备过程拆开每个环节都给能直接复制的代码和参数说明。2. 双格式的对应逻辑xml的绝对坐标与txt的归一化坐标差在哪很多人拿到双格式数据集会以为两份标注是冗余随便用哪份都行。实际操作下来并不是VOC格式的xml是可编辑、可回读的“源文件”YOLO格式的txt是简化后的“派生文件”。一旦需要增删类别、修正错框改完xml重新派生txt比直接改txt容易得多。这也是我劝你先别删xml的原因——后续任何一个改标注的需求都会绕回xml。2.1 两种标注的字段含义VOC的xml用绝对像素坐标描述目标核心字段是bndbox节点里的xmin、ymin、xmax、ymax以及object节点下的name。YOLO的txt每一行代表一个目标格式是class_index cx cy w h四个坐标值全部归一化到0~1之间class_index是类别索引从0开始编号。两边字段的对位关系如下。xml字段txt字段含义示例值nameclass_index类别名与类别索引的映射Aircraft Carrier - 0xmin, xmaxcx, w左右边界换算为中心点和宽度xmin120, xmax240 - cx0.14ymin, ymaxcy, h上下边界换算为中心点和高度ymin80, ymax160 - cy0.13原图宽高无归一化时的分母1280x720这套对应关系里最容易被忽略的是宽高归一化的分母必须来自当前图片实际尺寸而不是xml里size节点的旧值。xml是人工编辑过的size节点可能在多次压缩、裁剪后被改过一次而标签却仍由旧尺寸派生最终结果就是txt里的比例尺整体偏移模型训练时损失曲线正常但推理框全部偏高或偏左。我在另一份遥感数据上遇到过这个问题排查半天才发现是xml里的宽度比实际图窄了400像素。2.2 从bndbox换算到YOLO标签的验证脚本双格式数据集的另一个好处是可以互查。下面这段脚本把xml里的绝对坐标换算成YOLO归一化坐标再与已有的txt逐行对比专门用来确认“两份标注是不是真的同一套内容”。import xml.etree.ElementTree as ET import os import cv2 def xml_to_yolo(xml_path, img_path): # 宽高必须从真实图片读取不用xml里的size字段 img cv2.imread(img_path) h, w img.shape[:2] root ET.parse(xml_path).getroot() converted [] for obj in root.findall(object): cls_name obj.findtext(name) bnd obj.find(bndbox) xmin float(bnd.findtext(xmin)) ymin float(bnd.findtext(ymin)) xmax float(bnd.findtext(xmax)) ymax float(bnd.findtext(ymax)) # 中心点坐标换算 cx ((xmin xmax) / 2.0) / w cy ((ymin ymax) / 2.0) / h bw (xmax - xmin) / w bh (ymax - ymin) / h converted.append((cls_name, cx, cy, bw, bh)) return converted # 使用示例对任意一张图做换算 for xml_path in [Annotations/firc_ship_18.xml]: img_path xml_path.replace(Annotations, images).replace(.xml, .jpg) print(xml_to_yolo(xml_path, img_path))逻辑说明脚本用cv2.imread读取原始图像拿到真实宽高再对每个object节点取出bndbox的四个边界值中心点cx用左右边界相加除以2再除以宽宽度bw用右边界减去左边界再除以宽高度同理。输出是一个包含类别名和四个归一化值的元组列表。参数说明w和h的单位是像素这里用浮点除法Python 3里不会丢精度round到6位是YOLO训练脚本的常见习惯实际保留7~8位也行但不要改成整数否则框与框之间的微小差异会丢失密集排列的集装箱船容易被合并成一个大框。对比txt时把输出里的类别名替换成对应索引再与txt每一行做逐值对比误差超过1e-4就说明转换链路有偏差。2.3 最容易翻车的类别编号问题YOLO格式的txt里没有类别名只有索引0~16。这个索引对应的类名由训练端data.yaml里的names列表决定而不是由xml里的类名字典序决定。也就是说同一行0 0.5 0.5 0.1 0.1在names顺序为Aircraft Carrier在前时是航母顺序一换就变成了其他船。常见做法是分别打开一份单类目标的firc_ship_*.xml和对应的firc_ship_*.txt确认xml里的类名与txt首列索引指向的类名一致。尤其要注意Other Ship和Other Warship这类语义相近的类它们做标注时很容易被标混如果训练集里这两类标签本身错位模型就会长期无法区分货船与军舰。我一般会把这17个类名按资源给定的顺序先写进data.yaml再做一次全量交叉验证而不是直接拿未知顺序的txt开跑。3. 解压后先验证三件事文件完整性、坐标一致性、类别分布跳过校验直接训练是大多数检测项目翻车的起点。这份数据集的目录结构很规整但只要缺一张图或漏一个txtYOLO训练时对应的图像就会被静默跳过最终AP曲线看起来正常实际参与训练的样本却少了一批。所以拿到压缩包的第一天我先花半小时跑下面的三项检查。3.1 完整性jpg、xml、txt三套文件名必须一一对应文件命名规律是firc_ship_编号三种格式共用同一个编号。先在bash里统计总量。find images -name *.jpg | wc -l find Annotations -name *.xml | wc -l find labels -name *.txt | wc -l三个数字都应该是2238。数量一致不代表文件一一对应还得比对去掉后缀之后的文件名集合。ls Annotations/*.xml | sed s/.*\///;s/\.xml$// | sort xml_ids.txt ls images/*.jpg | sed s/.*\///;s/\.jpg$// | sort img_ids.txt comm -3 xml_ids.txt img_ids.txtcomm -3输出两个集合的差异行正常情况不打印任何内容。sed两步分别是去掉路径前缀、去掉.xml和.jpg后缀sort保证两边排序一致。若出现多余的txt文件比如某个firc_ship_*.txt额外残留导出的标签副本YOLO训练不报错但验证时统计框数与xml对不上这一类问题靠第3.2节的交叉验证才能抓出来。3.2 坐标一致性xml与txt的框数是否完全相等文件完整只说明“三份文件都在”不说明“标注内容一致”。下面这段脚本逐一统计每张图的xml对象数与txt行数不一致就把图名打出来。import glob import os import xml.etree.ElementTree as ET def count_xml_objects(xml_path): root ET.parse(xml_path).getroot() return len(root.findall(object)) def count_txt_lines(txt_path): if not os.path.exists(txt_path): return 0 lines [ln for ln in open(txt_path) if len(ln.split()) 5] return len(lines) for img_path in glob.glob(images/*.jpg): base os.path.basename(img_path)[:-4] xml_path fAnnotations/{base}.xml txt_path flabels/{base}.txt n_xml count_xml_objects(xml_path) n_txt count_txt_lines(txt_path) if n_xml ! n_txt: print(f不一致: {base} xml{n_xml} txt{n_txt})逻辑说明count_xml_objects统计object节点个数count_txt_lines只统计至少5列的行避免把空行或注释行也算进去。循环遍历所有jpg按前缀拼接出xml和txt路径数量不相等就输出。参数说明 5这层过滤很关键。有些标注工具会在txt末尾写一行类别颜色配置这行数据不足5列不该视为标签反过来如果txt每一行都恰好5列但首列数字超出0~16范围说明类别索引配置错了这属于第5章要讲的问题。我见过最隐蔽的情况是某一类停靠岸边的舰船全部没框xml里没有对应objecttxt行数自然比正常图少人工看不出脚本一跑就暴露了。3.3 类别分布先摸清17类的底子再决定训练策略只看总量不看分布类别不平衡会在训练中期突然暴雷。这步统计两类数据图片级类别覆盖数某类出现在多少张图里和标注框级类别计数某类总共有多少框。两者含义不同前者决定模型能否见到该类样本后者决定模型能否充分学习该类特征。from collections import Counter import glob import xml.etree.ElementTree as ET img_cls Counter() box_cls Counter() for xml_path in glob.glob(Annotations/*.xml): root ET.parse(xml_path).getroot() names [obj.findtext(name) for obj in root.findall(object)] img_cls.update(set(names)) # 图片级这张图里出现过的类 box_cls.update(names) # 框级每个类累计多少框 print(图片覆盖数:, dict(img_cls)) print(框数量:, dict(box_cls))逻辑说明img_cls.update(set(names))先去重再计数回答“有多少张图包含航母”box_cls.update(names)不去重回答“航母这个类总共多少标注框”。跑完这步基本能预测后续训练结果——样本量垫底的类AP一般也不太好看。参数说明如果某类框数量是两位数的量级比如常见数据集里Aircraft Carrier、Submarine这类样本天然就少那第5.2节的过采样和增强方案就要优先照顾它们如果跑出来Oil Tanker、Cargo这类商船数量远高于军舰模型会自然偏向商船验证时Cruiser、Frigate的AP就会被压得很低。此处的统计结果同时决定第4章的划分方式和第5章的增强比例值得在看训练曲线之前先打印出来。4. 从裸标注到可训练状态划分train/val、写data.yaml、定YOLOv8超参这份数据集的txt里没有分割路径也就是说官方没替你做train/val划分拿data.yaml指到images目录训练会把验证集混进训练集。实际做法是自己划分并组织目录这一章把从裸标注到可训练状态的完整链路跑一遍。4.1 划分策略固定随机种子而不是按文件名硬切文件命名与拍摄场景相关按文件名头尾切分会让某些舰船类型只出现在训练集或验证集。应按随机种子打乱后均匀切出训练、验证、测试三部分比例用0.8/0.1/0.1测试集留着做最终评估不参与训练和调参。import glob import random random.seed(42) # 种子固定确保每次划分结果一致 imgs sorted(glob.glob(images/*.jpg)) random.shuffle(imgs) n len(imgs) n_train int(n * 0.8) n_val int(n * 0.9) train_imgs imgs[:n_train] val_imgs imgs[n_train:n_val] test_imgs imgs[n_val:] for name, subset in [(train, train_imgs), (val, val_imgs), (test, test_imgs)]: with open(f{name}.txt, w) as f: for p in subset: f.write(p \n)逻辑说明random.seed(42)固定随机源保证多次运行生成的划分一致后续调参时能对比“同一批验证集”上的结果。sorted()先排序再打乱避免不同平台文件顺序差异干扰随机性。划分结果写入三个txt列表文件。参数说明比例参数0.8和0.9分别对应训练集1780张、验证集224张、测试集224张左右。如果某个小样本类恰好在测试集里出现次数过多验证集看不到该类样本逐类AP会失真此时应改用分层抽样按类别分布做train_test_split的stratify划分。常见做法是先用普通随机划分跑通流程确认类别底线后再决定是否上分层。4.2 目录组织与data.yaml的写法YOLOv8按目录结构对应标签不读取路径列表txt。因此必须把图片和标签按同样方式移动组织成images/train、images/val、images/test与labels/train、labels/val、labels/test。while read p; do f$(basename $p) mv images/$f images/train/ mv labels/${f%.jpg}.txt labels/train/ done train.txt${f%.jpg}去掉jpg后缀拼接出txt文件名再移动。val和test两个列表重复执行同样命令。执行完检查labels/train下的txt数量与images/train一致否则说明某个标签缺失。data.yaml按下面的结构写path: /data/ship train: images/train val: images/val test: images/test nc: 17 names: 0: Aircraft Carrier 1: Auxiliary Ships 2: Cargo 3: Commander 4: Container Ship 5: Cruiser 6: Destroyer 7: Dock 8: Frigate 9: Hovercraft 10: Landing 11: Oil Tanker 12: Other Ship 13: Other Warship 14: RoRo 15: Submarine 16: Yacht三个高频错误一是path路径不对YOLO会把train字段相对path拼接路径写错会直接报文件不存在二是nc与names数量不一致YOLO训练时按names数量决定输出头数量对不上loss计算就会错位三是names顺序与txt首列索引顺序不一致这种不报错但模型输出完全错乱。解决第三类问题最快的方法就是第2.3节说的单类图验证法。4.3 YOLOv8超参初调分辨率优先、batch按显存收缩卫星遥感舰船目标在原始影像里往往只占几十个像素输入分辨率直接决定小目标还能不能保留特征。先用一次小规模训练确定baseline再逐步加量。yolo detect train \ dataship.yaml \ modelyolov8s.pt \ imgsz1280 \ batch8 \ epochs100 \ ampTrue \ workers4imgsz1280是遥感舰船检测的关键起点。640输入下一艘只有30像素宽的目标经过5次下采样后只占约1个像素特征图里基本消失1280输入让它至少保有2~3个像素的响应。batch受显存约束8G显存跑yolov8s加1280输入很紧张可以降级到yolov8n显存不足时依次考虑调小batch、换成小模型、最后才把imgsz降到1024。参数说明ampTrue表示混合精度训练多数环境能省20%~30%显存但旧驱动或特定显卡下偶尔出现loss为NaN的情况一旦遇到就关掉amp再试。workers4是数据加载进程数Windows上建议改成0或2避免多进程与训练主进程冲突。第一次跑不要直接上yolov8x先用最小模型验证数据链路通了再换大模型。5. 避坑实录小目标漏检、类别样本失衡与非船类标注边界这一章是数据集落地时最容易翻车的几处每一条都对应实际训练里的真实事故。按现象、原因、解决三段记录踩过的坑能少走两圈冤枉路。5.1 小目标漏检舰船在影像里太小下采样后特征消失现象训练loss正常下降但val的mAP0.5在0.5上下徘徊放大检测结果图发现大量船只根本没被框出来尤其远离港口的海面目标整片区域没有任何预测框。原因舰船目标天然属于小目标范畴原图中可能只有20x40像素。输入尺寸设为640时经过YOLOv8的stride 32下采样目标在深层特征图上只剩不到2个像素分类头拿到的语义信息几乎为零。这不是模型不努力而是输入分辨率决定了它看不到。解决先按第4.3节把imgsz提到1280再看小目标是否改善。还不够时推理端用滑窗切图配合预测框合并把原图切成若干1280x1280的块每块独立预测再把框坐标映射回原图重叠区域的重复框用NMS合并。这个方案比单纯放大整图更省显存缺点是推理时间增加需要按场景取舍。5.2 类别样本失衡小样本类别被商船类压制现象训练完成后查看逐类APOil Tanker和Cargo的AP能到0.8以上而Submarine、Commander这类AP只有0.2甚至更低。整体mAP看着还行一拆开就露馅。原因数据集里商船类目样本占据大头模型学习时对高频类拟合得更充分低频类本就样本少加上随机划分时如果小样本类恰好没怎么进训练集模型等于没见过这类目标。解决第一步回看第3.3节的分布统计确定哪些类框数少于300第二步对这些类做过采样把小样本类图片在训练集中复制2~3份或者对同一张图做旋转、翻转、亮度扰动后作为多张图参与训练第三步如果类别之间特征差异不直观可以用复制粘贴增强把小样本目标贴到海面背景图里注意贴图时目标尺寸要缩放得和原图一致否则模型会把特殊分辨率当作特征。5.3 Dock与Landing的语义边界这些类不是“船”现象训练时把Dock当成一个普通目标类别后续业务里输出一堆码头框或者把Landing与Aircraft Carrier混在一起验证时互相拉低AP。原因这是标注语义设计问题不是模型问题。Dock的本意是港口泊位或船坞设施Landing在军事术语里可理解为登陆舰也可以指登陆作业区域。数据集的类别体系把“非船设施”和“半船概念”纳入了检测范围让模型既学船又学港口结构类间边界模糊。解决做任务前先明确检测目标。如果最终场景是海上舰船识别直接把Dock和Landing从训练数据中移除或合并到Other类如果场景是港口监控需要同时识别舰船和泊位则保留。Commander这类指挥舰外观与普通军舰接近样本数量少时优先合并到Other Warship等样本量上来再拆开。5.4 训练初期loss异常先查标签再调参现象首个epoch训练到几十步时loss突然变为NaN或者前几个epoch loss一直下降但第10个epoch后验证AP为0。多数人的第一反应是调学习率实际上问题出在标签。原因NaN最常来自两点一是txt里存在归一化越界值比如某行坐标大于1或为负数AMP混合精度下计算结果溢出二是某张图对应的标签框宽或高为0损失函数里出现除零。验证AP为0则往往是类别索引与data.yaml的names顺序不对齐模型输出的头顺序和标签索引对不上。解决遇到NaN先关amp再试一次不是amp就直接跑第3.2节的坐标交叉脚本把越界行过滤出来宽高为0的框直接删除或者回xml修正后重新导出txt。验证AP为0时拉出第2.3节的单类图检查法逐类确认索引。从那以后我每次换数据集做的第一件事都是完整跑一遍第3章的三个脚本再决定要不要调参。6. 进阶用法难例挖掘与逐类AP评估的闭环6.1 难例挖掘把val预测结果回贴到原图上模型训练完成后不要只看指标要把预测结果可视化地回贴到验证集图片上。先让模型输出验证集检测结果。yolo detect val \ modelruns/detect/train/weights/best.pt \ dataship.yaml \ imgsz1280 \ save_txtTrue \ save_confTruesave_txtTrue把预测框写成txtsave_confTrue附带置信度。逐张观察预测图时重点不是看框得多准而是看两类错误置信度低于0.4却确实是舰船目标的漏检框以及置信度很高但框在码头建筑或陆地上的误检框。把这些图单独收集起来与第3.3节的类别分布对照能直观看出是模型能力问题还是数据覆盖问题。6.2 逐类AP才是验收标准训练日志里最后打印的mAP是整体平均掩盖了类间差异。YOLOv8的验证输出会列出每一类的AP50与AP50-95逐类检查才能定位究竟是哪一类拖了后腿。判断规则很简单AP50低于0.5的类别优先补数据和增强AP50高但AP50-95低的类别优先调输入分辨率和锚框这类说明框基本位置对但边界不精细。从那以后我每次训练完都会强制把逐类AP表打出来看一眼确认改动确实提升了目标类别而不是整体均值涨了小样本类别却在跌。这套流程下来后面再换新数据集时踩坑次数一次比一次少希望帮到你。本文还有配套的精品资源点击获取