首页 / 资讯中心 / 文章详情

舰船卫星图目标检测数据集实战:切图、训练与避坑全指南

舰船卫星图目标检测数据集实战:切图、训练与避坑全指南 ★ FEATURED ARTICLE
简介面向遥感目标检测研究的舰船卫星可见光数据集涵盖航母hang-mu与舰船jun-jian两类目标适合计算机视觉、遥感解译方向的开发者、科研人员及高校学生用于算法研究、毕业设计或工程项目。包内共2000个文件包括1000张1024×1024的RGB卫星图、1000个VOC格式XML标注文件及1个说明文本压缩包约397.63MB图像与标注一一对应可直接接入TensorFlow、PyTorch、MMDetection等主流检测框架省去格式转换与手工标注环节。数据标签已预整理类别清晰目标框完整便于快速开展模型训练、跨域测试与精度对比现有2325人浏览学习适合作为舰船检测任务的基准数据集也可用于目标检测算法的可行性验证与性能评估。该系列后续还将分批发布可保持关注以持续扩充训练样本满足更大规模遥感识别实验需求。1. 舰船卫星图目标检测数据集先看清它难在哪再决定怎么训练很多同学第一次拿到「舰船卫星图1」这类人工智能目标检测数据集时第一反应是打开文件夹数有多少张图、多少个框然后急匆匆地丢进训练脚本里跑。结果是模型确实能跑但 mAP 很难看小目标一个都检不出来验证集上虚警一大堆。问题不在模型而在数据集本身——舰船卫星影像和自然场景数据集完全是两种玩法。卫星俯视图里目标小、背景杂、朝向任意近岸码头目标密集到框和框重叠海面区域又大面积相似纹理稍微处理不好就把检测器带偏。这篇笔记要讲的就是拿到这样一份舰船目标检测数据集后怎么组织数据、怎么选基线模型、怎么调参数、哪里最容易翻车。适合正在做遥感目标检测课题、人工智能大作业或者第一次用 YOLO 训练自己数据集的人照着重现一遍。2. 把舰船卫星图整理成可训练样本切片、坐标换算与目录规范2.1 舰船卫星图的三类特征小目标、极端比例、背景纠缠舰船卫星图数据集的第一个特征是目标尺寸小。卫星影像分辨率一般在 0.5 到 10 米每像素一艘 100 米长的货船在 0.5 米分辨率的图上也就 200 个像素宽摊到整张 5000×5000 的大图里占比往往不到 1%。用 YOLO 默认的 640 输入一缩放很多目标直接缩成几个像素的小点检测器根本学不到形状特征。所以处理这类数据我一般会把训练输入尺寸提到 1024甚至在显存允许时用 1280。第二个特征是正负样本比例极端不均衡。一张近海卫星图里可能只有三条船剩下全是海面、港口、陆地。如果不刻意控制训练数据的构成模型很容易学成「见到海面纹理就框一个」的假阳性机器。解决办法是在做数据集拆分时单独抽取一批纯海面区域负样本让模型见过「没有船的海」长什么样。第三个特征是目标朝向任意、形态差异大。船头的朝向可以是任何角度集装箱船、散货船、油轮的宽长比差别非常大。这意味着数据增强里默认的左右翻转不够需要加旋转增强后面我会给具体参数。2.2 把原始大图切成训练样本切片脚本与步长选择遥感影像很少直接拿整张大图训练。一张 10000×10000 的 TIF 放进 GPU 显存是不可能的常见做法是先按固定窗口切块把大图变成一批带重叠的小块。下面这个脚本是我常用的切图方案用重叠采样保证目标不会恰好被切在边缘。import os from PIL import Image def split_image(src_path, out_dir, tile_size1024, stride512): img Image.open(src_path) img_width, img_height img.size os.makedirs(out_dir, exist_okTrue) # 记录每块图在原图上的位置后续推理拼框时要用 boxes [] idx 0 for y in range(0, img_height - tile_size 1, stride): for x in range(0, img_width - tile_size 1, stride): tile img.crop((x, y, x tile_size, y tile_size)) tile.save(os.path.join(out_dir, f{idx:05d}_{x}_{y}.png)) boxes.append((x, y, tile_size, tile_size)) idx 1 # 右边和下边如果剩余不足一个窗口补一块到边缘 if img_width % stride ! 0 or img_height % stride ! 0: x img_width - tile_size y img_height - tile_size tile img.crop((x, y, min(x tile_size, img_width), min(y tile_size, img_height))) tile.save(os.path.join(out_dir, fedge_{idx:05d}_{x}_{y}.png)) print(f切出 {idx 1} 块原始尺寸 {img_width}x{img_height}) split_image(scene_001.tif, train_images, tile_size1024, stride512)这里的核心参数是tile_size和stride。tile_size决定模型能看到多大范围——舰船单目标不大1024 已经足够再大反而增加背景复杂度。stride小于tile_size意味着相邻切片有重叠区域目标即使落在某一片的边缘也会完整出现在另一片的中间位置不至于被截断。512 stride 配上 1024 tile 是 50% 重叠切片数量大约是原来的 4 倍数据量不够时这个重叠同时起到了扩增作用。切完之后要做一件事可视化抽查。随机抽 20 张切片人工确认有没有目标被硬生生切成两半、有没有整张全黑或全白的空片。这种空片如果进训练集会让模型学到的特征里多出莫名其妙的「纯色背景」建议直接删掉。2.3 把 VOC/COCO 标注转成 YOLO 格式坐标归一化别算错切完图之后就要处理标注。舰船数据集来源多样最常见的交接格式是 VOC XML里面记录每个目标的左上角和右下角绝对坐标。YOLO 训练需要的是归一化后的中心点坐标和宽高转换时最容易翻车的就是忘记除以当前图片的实际宽高尤其是切图后的尺寸和标注时的坐标系不一致。import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_path, img_width, img_height): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.iter(object): cls_name obj.findtext(name) if cls_name ! ship: continue # 舰船数据集一般只检船其他类先过滤掉 box obj.find(bndbox) x1 float(box.findtext(xmin)) y1 float(box.findtext(ymin)) x2 float(box.findtext(xmax)) y2 float(box.findtext(ymax)) # 裁掉超出图片边界的框避免出现负坐标或大于 1 的归一化值 x1 max(0, min(x1, img_width - 1)) x2 max(0, min(x2, img_width - 1)) y1 max(0, min(y1, img_height - 1)) y2 max(0, min(y2, img_height - 1)) if x2 x1 or y2 y1: continue x_center (x1 x2) / 2 / img_width y_center (y1 y2) / 2 / img_height w (x2 - x1) / img_width h (y2 - y1) / img_height lines.append(f0 {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(out_path, w) as f: f.write(\n.join(lines))这里有两处细节值得多说。第一舰船目标紧贴港口岸边时标框很容易画出图像边界转出来宽高可能是负的必须先 clip。第二类别编号从 0 开始如果后续还打算加入货船、渔船之类的细分类型编号要在 conversion 脚本里统一管理不要直接在 txt 里手工改。2.4 标注质量核查黑匣子里的数据要两眼看过才放心标注质量是决定精度的最大变量这一步偷懒后面全得还。常见做法是写一个可视化脚本把训练标签画回图片上随机抽查几百张。import cv2 def draw_labels(image_path, label_path, output_path): img cv2.imread(image_path) h, w img.shape[:2] with open(label_path) as f: for line in f: cls, xc, yc, bw, bh map(float, line.split()) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, fship {bw:.2f}x{bh:.2f}, (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) cv2.imwrite(output_path, img)检查重点有两个一是框有没有紧紧贴住船体——很多标注把船周围的白色尾迹或码头阴影也包进框里这会让模型学到错误的目标轮廓二是框是否严重偏离目标比如把浪花当成船。如果发现有系统性的偏移宁可返工修正标注也不要期望模型自己纠正数据错误。3. 用 YOLOv8 跑通舰船检测最小实验命令、yaml 与日志判读3.1 为什么用 YOLO 系做基线而不是三维目标检测或开放词汇做舰船卫星图检测选型第一原则是先跑通再追求先进。舰船检测这个任务说到底还是水平框检测为主YOLO 系有足够成熟的预训练权重和数据管线一套流程半小时内就能看到第一个结果。开放词汇目标检测看着厉害但舰船这种细长形态的目标没有丰富的词汇先验性价比不高。三维目标检测更是常被误用进了这个场景——卫星图是俯视正射影像没有可靠的深度线索强行做三维重建收益很低。我见过有人上来就上旋转框检测架构结果把大部分精力耗在处理标注格式上连 baseline 都没来得及跑。先把 YOLO 跑通至少能验证这批数据本身有没有救。3.2 整理数据目录与 data yaml路径和类别名必须和标注对上YOLOv8 的训练入口是 data 配置文件它告诉框架去哪里读训练集、验证集以及每个类别编号对应的名字。对舰船数据集来说目录结构按照官方约定整理最省心。ship_dataset/ ├── images/ │ ├── train/ # 已切好的船图 │ └── val/ # 按大图分出去的验证块 ├── labels/ │ ├── train/ │ └── val/ └── ship.yaml注意 labels 目录的组织方式必须和 images 一一对应——文件名相同、目录层级相同。建议用一个脚本从原始标注批量生成 labels不要在标注工具里手动另存否则文件名拼接错误排查起来极其痛苦。# ship.yaml path: /workspace/ship_dataset # 数据集根目录填绝对路径更稳 train: images/train val: images/val names: 0: ship这里提醒一件事names的序号一定要和 label txt 里的第一个数字一致。舰船数据如果只有船一个类别全部写成0没问题如果你手里这份数据还带着boat、vessel这样的混杂类别名合并时统一映射成 ship 或按业务拆成多类别让同义词混进一个类里。3.3 训练命令与关键参数imgsz、batch、mosaic 的取舍跑训练用下面这行命令作为起点yolo detect train \ dataship.yaml \ modelyolov8n.pt \ epochs120 \ imgsz1024 \ batch16 \ device0 \ projectruns_ship \ nameship_yolov8nimgsz1024是舰船检测最值得坚持的选择。前面讲过卫星图里的船尺寸偏小用 640 会把目标压没。代价是显存占用翻倍batch 得对应调小。8G 显存的情况下1024 输入配 batch16 可能直接 OOM我一般降到 batch8或者把model从yolov8n改成更轻的yolov8n加workers4即可。mosaic增强默认是开启的用来把小目标拼在一起增加上下文但它有个副作用切了 mosaic 之后框的真实性会下降最后 10 个轮次建议关掉。YOLOv8 的配置里直接加mosaic0.0就可以在收尾阶段关闭让模型回到原始分布上稳定收敛。还有一个经常被忽视的参数是pretrained。从 COCO 预训练权重yolov8n.pt开始舰船这类目标虽然和 COCO 的类别差异大但底层边缘、纹理特征仍然能复用效果比从头训好得多。除非你的数据集分布极端特殊否则不要轻易pretrainedfalse。3.4 训练日志怎么读哪些 loss 决定成败训练过程中终端输出的box_loss、cls_loss、dfd_loss和验证集的mAP50、mAP50-95是最需要盯的。box_loss不降说明模型没学到目标的尺度规律cls_loss不降说明框里的内容不足以区分船和背景。舰船场景里最常见的假象是cls_loss降得很快因为船在图像中外观相对一致但mAP50-95上不去——这往往意味着框的质量差定位偏移大。跑完一轮后去runs_ship/ship_yolov8n/下看results.png里面画了各项指标的曲线。重点看训练集 loss 和验证集 loss 的分离点如果验证 loss 从某个 epoch 开始反弹而训练 loss 还在降基本就是过拟合此时把 epochs 调回去或者增加数据增强。4. 舰船检测训练常见问题避坑5 个会让模型翻车的细节4.1 损失变成 nan学习率失控与标注里的零面积框现象训练跑到第 20 轮终端突然输出loss: nan后面所有的 loss 全是 nan训练直接废掉。原因最常见是两个。一是学习率设置太高尤其当 batch 很小而学习率还维持默认 0.01 时梯度更新过大导致数值溢出二是标注 txt 里存在宽度或高度为 0 的框归一化后出现0.000000 0.000000这样的无效行YOLO 读到后在计算 IoU 时除零。解决右 CtrlC 停掉训练先用下面这段脚本扫一遍 labelsgrep -rn 0.000000 0.000000 labels/train/ | head -20找到文件后把该行删掉或修正为正确的标注。学习率方面舰船数据集样本少时我会把lr0从默认 0.01 降到 0.005lrf0.001保持不变。改完这两处再重新训练。4.2 验证集 mAP 很高但实际推理却检不出来切图和推理尺寸不一致现象训练时 mAP50 到了 0.9导出模型后拿一张测试大图跑检测一个目标都检不到或者框严重偏移。原因这是典型的数据集不匹配问题。训练时用的是 1024×1024 切片推理时如果把 10000×10000 的原图直接缩放到 1024舰船目标会被缩小十几倍早低于检测器能感知的最小尺度。解决推理也必须走切片路线。大图先切成 1024 的小块每块独立推理把检测框坐标映射回原图坐标后再合并。后面第 6 章会给出完整的切片推理脚本思路。4.3 图太大直接爆显存pyTorch 报 CUDA out of memory现象执行yolo predict sourcebig.tif之后很快提示CUDA out of memory训练好的权重根本没法跑。原因对遥感大图来说imgsz1024是训练时的值但 predict 默认会按照传入图片的形状做缩放。一张 10000×10000 的图放进 8G 显存绝对越界。解决不要在 predict 阶段用大图直接推断。写一个循环把图切块后逐块推理或者直接用 YOLO 自带的yolo predict加上imgsz1024但它内部对大图的支持有限我仍然推荐自己控制切片逻辑反而更可控。4.4 模型把海面、码头误检成船虚警率压不下去现象验证集 mAP 正常但看测试图的可视化结果海面纹理、码头防波堤被大量标成 ship尤其置信度阈值调到 0.25 时一片红框。原因训练集中的负样本不够。舰船检测的正样本船和负样本无船的海面天然不均衡如果 train 文件夹里切出来的全是带船的区域模型没机会见过「干净的海面」推理时见到任何相似纹理都会给高置信度。解决从原始大图里裁剪一批不含任何船的纯海面切片标注为空文件txt 里没有任何行放进训练集。这个操作等价于给模型喂「背景类」样本能显著降低虚警。我一般会让负样本占训练集总量的 15%~25%。4.5 训练损失在降但 mAP 一直不动数据划分串了现象训练 100 轮loss 一路下降但 mAP50 一直停在 0.2val 集上的预测框像是随机的。原因切图时没有按原始大图分 train/val而是把切片随机打乱后再划分。这样同一个母图下的相邻切片会同时出现在训练集和验证集属于数据泄漏验证结果虚高反过来如果正好同一条船的多片被分到 val而 train 里没有类似尺度也会造成 mAP 奇低。解决划分应该在切图之前完成——按大图文件为粒度先把原图分成 train/val 两组再分别做切片。代码示例如下import os import random import shutil # 先按大图原始文件名分折再切图 all_scenes [f for f in os.listdir(scenes) if f.endswith(.tif)] random.seed(42) random.shuffle(all_scenes) split_idx int(len(all_scenes) * 0.8) train_scenes all_scenes[:split_idx] val_scenes all_scenes[split_idx:]一条铁规矩一个原始场景的所有切片必须同属一个集合。否则你看到的指标全是自嗨。5. 评测与调优mAP 怎么读、小目标与旋转框方向5.1 用 mAP50 还是 mAP50-95舰船场景的指标陷阱Ultralytics 训练结束会输出一组指标很多人只看 mAP50。舰船场景里这不够。舰船目标有细长形态IoU0.5 时一个偏了 20% 的框仍能算作命中mAP50 虚高而 IoU 从 0.5 拉到 0.95 的mAP50-95才能反映定位精度——这对靠框的宽度判断船型、排水量的任务尤为重要。调优时以 mAP50-95 为准。如果你的 mAP50 都不到 0.5问题多半在数据侧如果 mAP50 有 0.8 但 mAP50-95 只有 0.4问题在定位精度重点看box_loss和大目标/小目标的分布。5.2 小目标增强三板斧1024 输入、Mosaic、旋转增强舰船目标小增强方向就和自然场景不同。第一输入分辨率提到 1024 是基础钱不够显存就减小 batch 而不是降 imgsz。第二Mosaic 增强把一个训练批次里的四张图拼成一张相当于把四个不同位置的舰船放在同一张图里增加了每张图的目标密度让模型在小目标样本稀疏时有更多学习机会。第三旋转增强是舰船数据的刚需。YOLO 默认的训练增强只有左右翻转但卫星图中的船可以是任意朝向。在训练命令里加上yolo detect train ... \ degrees180 \ fliplr0.5 \ scale0.25degrees180表示每一轮训练图像在 0~180 度范围内随机旋转。卫星图是正射视角旋转不改变目标的物理意义所以可以放心给满 180。scale0.25允许图像随机缩放 25%相当于轻微改变船的大小对尺度泛化有帮助。另外还有mixup增强Ultralytics 默认mixup0.0我在舰船项目里会开到 0.1~0.2。它把两张图按透明度叠加能抑制模型对背景纹理的过拟合代价是训练时间变长。5.3 从水平框迈向旋转框舰船检测的正交方向如果横向框检测已经做到 mAP 不错但业务方要求精确的船向角、要求输出 Rotated Box 供后续舰船识别模块使用那就要转向旋转框检测了业界常用库是mmrotate。舰船目标细长旋转框比水平框更贴合船体尤其当船斜着停靠时水平框会包含大量水面背景旋框能显著降低前后景混淆。不过切到旋转框是一次不小的迁移成本。标注格式从cx cy w h变成cx cy w h angle数据增强管道里旋转、裁剪、拼接的逻辑都要重写原来的水平框验证脚本也不能直接用。我的建议是先用水平框验证数据质量和模型量级确有必要再迁移旋转框不要一上来就上旋转框。5.4 开放词汇检测用得上吗什么时候才值得尝试开放词汇目标检测近年很热但被误用的情况也多。它适合的是「目标类别不确定、需要用文本弹出来」的场景而舰船数据集通常类别空间封闭——就是船、船型、港口设施这几类标签不稀缺远没到需要开放词汇兜底的地步。如果数据里船的形态差异极大渔船、货轮、航母混在同一批用开放词汇模型先给粗标签、再做人工清洗是可行的组合拳但作为最终检测器它目前的速度和部署成本都比不过 YOLO 系。这块属于「先进但别乱上」的范畴跑通基线之后有余力再玩。6. 大图切片推理与结果可视化验证一个能直接抄的脚本思路训练结束后真正要面对的是模型对单张 1024 切片效果不错但业务方要给的是整张大图的检测结果。我建议写一个切片推理脚本流程是读大图 → 按训练时的 tile/stride 切块 → 每块独立推理 → 把框坐标加回偏移 → 对重叠区域做 NMS 去重 → 画到原图上。import cv2 import numpy as np from ultralytics import YOLO model YOLO(runs_ship/ship_yolov8n/weights/best.pt) def slice_predict(img, tile1024, stride512, conf0.3): h, w img.shape[:2] all_boxes [] # (x1, y1, x2, y2, score, cls) for y in range(0, h, stride): for x in range(0, w, stride): y2 min(y tile, h) x2 min(x tile, w) tile_img img[y:y2, x:x2] results model(tile_img, confconf, imgsz1024, verboseFalse) for r in results: for box in r.boxes.data.cpu().numpy(): tx1, ty1, tx2, ty2, score, cls box all_boxes.append([tx1 x, ty1 y, tx2 x, ty2 y, score, cls]) return all_boxesNMS 合并这步别省。切片重叠导致同一条船可能被检测两三次直接画框会显得一塌糊涂。常见做法是写一个简单的 NMS 函数按置信度排序后剔除 IoU 大于 0.5 的冗余框。验证方面我最后的习惯是每轮实验结束从 val 场景里随机抽 50 张原图用上面的脚本推理并保存可视化结果然后一张一张人眼扫。指标会骗人人眼不会。尤其要看的场景是港口密集停靠区、海面有白色浪花时、大型货船和码头融为一体时。这三个场景下模型不崩基本就可以交付了。我自己最开始的教训是拿一份标注精美的舰船数据集直接默认参数跑了一版mAP50 有 0.85心里觉得稳了结果放到真实影像上虚警多到没法看。后来才发现训练集里全是干净海面负样本缺位严重。从那以后我拿到任何遥感数据集都会先做负样本采样、再跑训练这个习惯帮后面省了无数返工时间。希望你也能少走这一步弯路。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站