简介面向YOLO目标检测入门与遥感舰船识别应用这份数据集包含5000张真实场景卫星遥感船舶影像场景丰富且使用LabelImg标注质量较高标签已按VOC(xml)、COCO(json)、YOLO(txt)三种格式分别存放在独立文件夹可直接用于YOLO系列模型训练与验证。压缩包共2000个文件以xml标注文件为主另含3个Python数据集划分脚本、5个txt列表文件及6个HTML环境搭建与训练教程包体约418.47MB。脚本可根据需要灵活划分训练集、验证集、测试集教程覆盖Linux与Windows双系统下的YOLO环境配置和自定义数据集训练修改有助于从数据整理到模型训练全流程落地。目前已有651人学习配套博文还提供数据集详情展示与更多同类资源入口。1. 卫星遥感舰船检测5000 张图的数据包先帮你解决的是「格式内战」做卫星遥感舰船检测第一步往往不是模型选型而是被数据集格式磨掉半条命。你费劲找到一批船图解压发现有的是 XML、有的是 JSON还有一批.txt而模型框架只认其中一种你只能连夜写转换脚本。标题里这个 YOLO 卫星遥感舰船检测数据集把 5000 张图片、VOC/COCO/YOLO 三种格式标签、划分脚本和训练教程打包到一起意味着你在动手调参之前数据侧最脏最乱的活已经被做掉了。它适合两类人一类是想入门 YOLO 的初学者需要一份规范遥感数据集来搞懂标签到底长什么样另一类是要快速验证舰船检测方案、不想把时间花在格式转换上的工程师。这套「数据集加脚本加教程」的组合本质是一条可复现的预处理链后面几乎所有的坑都出在我们怎么用它。2. VOC、COCO、YOLO 三种标签格式同一个框的三种说法与选型边界2.1 文件组织方式完全不同XML 逐图、JSON 聚合、TXT 归一化拿到数据集后先别急着解压训练。你要能一眼看出三种格式的脾气。VOC 格式Pascal VOC 系是每一张图片对应一个同名 XML 文件Annotations/000001.xml对应JPEGImages/000001.jpg。标注内容落在object节点里bndbox存 xmin、ymin、xmax、ymax坐标是像素绝对坐标还带着difficult、truncated这类附加属性。LabelImg 这类老牌标注工具默认输出的就是这种结构。COCO 格式是聚合成一个 JSON 文件images、annotations、categories三个数组把整批数据塞进同一个文件。annotations里每条记录有image_id、category_id、bbox和area其中 bbox 的写法是[x, y, width, height]坐标原点在左上角单位还是像素。Detectron2、MMDetection 以及不少在线标注平台都吃这一套。YOLO 格式我默认指 Ultralytics YOLOv5/v8 用的 txt 格式一张图对应一个同名.txt每一行是class x_center y_center width height。四个坐标值都被图像宽高归一化到 0 到 1 之间。这种格式与图像分辨率无关模型加载时直接把归一化坐标映射回当前尺寸训练效率最高但代价是丢失了 difficult、truncated 这些附加语义。维度VOC XMLCOCO JSONYOLO TXT存储单位逐图一个 XML全量一个 JSON逐图一个 TXT坐标表述绝对 xmin/ymin/xmax/ymax左上角 x,y 加宽高中心 x,y 加宽高归一化附加属性difficult/truncated 等area/iscrowd/关键点基本只有类别和框典型配套工具LabelImg、roboflow 导出Detectron2、MMDetection、标注平台Ultralytics YOLOv5/v8改动代价逐文件改动改一处需重写整个 JSON逐文件改动但格式极简三种格式的核心矛盾在于坐标的可读性。遥感舰船检测里目标往往只占几十个像素一旦坐标语义写错模型不会立刻报错只是训练完 mAP 塌在零点几很难排查。2.2 为什么要三种格式都备齐模型框架、标注工具与增强库的胃口不一样我见过不少项目数据集只有一种格式换个框架就得折腾半天。这个数据集把三种格式备齐真正原因是「生态隔离」。你要用 Ultralytics YOLO 训练开箱即吃 TXT想换成 Faster R-CNN 或 DETR多半走 COCO 接口想跑一些老牌遥感检测验证脚本它可能只认 VOC。没有三种标签每次换框架都要重写一遍转换脚本而这类脚本最容易在坐标语义上埋雷。数据增强库也对格式有隐含要求。albumentations 读 bbox 时你能传pascal_voc、coco或yolo三种格式但写增强管线时最容易出错的不是增强参数而是你忘记把格式参数改过来。比如你用 COCO 的 xywh 坐标直接喂给RandomSizedBBoxSafeCrop它按 VOC 的 xmin/ymin/xmax/ymax 解析增强出来的图框全偏。手头有三份现成标签对照着调试增强管线效率会高很多。2.3 遥感舰船的特殊性小目标、近岸密集与类别不平衡遥感舰船检测和通用目标检测在数据分布上有明显差异。船在几百甚至几千像素宽的大图里经常只有几十像素属于典型小目标近岸港口里船只会密集排列相互遮挡少但挨得很近这对 NMS 后处理是一个考验远海背景干净近岸背景却有码头、陆地、海浪纹理干扰。还有一个常被忽略的点大影像通过滑窗切成小切片后不同切片的目标数量波动很大。有的切片十几条船有的全空。这种类别不平衡会在划分训练集时被放大。所以拿到三种格式后我建议先做一次类别频次统计确认三种格式下每个类别的总数一致。如果对不上一定是某个转换环节把目标漏掉了这时候回头查脚本别急着开训练。3. 三种格式互转VOC 转 YOLO、YOLO 转 COCO 的最小脚本与四个边界坑3.1 互转的本质把同一物理框在三种坐标语义间平移互转不复杂核心是五个换算公式。VOC 转 YOLO已知图宽 W 和图高 Hx_center (xmin xmax) / 2 / Wy_center (ymin ymax) / 2 / Hw (xmax - xmin) / Wh (ymax - ymin) / H。YOLO 转 COCO先反推左上角x (x_center - w / 2) * Wy (y_center - h / 2) * H然后 bbox [x, y, w * W, h * H]。COCO 转 VOC 更直接xmin xymin yxmax x wymax y h。难点从来不是公式而是你拿到的数据未必像公式假设得那么干净。我下面给两个最常用的最小脚本一个是 VOC 转 YOLO一个是 YOLO 转 COCO按这个逻辑走一遍再去做划分和训练。3.2 先把 VOC XML 转成 YOLO TXT这是最小的自检脚本# voc2yolo.py # 作用把 Pascal VOC 的 XML 标注转成 YOLO 训练用的 TXT 标注 import xml.etree.ElementTree as ET import os class_names [ship] # 必须与训练时 data.yaml 的 names 顺序一致 def voc2yolo(xml_file, img_width, img_height): tree ET.parse(xml_file) root tree.getroot() lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_names: continue # 跳过不在类别列表里的目标 box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 防止标注越界clip 后转归一化 xmin max(0, min(xmin, img_width)) xmax max(0, min(xmax, img_width)) ymin max(0, min(ymin, img_height)) ymax max(0, min(ymax, img_height)) x_center ((xmin xmax) / 2) / img_width y_center ((ymin ymax) / 2) / img_height w (xmax - xmin) / img_width h (ymax - ymin) / img_height cls_id class_names.index(name) lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) return lines # 用法示例读取 XML 时宽高从 XML 的 size 节点拿而不是用 OpenCV 重读 # tree.getroot().find(size/width) 与 find(size/height)这段代码有三个关键点。一是class_names的顺序转换脚本里排第 0 的类别训练配置里也必须排第 0顺序一旦错位模型会把船训练成背景loss 还不一定高。二是分母img_width和img_height必须从 XML 的size节点读因为 VOC 标注可能基于原图而你手头的图片可能已经被压缩过两者宽高不一致会导致坐标整体偏移。三是我做了一次 clip把越界的 xmin/xmax/ymin/ymax 拉回图像范围内这一步是为了防止后续 YOLO 训练时出现大量 un-normalized 警告。3.3 再把 YOLO TXT 转成 COCO JSON注意 bbox 的平移# yolo2coco.py # 作用把 YOLO 的 TXT 标注转成 COCO 格式的 annotations import os, json from PIL import Image def yolo2coco(txt_path, image_path, image_id, category_names): img Image.open(image_path) img_w, img_h img.size # PIL 的 size 返回 (宽, 高) anns [] with open(txt_path, r, encodingutf-8) as f: for i, line in enumerate(f): parts line.strip().split() if len(parts) ! 5: continue cls_id, cx, cy, w, h parts cls_id int(cls_id) cx, cy, w, h float(cx), float(cy), float(w), float(h) # YOLO 给的是中心点加宽高的归一化值 x (cx - w / 2) * img_w y (cy - h / 2) * img_h box_w w * img_w box_h h * img_h anns.append({ id: i, image_id: image_id, category_id: cls_id, bbox: [x, y, box_w, box_h], area: box_w * box_h, iscrowd: 0 }) return anns # 说明COCO 的 bbox 是左上角 x,y 加宽高不是 YOLO 的中心点宽高不能直接搬这里最容易错的点是 bbox 的平移。YOLO 那行数据是x_center y_center width heightCOCO 要的是x y width height其中 x、y 是左上角坐标。不少转换脚本直接把 cx 当 x 用训练时框整体往右下偏目标被框切掉一块。另外area字段不要漏一些数据增强算子在计算面积比例时会读它漏了或者填 0跑增强时会报奇怪的分母错误。iscrowd统一填 0舰船检测场景不需要人群聚合标注。3.4 转换后的四个边界坑转换脚本写完先别急着全量跑我一般先抽 3 到 5 张图做对照。以下四个坑是我实际踩过的现象、原因、解决一次说清。坑一是坐标系来源不一致。有的 XML 里存的是原始大图的坐标而你训练用的图片是从大图裁剪出来的切片。如果直接把原始 XML 拿去转换框的坐标会整体超出切片范围clip 之后所有目标都贴在边界上。解决方法是先确认标签坐标系和图片是否同源切片数据的标签必须同步裁剪。坑二是宽高写反。OpenCV 读回来是 height, width而 XML 的size节点里通常先写 width 再写 height。赋值时一旦写反归一化分母错位视觉上框全部变形。这个错在代码里几乎看不出来只有可视化标注时才会暴露。坑三是类别编号漂移。VOC 里类别名是字符串YOLO 里类别名变成数字这个数字完全依赖class_names的排序。如果你在 VOC 转 YOLO 时用的是[ship, background]而训练时 data.yaml 里写的是[background, ship]那模型看到的船其实是背景。转换前先统一类别清单写进一个公共模块。坑四是空标注文件不能删。遥感切片经常有全背景图也就是一张图里没有任何船。有的转换脚本会把空 TXT 当成垃圾删掉但验证集里保留空标注图片模型推理时会输出误检框这样才能测模型的虚警率。删掉它们等价于只拿有目标的简单样本做评估mAP 虚高部署后立刻打回原形。4. 划分脚本训练集/验证集/测试集怎么切才不会让 mAP 虚高4.1 按文件随机切会让遥感数据「同源泄漏」很多数据集划分脚本是按文件随机打乱但遥感数据不能这么切。原因在于大图滑窗切片后相邻切片高度相似同一艘船可能横跨两张切片。如果按文件随机划分这艘船会同时出现在训练集和验证集里。训练时模型见过它验证时它又来测一遍mAP 高得离谱一部署到全新的遥感图上就崩。这个问题的学名叫同源泄漏。现象很典型训练 loss 正常收敛验证 mAP 漂亮到 0.95 以上但推理新图时漏检严重。如果你用的划分脚本是按文件名随机 shuffle而不是按原始大图分组那恭喜你踩中了。解决方法是按「源影像」分组划分同一张大图滑出来的所有切片必须进同一个集合不允许跨集合。4.2 划分脚本的三个设计选择比例、随机种子、分层抽样划分脚本值得单独给一份是因为它有三个设计选择直接决定训练效果能不能复现。第一个是比例。5000 张图不算多我一般用 8:1:1训练 4000、验证 500、测试 500。验证集 500 张足够算出波动不大的 mAP。如果你要跟论文比较可以用 7:2:1但对小数据集来说验证集占比太大反而让训练数据不够。第二个是随机种子。脚本里必须写死random.seed(42)这类固定值不然每次划分结果都不一样训练配置相同但成绩忽高忽低后面排查问题都无从下手。第三个是分层抽样。遥感舰船的类别分布天然不均衡有的近岸场景切片里船很多远海场景切片几乎全空。简单随机划分可能把船多的切片全部分到训练集验证集全空mAP 虚高。分层抽样的思路是保证每个集合里类别分布接近整体分布。# split_by_source.py # 作用按源影像分组划分 train / val / test避免同源泄漏 import os, random from collections import defaultdict random.seed(42) # 固定种子保证可复现 image_dir dataset/images label_dir dataset/labels source_prefix_len 10 # 按文件名前 N 位作为源影像标识按实际命名调整 # 1. 每个源影像下的图片分到同一组 groups defaultdict(list) for f in sorted(os.listdir(image_dir)): if not f.endswith(.jpg): continue source_id f[:source_prefix_len] # 例如 scene001_0001.jpg - scene001_ groups[source_id].append(f) # 2. 在源影像级别打乱再按 8:1:1 切分 keys list(groups.keys()) random.shuffle(keys) n_train int(len(keys) * 0.8) n_val int(len(keys) * 0.9) train_keys keys[:n_train] val_keys keys[n_train:n_val] test_keys keys[n_val:] # 3. 输出划分后的文件清单供 Ultralytics 直接使用 def write_split(keys, split_name, image_dir, label_dir): with open(f{split_name}.txt, w, encodingutf-8) as f: for k in keys: for img in groups[k]: f.write(f{image_dir}/{img}\n) write_split(train_keys, train, image_dir, label_dir) write_split(val_keys, val, image_dir, label_dir) write_split(test_keys, test, image_dir, label_dir)这段脚本的关键是第 1 步。source_prefix_len按数据集实际命名规则调整如果你的文件名是scene001_0001.jpg取前 10 个字符就能把同一场景的切片归到一组。如果你只有一个前缀无法区分源可以用正则把数字序号去掉再分组。注意这里只写了图片清单标签文件是同步的Ultralytics 训练时按图片路径自动找同名 txt所以清单里写图片路径就够。4.3 划分后必须做的三项校验划分脚本跑完不要直接开训。我习惯花五分钟做三项校验很多训练翻车都是在这时候能被拦下来。校验一集合无重叠。把 train、val、test 三个清单里的文件名取交集如果交集不为空说明源分组逻辑有 bug。常见原因是同一个源影像前缀匹配到了不同图片。校验二类别分布一致。分别统计三个集合里每类标注框的数量计算占比。验证集里船的占比应该和训练集接近一旦验证集全是空图训练就白做了。校验三标签可读。写一个小循环遍历验证集清单逐个确认对应 txt 文件存在且能按空格拆分出 5 个字段。这一步能提前发现空标签、格式错误、路径大小写不一致等隐蔽问题。校验完再进训练能省下不少调试时间。5. YOLO 训练教程与常见问题排查data.yaml、损失函数与四个高发坑5.1 训练前的目录与 data.yaml路径写错是第一个高发坑数据划分完成后目录结构应该长这样dataset/ ├─ train/ │ ├─ images/ │ └─ labels/ ├─ val/ │ ├─ images/ │ └─ labels/ ├─ test/ │ ├─ images/ │ └─ labels/ └─ data.yamlUltralytics YOLOv5/v8 约定标签目录与 images 同级且名字固定为 labels。它不会在 data.yaml 里找 labels 路径而是自动推断。所以你在移动数据集时train 下面的 images 和 labels 两个目录必须一起搬分开打包就会导致训练时找不到标签。data.yaml 是训练入口又是一个容易踩坑的地方path: ./dataset train: train/images val: val/images test: test/images nc: 1 names: [ship]path指向数据集根目录train、val都是相对这个根目录的路径。注意nc和names必须和转换脚本里的class_names完全一致。这里写[ship]是类别编号 0如果 VOC 转换时把 ship 排在第 1 位就要改成[none, ship]。我遇到过有人把 nc 写成 2names 只写一个 ship训练时直接报 class index 越界。5.2 跑通一次训练PyCharm、AGX Orin 与命令行训练环境有两种常见搭法。一种是在本地 PyCharm 里用解释器跑适合刚入门 YOLO 的开发者调试代码方便但 5000 张图在 CPU 上跑会等到怀疑人生另一种是在 AGX Orin 这类边缘设备上搭环境算力有限但可以直接验证后续部署。无论哪种先装依赖再训练命令是一样的pip install ultralytics yolo detect train dataship.yaml modelyolov8s.pt epochs120 imgsz640 batch16 device0 patience20这里modelyolov8s.pt是预训练权重建议从 s 版本起步v8s 对小目标场景速度与精度平衡较好。epochs120是遥感舰船场景的保守值收敛速度比 COCO 慢用默认 100 有时不够。imgsz640对船这种小目标来说是下限显存容量允许就提到 1280。batch16需要约 12GB 显存如果只有 4GB降 batch 而不是降 imgsz。patience20表示验证指标连续 20 轮不提升就早停省时间。关于 YOLO 损失函数Ultralytics v8 默认组合是分类损失、定位损失和置信度损失定位部分用的是 CIoU。对远海稀疏场景默认够用对近岸密集港口CIoU 对重叠框的优化不够常见做法是把回归损失换成 EIoU 或引入辅助分支。如果你用的是 v5 系还可以调box损失权重默认 0.05密集场景可以试 0.1。5.3 常见问题排查四个高发坑的现象、原因与修复训练过程不会一帆风顺我见过的四个高发坑按频率排序如下。坑一训练 loss 降不下去或者验证 mAP 一直是 0。先查标签文件确认 labels 目录里每个 txt 都存在且非空。再查类别 id打开一个 txt 文件第一列数字必须小于 data.yaml 里的 nc。如果类别 id 是 5nc 写 1训练时模型会把你所有目标当背景跳过。坑二终端刷出一片 un-normalized 警告。原因是转换时的归一化坐标超出了 0 到 1 范围个别框的右下角越过图像边界。解决方法是回到第 3 章的转换脚本加上 clip 逻辑然后重新生成标签。警告本身不致命但留着会让训练时数据增强阶段不断裁剪和丢弃框边际效应积累下来 mAP 会掉几个点。坑三验证集 mAP 很高但实际推理几乎检测不到船。先确认你没有用last.pt代替best.pt。Ultrallytics 训练结束会同时保存两个权重last.pt是最后 epoch 的模型可能已经过拟合best.pt是验证集上表现最好的模型推理用后者。再确认划分脚本没有同源泄漏回到第 4 章检查验证集是否混入了与训练集同场景的切片。这两个原因经常同时出现。坑四显存不足直接崩。表现为 CUDA out of memory。解决顺序是先把batch降到 8再把imgsz从 640 降到 512。在 AGX Orin 这类边缘设备上还要考虑开 AMP 混合精度v8 默认开启v5 需要在训练命令里加--amp。6. 滑窗推理与最后一公里5000 张舰船图训练出的模型怎么用才不翻车5000 张切片训练出的模型验证集 mAP 好看只代表训练顺利真正考验在推理阶段。遥感影像通常是几千乘几千的大图直接塞给 YOLO 会显存溢出即使不溢出小目标经过多次下采样后特征也丢得差不多了。我的习惯是训练和推理的分辨率保持一致训练用 640推理也用 640对大图做滑窗然后合并检测结果。# slide_infer.py # 作用对大图滑窗推理把窗口内的框映射回原图坐标 import cv2 from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) img cv2.imread(big_scene.jpg) H, W img.shape[:2] win 640 # 与训练 imgsz 一致 stride 512 # 重叠 128避免船体被窗口边缘切断 detections [] for y in range(0, H - win 1, stride): for x in range(0, W - win 1, stride): crop img[y:ywin, x:xwin] results model(crop, conf0.25, imgsz640, verboseFalse) for box in results[0].boxes: cx, cy, w, h box.xywh[0].tolist() detections.append((x cx - w/2, y cy - h/2, w, h, float(box.conf[0]), int(box.cls[0]))) # 合并阶段需要用 NMS 把重叠窗口里的重复框去掉可用 cv2.dnn.NMSBoxes滑窗有两个细节值得注意。第一是stride必须小于win我习惯留出 10% 到 20% 的重叠。船体横跨窗口边界时如果不重叠目标会被切成两半两个窗口各检测到半条船NMS 后结果也还是不完整。第二是置信度阈值conf0.25不要提太高遥感小目标经过裁剪后置信度天然偏低先用 0.25 收集候选再用 NMS 去重。直接调 conf 到 0.5漏检率会明显上升。验证阶段我还会把模型导出成 ONNX在边缘设备上跑一遍耗时测试。做法是yolo export modelbest.pt formatonnx imgsz640导出的动态维度版本可以接受不同输入尺寸。这一步主要是验证部署链路是否能通省的训练完才发现推理框架不支持某些算子。最后说一个踩过多次的教训永远不要在划分脚本上偷懒。我早期图省事用随机 shuffle 切了一次数据训练出来的模型在自家的测试视频上表现完美换到一新片区的遥感图直接漏掉一大半船只。后来按源影像重新划分mAP 从虚高的 0.98 回到真实的 0.87才明白数据划分才是决定模型泛化能力的隐藏开关。从那以后我拿到任何数据集的第一件事是检查划分逻辑数据格式和模型结构反而是其次。这份数据集把划分脚本和训练教程都给了但给得好不好你还是要按第 4 章的校验方法过一遍数据是自己的锅也只能自己背。希望帮到你。本文还有配套的精品资源点击获取