简介:针对YOLO目标检测框架的舰船检测数据集,精选自PASCAL VOC 2012,仅保留与“boat”相关的549张JPEG图像,并配套549个XML与550个TXT标注文件,共1648个文件,压缩包约56.31MB。文件类型各有用途:TXT按YOLO格式记录类ID与归一化坐标,XML遵循VOC标准描述边界框与类别,便于接入不同检测框架。资源面向需要训练专用舰船检测模型的算法工程师、学生及研究者,适合作为迁移学习、模型评估与部署验证的基础数据。由于仅包含单一类别,可在训练中提升对舰船的检测精度,也可结合数据增强或MS COCO船类子集扩充样本,增强泛化能力。目前已有2399人学习,热度较高。压缩包内图像与标注一一对应,目录结构清晰,能直接划分训练集和验证集,适合用于YOLOv3、YOLOv4等模型的算法对比与调参实验。
1. 舰船检测数据集里藏着 YOLO 最该重训的一类目标
港口调度、渔政巡航、内河航道监管这些场景里,摄像机拍回来的画面经常是几公里外一条细长船影,或者藏在防波堤前若隐若现的桅杆。这种"又小又长、贴着海天线"的目标,恰好是 YOLO 从 COCO 预训练权重里继承不来的能力——COCO 的 boat 类样本少、角度和尺度都偏规则,直接拿现成权重去推理,漏检率比想象中高。boat_VOCtrainval2012.zip 这类把 VOC 格式舰船图打包好的数据集,价值在于把原始图片、XML 标注和 train/val 划分一次给齐,而 VOC 的框标注质量稳定,转成 YOLO txt 的整个过程完全可自动化。对想从 YOLO 入门又不想零散爬图的工程师,这比从头标几百条船省下大量时间;对熟手来说,重点则是把这个压缩包变成一份可复现、可调优的 YOLO 训练配置。
2. VOCtrainval2012 舰船数据的目录结构与 VOC→YOLO 格式转换
2.1 解包后先认目录:JPEGImages、Annotations、ImageSets/Main
这类 zip 的解包布局基本沿用 Pascal VOC 2012 的原始约定,拿到手先别急着写训练命令,把结构认一遍。常见解压方式:
unzip boat_VOCtrainval2012.zip -d boat_data cd boat_data ls -F # Annotations/ ImageSets/ JPEGImages/解出来会有三个顶层目录:JPEGImages 放原始 JPG,Annotations 放同名 XML,ImageSets/Main 放按类别划分的 train/val 列表。VOC 的命名规则是"文件名不带扩展名",比如 JPEGImages/2012_000123.jpg 对应 Annotations/2012_000123.xml,这个对应关系在转换时靠文件主名维持。
| 目录/文件 | 内容 | 在 YOLO 训练中的角色 |
|---|---|---|
| JPEGImages | 原始图片,JPG 格式 | 作为训练输入图像 |
| Annotations | VOC 格式 XML 标注 | 转换后生成 labels/*.txt |
| ImageSets/Main/boat_train.txt | 训练用文件名列表,每行一个不带后缀的名字 | 决定训练集范围 |
| ImageSets/Main/boat_val.txt | 验证用文件名列表 | 决定验证集与调参基准 |
ImageSets/Main 里最有用的是 boat_train.txt、boat_val.txt。每个文件名占一行,不带 .jpg 后缀。YOLO 训练时并不直接读 XML,它需要的是"图片路径 + 同名 txt 标注",所以后面的转换步骤都以这两个列表为索引。若压缩包里只给了 boat_trainval.txt,就自己按 8:2 再切一次,不要硬凑 YOLO 内置的自动划分。列表行数可以先统计:
wc -l ImageSets/Main/boat_train.txt ImageSets/Main/boat_val.txt2.2 VOC XML 转 YOLO txt 的坐标变换
VOC 用的是绝对像素坐标(xmin, ymin, xmax, ymax),YOLO 数据集要的是归一化中心坐标(class, cx, cy, w, h)。归一化后数值与图像分辨率无关,训练时 YOLO 把输入缩放到 imgsz,这套坐标才能跟着一起缩放。针对船舶数据集,有一个常被忽略的细节:XML 中部分框贴着图像边缘,转换时要先做越界裁剪,否则归一化后 w/h 不为正数,训练时会拉高损失甚至直接丢弃标注。
import xml.etree.ElementTree as ET from pathlib import Path CLASS_MAP = {"boat": 0} def voc_to_yolo(xml_path: Path, out_dir: Path) -> None: root = ET.parse(xml_path).getroot() img_w = float(root.find("size/width").text) img_h = float(root.find("size/height").text) lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in CLASS_MAP: continue # 只保留 boat,其余类别丢弃 box = obj.find("bndbox") x1 = max(0, int(round(float(box.find("xmin").text)))) y1 = max(0, int(round(float(box.find("ymin").text)))) x2 = min(img_w, int(round(float(box.find("xmax").text)))) y2 = min(img_h, int(round(float(box.find("ymax").text)))) if x2 <= x1 or y2 <= y1: continue # 过滤裁剪后为空的框 cx = (x1 + x2) / 2.0 / img_w cy = (y1 + y2) / 2.0 / img_h bw = (x2 - x1) / img_w bh = (y2 - y1) / img_h lines.append(f"{CLASS_MAP[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") if lines: (out_dir / f"{xml_path.stem}.txt").write_text("\n".join(lines))逻辑说明:先取 size 字段拿到真实宽高,这是归一化的分母。坐标统一转 float 再计算,避免整数除法的精度丢失。类别字典只保留 boat,VOC 原图里常混入人、车辆、建筑物,这些框在舰船检测里都属于负样本,转换阶段直接丢弃,否则 data.yaml 的 nc 和类别索引会全部错位。
注意:XML 的 xmax/ymax 表示框右下下标,YOLO 的中心宽高计算公式不需要再减 1,直接按像素跨度相减即可。
2.3 按列表切分数据集并生成 data.yaml
有了转换函数和列表文件,下一步是组装 YOLO 标准数据集目录。常见做法是不动 JPEGImages,单独建 labels_train 和 labels_valid,再用软链接把图片按列表组织好,既省磁盘,后续清洗数据时也只需重跑一次脚本。
from pathlib import Path JPEG = Path("JPEGImages") XML = Path("Annotations") for split, out in [("train", "labels_train"), ("val", "labels_valid")]: out_dir = Path(out) out_dir.mkdir(exist_ok=True) list_file = Path(f"ImageSets/Main/boat_{split}.txt") for line in list_file.read_text().strip().split(): voc_to_yolo(XML / f"{line}.xml", out_dir) # 复用上一节函数接着用 bash 把图片软链到 images_train/images_valid:
mkdir -p images_train images_valid while read name; do ln -sf "$PWD/JPEGImages/$name.jpg" images_train/; done < ImageSets/Main/boat_train.txt while read name; do ln -sf "$PWD/JPEGImages/$name.jpg" images_valid/; done < ImageSets/Main/boat_val.txt目录就绪后在数据集根目录写 data.yaml:
path: /absolute/path/to/boat_data train: images_train val: images_valid nc: 1 names: 0: boatpath 建议直接写绝对路径。Ultralytics 会把 path 与 train/val 拼接成最终路径,相对路径在切换工作目录后经常失效。train/val 指向图片目录,YOLO 会按同名规则自动到 labels_train/labels_valid 里找标注,图片主名必须与 txt 主名一致。
3. 用 YOLO 训练舰船检测模型:环境、最小命令与日志判读
3.1 准备 YOLO 运行环境与版本选型
训练前先把 ultralytics 装好,验证安装可用:
pip install ultralytics yolo detect predict model=yolov8n.pt source=test.jpg版本选型上,不少人会纠结 YOLOv8、YOLO11 到底"第几代了"。对舰船这种百张级小数据集,我优先选 Ultralytics 主线的 YOLOv8 或 YOLO11,原因是两者对数据格式兼容性好,同一份 VOC 转出的 labels 不用改代码;一些新版本的导出链路反而会因为算子兼容性问题在边缘设备上返工。硬件方面,CPU 可以跑完整个训练流程,只是慢;AMD 显卡跑 YOLO 时训练侧没有同等的 CUDA 加速,常见做法是先用小 imgsz 验证流程,确认数据无误后再回到主力显卡上跑完整训练。
3.2 最小训练命令与关键参数
数据转换完成后,一条命令即可启动舰船检测训练:
yolo detect train data=boat.yaml model=yolov8n.pt epochs=200 imgsz=1280 batch=8 patience=30 project=runs_ship name=boat_yolov8n参数说明:
| 参数 | 建议取值 | 理由 |
|---|---|---|
| imgsz | 1280 | 船体细长、桅杆跨度大,640 输入容易把目标压成几个像素 |
| epochs | 200 | 样本量小,模型收敛慢,配合早停不会浪费计算 |
| batch | 8 | 8GB 显存左右够用;显存不足就降到 4 |
| patience | 30 | 验证指标连续 30 轮不升则早停,防止过拟合拖时间 |
| model | yolov8n.pt | 复用 COCO 预训练权重做迁移学习,船类基础特征可继承 |
显存只有 4GB 时把模型换成 yolov8n 并保持 imgsz 1280;显存 12GB 以上可以换 yolov8m,舰船检测的收益主要来自输入尺度而不是模型宽度,小模型加大图是性价比最高的组合。想要跑自己的数据集且从头训练时,把 model 参数换成 yolov8n.yaml 即可,但几百张样本的体量不推荐这么做。
3.3 从损失函数与 results.csv 判读训练状态
训练日志里会持续输出 train/box_loss、train/cls_loss、train/dfl_loss 和验证集的 mAP 指标。YOLO 损失函数中,box 回归用的是 CIoU 加 DFL,对细长目标非常敏感:船身长宽比动辄 4:1,CIoU 会把高宽比一致性计入回归惩罚,所以训练初期 box_loss 下降曲线比 cls_loss 更值得盯。如果 box_loss 下降但验证 mAP 不动,通常不是学习率问题,而是转换后的标注框有错位,回头检查 XML 坐标与图片尺寸是否匹配。
训练结束在 runs_ship/boat_yolov8n/ 下看结果。判断这一版能不能用,标准是看验证集上的 mAP50 与 mAP50-95,两者差距大说明框回归不稳定,模型只做到"找到船"却没做到"框准船"。快速看最优几轮:
cd runs_ship/boat_yolov8n tail -5 results.csv | column -t -s,results.csv 每一行对应一个 epoch,包含 precision(B)、recall(B)、mAP50(B)、mAP50-95(B)。单类数据集里 mAP50 做到 0.7 只是及格线,舰船场景真正要看的是 recall——漏掉一条船的代价远高于多框一次海浪反光。
4. 舰船检测训练参数调优与 VOC 数据带来的三个坑
4.1 增强参数:按船调,别用 COCO 默认硬扛
COCO 上表现良好的数据增强参数,搬到舰船上经常适得其反。海上场景颜色单调,饱和度和色相扰动太强,船体边缘会和灰蓝色背景糊在一起。我一般按下面这组起始值调:
| 参数 | 默认值 | 舰船建议 | 原因 |
|---|---|---|---|
| hsv_h | 0.015 | 0.005 | 大幅改色相会把船体与海水混成一片 |
| hsv_s | 0.7 | 0.4 | 灰蓝船体对饱和度扰动极敏感 |
| translate | 0.1 | 0.2 | 模拟船在画面中连续移动,港口轨迹更真实 |
| scale | 0.5 | 0.7 | 远近船尺寸差异大,加大尺度扰动 |
| fliplr | 0.5 | 0.5 | 船大体左右对称,可保持默认 |
这些参数在训练命令里直接追加,例如hsv_h=0.005 hsv_s=0.4 translate=0.2。如果小目标漏检多,常见的轻量改进是把 box 损失权重从默认 7.5 提到 10,让模型更关注回归分支;损失函数权重只改 box 和 cls,一般不动 dfl。改完先跑 30 轮看 loss 是否明显变化,不要一步到位追求新高分。
4.2 数据坑:漏标目标与类别不平衡
VOC 里 boat 的框并不完整,远处的船、半遮挡的船经常在图里但没被标出来。YOLO 训练时,漏标目标所在的区域会被当成背景,等于往数据里塞了带噪声的负样本。处理漏标最稳的做法是把这批图过一遍 CVAT 人工复核:拉进项目,基于已有的 txt 生成初始标注,再逐条检查船框,导出成 YOLO 1.1 格式后与原有 labels 合并。注意 CVAT 导出的 data.yaml 默认类名索引从 0 开始,与 boat.yaml 保持一致即可直接追加。
另一个隐蔽问题是框面积分布极不平衡。转换完之后写个小脚本扫一遍:
from pathlib import Path import numpy as np areas, ratios = [], [] for txt in Path("labels_train").glob("*.txt"): for line in txt.read_text().splitlines(): _, cx, cy, w, h = map(float, line.split()) areas.append(w * h) ratios.append(w / h) areas = np.array(areas) ratios = np.array(ratios) print("框面积分位点:", np.percentile(areas, [10, 50, 90]).round(4)) print("长宽比分位点:", np.percentile(ratios, [10, 50, 90]).round(2))框面积 10 分位如果低于 0.01,说明大量船在 1280 输入上只占几十像素,这类样本的梯度信号很弱,imgsz 应该继续往 1536 提。长宽比 90 分位超过 6,说明存在侧视角的横向拉长船,增强里的 scale 宜大不宜小。
4.3 尺度坑:多尺度输入与推理后处理
舰船检测最典型的场景是同一画面里远处一条小船,近处一条大船。训练时 Ultralytics 自带随机多尺度缩放,但前提是 imgsz 给足;建议把 imgsz 提到 1280 或 1536,而不是依赖后处理去补救。推理时的 YOLO 后处理流程里,conf 阈值和 NMS 的 IOU 阈值直接决定输出框数量:默认 conf=0.25、iou=0.45 对远距离小船偏严,常见做法是推理时把 conf 降到 0.1,保留更多低置信度框再做业务侧过滤。注意 conf 过低会让海浪反光、浮标跟着出框,搭配一个"目标面积必须大于全图 0.1%"的外部过滤器比反复调阈值更干净。
5. 舰船模型训练后的验证细化与部署导出
5.1 用混淆矩阵定位真实漏检
单类数据集也要看混淆矩阵,不能只看 mAP。训练目录下会自动生成 confusion_matrix.png,重点关注 background 列的占比:纵轴真实框、横轴预测框,background 列高,说明漏检集中在极端小目标或遮挡目标,单纯调 conf 没用,要回头补标注或者提 imgsz。复核最好的材料是验证集预测图:
yolo detect val data=boat.yaml model=runs_ship/boat_yolov8n/weights/best.pt imgsz=1280跑完后到 runs_ship/boat_yolov8n/val_batch*_pred.jpg,挑几张漏检严重的图看原因:是海天线反光、码头遮挡,还是标注本身漏了目标。这一步做过之后,再去决定是补标、加 imgsz 还是调 patience,方向会准很多。
5.2 导出 ONNX 并适配 RK3588 边缘部署
验证通过后,舰船模型常见落地是 RK3588 这类带 NPU 的边缘设备。导出常用 ONNX 中间格式:
yolo export model=runs_ship/boat_yolov8n/weights/best.pt format=onnx imgsz=1280 opset=12导出后先检查输出张量形状是否为 N×(5+nc),N 是网络输出候选框数,5 是中心点、宽高和置信度。后续转入 RKNN 做 int8 量化前,要单独准备几百张无标注的代表性图片做校准,夜间港口和白天海域各占一半,否则量化后漏检会明显上升。最后一个实用技巧:export 的 imgsz 必须和训练时的 imgsz 一致,边缘端推理若用 640 去跑一个 1280 训练的模型,stride 32 的输入对齐会算出完全不同的有效框范围,部署阶段排查起来非常耗时。
本文还有配套的精品资源,点击获取