简介:一套面向潜艇卫星图像识别场景的人工智能目标检测数据集,专为需要高质量标注样本的算法研究者与开发者设计。压缩包内共2001个文件,由1000张尺寸为1024×1024像素的卫星jpg图像、1000个对应潜艇位置的xml标注文件以及1个txt格式的说明文档组成,整体大小约345.83MB。所有图像均已完成潜艇目标的位置标注,边界框信息清晰,可直接适配YOLO、Faster R-CNN、Mask R-CNN等主流检测框架,省去数据清洗和人工标注环节。资源按图像、标注、说明三类文件组织,便于解析与调用;配合高清卫星图,适合用于算法训练、精度对比及模型泛化能力验证。目前该数据集已有1687人学习使用,尤其适合海洋监测、船舶识别、遥感影像分析等中高级AI项目作为实验底座,帮助研究者快速上手并优化水下目标检测方案。
1. 潜艇卫星图目标检测,卡点从来不是模型而是数据
把“人工智能目标检测数据集(潜艇卫星图)”这个标题抛给做遥感检测的工程师,对方多半会先笑一下:这类场景的公开标注数据接近零,网上能直接拿到手的“潜艇卫星图 + 框”少得可怜。标题看着具体,但真正动手时第一关不是“选哪个模型”,而是“数据从哪来、长什么样、怎么变成能喂给训练器的格式”。这篇文章想给正在碰海洋遥感、智能船只识别、海上目标监控这类方向的你一条可落地的路径:从认清目标形态,到用公开数据迁移、合成渲染、半自动标注三条路凑数据集,再到切片、格式转换、训练调参与排坑。适合手里没有现成数据、又不想被“数据集稀缺”卡住项目进度的人。
2. 数据从哪来:三条路凑齐潜艇卫星图样本
2.1 先认清目标:潜艇在卫星图里到底长什么样
做数据集之前,得先弄明白检测器要面对的是什么。光学遥感卫星影像里,常见公开产品的分辨率大概在 0.5 米到 2 米之间。一条常规潜艇的长度按 60 到 120 米估算,在 2 米分辨率下只占 30 到 60 个像素;在 0.5 米分辨率下也就一百多个像素长、十几个像素宽。放在一张几千乘几千像素的大范围遥感图里,目标面积占比往往在 0.001% 上下,属于典型的小目标检测。
更难办的是外观特征不稳定。潜艇在水面或半潜状态下,可见光影像通常是一段深灰色、深绿色的长条形艇体,但艇体周围往往跟着白色的尾流或航迹,有时尾流比艇体本身还亮、还要显眼。海面纹理、碎云阴影、太阳耀斑、风浪产生的泡沫带都会形成“像目标又不像目标”的结构。也就是说,模型要学的不是简单纹理,而是“深色长条 + 可能的亮色尾迹”这个组合,还要学会把它和海浪干扰区分开。你在标注和设计负样本时都得围绕这个特征来。
另一个影响数据设计的事实是:潜艇图像不会像车辆、行人那样大量公开。即使在一些开源的遥感舰船检测数据里,也大多是水面舰船、航母、货轮,纯潜艇标注非常少。所以标题里的“数据集”三个字,实战中更多指“你需要自己构建的一套数据管线”,而不是“下载一个 zip 就能开训”。理解了这一点,后面所有步骤才不跑偏。
2.2 三条数据路径:公开迁移、合成渲染、半自动标注
我一般把数据来源拆成三条路,按成本从低到高排序:
第一条是公开遥感舰船检测数据迁移。市面能找到的开源舰船检测数据大多包含大量不同角度的水面船只和港口场景。虽然类别不是潜艇,但船体和潜艇在水面上的形态有共通点:长条形、深色、带方向性。先用这类数据把模型训到能稳定“看见海面上的条形物体”,再拿到潜艇场景上做微调,收敛速度会快很多。实际操作时,把公开数据里的大船样本当成一个“近似类”参与预训练就行。
第二条是合成数据渲染。用开源 3D 建模工具加渲染引擎,搭建一个潜艇模型,放在程序化生成的海洋环境里,随机调整海况、光照、云影、传感器模糊,批量输出带自动标注的图像。渲染不是新鲜玩法,遥感领域常用它补稀缺类别。关键在于随机化:每次渲染时的海浪贴图、太阳角度、大气散射强度、相机高度角都要变化,否则模型会把“某一种固定海洋纹理”当成正样本特征。
第三条是半自动标注管线。如果手里有一些公开的历史影像、港口照片或截图类素材,可以用分割基础模型先跑一遍,把海面上的候选目标切出来,再接入一个小分类器做二次筛选,最后由人工审核框的位置和类别。这样能把人工标注量压低不少。常见的做法是:先由分割模型生成粗掩码,拿掩码的最小外接矩形当初始框,人工只需要调整那些被海浪干扰的错框,而不是从零画框。
三条路不是互斥的。我通常的做法是公开数据做底座、合成数据补充稀有姿态、真实图做验证和微调;最后再混到一起训练。这样既控制了成本,又留住了真实场景的分布。
3. 把卫星大图变成训练样本:切片、标注与格式转换
3.1 大图必须切,但不是随便切:切片尺寸与重叠率
遥感原始图动辄上万像素,直接缩放到模型输入尺寸会把目标压没。比如一张 8000×8000 的图里,一个 60 像素长的潜艇目标经过 8 倍缩放后只剩 7 到 8 个像素,再经过骨干网络下采样,特征图里基本就没了。所以第一步是切片,让目标在切片里的相对尺寸变大。
我一般会把切片尺寸设在 640 到 1024 之间,具体取值看你显卡显存和目标大小。如果目标平均长度为 80 到 120 像素,用 1024 切片比较合适;如果目标更小,就用 640。切片必须带重叠率,不然目标刚好压在切缝上就会被硬生生切掉一半。重叠率我习惯设 15% 到 20%,太低起不到保护作用,太高会让同一个目标出现在太多切片里,增加训练冗余。
下面这段脚本是我常用的大图切分方式,按步长滑窗切图,并保存每一块在原图中的坐标:
import cv2 import json import os def crop_large_image(img_path, out_dir, tile_size=1024, overlap=0.15): img = cv2.imread(img_path) h, w = img.shape[:2] step = int(tile_size * (1 - overlap)) idx = 0 for y in range(0, h, step): for x in range(0, w, step): y1, y2 = y, min(y + tile_size, h) x1, x2 = x, min(x + tile_size, w) tile = img[y1:y2, x1:x2] # 边缘不足时用灰色填充,保证所有切片尺寸一致 if tile.shape[0] != tile_size or tile.shape[1] != tile_size: tile = cv2.copyMakeBorder( tile, 0, tile_size - tile.shape[0], 0, tile_size - tile.shape[1], cv2.BORDER_CONSTANT, value=(114, 114, 114) ) tile_path = os.path.join(out_dir, f"crop_{idx}.jpg") cv2.imwrite(tile_path, tile) # 记录该切片在原大图中的偏移,推理时坐标还原要用 meta_path = os.path.join(out_dir, f"crop_{idx}.json") with open(meta_path, "w") as f: json.dump({"x": x1, "y": y1}, f) idx += 1这段代码的关键点是overlap参数和坐标记录。overlap控制的是相邻切片的重叠比例,计算步长时用tile_size * (1 - overlap)得到实际滑动距离;重叠部分会让同一个目标出现在多个切片里,训练时相当于做了隐式的位置增强。坐标 JSON 必须在切图时同步生成,否则推理阶段把框还原回大图时没有偏移量可加,后面所有评估都会出错。
填充边缘时用的灰色值 114 是默认值的通用选择。要注意如果目标恰好落在填充区域内,那这一块就不该保留对应标注;实际项目中我会对切片做一次标注过滤,凡是目标框有超过一半落在填充区的,直接丢弃该目标,避免模型学到“目标边缘带灰边”的假规律。
3.2 VOC 到 YOLO 格式的转换脚本与四个参数坑
切片完成后,下一步是把标注统一成训练框架需要的格式。公开遥感数据常见的标注是 VOC 的 XML 格式,而现有检测工具链里 YOLO 系采用的 TXT 格式更常用。VOC 里存的是xmin, ymin, xmax, ymax绝对坐标,YOLO 需要的是归一化后的中心点坐标和宽高。转换脚本本身不难,但参数踩坑不少。
import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, class_names, out_dir): 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.findall("object"): name = obj.find("name").text if name not in class_names: continue bbox = obj.find("bndbox") x1 = float(bbox.find("xmin").text) y1 = float(bbox.find("ymin").text) x2 = float(bbox.find("xmax").text) y2 = float(bbox.find("ymax").text) # YOLO格式:中心点加宽高,全部归一化到0-1 xc = (x1 + x2) / 2.0 / img_w yc = (y1 + y2) / 2.0 / img_h bw = (x2 - x1) / img_w bh = (y2 - y1) / img_h lines.append(f"{class_names.index(name)} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}") out_path = os.path.join(out_dir, os.path.basename(xml_path).replace(".xml", ".txt")) with open(out_path, "w") as f: f.write("\n".join(lines))这里有一个先决条件:class_names列表必须固定,且与最终训练配置里的类别顺序一致,顺序一变所有标注索引就全错位。
实际转换时我踩过几个具体的坑:
第一个坑是坐标出界。有些公开数据的框会略微超出图像边界,比如x2比宽度还大几个像素。直接除以宽度没问题,但算出来的框中心被带偏。我在脚本里会加一句x2 = min(x2, img_w)和y2 = min(y2, img_h)做截断。
第二个坑是切片后转换。如果你先切图再转换标注,必须按切片在整图中的偏移量重新计算坐标,而不是用原图的xmin。上面的脚本没有处理偏移,因为它是给整图标注用的。切片场景下,正确的做法是先根据 3.1 节保存的 JSON 偏移量把xmin/xmax减去偏移值,再做归一化,分母也换成切片尺寸。
第三个坑是类别过滤。VOC 里常混入背景类或“不确定”类,转换时如果不加if name not in class_names的过滤,模型会多出一个没有意义的类别。潜艇场景还可能遇到“无法确认目标”的标注,我一般单独放到一个 ignore 列表里,不参与训练也不参与评估。
第四个坑是文件对应关系。YOLO 系训练时靠相同主文件名关联图片和标签,转换出来的 TXT 文件名必须和图片名完全一致,包括后缀替换方式。脚本里用.replace(".xml", ".txt")是安全的,只要你的数据没有同名不同目录的情况,否则还得加一层目录结构校验。
3.3 标注策略:要不要旋转框,负样本怎么留
标注策略直接影响模型能学到什么。潜艇在卫星图里姿态任意,水平框会把大量海面背景包进来,导致模型学习时背景干扰占比过高。如果目标细长且斜着停泊,一个水平框的面积可能是目标真实面积的好几倍,正样本被背景稀释。
我建议有条件就上旋转框。旋转框的标注维度在水平框基础上多一个角度,例如xc, yc, w, h, angle。它的好处是框紧贴目标,模型输入的背景噪声少;坏处是很多检测头不原生支持旋转框,你需要选支持旋转目标的检测头,或者把旋转框转成水平框加角度分支的混合方案。如果第一版只想快速跑通,就用水平框,但标注时定一条死规矩:框要尽量贴合艇身可见部分,尾流不框进去。尾流是模型最容易被带偏的特征,把它留在框外,模型才会更关注艇身。
负样本的处理同样关键。很多遥感数据的负样本来自陆地、港口、城区,但潜艇检测的难点集中在海面。训练时误检基本上都来自海浪、云影、碎云、浮标这类海上物体。所以负样本的采集范围应该聚焦在“有海洋纹理的真实大图”,而不是随便拿一堆陆地遥感图充数。我习惯在切图阶段把不含任何标注目标的海面切片单独归到一个负样本目录,训练时按正负比 1:2 或 1:3 混入。负样本占比太少,模型会倾向把所有海面物体都当成目标;占比太多,又会让模型变得“保守”,漏检率飙升。正负比这个参数值得多试几组,通常比调整模型结构更见效。
4. 训练配置怎么调:模型选型、切片尺寸与不平衡样本策略
4.1 模型选型:先 YOLO 系跑通,再考虑检测 Transformer
小目标检测听起来很适合检测 Transformer 这类带全局注意力的结构,它理论上能建模长距离上下文,对“目标太小、需要看周围环境判断”的场景更友好。但现实是:这类模型训练开销大,在合成数据占比高、真实样本少的场景里收敛慢,对超参也更敏感。我第一版项目几乎不会直接上这类模型。
优先推荐 YOLO 系检测框架跑第一版,原因是它训练成本低、部署生态成熟、社区案例多。数据集本身已经够难伺候了,模型选型上先选一个能快速迭代的。流程上我一般会:先用公开舰船数据预训练或直接下载通用预训练权重,再用潜艇数据集微调。微调时冻结骨干前几层、只放开检测头和高层特征,先跑 30 到 50 轮确认数据管线没有致命问题,再全量微调。这个节奏能帮你把数据问题和高频问题分开。等第一版跑通、数据质量稳定了,再对比检测 Transformer 看有没有明显收益,用它替换检测头做二阶段实验。
框架选型之外,输入尺寸是比模型结构更需要上心的参数。前面切图时我们定了 1024 或 640,训练时输入尺寸要和切片尺寸保持一致,别再额外缩放。目标已经很小了,任何一次下采样都是在和有限像素作对。
4.2 必调参数:输入尺寸、训练轮数与增强策略
这一节给出我常用的训练配置作为起点。注意它是起点而不是标准答案,因为不同来源的卫星图分辨率差异很大,务必根据你的实际目标像素数调整。
| 参数 | 初始值 | 调整方向 |
|---|---|---|
| 输入尺寸 | 1024 | 目标中位长度小于 50 像素时降到 640 |
| 迭代轮数 | 300 | 看验证集 mAP 曲线是否平台期 |
| 批大小 | 16 | 显存不够时降到 8,同时调低学习率 |
| 马赛克增强 | 0.3 到 0.5 | 小目标占比高时调低,避免目标被缩小 |
| 关闭马赛克轮数 | 最后 10 轮 | 防止增强分布和真实分布偏移 |
| 负样本比例 | 0.5 到 0.7 | 误检多就加,漏检多就减 |
训练指令大致长这样,不同版本的工具链写法略有差异,但参数项是通用的:
# 训练入口,权重改成你实际下载的YOLO系预训练文件 yolo train \ data=submarine.yaml \ model=轻量预训练权重.pt \ imgsz=1024 \ epochs=300 \ batch=16 \ mosaic=0.5 \ close_mosaic=10mosaic参数值得特别说。马赛克增强会同时拼四张图,目标相对被缩小,这对中等目标有效,但对极小的潜艇目标反而有害。我见过不少项目把mosaic开到 0.8 到 1.0,结果小目标 AP 明显下降。处理方法是把mosaic控制在 0.3 到 0.5,并在最后 10 轮彻底关闭,让模型在接近真实分布的增强条件下收尾。close_mosaic就是干这个的。
训练轮数方面,合成数据多、真实数据少的场景,300 轮左右通常就会看到验证 loss 开始抖动。不要盲目跑上千轮。我一般盯两个信号:验证集上的小目标 AP 是否还在涨,以及难例误检数是否在降。两个指标都停滞了,就停下来加数据或调损失权重,比继续跑更有效。
4.3 数据不平衡与小目标难例挖掘
类别不平衡在潜艇检测里不是“正负样本几十比一”那么简单。正样本里,水面航行状态多、半潜状态少、港口停泊状态更少,模型天然倾向于学好样本量多的那类。我分成两个层面处理:一是按子类别做采样均衡,把半潜、驻泊这些稀有姿态的样本在每轮训练里多复制几份,或调整采样权重;二是在损失函数上给稀有类别加权重。
损失权重怎么加,取决于你的工具链。常见的做法是给分类损失里的正样本分支提高权重,让模型在“看到疑似目标但不确定”的情况下更倾向于预测出来,而不是直接归为背景。漏检和误检在遥感场景里成本不一样,我通常宁可让模型误检多一些,也不让它漏检,因为误检可以通过后处理过滤,漏检一旦发生就要回去翻整张大图。
难例挖掘是另一个有效手段。做法很朴素:第一轮训练完成后,把模型放到一批还没有标注的大图上推理,取中高置信度的检测结果,比如 0.3 到 0.7 之间的框,人工快速检查一遍。凡是标错的、模型过度自信的背景样本,统一回填到负样本池;凡是模型漏掉的,补充进正样本池。整个过程重复两到三轮,数据集质量会明显提升。这个操作看着笨,但在稀缺目标场景里比换模型结构管用得多。难例挖掘的产出是数据,而数据在后续所有迭代里都会持续发挥价值。
5. 潜艇目标检测数据集的常见翻车与排查记录
5.1 误检集中在浪花和碎云:负样本配比失衡
现象:模型训练完,在验证集上的 mAP 还算体面,但是拿到一段新的海岸带大图上一跑,检测结果里全是海面上的亮白色斑点,潜艇没检出几个,浪花倒是框出来一堆。
原因:训练集里的负样本大多来自陆地、港口或城市区域,模型没见过足够多的海面纹理。浪花在某些分辨率下的轮廓和潜艇尾流很像,模型学到的是“白色条形结构”这个表层特征,而不是“艇身 + 尾流”的组合。
解决:把负样本的采集范围改成以海洋切片为主。做法是把不包含任何目标的纯海面大图切成负样本切片,按正负比 1:2 到 1:3 混入训练集。碎云多的多云天图像、不同海况的照片都保留一些。负样本配比提高后,误检通常会在一到两轮训练内明显下降。
5.2 合成数据训练完在真实图上掉点:域差距没控住
现象:合成数据训练时 AP 能到 0.9 以上,一换到少量真实影像上直接掉到 0.4 甚至更低。某些合成数据的特征在真实图上完全失效。
原因:合成的海洋纹理、光照、和真实卫星影像之间存在域差距。真实影像有大气散射、传感器模糊、光谱响应差异,合成渲染默认是“完美清晰”的,模型就把清晰度和颜色分布当成了正样本的隐性特征。
解决:我习惯在渲染阶段就给图像加随机扰动。具体包括高斯模糊、亮度扰动、对比度扰动、轻微噪点,让合成图的清晰度区间覆盖真实图的分布。更重要的是,训练时无论如何都要预留一小部分真实标注样本,哪怕只有几十张,专门用来做最终微调和检查点筛选。模型在合成数据上表现好不算数,真实样本上的 AP 才作数。
5.3 目标被切片切两半:重叠率与坐标还原没处理好
现象:某个中等大小的目标在切片 A 里只露出一小段,在切片 B 里露出另一小段。训练时模型看到的是残缺目标,验证时又因为坐标还原出错算成漏检。
原因:切片重叠率设得太低,或者干脆没设重叠。目标长度一旦接近步长,就很容易落在切缝附近。
解决:重叠率至少设 15% 到 20%。推理阶段同样用重叠切片跑,把所有切片上的检测框加回原图坐标,再做一次全局 NMS,把重叠切片产生的重复框合并掉。这里最容易被忽略的是 3.1 节里保存的坐标 JSON,推理脚本必须读取它,否则还原步骤无从谈起。
5.4 真值框不规范:模型学到的是尾流纹理
现象:训练时 loss 降得很快,但模型在真实图像上的定位精度很差,框总是偏大,而且偏向尾流方向。
原因:标注时把尾流也算进了目标框。尾流亮白色的边缘比艇身更清晰,模型自然优先拟合高对比度区域。结果就是检测框被尾流“拉走”,定位不稳定。
解决:重新统一标注规则。框只能包住可见的艇体结构,尾流无论多亮都不进框。如果团队多人标注,需要先在样本上画出示例框,评审后才开始批量标注。我常开玩笑说这步是“数据集的玄学核心”——框歪一点,后面全部训练都在为错误的边界服务。
5.5 loss 很低但验证 AP 不涨:评估口径不一致
现象:训练 loss 降得挺漂亮,但验证 AP 连续几十轮不动。怀疑是模型问题,折腾半天发现是评估流程的问题。
原因:训练时用的是切片,验证时却把检测框还原到整张大图上再算 mAP。整图分辨率高,目标像素占比极小,mAP 计算时会被大量背景区域稀释。另外,如果还原坐标时偏移量没加对,目标框全错位,AP 自然上不去。
解决:验证和训练保持同一评估口径。在切片层面计算 mAP,再把每个类别的 AP 按目标尺寸分组看。小目标 AP 单独统计。如果小目标 AP 低,说明切片尺寸或增强策略需要调整;如果切片 AP 和整图 AP 差距大,优先检查坐标还原逻辑,而不是调模型。
6. 更高一层的玩法:小目标评估、难例回填与数据闭环
第一版跑通后,我会把精力转向评估和迭代,而不是继续堆模型实验。遥感小目标检测里,整体 mAP 会骗人,两个模型 mAP 相同,小目标 AP 可能差一倍。所以验证时我习惯把检测结果按目标尺寸分成三组,重点关注最小那一组的 AP,同时把整张大图的检测结果渲染成热力图。热力图上能看到哪些区域频繁产生低置信度预测,哪些区域目标漏得整片空白。这一步不需要复杂工具:
import numpy as np def build_heatmap(predictions, img_size=(4096, 4096), gauss_sigma=80): heat = np.zeros(img_size, dtype=np.float32) for det in predictions: xc = int(det["x_center"]) yc = int(det["y_center"]) score = det["score"] # 只撒点,不做框填充,方便看聚集区域 heat[yc - gauss_sigma:yc + gauss_sigma, xc - gauss_sigma:xc + gauss_sigma] += score return heat统计误检聚集区域后,把每一轮训练中“模型高置信度但实际是背景”的框收集起来,下一轮作为负样本回填。反复两到三轮,误检率下降的幅度通常比换一个更强的主干网络还明显。
合成数据和真实数据混合训练也有技巧:每轮迭代时不要把所有真实样本都一次性投进去,而是分成若干小份,轮流混合合成数据训练。这样模型每轮都会重新见到真实样本,不容易在某一批数据上过拟合。配合渲染阶段的随机扰动,域差距会逐步被压缩。
做这件事几年下来,我最大的教训是:数据集这样的项目,80% 的精力应该花在数据本身,而不是模型。谁先把标注规则定死、把切片和格式转换脚本跑稳、把负样本配比调明白,谁就能在小目标检测场景里领先。模型结构是通用轮子,数据管线才是这个项目里真正要打磨的专用零件。希望帮到你。
本文还有配套的精品资源,点击获取