做海事监控、船舶识别相关项目也有两三年了,手头攒下来的数据集零零散散,踩过的坑更是一个接一个。今天整理的这份3000张YOLO格式船舶检测数据集,是我把过去反复用过的海事影像、港口监控片段和公开场景数据重新清洗、筛选、统一标注以后固化下来的版本。它不是那种从某个比赛页面一键下载就能用的“标准化数据集”,而是更接近真实项目里能落地的状态:样本分布不均衡、天气条件复杂、远处目标大量存在,类别也不是单一“ship”,细分成货船、客船、渔船、拖船、帆船和小艇六类。如果你正在做海事监测、港口安全管理、内河航运监管、无人机近海巡检之类的工作,这份数据集可以直接拿来训练YOLOv8/YOLOv5模型,也可以作为你自建船舶数据集时的格式参考和标注规范模板。
我见过太多人拿公开数据集训练出来指标很好看,一上真实监控画面立刻现原形。原因很简单,公开数据集太“干净”了:目标大、背景简单、光照统一。真实海事场景里的难点——水面反光、雨雾干扰、目标密集、船只遮挡、远处小目标——在那种数据集里基本见不到。所以这次我重点说明的不只是“数据集里有什么”,还包括每个设计决策背后的理由、标注时反复修改过的规范、训练时踩过的坑,以及一套能直接照做的完整流程。
1. 数据集整体设计与内容概览
1.1 为什么需要一份面向真实场景的船舶检测数据集
船舶检测本质上属于遥感与监控场景下的目标检测,但它和通用物体检测有个非常大的差别:背景复杂度不高,目标形态却极其多变。同一艘渔船,在港口里是一副样子,在开阔海域又是另一副样子;晴天顶光、雾天散射光、夜间红外,成像特征完全不同。用VOC或者COCO里的通用类别去训练,完全没法覆盖这种差异。
另外一个问题是标注对象本身。船上目标不好标:船体和背景水域对比度低、船舷和浪花颜色接近、部分船只相互遮挡严重。加上海上监控摄像头安装高度和角度差异大,同一个目标在画面里的尺度和视角变化非常剧烈。这些因素决定了一份可用的船舶检测数据集必须在场景多样性、目标尺度分布和标注规范上下足功夫,否则模型训练出来只是一个“会跑”的空壳。
我构建这份3000张的数据集时,第一原则是场景优先。单纯堆数量没意义,重复画面只会让模型过拟合到特定机位。我在筛选时按来源视频和拍摄机位做了去重,确保同一场景、同一角度的画面最多占数据集的很小比例,尽量让每一张图都带来新的环境信息。
1.2 数据集的组成与统计
整个数据集一共3000张图像,分辨率以1920×1080为主,少部分来自4K监控画面裁剪,统一保留了原始长宽比。场景覆盖了港口航道、近海开阔水域、内河航运、入海口、码头装卸区等常见海事监控场景。天气条件方面,晴天约占45%,阴天约占25%,雾天和雨天各占10%左右,另外还有10%左右的夜间红外图像。之所以刻意保留恶劣天气样本,是因为真实监测场景里这些才是最考验模型鲁棒性的部分,只训练晴好天气会把部署后的召回率拉得很低。
类别方面共6类,按标注数量从多到少排列:小艇占比约35%、渔船约25%、货船约20%、客船约10%、拖船约5%、帆船约5%。类别之间数量差距存在,但没有到悬殊到无法训练的程度,小艇和渔船占比高是因为它们在近海和内河确实更常见。每张图像平均包含4到8个目标,最多的港口密集停泊场景单图超过30个目标,远处小目标占比接近30%。这些数据特性后面会直接影响到训练时的增强策略和损失函数配置。
标注文件全部为YOLO格式,每张图片对应一个同名txt文件,类别编号从0到5依次对应上面的六个类别。坐标信息全部归一化到0到1区间,保留六位小数。部分中文标注工具导入导出时容易丢精度,我在处理时统一用脚本重新校验过一遍坐标范围、宽高正值和归一化格式,确保所有标注文件可以直接被YOLOv5和YOLOv8读取。
2. 标注细节与YOLO格式解析
2.1 YOLO标注格式必须知道的几个点
YOLO格式的标注文件看起来简单,每行就五个数字,但很多人第一次用就栽在细节上。标准格式是:类别编号、归一化中心点x坐标、归一化中心点y坐标、归一化目标宽度、归一化目标高度。这里的归一化指的是相对图片原始宽高进行除法后的结果,也就是说中心点坐标和宽高都是0到1之间的浮点数。
举个例子:一张1920×1080的图上,某个目标边界框左上角在(240, 120),右下角在(720, 480),那么实际中心点x是(240+720)/2=480,y是(120+480)/2=300,目标宽是720-240=480,目标高是480-120=360。归一化之后就是:类别编号、480/1920=0.25、300/1080=0.28、480/1920=0.25、360/1080=0.33,也就是一行“0 0.250000 0.280000 0.250000 0.330000”。
这里最容易出错的有三处。第一处是坐标搞混,把左上角和右下角当作中心点和宽高来写。第二处是忘记归一化,直接把像素坐标往里填,训练时loss直接炸掉。第三处是宽高写成0或负数,通常是标注工具导出异常或者手工编辑文件时误操作导致,轻则训练警告,重则训练中断。我写了一个Python脚本全量检查这五个字段的有效性,检测非法边界框、坐标越界和重复标注问题。
数据集中每张图像的标注文件与图像一一对应,命名保持一致。这套目录结构是YOLO系列约定俗成的标准组织方式,训练时不需要额外写复杂的配置去适配自定义路径,直接按下面这个结构摆放即可。
ship_detection_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml2.2 标注工具选择与质量控制
标注工具我前后试过LabelImg、LabelMe和X-AnyLabeling,最后主力用的是X-AnyLabeling。原因有两个:一是它原生支持YOLO格式的导入导出,不需要频繁转换;二是它内置了YOLO预标注功能,可以先让模型跑一遍生成伪标签,人工只需要修正漏检和错检。这种做法能在保证质量的前提下把标注效率提升至少一倍。
不过预标注有个坑,就是模型对远距离小目标的漏检率比较高,如果完全信任预标注结果,小目标样本会被系统性低估。我的处理办法是:预标注只是起点,每张图都要求人工过一遍,重点检查三类区域——远距离海天线附近、港口密集停泊区、水面反光区域。小目标宁可多标十次,不可漏一次,因为后处理时补标比训练时补数据痛苦得多。
质量控制环节我做了三层审核。第一层是格式校验,脚本检查坐标归一化、类别编号范围、目标宽高有效性。第二层是画框可视化,把所有标注框画回图像,按类别随机抽检10%,重点看边界框和船体轮廓是否贴合。第三层是类别一致性复查,容易混淆的情况主要有:远处小艇被标成渔船、拖船被标成货船、帆船和小艇在侧面视角下难以区分。这类混淆只能靠人工一遍一遍看,AI再强也替代不了。
3. 用YOLOv8训练船舶检测模型的完整流程
3.1 环境准备与数据集目录组织
训练这套数据集我推荐直接用ultralytics的YOLOv8。相比YOLOv5的仓库结构,YOLOv8把训练、验证、导出、推理都统一进了yolo命令行工具,对新手友好很多。安装方式很直接,在PyCharm的终端或者系统终端里执行:
pip install ultralytics如果你是NVIDIA显卡用户,一定注意PyTorch版本要和CUDA版本匹配。我建议直接用PyTorch官网给的安装命令装,先装torch再装ultralytics,顺序反了容易出现torch CPU版本被覆盖的情况。装完之后跑一行代码确认环境是否正常:
yolo predict model=yolov8n.pt source=test.jpg能正常出结果就说明环境没问题。这一步先别急着用自定义数据,把环境跑通了再说,能省掉很多排查环节。值得注意的是PyCharm里如果多个解释器切换,容易装错环境却不自知,推荐用pip list检查ultralytics和torch装到了哪个site-packages下面。
目录组织按前面贴的结构摆放即可。把3000张图按8:1:1比例划分train、val、test。不过我在划分时没有简单随机切分,而是先把所有图像按来源视频分组,再把视频组随机分到三个集合里。这么做是为了避免来自同一段视频的相邻帧同时出现在训练集和验证集里,否则验证指标会虚高,模型实际上没有真正面对过未见过的场景。
3.2 关键参数设置与训练脚本
数据集准备好之后,需要在data.yaml里声明类别信息和路径。我的这个文件内容如下:
train: ship_detection_dataset/images/train val: ship_detection_dataset/images/val test: ship_detection_dataset/images/test nc: 6 names: ['small_boat', 'fishing', 'cargo', 'passenger', 'tug', 'sailing']nc是类别数,names的顺序必须和标注文件里类别编号一一对应,顺序错了模型就白训了。确定无误之后开始训练,我用的训练命令是:
yolo detect train data=ship.yaml model=yolov8m.pt epochs=150 imgsz=1280 batch=16 lr0=0.005几个关键参数的选择逻辑我展开说一下。模型选yolov8m而不是最小的nano,因为船舶检测中小目标占比高,nano的骨干网络太轻,特征提取能力不够,对远处船只召回率明显偏低。而yolov8l以上在这个数据量下又容易过拟合,m算是一个平衡点。
imgsz=1280是这份数据集训练效果提升最明显的一个参数。默认的640在训练多数目标较大的通用数据集时够用,但船舶场景里大量目标只有几十个像素大小,640分辨率下这些目标的信息几乎丢光了。提到1280之后,小目标的召回率肉眼可见地往上涨。代价是显存占用大幅上升,我实测batch=16在24G显存上才能稳跑,如果你的显卡显存不够,把batch降到8甚至4,优先保证分辨率不降。
这里补充一个经验:如果你用yolov8m加1280分辨率,可以先把训练轮数设为100,训练过程中观察验证集指标,到后期指标不再提升时再适当增加轮数。不要一上来就150轮硬跑,浪费算力不说,还可能过拟合。
3.3 训练结果评估与模型导出
训练结束后不要急着看loss曲线,那个只能反映拟合程度,不能反映检测效果。真正要关注的是验证集上的mAP50、mAP50-95和PR曲线。mAP50对定位精度要求没那么苛刻,是船舶检测任务的主要参考;mAP50-95对框的贴合程度更敏感。我在实测中,这套配置训练出来的模型mAP50约在0.82到0.86之间,mAP50-95约在0.55到0.60之间,小艇和渔船两类指标最高,拖船和帆船因为样本量少,mAP50-95会明显偏低。
训练完成后输出到runs/detect/train目录下,里面有weights/best.pt和weights/last.pt两个权重文件。best.pt是根据验证集指标保存的最优权重,部署时用这个。如果想部署到NVIDIA边缘设备或者服务端,先用导出命令转成ONNX:
yolo export model=runs/detect/train/weights/best.pt format=onnx imgsz=1280导出ONNX之后再转TensorRT引擎,在Jetson系列设备上推理速度能快一个量级。不过转TensorRT时要注意版本匹配,TensorRT版本和CUDA版本、PyTorch版本三者必须兼容,否则转换过程会报一堆莫名其妙的错误。我在AGX Orin上搭建YOLO环境时吃过不少亏,后来固定用JetPack自带的TensorRT版本配套导出,才稳定下来。
4. 常见问题与排查技巧实录
4.1 标注与数据集层面的典型问题
第一个高频问题是类别不平衡导致个别类mAP很低。我的数据集里帆船只有5%、拖船只有5%,训练出来这两类的召回率明显差于小艇和渔船。这个问题的解决思路不是粗暴增加图像数量,而是针对少数类做复制粘贴增强,把帆船和拖船的实例贴到不含船的纯背景图上,生成新的训练样本。我试过用这种方式把拖船的有效样本扩大了一倍,mAP50提升了约4个百分点。
第二个问题是标注边界框与船体轮廓贴合度不高。监控画面里水面反光经常让船体边缘模糊,标注的时候如果贪快,框会比船体大一圈,训练出来的预测框也会有同样的偏差。处理这类问题没有捷径,只能靠可视化抽检加人工重标。我习惯每训练一轮就随机抽100张图把预测结果画出来,重点看边界框和船体边缘之间的白色间隙,出现系统性的框偏大或偏小,就回过去修标注。
第三个问题比较隐蔽:数据泄露。如果训练集和验证集来自同一段视频的连续帧,验证指标会显著虚高。这个问题在公开数据集里很常见,很多视频抽帧数据的train和val切分不严谨,导致模型其实已经“见过”验证集画面。我在这份数据集里专门做了视频分组再切分,实测相同模型在严格分组切分下的mAP50比随机切分低5到10个百分点。如果你在训练自己的视频抽帧数据,务必检查切分逻辑。
4.2 训练与调参层面的典型问题
训练时最常见的现象是loss居高不下或直接NaN。loss为NaN大概率是学习率太大,把lr0从默认值调低一个数量级通常就能解决。loss不下降则要先排除数据问题,用脚本检查标注坐标有没有越界、宽高是否为0、类别编号是否超出nc范围。这些异常数据会让模型无法收敛。我排查时写过一个小脚本,可以快速把非法标注行打印出来:
import os label_dir = "labels/train" for f in os.listdir(label_dir): with open(os.path.join(label_dir, f), "r") as fp: lines = fp.readlines() for i, line in enumerate(lines): parts = line.strip().split() if len(parts) != 5: print(f"{f} 第{i + 1}行 字段数错误") continue cls, cx, cy, w, h = map(float, parts) if not (0 <= cx <= 1 and 0 <= cy <= 1 and w > 0 and h > 0): print(f"{f} 第{i + 1}行 坐标非法")另一个常见问题是模型在复杂背景下误检,把浪花反光、岸边建筑、集装箱误识别成船舶。这类误检在验证集上往往看不出来,因为验证集的正样本居多、难负样本不足。我的解决策略是增加负样本:专门收集一段不含任何船只的纯水域画面,切成小图放进训练集,标注文件留空即可。这样模型能学到“没有船时输出空白”的能力。实测投入30到50张纯背景图,误检率能有明显下降。
水面目标还有一个通病是形变和遮挡。船只在港口密集停泊时相互遮挡严重,标注框会有大量重叠;在开阔水域两船并行也可能发生遮挡。YOLO系列对重叠目标的能力还可以,但如果遮挡太严重,检测框会合并到其中一个目标上收紧或者跳变。缓解办法是在训练时把mosaic增强比例适当调高,让模型见过更多目标截断的情况。我在训练时开启mosaic并设置mosaic=1.0,比默认设置对密集场景的稳定性更好。
4.3 部署与推理环节的注意事项
训练好的模型落地到实际监控系统时也会遇到新问题。首先是分辨率适配。训练用了1280分辨率,但很多监控摄像头输出的是25帧的1080p流,如果直接把全帧送进模型,推理速度必然跟不上。常见的折中方案是维持1280作为输入,但通过降低帧率来处理,比如每5帧检测一次;更高要求时再做边缘端的批处理优化。
其次要考虑目标重叠时的NMS阈值调整。船舶场景里小目标数量多,且部分船只距离很近,如果NMS阈值设得过高,重叠的检测框会被合并成一个大框,导致目标数量统计不准确;阈值设得太低又会保留大量重复框。我实测在船舶任务上NMS的IoU阈值设在0.4到0.5之间比较合适,具体数值还是要在实拍数据上多试几组对比。
最后说一个很容易被忽视的点:部署后一定要建立闭环反馈机制。模型上线不代表工作结束,要定期把部署环境的难例收集回来,进入下一轮训练。海事场景受季节、潮汐、时段影响很大,夏冬两季的逆光和日照角度完全不同,秋天的雾和春天的雾也不一样。模型如果长期不更新,过一段时间指标退化是必然的。我现在的做法是每个月从监控系统导出一批表现不佳的新样本,人工标注后叠加到原数据集里重新训练,始终保持数据集是活的而不是一潭死水。
最后再分享一个小技巧:做船舶检测数据集,空船背景图的比例不要低于10%。很多人只关注目标样本,忽略了负样本对误检率的影响。我们这边第一次训练时纯水域背景只占了不到3%,结果模型在真实码头画面里把岸吊、堆垛、防波堤全框了一遍。后来把背景图补到10%以上,误检直接降了一大截。数据集构建这件事,很多时候不是拼数量,而是拼对真实场景的理解深度。希望这份整理出来的3000张YOLO格式船舶检测数据集和相关经验,能让你在海事监测项目里少走几步弯路。