简介:面向海事监控、遥感图像分析与深度学习初学者,这份压缩包基于YOLOv2算法与SSDD舰船数据集,实现了从模型构建到目标检测的完整流程。资源共60个文件,压缩后仅2.33MB,体量轻但结构完整:55张jpg舰船图像覆盖不同海况与光照环境,作为训练和测试样本;2个mat文件分别保存预训练模型与gTruth真值标注,便于直接调用;2个m脚本对应训练与检测两个环节,另附txt说明辅助阅读。已有127人学习下载,适合快速理解YOLO系列在舰船检测任务中的具体实现。借助Matlab的深度学习工具箱,使用者可直接运行检测脚本查看识别结果,也可基于自带数据调整网络结构或超参数、替换测试图像,甚至扩展至自己的数据集。整个资源包内容紧凑,适合课程设计、毕业设计或入门实践,为复现实验、掌握目标检测建模与模型评估流程提供低成本上手路径。
1. 用 YOLO 做舰船检测,真正的门槛在尺度和误检
基于 YOLO 的舰船目标检测,第一课往往不是 YOLO 原理,而是先搞清楚输入图像长什么样。把一张 1.5 万像素宽的港口遥感图直接塞进 YOLO,最常见的结局不是漏检,而是模型把波浪纹理和岸线吊机都当成了船。舰船目标可能只占图像的千分之一到万分之一,背景却是大海、岸线和港口设施,这就决定了方案重心在数据组织、输入尺度和后处理,而不是模型选型。
下面按实际工程路径展开:先解决舰船数据标注到 YOLO 格式的问题,再讲训练配置里对舰船场景最敏感的超参与损失函数表现,然后处理小目标漏检,最后落到部署与指标验证。示例统一用 YOLOv8,YOLO11 及后续版本用法与其保持一致。
2. 舰船检测数据准备:从公开数据集与自采影像到 YOLO 标签格式
2.1 选数据源:光学、SAR、红外,场景分布决定模型上限
舰船检测的数据来源常见有三类。光学影像贴近人眼习惯,港口类数据多,最容易做迁移学习;SAR 图像不受云层和昼夜影响,目标多以点状或短条状呈现在灰度纹理上;红外数据适合夜间场景与海上搜救,但公开数据量明显更少,多数要靠现场采集。选择哪一类先看部署场景,而不是看哪类数据集排行榜更好看。同一个 YOLO 结构,光学数据训出的模型直接拿去跑红外,基本是重做一遍数据循环的结局。
拿到数据以后,我一般会先做一轮统计而不是直接开训。统计内容包括每张图的舰船数量、目标像素占全图的比例、目标长宽比分布以及背景类型占比。当目标像素占比低于 0.1% 的样本超过一半时,这个任务必须显式走小目标优化路线,而不是靠堆模型深度或通道数解决。规模上,公开的光学舰船数据集有数万框级的选择,但落到具体航道和港口的工程里,几千张图加两万框左右,配合合适的增强,通常就能到达可部署的精度基线。
提示:公开数据集的场景分布和部署现场往往不一致,码头朝向、拍摄高度、雾霾程度都会影响迁移效果。迁移之前先抽样看 30 张图,确认目标尺度和背景形态是否和现场一致。
2.2 标注工具与格式转换:从 VOC/COCO 到 YOLO txt
YOLO 训练要求每张图对应一个同名 txt 文件,每行格式是class x_center y_center width height,四个坐标都是相对图像宽高的归一化浮点数,值域在 0~1。公开的舰船数据集多数给的是 VOC XML 或 COCO JSON,直接改文件后缀行不通。标注阶段,用 CVAT 或 X-AnyLabeling 会更省事,导出时直接选 YOLO 格式;如果拿到的是历史 VOC 标注,就需要写脚本转换一遍。KITTI 标注转 YOLO 也是同一个思路,区别只在字段解析。
| 工具 | 主要导出格式 | 适合场景 |
|---|---|---|
| CVAT | YOLO txt / COCO JSON / VOC XML | 多人协作标注,任务流管理 |
| X-AnyLabeling | YOLO txt / VOC XML | 单人快速标注,支持模型辅助预标注 |
| labelImg | VOC XML | 小规模临时标注 |
转换脚本里最容易被忽略的是边界处理。遥感大图的标注经常出现框的一侧超出图像范围,如果不先裁剪,计算出的归一化坐标会小于 0 或大于 1,训练时轻则产生不稳定梯度,重则直接 NaN。
import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_path: Path, out_dir: Path, classes: list[str]) -> int: root = ET.parse(xml_path).getroot() size = root.find("size") img_w = float(size.find("width").text) img_h = float(size.find("height").text) lines = [] for obj in root.iter("object"): name = obj.find("name").text if name not in classes: 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) # 关键: 把越界坐标裁剪回图内, 并跳过退化框 xmin = min(max(xmin, 0.0), img_w) xmax = min(max(xmax, 0.0), img_w) ymin = min(max(ymin, 0.0), img_h) ymax = min(max(ymax, 0.0), img_h) if xmax - xmin < 1 or ymax - ymin < 1: continue dw, dh = 1.0 / img_w, 1.0 / img_h xc = (xmin + xmax) / 2.0 * dw yc = (ymin + ymax) / 2.0 * dh bw = (xmax - xmin) * dw bh = (ymax - ymin) * dh lines.append(f"{classes.index(name)} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}") if lines: out_path = out_dir / (xml_path.stem + ".txt") out_path.write_text("\n".join(lines) + "\n") return len(lines)逻辑分四步:读出原图宽高;解析每个 object 的 bndbox;把四角坐标裁剪进图内并过滤掉宽高小于 1 像素的退化框;最后把四角坐标换算为中心点加宽高的归一化表示。classes列表的顺序就是模型输出的类别 ID,后续增删类别只改这个列表,不动模型结构。dw、dh是提前算好的倒数,批量转换上万张图时能省下不少不必要的浮点除法。
转换完成不等于数据可用。我会抽样 20 张图,把 txt 坐标画回图像上人工核对,重点看两类错误:一是标注框类别与目标不符,二是越界裁剪后框位置偏移。这一步花不了十分钟,但能拦下后面训练阶段最浪费时间的问题。
2.3 数据集划分:按场景切分而不是随机切分
舰船检测数据集翻车最多的地方在这里。按图像随机划分 train/val,同一张港口图里的多条船会同时出现在训练集和验证集,背景高度相似,验证指标虚高,上线后发现泛化能力远差于 val 表现。正确做法是按场景或采集批次划分:同一个港口的连拍帧、同一次飞行的相邻航带,整体划到同一边。留出 10% 场景做验证、10% 做测试,剩下 80% 训练;如果只有少量场景,验证集比例可以提到 20%,哪怕训练集小一点也值得。舰船检测的长尾主要体现在未知港口、未知角度,而不是框数量不够。
3. 用 YOLO 训练舰船检测模型:配置、超参与损失函数调优
3.1 环境配置与最小训练流程:从 pretrained 权重到 ship.yaml
先把训练链路跑起来。Python 3.10 加 PyTorch 2.x,配 Ultralytics 的库,训练和评估都是同一套接口。显卡优先 NVIDIA,显存 12GB 起步;训练阶段如果只有 AMD 显卡,一般不会在本地硬啃,CPU 开小 batch 调试代码,正式训练借用云上 NVIDIA 实例更划算。下面这份代码是最小可跑的训练入口,参数刻意按舰船场景做了调整。
from ultralytics import YOLO model = YOLO("yolov8n.pt") result = model.train( data="ship.yaml", epochs=150, imgsz=1280, batch=16, patience=25, optimizer="SGD", lr0=0.01, close_mosaic=15, plots=True, device=0, )对应的ship.yaml声明数据路径、类别数和类别名:
path: ./ship_dataset train: images/train val: images/val nc: 1 names: 0: ship几个关键参数的取舍在后面小节展开,这里先说明两个最常见的坑。第一,imgsz=1280会让显存占用按面积翻四倍,batch 从默认的 16 降到 8 甚至 4 是正常的,不要为了维持 batch 而强行压缩分辨率;第二,close_mosaic=15表示训练最后 15 个 epoch 关掉马赛克增强。马赛克拼接会频繁把舰船目标拦腰截断,制造大量半船样本,后期关掉能让模型在接近真实目标形态的分布上做最后精修。
3.2 舰船场景必调的 5 个超参
YOLO 训练参数很多,但对舰船场景真正敏感的是数据增强环节的这几个。默认值是为 COCO 那类居中分布的目标设计的,直接照抄会放大舰船数据的长尾。
| 参数 | COCO 默认 | 舰船建议 | 理由 |
|---|---|---|---|
| imgsz | 640 | 1280~1536 | 小目标像素占比低,提高输入分辨率收益最直接 |
| hsv_h | 0.015 | 0.005 | 海水色调集中,过大的色相扰动制造虚假纹理 |
| hsv_s | 0.7 | 0.2 | 饱和度扰动过强会让船体和海浪颜色混淆 |
| degrees | 0 | 5~10 | 舰船在图像中朝向任意,小角度旋转增强泛化 |
| fliplr | 0.5 | 0.0~0.3 | 船艏艉结构不对称,全概率翻转会产生语义不自然的样本 |
颜色参数建议先降后调。把hsv_s从 0.7 降到 0.2,误检率往往肉眼可见下降,原因是舰船检测的背景是单一色相的海面,饱和度扰动制造出来的“彩船”并不存在于部署场景。degrees对遥感大图收益明显,因为舰船在航道上朝任意方向行驶,而默认训练角度扰动为 0。scale参数保持默认或降到 0.3,舰船长宽比介于 2:1 到 6:1,过大的缩放会把整船压成细线,模型学到错误的比例先验。
3.3 损失函数与正负样本分配:舰船场景收敛慢在哪
YOLOv8 和后续版本用 DFL(Distribution Focal Loss)加 CIoU 的组合做回归,分类损失为标准交叉熵。DFL 不直接回归框的宽高,而是让模型预测框边位置的离散分布,这对长宽比极端的目标更稳,也是 YOLO 在舰船这类细长目标上比早期 anchor-based 版本好用的原因之一。
训练里有两类现象是舰船任务特有的。第一,分类损失下降非常慢,海面波浪纹理和船体边缘在浅层特征上高度相似,模型早期分不清“船边”和“浪边”,这个阶段强推大学习率反而会让框回归崩溃。第二,小目标占比高时,框回归损失后期震荡明显,因为几个像素的偏移就会触发较大梯度。应对方式是先调增强参数,再考虑改损失函数权重,不建议一上来就动 box loss 的系数;box=7.5这类默认值是多个数据集上平衡出来的,对舰船并不会产生质变。至于换 DETR 等 Transformer 结构讨论精度提升的文章很多,但工程上延迟、显存和部署链路仍然是 YOLO 系的优势,这也是舰船巡检场景普遍回到 YOLO 的核心理由。
4. 舰船小目标与多尺度检测:输入分辨率、切片推理与 NMS 调参
4.1 为什么舰船检测天然属于小目标检测
舰船在图像里“小”是物理事实。以航道监控为例,一个 80 米长的目标在 1080p 画面远端只有十几个像素宽,经过 YOLO 的多次下采样,到 P3 特征层上只剩一两个有效像素,检测器的感受野远大于目标本身。红外小目标检测里的评价思路在这里仍然适用:目标信杂比低、目标像素数少、背景纹理强度高,三个条件叠加,普通置信度过滤会把真目标先杀掉。
这也能解释为什么很多人对比过 DETR 这类 Transformer 检测器和 YOLO 之后,最后还是回到 YOLO 系。Transformer 的全局注意力在低信杂比场景有潜力,但训练成本、部署依赖和工程代价更高,而 YOLO 通过提高输入分辨率和切片推理就能把同一批数据跑出可用精度。选型结论先放在这儿:舰船小目标的问题,七成靠数据组织和输入尺度解决,三成靠后处理,模型结构本身不是主要瓶颈。
4.2 提高输入分辨率与切片推理(tiling)的完整写法
提高 imgsz 是最直接的解法:640 提到 1280,小目标分档 AP 通常涨 10 个点以上,显存占用却几乎翻四倍。显存不够时,训练用 960 或 1280,推理端做切片。切片推理是把大图切成相互重叠的小块分别预测,再把结果投影回原图坐标做一次全局 NMS。重叠的目的是保证跨切片的同一个目标不会被切成两半而丢失。
import numpy as np from ultralytics import YOLO model = YOLO("best.pt") def slice_predict(image, slice_size=640, overlap=0.2, conf=0.15, iou=0.45): h, w = image.shape[:2] step = int(slice_size * (1 - overlap)) all_boxes = [] for y in range(0, h, step): for x in range(0, w, step): x2, y2 = min(x + slice_size, w), min(y + slice_size, h) x1, y1 = max(0, x2 - slice_size), max(0, y2 - slice_size) crop = image[y1:y2, x1:x2] # 预测块内目标, 再把坐标投影回原图 result = model.predict(crop, imgsz=slice_size, conf=conf, iou=iou, verbose=False)[0] for box in result.boxes: bx1, by1, bx2, by2 = box.xyxy[0].cpu().numpy() all_boxes.append([bx1 + x1, by1 + y1, bx2 + x1, by2 + y1, float(box.conf[0]), int(box.cls[0])]) return nms(all_boxes, 0.5) def nms(boxes, iou_thr=0.5): boxes = sorted(boxes, 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) < iou_thr] return keep def iou(a, b): ax1, ay1, ax2, ay2 = a[:4] bx1, by1, bx2, by2 = b[:4] ix1, iy1 = max(ax1, bx1), max(ay1, by1) ix2, iy2 = min(ax2, bx2), min(ay2, by2) iw, ih = max(0.0, ix2 - ix1), max(0.0, iy2 - iy1) inter = iw * ih union = (ax2 - ax1) * (ay2 - ay1) + (bx2 - bx1) * (by2 - by1) - inter return inter / union if union > 0 else 0.0切片大小取训练 imgsz 的一半,比如训练用 1280,切片用 640,显存压力小且模型见过的尺度一致。overlap 取 0.2 以上才能保证跨切片的舰船目标至少完整出现在一个块里。合并后只需要过一次全局 NMS,不需要对置信度加惩罚系数。代价是推理计算量变成原来的数倍,对离线巡检任务可接受,实时视频流则需要配 TensorRT 才能兜住帧率。
| 切片大小 | 相对训练尺度 | 适用场景 |
|---|---|---|
| 512 | 0.4× | 显存小于 8GB 的小卡推理 |
| 640 | 0.5× | 训练 imgsz=1280 时的常见选择 |
| 896 | 0.7× | 大目标占比偏高或对延迟不敏感 |
4.3 conf 与 NMS 参数:舰船场景怎么定
小目标天然低置信度,海上误检又天然高置信度(波浪纹理有时很像船),所以 conf 的选取不能拍脑袋。我一般先在验证集上把 conf 从 0.001 到 0.5 扫一遍,画 F1-confidence 曲线,取 F1 峰值对应的阈值作为基准值。舰船任务这个值通常在 0.15~0.3 之间。NMS 的 iou 阈值管的是框合并:港口密集停靠的船互相遮挡,iou 设 0.45 容易把紧挨的两条船合并成一个框,建议下调到 0.4;但低于 0.3 时同一条船会输出多个框,舰船这类矩形目标尤其明显。
注意:推理端的 iou 参数只影响框合并,不要拿去改训练时的增强配置。YOLOv8 之后 anchor-free 是默认,训练阶段随意改 iou 相关参数反而会引入不稳定。
5. 部署与验证:ONNX 导出、跨硬件推理与舰船指标落地
5.1 ONNX 导出与跨硬件推理
模型固化用 ONNX 最通用。导出时固定输入分辨率,因为舰船推理的 imgsz 是部署方案定死的,静态图能省下一部分转换开销。export 之后用 ONNX Runtime 推理,CPU 和 AMD 显卡都能跑,NVIDIA 再走 TensorRT 提速。
from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") model.export(format="onnx", imgsz=1280, opset=17, simplify=True)导出的best.onnx不包含 NMS 和坐标还原,这两部分在部署代码里显式写出来或直接复用 Ultralytics 的推理封装。AMD 显卡上跑 ONNX,Windows 下用 DirectML provider,Linux 下走 ROCm,两者都能做到明显快于 CPU 但低于同价位 NVIDIA 卡的推理速度;Jetson 等边缘设备则直接导出 TensorRT 引擎。多路视频流场景,建议按 batch 合帧推理,比逐帧推理节省三分之一以上的 GPU 时间。
5.2 舰船检测验证:mAP 分档与误检定位
部署前用验证脚本完整评估一遍。conf 传 0.001 是为了保留所有低置信度预测,这样画出来的 PR 曲线和 F1-confidence 曲线才是完整的,后续选阈值的依据就在这里。
from ultralytics import YOLO model = YOLO("best.pt") metrics = model.val(data="ship.yaml", imgsz=1280, conf=0.001, plots=True) print(f"mAP50={metrics.box.map50:.4f} " f"AP_s={metrics.box.map_s:.4f} " f"AP_m={metrics.box.map_m:.4f} " f"AP_l={metrics.box.map_l:.4f}")四行输出里,AP_s是当前模型在小目标档的真实水平,建议把 AP_s 作为舰船任务的立项指标:整体 mAP 高但 AP_s 低的模型,部署后远端目标会大面积漏检。配合 plots 生成的 F1-confidence 曲线选上线阈值,而不是沿用 conf=0.25 的默认值。误检定位看混淆矩阵和预测图,误检落在波浪上就降 hsv_s、调高 conf;落在港口设施上就补对应场景的样本。
最后给一个工程上很实用的验证技巧:找一段部署场地一周的历史影像,固定检测帧间隔跑全量推理,把每帧检出的框按时间串起来。真实舰船的检测框会形成连续轨迹,误检则呈散点炸开,统计轨迹连续性就能在不需要新标注的前提下估算部署误检率。这个分布曲线同时是后续接多目标跟踪模块的输入基线——接入时检测器不要按 F1 最优阈值提前截断,跟踪模块通常还要消费低置信度的候选框来维持轨迹。
本文还有配套的精品资源,点击获取