简介:一批舰船卫星可见光成像目标检测数据集,面向计算机视觉目标检测方向的研究者与算法学习者,可用于舰船、航母载具等遥感目标的识别模型训练与验证。数据集中包含1000张RGB彩色卫星图,尺寸统一为1024x1024,覆盖两个类别:hang-mu(飞机载具/航母)与jun-jian(军舰),已完成VOC格式XML标注,方便直接接入主流检测框架。压缩包共2000个文件,由1000个jpg图像、1000个xml标签文件及1个说明txt构成,整体大小约397.63MB,文件命名规律清晰,便于按序检索与划分训练集。目前该数据集已有2320人学习使用,属于分批发布中的第一批,适合需要舰船遥感样本开展目标检测算法实验、对比不同检测器性能或扩充自有数据集的读者。
1. 舰船卫星图目标检测,数据集是第一个瓶颈
卫星图里的舰船检测,和日常照片里的目标检测完全是两回事。一张 1024×1024 的遥感图像里,一艘货轮可能只占 40×20 像素,甲板纹理、船头方向、尾迹形态全挤在这巴掌大的区域里;港口场景还有码头、集装箱、岸桥这类外观高度接近的干扰物。很多人拿着 COCO 预训练权重直接跑,mAP50 能到 0.6 以上,但放到真实港口视频里一测,误检率立刻失控。问题不在模型,而在数据:目标检测数据集的标注密度、类别定义、图像来源和分辨率,直接决定了模型能不能把「船」和「长得像船的东西」分开。
这个标题里的人工智能目标检测数据集(舰船卫星图1),对应的是遥感领域最常见的一类私有数据包:从历史卫星图或公开遥感影像里裁剪出舰船样本,按目标检测格式标注好,供 YOLO、Faster R-CNN、DOTA 系旋转框模型训练使用。适合三类人:做海事监管、渔业管理、港口调度相关项目的工程师;高校里用遥感数据做人工智能大作业的学生;以及想验证 YOLOv8 在自己数据上效果、又不愿意从零标注的算法岗新人。
先说结论:拿到这类数据集,第一件事不是训练,而是把标注格式、图像分辨率和类别定义摸清楚。舰船检测的绝大多数翻车现场,都发生在数据预处理阶段。
2. 舰船卫星图数据集的目录结构、标注格式与质量检查
2.1 典型目录布局与图像规格
公开渠道能拿到的舰船卫星图数据集,目录结构通常长这样:
ship_satellite_v1/ ├── images/ │ ├── train/ # 训练图像,JPG 或 PNG │ ├── val/ # 验证图像 │ └── test/ # 测试图像 ├── labels/ │ ├── train/ # 与图像同名的 txt 文件 │ ├── val/ │ └── test/ ├── classes.txt # 类别列表,一行一个 ├── train.txt # 图像路径列表(VOC 风格会用到) ├── val.txt └── data.yaml # YOLO 训练配置文件图像规格上,舰船卫星图数据集和自然图像数据集有两个明显差异。一是单张图很大,常见 1024×1024 到 4096×4096 不等,直接缩放后小船会丢失细节,后面第 4 章会讲切片策略。二是地面采样距离(GSD)不统一,0.5 米分辨率的图像里一艘渔船有上百像素,10 米分辨率的图像里同一条船只有十几个像素,这决定了模型能学到的是「船的轮廓」还是「船的纹理」。
拿到数据后第一件事是看 classes.txt。很多舰船数据集只有一个类别ship,但也有把船细分为cargo、fishing、warship的版本。单一类别训练简单,mAP 容易做高;多类别则需要考虑类别间外观相似造成的混淆,尤其是渔船和货轮在低分辨率下几乎无法区分。
2.2 标注格式转换:YOLO、VOC、COCO 之间怎么安全切换
舰船卫星图数据集最常见的标注格式是 YOLO txt 格式,每行一个目标:
class_id x_center y_center width height所有值都用图像宽度和高度归一化到 0~1 之间。比如0 0.5234 0.3102 0.0411 0.0289,代表一个类别 0 的目标,中心点在图像横向 52.34%、纵向 31.02% 的位置,框宽占整张图的 4.11%,高占 2.89%。
YOLO 格式转 COCO 的代码很简单,但转换时的坐标还原要小心:
import os import json from PIL import Image def yolo_to_coco(img_dir, label_dir, class_file, output_json): # 读取类别列表,写入 COCO categories categories = [] with open(class_file, 'r') as f: for i, line in enumerate(f.readlines()): categories.append({"id": i, "name": line.strip()}) images = [] annotations = [] ann_id = 1 for img_id, label_file in enumerate(os.listdir(label_dir)): # 通过标签文件名找到对应图像,获取宽高用于坐标还原 img_name = label_file.replace('.txt', '.jpg') img_path = os.path.join(img_dir, img_name) if not os.path.exists(img_path): # 有些数据集用 png 后缀,没找到 jpg 再试 png img_path = os.path.join(img_dir, label_file.replace('.txt', '.png')) with Image.open(img_path) as img: width, height = img.size images.append({ "id": img_id, "file_name": os.path.basename(img_path), "width": width, "height": height }) # yolo 格式一行: class cx cy w h,都是归一化到 [0,1] 的浮点数 with open(os.path.join(label_dir, label_file), 'r') as f: for line in f.readlines(): parts = line.strip().split() if len(parts) != 5: continue # 跳过空行或格式异常的行 cid = int(parts[0]) cx, cy, w, h = map(float, parts[1:]) x = (cx - w / 2) * width y = (cy - h / 2) * height box_w = w * width box_h = h * height annotations.append({ "id": ann_id, "image_id": img_id, "category_id": cid, "bbox": [x, y, box_w, box_h], "area": box_w * box_h, "iscrowd": 0 }) ann_id += 1 coco = {"images": images, "annotations": annotations, "categories": categories} with open(output_json, 'w') as f: json.dump(coco, f) if __name__ == '__main__': # 用法: python yolo2coco.py yolo_to_coco('images/train', 'labels/train', 'classes.txt', 'train_coco.json')这段代码的逻辑分三步:读类别文件构建 COCO categories,遍历标签目录拿到每个目标的归一化坐标,最后乘图像宽高还原成 COCO 的 xywh 格式。需要注意两个坑:一是 YOLO 格式的 width/height 是归一化的框宽高,不是右下角坐标,换算时一定要先算x = cx - w / 2;二是 COCO 的 bbox 要求是整数或浮点都可以,但area必须匹配 bbox,否则后续评估工具会报警告。
反过来 VOC 格式转 YOLO 更常见,因为很多遥感标注工具导出的是 PASCAL VOC 的 XML。核心就是把xmin, ymin, xmax, ymax换算成cx, cy, w, h再除以图像宽高:
x_center = (xmin + xmax) / 2 / width y_center = (ymin + ymax) / 2 / height box_w = (xmax - xmin) / width box_h = (ymax - ymin) / height这个换算方向容易搞反,写脚本时建议先用一张已知标注的图验证:转换后把 bbox 画回原图,叠上原标注对比,肉眼确认一致再批量跑。舰船目标小、数量多,一个错位的框混在几百个正确框里很难发现,等训练完才发现就晚了。
2.3 历史卫星图数据集的标注质量检查
标题里强调「舰船卫星图1」,很多数据集来自历史卫星图切片。这类数据的标注质量相比人工拍摄的无人机数据集要差一截,主要体现在几个方面。
一是漏标。一张港口图里码头边上停了十几条船,标注员可能只标了视觉明显的几条,小型渔船直接略过。训练时漏标的船会被当作背景,模型学到「有船的地方也可以是背景」,推理时就倾向漏检。
二是目标框方向不一致。水平框标注下,一条斜靠码头的船框里会包含大量水面,模型被迫学习「船 + 水」的组合特征,而不是船本身。旋转框数据集不存在这个问题,但标注成本高。
三是类别标签噪声。外卖平台上搜到的舰船数据集,有的会把「船坞里的维修船」标成码头设施,或者把两个紧邻的船标成一个框。训练前抽 200 张图用 OpenCV 或 labeling 工具逐张过一遍,重点看标注框是不是贴合船身轮廓、有没有同一个目标被标两次。
检查代码可以用一个简单的脚本统计异常标注——宽高比极端的框、面积小于 16 像素的目标、越界的框坐标:
import os label_dir = 'labels/train' anomalies = 0 for fname in os.listdir(label_dir): with open(os.path.join(label_dir, fname), 'r') as f: for line in f.readlines(): parts = line.strip().split() if len(parts) != 5: print(f"{fname}: 格式错误 -> {line.strip()}") anomalies += 1 continue _, cx, cy, w, h = float(parts[0]), *map(float, parts[1:]) # 舰船宽高比通常不超过 8:1,超过多半是标注错误 if w / h > 8 or h / w > 8: print(f"{fname}: 宽高比异常 ({w:.3f}/{h:.3f})") anomalies += 1 # 归一化坐标超出 [0,1] 说明标注越界 if not (0 <= cx <= 1 and 0 <= cy <= 1 and 0 < w <= 1 and 0 < h <= 1): print(f"{fname}: 坐标越界 ({cx}, {cy}, {w}, {h})") anomalies += 1 print(f"共发现 {anomalies} 处异常标注")这个脚本本身不修复任何东西,它的价值是告诉你数据的可信度。异常标注超过总目标数的 1%,就别直接训练,优先清洗。清洗方式很简单:把异常框所在图像单独抽出来,逐张手工修正,通常一个几千张的数据集只需处理几十张异常图,耗时半小时以内,但训练稳定性能提升一个档次。
3. 用 YOLOv8 在本地跑通舰船目标检测的最小流程
3.1 数据划分与 YAML 配置
拿到格式干净的数据集后,先做数据划分。常见做法是训练集 70%、验证集 20%、测试集 10%,但舰船数据集的特殊性在于同一场景的多张切片之间有大量重复目标——一张港口大图切成 16 个小块后,相邻块边缘的船可能被切掉一半。划分时一定要按来源图分组,别把同一张原始图的切片同时分进训练集和验证集,否则验证集 mAP 虚高,部署后立刻露馅。
划分完成后写data.yaml:
path: /data/ship_satellite_v1 train: images/train val: images/val test: images/test nc: 1 names: 0: ship这段配置的path是数据集根目录的绝对路径,train和val是相对该路径的图像目录,nc是类别数,names是类别名映射。YOLOv8 会自动去同名 txt 目录找标签:图像在images/train,标签就在labels/train,文件名一致、扩展名是.txt。如果标签目录结构不一样,会报「label not found」或者直接跳过所有图像,训练时 loss 不下降——这是最容易踩的第一个坑。
3.2 最小训练命令与关键参数
数据划分好之后,命令行直接跑:
yolo detect train \ data=/data/ship_satellite_v1/data.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=1280 \ batch=16 \ workers=4 \ device=0 \ project=ship_yolo \ name=exp1 \ pretrained=True这段命令的关键参数逐个说。model=yolov8s.pt表示基于 COCO 预训练的 Small 版本做迁移学习,相比从零训练收敛更快,卫星图这类与自然图像分布差异大的数据,迁移学习通常能把收敛时间缩短一半以上。imgsz=1280是输入分辨率,舰船是小目标,用默认 640 会丢掉大量纹理信息,1280 是性价比比较高的起点,显存不够就降到 960。batch=16在 24GB 显存的显卡上配合 1280 输入刚好跑得动,显存不够优先降 batch 而不是降分辨率。pretrained=True让 YOLOv8 加载 COCO 权重,这对舰船检测是有利的——COCO 里有船这个类别,模型已经学过船的底层特征(轮廓、甲板、桅杆),迁移过来只需要微调高层语义。
训练过程中盯两个指标:验证集的mAP50和mAP50-95。舰船检测场景里目标小、跟背景对比度低,mAP50反映的是「大致找到船」的能力,mAP50-95反映的是「框得多准」的能力。如果mAP50从 0.7 开始增长缓慢,而mAP50-95一直贴着 0.3 左右不升,说明框的定位精度不够,优先考虑提升输入分辨率或者换旋转框方案,而不是盲目加训练轮数。
3.3 训练阶段的三个常见报错与规避
第一个报错是「CUDA out of memory」。舰船数据集图像尺寸大,YOLOv8 默认会做 Letterbox 缩放,把 1024×1024 缩到 1280×1280 反而增大了显存占用。处理顺序:先降 batch 到 8,再降 imgsz 到 960,最后关掉cache=True(数据集缓存到内存会额外占显存之外的资源)。如果 24GB 显存跑 1280 还 OOM,检查是不是开了plots=True和频繁的验证评估,把val频率从默认改成val=20可以省去中间验证的显存峰值。
第二个报错是训练 loss 正常但 mAP 始终为零。这种情况九成是数据划分出了交叉污染,或者标签类别编号和data.yaml不一致。用训练集里随机抽 3 张图,把标注框画出来人工检查一次:
python -c " from ultralytics import YOLO model = YOLO('yolov8s.pt') model.train(data='data.yaml', epochs=1, imgsz=1280, batch=4, plots=True) "跑一个 epoch,然后看runs/detect/exp/目录下的train_batch0.jpg,图像上会画出标注框。如果框的位置明显偏离船体,说明标签格式或坐标换算有问题;如果完全没有框,说明标签没被读进去。
第三个问题是训练速度慢到无法接受。舰船数据集普遍图像数量少(几千张)但单张尺寸大,YOLOv8 的rect=True参数可以按宽高比分组批处理,减少 Letterbox 带来的填充浪费,训练速度提升 20% 到 30%。另外确认workers大于 0,数据加载不吃满 CPU 时 GPU 只能空转。
4. 卫星图舰船检测的模型选择与调参策略
4.1 水平框还是旋转框:MMRotate 在什么时候值得上
舰船在卫星图里的朝向是任意的,港口里并排停靠的船还会互相遮挡。水平框标注天然包含大量背景水面,模型学到的特征被稀释,尤其在船间距小、停靠密集的港口场景,两个相邻的水平框 IoU 可能超过 0.5,NMS 会把其中一条船直接压掉。这时就需要旋转框目标检测。
旋转框方案里,MMRotate 是目前社区最完整的工具链,官方发布的模型大多基于 DOTA 数据集训练,而 DOTA 数据集的舰船类别正好可以迁移到卫星图场景。用 MMRotate 训练自己的舰船旋转框数据集的典型配置:
# rotated_retinanet.py 关键配置 angle_version = 'le90' # 角度范围 -90 到 90 model = dict( type='RotatedRetinaNet', backbone=dict(type='ResNet', depth=50, pretrained='torchvision://resnet50'), bbox_head=dict( type='RotatedRetinaHead', num_classes=1, angle_coder=dict( type='DeltaXYWHAOBBoxCoder', target_means=(0., 0., 0., 0., 0.), target_stds=(1.0, 1.0, 1.0, 1.0, 1.0) ) ) ) train_pipeline = [ dict(type='LoadImageFromFile'), dict(type='LoadAnnotations', with_bbox=True, box_type='qbox'), dict(type='ConvertBoxType', box_type_mapping=dict(gt_bboxes='rbox')), dict(type='RandomRotate', angle_range=180, rotate_prob=0.5), dict(type='PackDetInputs') ]这里的angle_version='le90'是旋转框的角度表示方式,MMRotate 支持le90和oc两种,DOTA 数据集官方用的是le90,舰船这类没有方向语义的目标用le90就够了。RandomRotate增强旋转框模型极其重要,因为旋转框标注本身依赖角度,训练时加随机旋转可以让模型对任意朝向的船都鲁棒。
旋转框不是免费的。标注成本高(需要标角度)、推理速度比水平框慢 30% 以上、NMS 实现更复杂。我的判断标准是:如果场景是开阔海域、船之间间距大于船身长度,水平框足够;如果是密集港口、船只并排停靠,直接上旋转框,别在水平框上浪费时间调 NMS 参数。
4.2 图像切片策略:大图不缩放,切片进模型
舰船卫星图的典型痛点是:整图分辨率高,但放大到模型输入尺寸后船太小。常见做法是 SAHI(Slicing Aided Hyper Inference)——推理和训练时都先把大图切成有重叠的切片,分别检测,再用 NMS 合并结果。
切片参数有三个关键值:切片大小、重叠率、模型输入尺寸。我常用的组合是 640×640 切片、20% 重叠率、模型输入 640。切片太大会导致边缘目标被截断,太小则目标可能被切成两半、上下文丢失。重叠率越高,边缘目标被完整捕获的概率越大,但推理时间线性增长。
用 SAHI 做舰船检测推理的代码:
from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction # 加载 YOLOv8 训练好的模型 detection_model = AutoDetectionModel.from_pretrained( model_type='yolov8', model_path='ship_yolo/exp1/weights/best.pt', confidence_threshold=0.3, image_size=640, device="cuda:0" ) # 在大图上执行切片推理 result = get_sliced_prediction( image="test_imgs/harbor_001.png", detection_model=detection_model, slice_height=640, slice_width=640, overlap_height_ratio=0.2, overlap_width_ratio=0.2, postprocess_type='NMS', postprocess_match_metric='IOS', postprocess_confidence_threshold=0.3, postprocess_match_threshold=0.5, ) result.export_visuals(export_dir="output/")postprocess_match_metric='IOS'是切片推理合并时的关键参数。切片重叠区域里的同一个目标会被检测多次,合并时需要判断两个框是否指向同一个目标。IOS(Intersection over Smaller box)比IoU更适合处理两个框一大一小的情况——当切片边缘只截到目标一半时,小框是完全包含在大框里的,IoU 会很小但 IOS 接近 1,用 IOS 才能正确合并。
切片推理的精度提升是实打实的。在 0.5 米分辨率卫星图上,直接用 1280 输入检测的 mAP50 大约 0.72,切成 640 切片检测后能到 0.81 以上——前提是训练时也用了同样的切片策略,否则推理端切片、训练端整图,尺度不一致,模型反而会困惑。
4.3 数据增强与类别不平衡:舰船数据集的特殊处理
在 YOLOv8 的增强参数里,舰船检测和通用目标检测有几个明显不同的设置。第一个是degrees=180,舰船朝向任意,水平翻转会破坏船只的左右对称性吗?会,但船在卫星图里本来就没有固定的「左」和「右」概念,旋转增强 180 度让模型学到的是「任意朝向的船」,对开阔海域场景帮助很大。第二个是fliplr=0.5、flipud=0.5可以保留默认,但mosaic=0.5调低——舰船数据集的图像切片之间本来就存在大量重复目标,Mosaic 增强会把不同来源图的船拼在一起,模型可能学到「船旁边必须有另一艘船」的错误关联。第三个是close_mosaic=10,最后 10 个 epoch 关闭 Mosaic,让小目标不会被过度裁剪。
类别不平衡在纯舰船数据集里不太常见,因为通常只有一个类别。但如果数据集包含多种船型,货轮样本量可能是渔船的几十倍,这时要用class_weight或者简单的过采样。更常见的问题是难例不平衡:一艘靠岸的货轮被码头遮挡了一半,另一艘停在开阔水面的货轮完整可见,前者才是真正影响部署效果的样本。针对这个问题,训练后单独收集误检样本,加入训练集做一轮增量训练(yolo detect train ... resume=True不必,直接加载best.pt继续训),效果比调损失函数更直观。
4.4 从单模态到多模态:舰船检测的方向
舰船检测的进阶方向是融合红外和可见光双模态数据。DMSD 这类船舶红外可见光双模态数据集的思路是:同一场景下同时提供热红外和可见光两路图像,可见光提供纹理细节,红外提供温度特征,两者互补。融合方式通常有早融合(输入层拼接)、晚融合(特征层相加或注意力加权)两种,早期试验阶段用 late fusion 更容易调试——两个模态各有独立的检测分支,最后用注意力模块加权合并,而不是一开始就做像素级对齐。
多模态需要的数据标注量翻倍,对 GPU 显存的要求也更高,如果是大作业或初探,先把单模态做到极致再考虑扩展。
5. 评估、推理加速与把检测结果落到经纬度上
训练出的模型不能只看 mAP,舰船检测的评估要单独验证三个维度:小目标召回率、密集场景的漏检率、以及跨数据集的泛化能力。YOLOv8 的验证命令会输出完整指标:
yolo detect val \ model=ship_yolo/exp1/weights/best.pt \ data=data.yaml \ imgsz=1280 \ conf=0.001 \ iou=0.6conf=0.001是评估时的关键参数,推理时用 0.25 的置信度阈值会漏掉大量低置信度的真实小船,评估时降到 0.001 才能看到模型的真实上限。看mAP50-95和验证集生成的一个confusion_matrix.png,重点看船这一类别的漏检率(false negative),而不是只看 mAP 数值——舰船检测部署到港口监控里,漏检比误检严重得多。
推理加速上,用 TensorRT 导出是部署前必做的一步:
yolo export model=ship_yolo/exp1/weights/best.pt format=engine device=0 imgsz=1280 half=True导出的 engine 文件在相同 GPU 上推理速度比 PyTorch 版本快 2 到 3 倍,显存占用降低约一半。half=True开启 FP16 精度,卫星图舰船检测这类背景相对干净的任务不会有明显精度损失,但如果发现小目标召回率下降,改回 FP32。
业务落地时,舰船检测的最终输出往往不是像素框,而是经纬度坐标。假设你的卫星图带地理信息文件,把检测框中心从像素坐标转成经纬度,方法很简单:读取 GeoTIFF 的地理变换参数,用仿射变换换算。
from osgeo import gdal # 打开带地理信息的卫星图 ds = gdal.Open('harbor_001.tif') gt = ds.GetGeoTransform() # gt 的六个参数: 左上角x坐标, x方向像素分辨率, x方向旋转, 左上角y坐标, y方向旋转, y方向像素分辨率(负值) def pixel_to_geo(pixel_x, pixel_y, gt): # 仿射变换:地理坐标 = 左上角坐标 + 像素偏移 * 分辨率 geo_x = gt[0] + pixel_x * gt[1] + pixel_y * gt[2] geo_y = gt[3] + pixel_x * gt[4] + pixel_y * gt[5] return geo_x, geo_y # 假设检测框中心像素坐标为 (512, 384) lon, lat = pixel_to_geo(512, 384, gt) print(f"目标经纬度: {lat:.6f}, {lon:.6f}")gt[5]在北半球是负值,代表 y 轴向下、纬度递减,很多人在这里踩坑——直接拿gt[3] + pixel_y * gt[5]算出来纬度方向是对的,但如果不取负值会得到反向结果。验证方法很简单:把算出的经纬度放进任何地图工具里,看是否落在港口陆域或近海水域,如果落在陆地中央,检查是不是 y 方向符号搞反了。
最后提一个部署端常用但容易被忽略的技巧:给检测结果加航向或尾迹信息。卫星图序列帧里,同一艘船在相邻两帧的位置变化可以估算航速和航向,这比单帧检测框更有业务价值。做法朴素——用 ByteTrack 或 DeepSORT 做跨帧关联,船只目标外观差异小、容易跟丢,关联时优先用位置预测而不是外观特征,简单可靠。
舰船卫星图检测的完整链路从数据清洗开始,到经纬度输出结束,每一步都有对应的验证方式。把调参精力集中在数据质量和部署场景的贴合度上,比反复换模型结构更出效果。
本文还有配套的精品资源,点击获取