☰
潜艇卫星图目标检测数据集实战:从VOC标注转换到YOLOv8训练与切片推理
2026/10/10 15:08:11 网站建设 项目流程

简介:这份资源是面向人工智能与计算机视觉方向研究者、算法工程师及学生的目标检测数据集,聚焦潜艇卫星图像这一特定场景,可用于训练和验证水下目标识别模型,适用于军事侦察、海洋安全与环境监测等应用方向。压缩包共2001个文件,包含1000张1024×1024像素的jpg卫星图像、1000个xml格式标注文件以及1个info.txt说明文档,整体约345.83MB,标注以边界框形式记录潜艇位置与类别,可直接对接YOLO、Faster R-CNN等主流检测框架。目前已有1686人学习下载。数据集预先完成标签处理,省去自行标注成本,读者可据此完成图像预处理、模型训练、验证与测试全流程,并对比不同算法在真实卫星图上的检测精度与实时性,是研究水下目标识别与目标检测技术的实用实验素材。

1. 潜艇卫星图目标检测数据集:从1000张1024图里挖出可训练样本

拿到一个标注好的潜艇卫星图数据集,第一反应往往不是兴奋,而是怀疑——卫星图里的潜艇目标到底长什么样,1000张够不够训一个能收敛的检测器,标注格式能不能直接喂给 YOLO 或 Faster R-CNN。这个数据集的核心价值在于它把「遥感目标检测」里最难搞的一环——水下/海面小目标标注——提前做完了。每张图 1024×1024 像素,目标类别只有潜艇一类,属于典型的单类检测任务,适合用来验证模型在低对比度、复杂海况背景下的定位能力。它适合三类人:想跑通遥感检测全流程的新手、需要单类小目标 baseline 的算法工程师、以及做海洋监控方向课题的研究生。压缩包里 images 存原图、annotations 存标注、info.txt 给元信息,结构清晰,不用自己写爬虫和标注工具就能直接进入训练环节。

2. 拆包与标注格式对齐:把 annotations 转成 YOLO 能吃的 txt

2.1 先看清目录结构和标注格式

解压后典型结构是 images/、annotations/、info.txt 三部分。annotations 里常见的是 Pascal VOC 风格的 XML,每张图对应一个同名 xml,里面记录了 bndbox 的 xmin、ymin、xmax、ymax 和类别名。也有部分数据集直接给 COCO 格式的 json,把所有标注塞进一个文件。先别急着写训练脚本,用几行 Python 把格式摸清楚,否则后面转换脚本写错了,模型训到一半 loss 不降,回头查标注能查到崩溃。

import os, xml.etree.ElementTree as ET ann_dir = "annotations" files = [f for f in os.listdir(ann_dir) if f.endswith(".xml")] print("标注文件数:", len(files)) # 抽一个样本看结构 sample = os.path.join(ann_dir, files[0]) tree = ET.parse(sample) root = tree.getroot() for obj in root.findall("object"): name = obj.find("name").text bbox = obj.find("bndbox") xmin = int(bbox.find("xmin").text) ymin = int(bbox.find("ymin").text) xmax = int(bbox.find("xmax").text) ymax = int(bbox.find("ymax").text) print(name, xmin, ymin, xmax, ymax)

这段代码做两件事:统计标注文件数量,确认是否和 images 里的图片一一对应;打印第一个样本的类别和边界框坐标。参数上注意 xmin/ymin 是左上角,xmax/ymax 是右下角,VOC 格式用的是绝对像素坐标。如果打印出来发现某个框的 xmax 小于 xmin,说明标注本身有问题,这种样本要么修要么丢,别硬训。

2.2 转成 YOLO 需要的归一化 txt

YOLO 系列吃的是每张图一个 txt,每行class_id x_center y_center width height,全部归一化到 0~1。单类任务 class_id 固定为 0。转换时最容易翻车的地方是坐标越界和图片尺寸不一致——虽然这个数据集标称 1024×1024,但最好还是从图片实际尺寸读,别写死。

import os from PIL import Image import xml.etree.ElementTree as ET img_dir = "images" ann_dir = "annotations" out_dir = "labels" os.makedirs(out_dir, exist_ok=True) for xml_file in os.listdir(ann_dir): if not xml_file.endswith(".xml"): continue stem = os.path.splitext(xml_file)[0] img_path = os.path.join(img_dir, stem + ".jpg") if not os.path.exists(img_path): print("缺图:", stem) continue w, h = Image.open(img_path).size tree = ET.parse(os.path.join(ann_dir, xml_file)) root = tree.getroot() lines = [] for obj in root.findall("object"): bbox = obj.find("bndbox") xmin = float(bbox.find("xmin").text) ymin = float(bbox.find("ymin").text) xmax = float(bbox.find("xmax").text) ymax = float(bbox.find("ymax").text) # 裁剪到图像范围内,防止越界 xmin, xmax = max(0, xmin), min(w, xmax) ymin, ymax = max(0, ymin), min(h, ymax) xc = (xmin + xmax) / 2.0 / w yc = (ymin + ymax) / 2.0 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"0 {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}") with open(os.path.join(out_dir, stem + ".txt"), "w") as f: f.write("\n".join(lines))

逻辑说明:先读图片真实宽高,再把 VOC 的绝对坐标转成中心点加宽高的归一化值。参数上保留 6 位小数足够,YOLO 官方也是这个精度。裁剪那一步是血泪经验,有些标注框会超出图像边界一两个像素,不裁的话归一化后会出现负值或大于 1 的值,训练时直接报错。转换完抽查几个 txt,确认每行 5 个字段、数值在 0~1 之间。

2.3 划分训练集和验证集

1000 张图按 8:2 切,训练 800 验证 200。别用随机切分后忘了同步移动图片和标签,常见做法是生成两个 txt 清单文件,训练脚本按清单读。

import random, os stems = [os.path.splitext(f)[0] for f in os.listdir("labels") if f.endswith(".txt")] random.seed(42) random.shuffle(stems) split = int(len(stems) * 0.8) train, val = stems[:split], stems[split:] for name, subset in [("train", train), ("val", val)]: with open(f"{name}.txt", "w") as f: for s in subset: f.write(f"images/{s}.jpg\n") print(name, len(subset))

固定随机种子保证可复现,清单里写相对路径,方便后面换机器不用改绝对路径。到这一步数据侧就齐了,图片、标签、划分清单三样东西对齐,可以进模型。

3. 用 YOLOv8 跑通单类检测:配置、训练与指标解读

3.1 写数据集配置文件

YOLOv8 用 yaml 描述数据路径和类别。单类任务 nc 填 1,names 里写 submarine。路径用绝对路径最稳,相对路径在不同工作目录下容易找不到文件。

# submarine.yaml path: /data/submarine train: train.txt val: val.txt nc: 1 names: 0: submarine

path 是数据集根目录,train 和 val 指向刚才生成的清单文件。如果清单里写的是相对路径,YOLO 会基于 path 去拼,所以清单里images/xxx.jpg这种写法要保证 path 下确实有 images 目录。

3.2 启动训练与关键参数

yolo detect train \ data=submarine.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=1024 \ batch=8 \ lr0=0.01 \ patience=20 \ project=runs/submarine \ name=exp1

参数逐个说:model 选 yolov8n 是最小的 nano 版,显存占用低,适合先跑通流程;imgsz 设 1024 是因为原图就是 1024,缩小会丢小目标细节;batch 8 在 8G 显存上比较稳,显存大可以加到 16;lr0 初始学习率 0.01 是 YOLO 默认值,单类任务数据量不大时别调太高;patience 20 表示 20 轮指标不涨就早停,省时间。训练过程中重点看 box_loss 和 mAP50,前 10 轮 loss 下降慢是正常的,卫星图目标对比度低,模型需要多轮才能学到特征。

3.3 看指标判断模型是否可用

训练完在 runs/submarine/exp1 下有 results.csv 和权重文件。核心看三个指标:mAP50、mAP50-95、precision 和 recall。单类潜艇检测,mAP50 能到 0.7 以上算及格,0.85 以上算不错。如果 mAP50 高但 recall 低,说明漏检多,可能是目标太小或者标注框太紧;如果 precision 低,说明误检多,海面波浪、船只尾迹容易被当成潜艇。这时候别急着加数据,先把验证集的预测结果可视化出来看,YOLO 自带 val 模式可以存预测图。

yolo detect val \ model=runs/submarine/exp1/weights/best.pt \ data=submarine.yaml \ save_txt=True \ save_conf=True

save_txt 会把预测框存成 txt,save_conf 带上置信度,方便和真值对比分析漏检和误检的具体位置。

4. 避坑与排查:标注、显存、过拟合的五个真实翻车点

4.1 现象:训练 loss 一直不降,mAP 接近 0

原因:标注格式转错了,最常见的是 VOC 的 xmin/ymin 和 YOLO 的中心点坐标搞混,或者忘了归一化。另一个可能是类别 id 从 1 开始写,YOLO 要求从 0 开始。解决:抽一张图把转换后的 txt 画回原图上看,框的位置对不对一眼就能看出来。写个可视化脚本,用 PIL 画矩形,比盯着数字猜快得多。

4.2 现象:训练到一半报 CUDA out of memory

原因:imgsz 1024 加 batch 8 在某些显卡上会爆显存,尤其是开了 AMP 混合精度但模型没适配好。解决:先把 batch 降到 4,或者用yolo detect train ... amp=False关掉混合精度。如果还爆,把 imgsz 降到 768,但要注意小目标可能因此丢检。另一个隐藏原因是 dataloader 的 workers 设太大,内存被占满,设 workers=4 试试。

4.3 现象:验证集 mAP 高,但拿新图预测全是误检

原因:过拟合。1000 张图对检测任务来说偏少,模型可能记住了训练集的背景纹理。解决:开数据增强,YOLO 默认有 mosaic 和 flip,可以再加degrees=10、translate=0.1、scale=0.5做几何增强。另外早停 patience 别设太大,20 到 30 轮足够。如果还不行,考虑用预训练权重更强的模型,比如 yolov8s 或 yolov8m,小模型容量有限,容易欠拟合或过拟合两头不讨好。

4.4 现象:某些图片标注框数量为 0,训练时报空标签警告

原因:这些图是负样本,即没有潜艇的纯背景图。YOLO 对空标签文件是支持的,但要求文件存在且为空。如果转换脚本遇到没有 object 的 xml 直接跳过不生成 txt,YOLO 会认为图片没有对应标签而报错。解决:转换时对没有目标的图也生成一个空 txt 文件,确保图片和标签一一对应。

4.5 现象:预测框重叠严重,同一个目标出好几个框

原因:NMS 的 iou 阈值设太高,或者模型置信度阈值太低。解决:推理时调conf=0.25和iou=0.45,这两个是 YOLO 默认值,如果误检多就把 conf 提到 0.4 以上。另外检查标注里有没有同一个目标被标了多个框,这种脏数据会让模型学出重复检测的习惯。

5. 进阶技巧:用切片推理和置信度分析榨干小目标召回

卫星图里的潜艇目标往往只占几十个像素,直接整图推理时下采样到 1024 已经丢了不少细节。一个实用技巧是切片推理(SAHI 思路),把 1024 的图切成 4 块 512 的,分别推理再合并框。这样等效于把输入分辨率翻倍,小目标召回能明显提升。实现上不用装额外库,自己写个滑动窗口切图再调 YOLO 预测就行。

from PIL import Image from ultralytics import YOLO model = YOLO("runs/submarine/exp1/weights/best.pt") img = Image.open("images/submarine-001-0452.jpg") w, h = img.size tile = 512 overlap = 64 boxes = [] for y in range(0, h, tile - overlap): for x in range(0, w, tile - overlap): crop = img.crop((x, y, min(x + tile, w), min(y + tile, h))) results = model.predict(crop, conf=0.3, verbose=False) for r in results: for b in r.boxes: x1, y1, x2, y2 = b.xyxy[0].tolist() boxes.append([x1 + x, y1 + y, x2 + x, y2 + y, float(b.conf[0])]) # 简单 NMS 合并 boxes.sort(key=lambda b: b[4], reverse=True) keep = [] while boxes: best = boxes.pop(0) keep.append(best) boxes = [b for b in boxes if iou(best, b) < 0.5] print("合并后框数:", len(keep))

这段代码的核心参数是 tile 和 overlap。tile 设 512 是因为潜艇目标通常不超过 100 像素,512 的切片里目标占比够大;overlap 设 64 是为了避免目标正好被切在边界上导致漏检。合并时用 NMS 去重,iou 阈值 0.5 是常用值。代价是推理时间变成原来的 4 倍左右,但召回提升对小目标检测是值得的。另一个技巧是分析预测置信度分布,把 conf 在 0.3 到 0.5 之间的框单独拿出来看,这些是模型的「犹豫区」,往往对应难样本,可以挑出来人工复核后加进训练集,做一轮 hard negative mining。我一般会在第一版模型跑完后,把验证集里漏检的图挑出来,看看是标注问题还是模型能力问题,再决定是补数据还是调模型。从那以后我每次拿到新数据集,都强制先跑一遍标注可视化,确认框画对了再进训练,这个习惯帮我省了至少三次通宵排查。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询