1. 海事视觉检测的项目背景与数据集定位
1.1 为什么船舶检测是个被低估的刚需场景
我最早接触船舶检测,是在一个港口卡口的项目里。当时甲方给的需求很朴素:把进出港口的船只自动识别出来,记录船型、大致尺寸、进出时间。听起来简单,但真正落地的时候才发现,水面反光、雾气、船体遮挡、远近尺度差异巨大,这些问题叠加在一起,比常规的道路车辆检测要棘手得多。
船舶检测属于典型的目标检测任务,它的应用面其实比很多人想象的宽:港口进出港统计、航道船舶流量监测、水域非法捕捞船只识别、海上风电运维船只调度、桥梁防撞预警、内河航运监管、渔港渔船管理,甚至游艇俱乐部的泊位管理。这些场景有一个共同点——人工盯屏成本极高,且容易漏看。一个中等规模的港口,一天进出船只可能上千艘次,靠人眼盯着几十路摄像头,基本不现实。
这就是为什么海事监测数据集的价值会被反复提起。算法再强,没有贴合场景的数据,模型就是空中楼阁。我见过太多团队拿着COCO预训练权重直接去跑港口视频,结果船和背景的对比度一塌糊涂,小目标几乎全丢。问题的根子不在模型,而在数据分布不匹配。
1.2 3000张YOLO格式数据集到底意味着什么
先把这个标题拆开看:3000张、YOLO格式、海事监测、船舶检测。这四个词每一个都对应着具体的工程含义。
3000张这个量级,说多不多,说少不少。对于单一类别(船舶)的检测任务,3000张标注图像如果分布合理,足以训练出一个可用的基线模型。但如果这3000张里80%都是同一种天气、同一个角度、同一种船型,那实际有效样本可能只有几百张。所以拿到数据集的第一件事,不是急着训练,而是做数据分布统计。
YOLO格式意味着标注是.txt文件,每行是class_id x_center y_center width height,坐标全部归一化到0到1之间。这种格式的好处是轻量、读取快、和Ultralytics生态无缝衔接。坏处是它不存储图像尺寸信息,所以你在做数据增强、坐标还原、可视化检查的时候,必须自己去读原图尺寸。这一点新手特别容易踩坑。
海事监测这个限定词,决定了数据的场景特征:水面、天空、船体、码头、浮标、岸线。这些元素的纹理和常规的COCO数据差异很大。水面有波纹和反光,天空有过曝和云层,船体有锈迹和涂装,这些都会影响特征提取。
1.3 这份数据集适合谁用
我把适用人群分成三类,你可以对号入座。
第一类是刚入门目标检测的学生或转行者。你可能刚学完YOLO的基本原理,跑通了官方示例,但苦于没有真实场景数据练手。这份数据集规模适中,单类别,标注格式标准,非常适合作为第一个完整的训练项目。你能完整体验从数据检查、划分、训练、评估到推理的全流程。
第二类是做智慧港口、水域监管的工程团队。你们可能已经有业务系统,缺的是一个能快速验证的基线模型。用这份数据先跑出一个mAP可接受的模型,再去补充自己采集的私有数据做微调,是最省时间的路径。
第三类是做算法改进研究的人。船舶检测里的小目标、密集遮挡、水面干扰,都是很好的改进切入点。你可以拿这份数据作为baseline,验证你的注意力机制、特征融合、损失函数改进是否真的有效。
提示:无论你属于哪一类,都不要跳过数据检查这一步。我见过太多人直接开训,训完发现标注框错位、类别混乱、图像损坏,白白浪费几个小时甚至几天的算力。
2. 数据集结构与YOLO标注格式深度解析
2.1 标准目录结构应该长什么样
一份规范的YOLO数据集,目录结构通常是这样组织的:
ship_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yamlimages和labels必须严格对应,文件名相同,只是扩展名不同。图像是.jpg或.png,标签是.txt。这种镜像结构是Ultralytics默认要求的,如果你自己乱放,训练时会直接报找不到标签。
data.yaml是数据集的核心配置文件,内容大致如下:
path: /home/user/ship_dataset train: images/train val: images/val test: images/test nc: 1 names: ['ship']nc是类别数,names是类别名列表。船舶检测如果只做单一类别,nc就是1。但实际项目里,有时候会细分货船、渔船、客船、工程船,那nc就要相应调整,标注文件里的class_id也要对应修改。
2.2 标注文件逐行拆解
打开一个标签文件,你看到的是这样的内容:
0 0.5234 0.6120 0.1205 0.0890 0 0.3410 0.5980 0.0870 0.0650每一行代表一个目标框。五个数字分别是:类别索引、中心点x坐标、中心点y坐标、框宽、框高。后四个全部是相对于图像宽高的归一化值,范围0到1。
这里有个关键点:中心点坐标和宽高都是归一化的,不是像素值。假设图像是1920×1080,某个框的中心点x是0.5234,那实际像素位置就是0.5234×1920≈1005。宽0.1205对应0.1205×1920≈231像素。你在做可视化检查或者坐标转换时,必须乘回原图尺寸。
为什么YOLO要用归一化坐标?因为这样标注就和图像分辨率解耦了。同一张图缩放、裁剪后,标签不需要重新计算,只要同步变换即可。这也是YOLO格式比VOC的绝对坐标更灵活的地方。
2.3 数据分布统计该怎么做
拿到3000张数据,我建议先跑一遍统计脚本,把下面这些信息摸清楚:
| 统计维度 | 具体指标 | 为什么重要 |
|---|---|---|
| 图像尺寸 | 宽高分布、是否统一 | 决定训练时的resize策略 |
| 目标数量 | 每图平均框数、最大框数 | 判断密集程度,影响anchor设置 |
| 框尺寸 | 宽高分布、长宽比 | 判断小目标占比,决定输入分辨率 |
| 类别分布 | 各类别实例数 | 判断是否类别不平衡 |
| 位置分布 | 框中心点热力图 | 判断目标是否集中在画面某区域 |
我实际做的时候,会写一个脚本遍历所有标签文件,统计框的宽高。如果发现大量框的宽高小于0.05(归一化后),那说明小目标很多,训练时输入分辨率就不能太低,否则这些目标在特征图上只剩几个像素,根本学不到。
船舶检测里,远距离的小船和近距离的大船尺度差异可能达到几十倍。这种尺度跨度是检测的难点,也是为什么很多人会在YOLO基础上加多尺度特征融合的原因。
2.4 标注质量检查的实操方法
标注质量直接决定模型上限。我通常用三种方式检查:
第一种是可视化叠加。把标签框画回原图,人工抽查几十张,看框是否贴合船体、有没有漏标、有没有把浪花或浮标误标成船。
第二种是异常值检测。统计所有框的宽高,找出明显偏离分布的。比如某个框宽0.9、高0.9,几乎覆盖整张图,那大概率是标注错误。再比如宽高比极端异常的,也要重点看。
第三种是空标签和损坏图像检查。有些图像可能没有对应标签文件,或者标签文件是空的。空标签在YOLO里表示这张图没有目标,是合法的负样本,但如果比例过高,会影响正负样本平衡。损坏图像则要在训练前剔除,否则dataloader会直接报错。
import os from PIL import Image def check_images(img_dir): bad = [] for f in os.listdir(img_dir): p = os.path.join(img_dir, f) try: img = Image.open(p) img.verify() except Exception as e: bad.append((f, str(e))) return bad这段代码能快速筛出损坏图像。我一般会在数据准备阶段就跑一遍,避免训练中途崩掉。
3. 从零到一:船舶检测模型的完整训练流程
3.1 环境配置与依赖安装
训练环境我推荐用Python 3.9到3.11之间的版本,太新或太旧都可能遇到依赖冲突。核心依赖就是Ultralytics这个库,它把YOLO系列的训练、验证、导出都封装好了。
conda create -n ship_det python=3.10 -y conda activate ship_det pip install ultralytics如果你有GPU,还要确认CUDA和PyTorch版本匹配。用torch.cuda.is_available()验证一下,返回True才说明GPU可用。我踩过的坑是:装了CPU版的PyTorch,训练时速度慢十倍,还以为是数据问题,排查半天才发现是环境装错了。
注意:不要盲目追求最新版本。Ultralytics更新很快,有时候新版本会改默认参数或API。如果你参考的教程是几个月前的,建议先确认版本兼容性,必要时锁定版本号安装。
3.2 数据划分策略与坑点
3000张数据,常见的划分比例是训练集70%、验证集20%、测试集10%,也就是2100/600/300。但这个比例不是死的,如果数据本身很难获取,验证集可以降到15%。
划分时最大的坑是数据泄漏。如果同一段视频抽帧得到的图像,一部分进了训练集,一部分进了验证集,那验证集上的指标会虚高,因为模型见过几乎相同的画面。正确做法是按视频源或按场景划分,保证训练集和验证集来自不同的视频、不同的天气、不同的港口。
我一般会先给每张图打上来源标签,比如port_A_sunny、port_B_rainy,然后按来源分组划分。这样验证集才能真正反映模型的泛化能力。
划分完成后,把图像和标签分别复制到对应的train/val/test目录。复制而不是移动,是为了保留原始数据,方便后续重新划分。
3.3 训练参数的选择逻辑
YOLO训练涉及一堆参数,新手最容易懵。我把关键参数和选择逻辑列出来:
| 参数 | 常用值 | 选择逻辑 |
|---|---|---|
| imgsz | 640 / 1280 | 小目标多用1280,显存不够降640 |
| epochs | 100-300 | 数据少可以多训,但要防过拟合 |
| batch | 8-32 | 看显存,越大越稳但显存吃紧 |
| lr0 | 0.01 | 初始学习率,太大震荡太小收敛慢 |
| optimizer | SGD / AdamW | SGD泛化好,AdamW收敛快 |
| patience | 50 | 早停耐心值,防止无效训练 |
imgsz这个参数对船舶检测特别关键。如果你的数据里小目标占比高,640的输入会让小船在特征图上只剩几个像素,检测效果会很差。我实测下来,同样一份数据,imgsz从640提到1280,小目标的召回率能提升十几个点。代价是显存占用翻倍,训练时间也翻倍。
batch的选择要结合显存。有个经验公式:显存占用大致和batch × imgsz²成正比。如果你在640下能跑batch=32,换到1280大概只能跑batch=8。这时候可以用梯度累积来模拟大batch,Ultralytics里通过nbs参数控制。
3.4 启动训练与过程监控
配置好data.yaml后,一条命令就能启动训练:
yolo detect train \ data=ship_dataset/data.yaml \ model=yolov8n.pt \ epochs=150 \ imgsz=1280 \ batch=8 \ patience=50 \ project=runs/ship \ name=exp1model参数指定预训练权重。yolov8n.pt是最小的nano版本,速度快但精度低;yolov8m.pt或yolov8l.pt精度更高但更慢。船舶检测如果追求精度,建议从m或l起步;如果要做边缘部署,n或s更合适。
训练过程中,Ultralytics会在runs/ship/exp1下生成一堆文件,包括损失曲线、mAP曲线、混淆矩阵、验证集预测可视化。我习惯每隔几十个epoch去看一眼验证集的预测图,直观感受模型有没有学到东西。如果预测框乱七八糟,说明学习率或数据有问题,早点停掉调整,别等跑完。
损失函数这块,YOLO用的是分类损失加框回归损失加置信度损失的组合。框回归里又分CIoU或DFL等形式。你不需要手动实现,但要知道它的作用:分类损失管类别对不对,框回归管位置准不准,置信度管有没有目标。如果训练时分类损失降不下去,可能是类别标注有问题;如果框回归损失震荡,可能是学习率太大。
4. 模型评估、推理与常见问题排查
4.1 评估指标怎么看才不被误导
训练完,第一件事是看验证集指标。核心指标有这几个:
- Precision(精确率):预测为船的框里,有多少真的是船。高精确率意味着误报少。
- Recall(召回率):真实的船里,有多少被检测出来。高召回率意味着漏报少。
- mAP@0.5:IoU阈值为0.5时的平均精度,是最常用的综合指标。
- mAP@0.5:0.95:多个IoU阈值下的平均,更严格,反映定位精度。
船舶检测里,召回率往往比精确率更重要。漏掉一艘船可能意味着漏统计、漏预警,而多报一个框人工复核一下就行。所以调参时,如果要在精确率和召回率之间取舍,我一般偏向保召回。
但要注意,mAP高不代表实际好用。我见过mAP@0.5到0.9的模型,实际推理时在雾天图像上几乎全漏。原因是验证集里雾天样本太少,指标被晴天样本拉高了。所以分场景看指标很重要,把验证集按天气、按距离分组,分别算mAP,才能看出模型的短板。
4.2 推理部署的几种方式
训练好的模型,推理方式有好几种,看你的部署场景。
如果是离线批量处理,直接用命令行:
yolo detect predict \ model=runs/ship/exp1/weights/best.pt \ source=test_images/ \ conf=0.25 \ save=Trueconf是置信度阈值,低于这个值的框会被过滤。船舶检测里,如果背景干扰多,可以适当调高到0.3或0.4;如果怕漏检,调到0.15。
如果是集成到Python服务,用API调用:
from ultralytics import YOLO model = YOLO('best.pt') results = model('ship.jpg', conf=0.25) for r in results: boxes = r.boxes for box in boxes: xyxy = box.xyxy[0].tolist() cls = int(box.cls[0]) conf = float(box.conf[0]) print(f"类别:{cls} 置信度:{conf:.2f} 坐标:{xyxy}")如果是边缘设备部署,需要导出成ONNX或TensorRT格式。导出ONNX:
yolo export model=best.pt format=onnx imgsz=640导出后可以用ONNX Runtime推理,速度比PyTorch快不少,而且不依赖训练环境。
4.3 常见问题速查表
我在船舶检测项目里踩过的坑,整理成一张表,方便你对照排查:
| 问题现象 | 可能原因 | 排查与解决 |
|---|---|---|
| 训练loss不下降 | 学习率太小、数据标注错、预训练权重不匹配 | 检查标注可视化,调大lr0,换预训练权重 |
| 验证mAP远低于训练 | 过拟合、数据泄漏、验证集分布不同 | 加数据增强,重新按来源划分,检查验证集 |
| 小目标全漏 | 输入分辨率太低、anchor不匹配 | 提高imgsz,检查小目标占比 |
| 推理速度慢 | 模型太大、没用GPU、没导出优化格式 | 换小模型,确认CUDA,导出ONNX/TensorRT |
| 误检严重 | 置信度阈值太低、负样本不足 | 调高conf,补充背景负样本 |
| 框位置偏移 | 标注坐标错误、resize没同步 | 可视化检查标注,确认预处理一致 |
4.4 提升效果的几个实用技巧
第一,数据增强要贴合场景。船舶检测里,水平翻转是安全的,因为船左右翻转还是船。但垂直翻转要谨慎,倒过来的船在现实中几乎不存在,加了反而引入噪声。Mosaic增强能提升小目标效果,但会让图像边缘出现拼接痕迹,如果测试场景是干净的单图,可以适当降低Mosaic概率。
第二,负样本不能少。很多人只标船,不标水面、浮标、云。结果模型看到反光就报船。正确做法是收集一些没有船的纯水面、纯码头图像作为负样本,让模型学会区分。
第三,分阶段训练。先用低分辨率快速训一个baseline,确认流程通了,再上高分辨率精调。这样能省下大量试错时间。
第四,测试时增强(TTA)。推理时对同一张图做多种变换,综合结果,能小幅提升精度,代价是推理时间增加。对精度要求高、速度不敏感的离线场景值得开。
5. 数据集扩展与模型迭代的长期思路
5.1 3000张之后该怎么补数据
3000张能训出可用模型,但要上生产,通常不够。补数据有几个方向:
一是主动学习。用当前模型跑未标注数据,挑出置信度低或预测分歧大的图像,优先标注。这些是模型最"困惑"的样本,标注价值最高。
二是场景补全。统计现有数据缺什么场景——夜间的、雨天的、大雾的、远距离的、密集船群的,针对性采集。船舶检测里,夜间和恶劣天气往往是效果最差的,也是最需要补的。
三是合成数据。用3D渲染或图像合成技术生成船舶图像,可以快速扩充稀有场景。但合成数据和真实数据有域差异,最好混合训练,或者用域适应方法。
5.2 模型迭代的节奏把控
模型迭代不是越频繁越好。我的经验是:先定基线,再定指标,最后才迭代。
基线就是当前数据训出的最好模型,记录它的mAP、召回率、推理速度。然后明确这次迭代要解决什么问题——是小目标漏检,还是误报太多,还是速度不够。针对问题选改进方向,改完在同一验证集上对比,指标有提升才保留。
我见过团队盲目追新,今天换注意力,明天换损失函数,结果指标忽上忽下,根本不知道哪个改动有效。控制变量是迭代的铁律,一次只改一个地方。
5.3 从检测到更复杂的海事视觉任务
船舶检测只是起点。往上走,可以做船舶分类(货船、渔船、客船)、船舶跟踪(跨帧关联,统计流量)、船舶行为分析(停泊、航行、掉头)、船体分割(精确轮廓,用于尺寸测量)。
这些任务可以基于检测模型扩展。比如跟踪,就是在检测框基础上加一个关联算法,把相邻帧的同一个船关联起来。分割则是在检测基础上加一个分割头,输出像素级掩码。
海事场景还有个特殊需求:船舶身份识别。通过船体特征或船名文字识别,关联到具体船只。这就涉及OCR和细粒度分类,是另一个技术栈了。
5.4 我个人的一些实操体会
做船舶检测这几年,最大的体会是:数据质量比模型结构重要得多。同样用YOLOv8,标注干净的数据能比标注混乱的数据高出二三十个mAP点。与其花时间调模型,不如先把标注质量抓上去。
另一个体会是:不要迷信通用指标。mAP高不代表业务好用。真正上线前,一定要拿真实场景的视频跑一遍,看漏检和误检在业务上能不能接受。我遇到过一次,模型mAP 0.85,但实际视频里因为水面反光,误报率高达30%,最后是靠补充负样本和调高置信度阈值解决的。
最后,保持数据迭代的闭环。模型上线后,把误检和漏检的样本收集起来,定期回流到训练集,重新训练。这样模型才能持续适应真实场景的变化。船舶检测的场景会变——新船型、新涂装、新天气,模型不更新就会慢慢退化。
这套流程跑下来,从3000张数据到可用的船舶检测模型,快的话一周,慢的话两三周,取决于数据质量和调参深度。关键是每一步都要有验证,不要凭感觉。