
简介面向遥感图像处理与计算机视觉应用提供一套完整的舰船目标检测数据集覆盖航空母舰、驱逐舰、潜艇、货船、巡洋舰等17个类别共计2238张遥感影像及对应标注数据集已同时整理为Pascal VOC和YOLO两种主流格式图片与XML、TXT文件一一对应可无缝接入YOLOv5、YOLOv8等常见训练框架。压缩包约109.32MB包含2000个文件其中以XML标注文件为主另含一份使用说明TXT目录结构清晰便于按需取用。该数据集已有835人浏览学习既适用于遥感舰船识别、海域监测、智慧海洋等工程应用也方便高校师生复现目标检测算法。对于初学者跳过了数据采集与格式转换的繁琐步骤对于研究者提供了较充分的类别覆盖和样本分布可用于模型性能对比、误报分析等科研工作。数据集中的17个类别涵盖航母、巡洋舰、补给舰、油轮等既有军用也有民用船舶能够支撑细粒度识别研究整体包体适中下载与离线使用都很便捷。1. 拿到这份舰船数据集之前先弄懂它解决什么问题做卫星遥感舰船检测的人最痛苦的不是模型选型而是找不到一份能直接用、格式不用改的训练数据。卫星遥感舰船检测数据集VOCYOLO格式2238张17类别.7z就是冲着这个痛点来的2238张遥感影像覆盖17个船型类别同时交付VOC和YOLO两套标注解压后就能喂给YOLO训练脚本。它解决的是两件事一是省掉几周的人工标注时间二是把“VOC转YOLO时类别索引错位”这种经典翻车点直接消灭。适合三种人做港口监管和海洋执法识别的算法工程师、拿数据做毕设或论文的研究生、刚入门YOLO想找一份干净数据练手的人。需要提醒的是2238张对深度学习来说不算大它的价值在于“开箱即用”不在于“量大管饱”。2. 解压与查标先摸清2238张图的格式家底再谈训练拿到压缩包后的第一个动作不应该是解压完直接开训而是把这份数据的目录结构、标注格式、图片尺寸全部核一遍。卫星遥感舰船数据的坑一半在标注上另一半在“格式看着对、实际用不了”上。VOC和YOLO两套格式并存意味着你至少要先确认两件事两套标注是不是同一套图以及坐标换算后能不能对得上。2.1 用7z解压后先看目录结构两条标注线要能对上.7z压缩包用7z命令解压Windows下用7-Zip图形界面也可以但服务器上我习惯直接命令行。命令如下7z x 卫星遥感舰船检测数据集VOCYOLO格式2238张17类别.7z -o/data/ship17 tree -L 2 /data/ship177z x表示以完整路径解压-o指定输出目录。注意-o和路径之间不能有空格这是7z命令里比较容易写错的小细节目标目录如果已经存在文件会直接写入其中不会额外建一层同名文件夹。解压后的目录结构通常会看到两套。一套是VOC风格JPEGImages放原始jpgAnnotations放同名xmlImageSets/Main下是train.txt、val.txt这类索引文件。另一套是YOLO风格images放图片labels放同名txt根目录或某个配置目录里有一个classes.txt记录类别顺序。VOC和YOLO两套格式并存多数情况是同一套图各配一份标注少数情况是只有一套标注加转换脚本。无论哪种先确认JPEGImages/images里的图片和Annotations/labels里的标注一一对应。ls /data/ship17/VOC/JPEGImages | wc -l ls /data/ship17/VOC/Annotations | wc -l ls /data/ship17/YOLO/images | wc -l ls /data/ship17/YOLO/labels | wc -l这四个数字应当相等正常情况都是2238。如果图片数量大于标注数量说明有图没标训练时候那些图相当于白送进模型学背景如果标注数量大于图片数量可能有多余xml或txt需要进一步排查是不是重复标注。我一般把四个数字不一致直接判定为“这份数据要返工”而不是继续往下走。2.2 用脚本清点图片尺寸与标注框小目标统计决定训练参数解压完检查完文件数量我会写一个几十行的脚本统计图片尺寸分布和标注框的合法性。这一步几乎不需要跑模型却决定了后面训练参数怎么设。import os from PIL import Image import xml.etree.ElementTree as ET from collections import Counter root /data/ship17/VOC jpg_dir os.path.join(root, JPEGImages) xml_dir os.path.join(root, Annotations) size_count Counter() bad_files [] for fn in sorted(os.listdir(jpg_dir)): if not fn.lower().endswith((.jpg, .jpeg, .png)): continue img Image.open(os.path.join(jpg_dir, fn)) w, h img.size size_count[(w, h)] 1 xml_path os.path.join(xml_dir, os.path.splitext(fn)[0] .xml) if not os.path.exists(xml_path): bad_files.append((fn, missing xml)) continue root_node ET.parse(xml_path).getroot() for obj in root_node.findall(object): box_node obj.find(bndbox) x1 float(box_node.find(xmin).text) y1 float(box_node.find(ymin).text) x2 float(box_node.find(xmax).text) y2 float(box_node.find(ymax).text) if x2 x1 or y2 y1 or x1 0 or y1 0 or x2 w or y2 h: bad_files.append((fn, fbad box {x1:.1f},{y1:.1f},{x2:.1f},{y2:.1f})) print(图片尺寸分布TOP3:, size_count.most_common(3)) print(异常文件数:, len(bad_files)) for item in bad_files[:20]: print(item)这个脚本的逻辑很直接遍历所有图片检查有没有对应的xml再检查每个标注框是否满足“右下角大于左上角、不超出图片边界”。异常文件和标注框坐标越界是遥感数据集里最常见的问题根源在于标注工具的坐标系和图片的原始像素尺寸不一致尤其是从不同卫星源收集数据时经纬度投影转换后图片被裁切坐标没有跟着更新。这里有个容易被忽略的点图片尺寸分布。如果2238张图有七八种不同的宽高比后面的YOLO训练会自动letterbox填充小目标会被进一步压缩。统计完尺寸后我还会顺手统计一下标注框的面积分布重点看有多少框的像素宽度小于32像素。如果这类小目标占比超过30%就要在mosaic增强和输入分辨率上做文章这个在第3章和第4章会具体展开。2.3 核验VOC与YOLO两套标注是否一致防止格式错位同时交付VOC和YOLO两套标注不代表两套标注天然一致。VOC的xml保存的是绝对像素坐标YOLO的txt保存的是归一化中心点坐标和宽高两者之间的转换公式不难但很容易出现在某个中间环节出错的情况。对比维度VOCXMLYOLOTXT坐标含义左上角(xmin,ymin)和右下角(xmax,ymax)绝对像素值中心点(cx,cy)、宽w、高h全部归一化到0~1类别表示name直接写船型字符串每行第一个数字是类别索引对应classes.txt文件组织每张图一个xml文件树形结构每张图一个同名txt文件每行一个目标适用工具LabelImg导出、TFRecord转换、老工具链YOLO系训练和验证脚本直接读取VOC转YOLO的公式是cx (xmin xmax) / 2 / 图片宽度cy (ymin ymax) / 2 / 图片高度w (xmax - xmin) / 图片宽度h (ymax - ymin) / 图片高度。反向转换也同理。如果别人交付给你的YOLO标签是由VOC标签脚本转换来的最稳妥的做法是自己写脚本反向校验一遍。import os import xml.etree.ElementTree as ET def parse_xml_boxes(xml_path, img_w, img_h): boxes [] root ET.parse(xml_path).getroot() for obj in root.findall(object): name obj.find(name).text box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) boxes.append((name, (x1 / img_w, y1 / img_h, x2 / img_w, y2 / img_h))) return boxes def parse_yolo_txt(txt_path, class_names): boxes [] with open(txt_path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_idx, cx, cy, w, h int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) boxes.append((class_names[cls_idx], (cx - w / 2, cy - h / 2, cx w / 2, cy h / 2))) return boxes对比的时候用0.01作为容差也就是归一化后1%像素的偏差超过就提示告警。两套标注如果对不上训练时可能你喂给模型的是A格式的标签但在可视化脚本里看到的是B格式的框排查起来非常费劲。所以这份数据我在解锁训练前一定会把格式校验这一关过完。3. 最小训练路径从数据划分到读懂yolo损失函数格式摸清了接下来就是把这份数据跑通YOLO训练的最小路径。很多yolo入门学习的人会把大部分精力花在挑模型上但第一步真正该做的是把数据划分、配置文件和损失曲线判断这三件事做对。卫星遥感舰船检测有个特点船是典型的小目标直接照搬COCO的默认参数训练效果往往很差。3.1 按文件级划分train/val/test固定随机种子别按文件夹分数据集划分看起来简单实际坑不少。VOC格式的train.txt和val.txt如果源数据已经分好了可以直接沿用如果没分自己写脚本划分时有一个原则必须按“图片文件名列表”来分不能按文件夹来分更不能图省事把同一次采集的同场景图全部扔进训练集。import os import random random.seed(42) img_dir /data/ship17/YOLO/images files [f[:-4] for f in os.listdir(img_dir) if f.endswith(.jpg)] random.shuffle(files) n len(files) train_files files[: int(n * 0.8)] val_files files[int(n * 0.8): int(n * 0.9)] test_files files[int(n * 0.9):] for name, lst in [(train, train_files), (val, val_files), (test, test_files)]: with open(f/data/ship17/{name}.txt, w) as f: for stem in lst: f.write(f/data/ship17/YOLO/images/{stem}.jpg\n) print(ftrain{len(train_files)}, val{len(val_files)}, test{len(test_files)})固定seed42是为了让每次划分结果一致方便复现实验。val用来做模型选择和早停test只在最终评估时用一次不能在调参过程中反复看test结果否则等于把test集学进了调参过程最终指标会虚高。2238张图按8:1:1划分训练集约1790张这个量级对17类来说属于“刚好够用”需要靠后续增强和数据补充来撑精度。3.2 用YOLOv8训练自己的数据集imgsz1280与小目标先行的参数调法划分文件准备好了接下来是data.yaml配置。数据集文件里如果已经带了data.yaml或ship17.yaml直接改路径就行没有就自己写一个。注意类别名列表必须和classes.txt严格一致这个顺序错一位训练结果就会全乱。path: /data/ship17 train: train.txt val: val.txt test: test.txt nc: 17 names: [class0, class1, class2, class3, class4, class5, class6, class7, class8, class9, class10, class11, class12, class13, class14, class15, class16]names列表里的占位名需要替换成classes.txt里的实际类名。写配置时一个经验是把path写成绝对路径不要写相对路径否则从不同目录启动训练时很容易报图片找不到。训练命令用ultralytics的yolo命令行就可以我一般从yolov8n这个最小的模型开始跑通全流程确认数据和标签没有问题再换更大的模型。yolo detect train dataship17.yaml modelyolov8n.pt epochs100 imgsz1280 batch8 device0这里最关键的参数是imgsz1280。卫星遥感图里的船很多只有几十个甚至十几个像素如果用默认的640输入这些小目标在缩放到640的过程中直接被压没了。提升到1280后模型能看到更多船体细节代价是显存需求增加、训练速度变慢。如果你显卡只有8G显存batch8配imgsz1280可能爆显存常见做法是把batch降到4或者imgsz降到960牺牲一部分小目标精度先跑通流程。yolov8n是参数量最小的模型预训练权重从COCO来这不算理想但做迁移学习起点够用。提示显存不够时优先降batch而不是降imgsz。遥感小目标对输入分辨率很敏感batch小一点只是训练慢imgsz降了会直接影响模型能看到的最小目标尺寸。3.3 训练中读yolo损失函数的三个信号box_loss、cls_loss、val曲线训练跑起来后看tensorboard或终端输出的loss曲线。yolo损失函数通常由三块组成box_loss负责边界框回归cls_loss负责分类dfl_loss负责框的分布建模。卫星遥感舰船场景里我主要盯三个信号。观察点正常信号异常信号box_loss前20个epoch快速下降之后平滑收敛始终平整不降或剧烈振荡cls_loss和box_loss同步下降最终低于0.05偏高且长时间不动val曲线随训练集loss同步下降差距小val掉头上升train还在降如果train还在降而val开始回升典型的过拟合信号可以直接用早停或减少epoch。如果box_loss头几个epoch就不降先别调模型回第2章检查标签坐标有没有越界、类别索引有没有错位。很多“训练不正常”的锅不在模型结构而在标签格式。我习惯跑20个epoch先用一个最小验证脚本看loss走势确认曲线健康再挂长训。4. 卫星遥感舰船检测的5个经典坑现象、原因、解法这份数据集的格式让你跳过了“转换格式”这一步但训练和落地过程中的坑一个都不会少。下面五条是卫星遥感舰船检测场景里最常遇到的每一条我都按现象、原因、解决的顺序写清楚。4.1 类别索引错位loss在降但推理全乱现象训练正常启动前几个epoch的loss曲线看起来健康但推理时模型输出的类别要么集中到某一个类要么全部错乱。val/box_loss在中后期停止下降val曲线剧烈抖动。原因VOC转YOLO时最常见的翻车点。VOC的xml里类别是字符串YOLO的txt里只能写数字索引。如果转换脚本按照某种字典序生成索引而classes.txt是按标注时的顺序写的同一个“货轮”在xml里叫cargo在txt里对应的索引却是另一个数字。模型在训练时学的是“错误的编号对应船的视觉特征”甚至把边缘样本学成了背景。解决先写脚本检查标签索引是否越界再抽查可视化。import os from pathlib import Path labels_dir Path(/data/ship17/YOLO/labels) max_idx -1 with open(/data/ship17/YOLO/classes.txt) as f: class_names [line.strip() for line in f] for p in labels_dir.glob(*.txt): for line in p.read_text().strip().splitlines(): if line: cls_idx int(line.split()[0]) max_idx max(max_idx, cls_idx) print(f类别数{len(class_names)}, 标签最大索引{max_idx}) if max_idx len(class_names): print(索引越界: classes.txt和txt标签不一致)数字核对只是第一步更稳妥的是随机抽几张图把txt里的框画出来看看类别对不对。这一步能发现“所有框都偏移了”这种数字上看不出来的问题。从这之后我不管用哪份数据集classes.txt和标签索引的校验永远排在训练脚本前面。4.2 标注框过大近岸误检暴增现象模型在近岸区域疯狂误检对码头、栈桥、防波堤产生大量高置信度框精确率掉到70%以下。可视化的结果里很多框比真实船体大一圈把船周围的水面也圈了进去。原因卫星图里船体边缘清晰标注者图省事把“整个可见物体”当成船框。尤其常见的是把船连同阴影、拖尾水痕一起画进框里。模型学到的特征变成了“水面上一块较大的灰斑”自然会把防波堤和浮标都当成船。解决先用几何规则筛选嫌疑框。宽高比超过3、或者框面积占全图面积超过1%的框重点回头看。判断标准很简单框应该紧贴船体轮廓允许外扩5%的边距但绝不能包进岸线和码头。如果你拿到的是VOC格式可以在第2章的统计脚本里加一行面积和宽高比的输出把超限框的图片名打出来。4.3 直接resize到640小目标消失现象模型对小船几乎没有响应只有大船出框。把一张宽高都是上万像素的卫星原图直接压缩到640后原本只有8×8像素的船变成不到1像素直接消失。原因输入分辨率不足anchor尺寸和实际目标分布不匹配。遥感图片和普通照片差异极大普通照片的主要目标占画面比例高而遥感图里大量目标是几十像素的小船。解决训练时用imgsz1280甚至更高推理时不整图压缩而是用滑窗切图。常见做法是窗口640、overlap取20%把每一块子图送进模型再把框坐标映射回原图坐标系最后做一次NMS合并重复框。# 滑窗推理伪代码核心是子图坐标还原到原图 H, W original_image.shape[:2] ROI 640 STEP int(ROI * 0.8) all_boxes [] for y in range(0, H - ROI 1, STEP): for x in range(0, W - ROI 1, STEP): patch original_image[y:y ROI, x:x ROI] preds model(patch) # 每张patch返回框和分数 for box in preds.boxes: x1, y1, x2, y2 box.xyxy[0].tolist() x1 x; x2 x; y1 y; y2 y # 关键坐标平移到原图 all_boxes.append((x1, y1, x2, y2, box.conf))这类切图推理的开源实现有不少核心就是滑窗加坐标还原最后用NMS把窗口重叠区域的重复检测合并掉。在工程里这一步能让小目标召回率直接提升一个档次代价是推理耗时变长。4.4 Mosaic把船拼没了增强策略要跟着目标尺寸走现象默认开启mosaic增强后训练早期loss曲线走势正常但mAP始终上不去小目标AP尤其低。把训练集可视化出来发现不少船被裁剪掉一半或者正好落在拼接缝上。原因mosaic增强把四张图拼在一起再做随机裁剪。船本身很小一旦目标落在拼接缝附近被裁掉后剩余的像素不足以构成有效训练样本这批目标等于直接丢了。解决针对小目标数据把mosaic强度降下来。在ultralytics的训练命令里可以直接调yolo detect train dataship17.yaml modelyolov8n.pt epochs100 imgsz1280 mosaic0.5 copy_paste0.3mosaic0.5表示一半的训练样本不做mosaic另一半按默认方式拼。copy_paste对小目标很友好它从别的图里把目标复制过来贴到当前图的空白区域相当于给小目标“加样本”。如果小目标占比极高甚至可以直接mosaic0.0用最朴素的训练方式先跑一版结果出来。4.5 类别严重不平衡少数类AP和损失函数读法现象某几类的AP始终在0附近验证结果里少数类那一列长期为零cls_loss居高不下。原因港口泊位的船型分布天然不均匀散货船几百条救援船只有几条。模型把少类样本当成噪声训练时权重更新完全被大类主导。解决先统计类别分布再决定策略。from collections import Counter from pathlib import Path cnt Counter() for p in Path(/data/ship17/YOLO/labels).glob(*.txt): for line in p.read_text().strip().splitlines(): if line: cnt[int(line.split()[0])] 1 print(cnt)如果最少的类只有个位数或十几个样本别指望AP能有多好看优先做两件事一是过采样把少类样本复制几遍放进训练集让模型至少不把它们全学成背景二是用复制粘贴增强把少类目标拼到海上空白区域。加权loss在个位数样本上作用有限这是样本量决定的不是损失函数能解决的。5. 精度不够的补强路线伪标签、类别加权与旋转框判断用这份数据集跑通基线不难但如果验收标准更高比如要求mAP50到0.85以上、近岸误检率低于某个阈值就需要在数据层面做补强。这里的思路和yolov8训练自己的数据集之后的优化路径一致提高有效样本量、平衡类别分布、确认检测框类型是否匹配业务口径。5.1 用best.pt跑伪标签把漏检目标半自动补回训练集训练完一版模型后常见做法是用它在同区域或同源影像上做预测生成伪标签扩充训练集。卫星遥感舰船检测的逻辑是船的样子变化不大能用已经学会的模型找候选框人工复核成本远低于从头画框。yolo predict modelruns/detect/train/weights/best.pt sourcescene_0819.tif imgsz1280 conf0.35conf0.35而不是默认的0.25或0.5是为了多召回一些候选框宁可误检多一点让审核人删也不要漏掉小船。这一步生成的结果是YOLO格式的预测txt后续处理逻辑是人工打开每张候选图把明显是船的框留下去掉陆地上的误检再按第2章的命名规则把txt复制到labels目录。参数说明conf是置信度阈值调低会增加误检调高会漏掉弱目标具体值根据实际模型表现浮动第一轮先跑0.35看一张图里的候选密度再微调。5.2 先统计类别分布再决定加权还是重采样扩充数据之前先把17个类别的数量分布拉出来看。第4章的统计脚本可以继续用拿到数量表之后做决策。常见做法是如果最少的类少于20个样本过采样把少类样本复制3到5倍进训练集。如果某几类明显超过500个样本对这些类做随机欠采样。如果用ultralytics这类框架且支持类别权重可以传class weights参数不理解权重接口时过采样是最朴实又稳的方案。这类处理的意义在于类别不平衡在yolo损失函数上的体现是cls_loss被大类主导重采样后少类的cls_loss才有机会降下来。没有哪个技巧能替代先看统计结果这一步。5.3 水平框够不够结合业务验收标准决定是否转旋转框这份数据集交付的是水平框VOCYOLO格式但遥感场景里船只在锚地停泊方向各异水平框会框进大量水面彼此之间还可能互相重叠。NMS时重叠度高的框会被抑制掉结果就是十几条并排停靠的船只检出两三条。这时要考虑旋转框检测常见做法是用mmrotate这类工具库训练旋转框模型数据格式和DOTA那套类似需要把水平框转成旋转角点。我的判断标准是先问验收口径是“船存在、位置大概在哪”还是“要船头朝向和精确轮廓”。前者水平框完全够用后者才值得转旋转框。17类水平框模型在工程里能覆盖港口监控、船舶流量统计、禁渔期非法捕捞取证这类场景没必要一上来就上旋转框增加复杂度。6. 让人放心的验证清单从mAP到切片推理的收尾流程模型训练完验证不能只看训练日志里的最后一个mAP。我每次评估都按固定顺序过五件事确保结果是可靠的。第一步在test集上跑一次验证只看mAP50、mAP50-95和每类AP三项。test是第3章划分时留出来的不能提前看。yolo val modelruns/detect/train/weights/best.pt dataship17.yaml splittestmAP50过0.8说明基础能打mAP50-95过0.5才算稳定。如果mAP50-95和mAP50差距过大说明框的定位不准小船边框抖动厉害优先查第4.3条的输入分辨率问题。第二步打开混淆矩阵看哪些类别互相混。卫星遥感舰船场景里最常见的是货船和集装箱船搞混、渔船和拖船搞混因为外形确实接近。如果混淆集中在这几对优先想办法补对应类别的样本而不是调模型。第三步抽20张带预测框的图专门看近岸区域。好的模型在码头和栈桥附近不应该有高置信度误检这一条不靠mAP反映必须肉眼扫图。第四步找一张大图做切片推理和直接整图缩放推理做对比。小目标召回率的差距就是这项工作的提升空间。第五步把第2章的标签校验脚本固定成一个shell脚本以后换任何数据集都先跑一遍再短训20个epoch确认loss走势。这套流程跑下来心里才有底。有一次我跳过标签校验直接长训练到半夜发现val/box_loss一直不降第二天排查出来是classes.txt顺序和转换脚本不一致整个晚上都在浪费。从那以后我的习惯是标签校验永远排在训练前短训验证永远排在长训前。希望帮到你。本文还有配套的精品资源点击获取