1. 做海事监测,为什么不能直接套用通用目标检测数据集的思路
先交代一下背景。我之前一直在做航运相关的视觉项目,手头需要一套能跑船舶检测的底料。市面上的通用目标检测数据集(比如COCO、VOC那一类)里也有boat这个类别,但真放到海事监测场景里,用过的人都知道有多难受:江面上一条小舢板和远洋货轮的检测难度完全不在一个量级,加上水面反光、雾霾、浪花干扰,直接用通用权重去推理,漏检和误检能让人怀疑人生。
后来我干脆自己攒了一套船舶检测数据集,用YOLO格式标注,规模在3000张左右,专门针对海事监测场景。这套数据集早期是我做航道安全项目时的内部产物,后来整理成了相对规范的版本,跑过YOLOv5、YOLOv8和YOLOv11,效果比我预想中好不少。这篇文章就把这套数据集从取材、标注、清洗到训练的完整链路拆开讲一遍,重点讲海事场景下那些“不试不知道”的坑。
先说结论:通用数据集解决的是“有没有船”的问题,海事专用数据集解决的是“这是什么类型的船、它在哪、有多大、能不能被稳定追踪”的问题。如果你只是做个Demo,COCO的boat类别勉强够用;如果你想做真实的航道监控、港口调度辅助、非法捕捞识别,就必须有一套贴近现场的数据集来支撑。
这套数据集的核心价值在三个地方:一是贴近真实海事视角,图像大多来自岸基摄像头、巡逻艇搭载的相机、无人机巡检视角,跟卫星图那种俯瞰完全不同;二是类别划分按海事业务需求来,不是简单的一个boat完事;三是标注细节上考虑了船只密集、相互遮挡、远小目标这些现实问题。下面我会把这套数据集从里到外讲清楚。
2. 数据集设计与构成:3000张图里面到底装了什么
2.1 图像来源与场景分布
数据来源我尽量做到多元,单一来源的数据喂出来的模型会有严重偏向。这3000张图大致来自三个方面:
- 岸基固定摄像头:占比大约55%。这类视角的特点是画面相对固定,船只从左到右或从右到左穿行,中近景较多,目标尺寸适中,但容易出现船与船之间的交错遮挡。
- 巡逻艇/执法船随船拍摄:占比大约25%。视角贴近水面,晃动明显,经常出现近距离大目标,一张图里可能只有一艘船占据画面三分之一的情况。
- 无人机巡检航拍:占比大约20%。视角是从上往下斜拍,目标整体偏小,密集停泊场景多,对模型的小目标检测能力要求高。
这三个视角的差异非常大,如果只用单一视角训练,部署到另一种视角上精度会掉得很难看。我见过有人用纯无人机航拍数据训练模型,然后直接丢到岸基摄像头上用,结果大部分时候都在漏检,这不是模型不行,是数据分布不匹配。
场景分布上也做了刻意均衡:开阔水域的单船场景约占30%,港口密集停泊场景约占25%,航道多船同向/对向行驶场景约占25%,恶劣天气(雾天、雨天、夜间低照度)约占20%。恶劣天气这部分国内公开数据集里很少见,但对真实海事监测来说恰恰是刚需。夜间低照度图不多,因为纯夜视数据需要红外或低照度相机,当时采集条件有限,但雾天和雨天样本是够的。
2.2 类别定义:不是只有“船”这一个框
海事业务里,只标一个“船”是远远不够的。这套数据集一共标注了6个类别:
| 类别 | 标注名称 | 典型特征 | 占比 |
|---|---|---|---|
| 货船 | cargo | 体型大,吃水深,甲板有集装箱或舱盖 | 约28% |
| 渔船 | fishing | 中小型,甲板设备杂乱,常有桅杆 | 约22% |
| 客船/渡轮 | passenger | 上层建筑明显,多楼层,航速较快 | 约16% |
| 工程船/驳船 | engineering_barge | 平甲板,无动力或动力弱,常被拖带 | 约14% |
| 小型艇/摩托艇 | small_boat | 尺寸小,速度快,无明显上层建筑 | 约13% |
| 其他船只 | other_vessel | 不便于归入上述类别的船舶 | 约7% |
有人可能会问,为什么把渔船和货船分开?因为从业务角度来说,渔船和货船的监管逻辑完全不同——货船查航道偏离、靠离泊安全,渔船查的是非法捕捞、禁渔区闯入。而且从技术角度,这两类船的形态差异很大,货船边缘锐利、结构规整,渔船甲板杂乱、轮廓不清晰,混成一个类别会让模型学得很困惑。实测下来,分开标注的mAP平均比合并标注高4到5个点。
2.3 标注总量与信息
- 图像总数:3000张
- 标注框总数:约11800个
- 平均每张图标注目标数:约3.9个
- 最大单图标注数:52个(港口密集停泊场景)
- 图像分辨率:大部分为1920×1080,少部分为1280×720和3840×2160
标注格式严格按YOLO格式存放,每个图像对应一个同名txt文件,每行是“class_id x_center y_center width height”,坐标全部归一化到0~1之间。这个不做赘述,做过目标检测的人都熟。
需要强调的是,标注不是一次性做完的。第一轮用标注工具人工框完,第二轮统一审核,第三轮我用训练好的初版模型对全量数据做了一次预标注,发现漏检目标又补了大约700个框。三轮下来,标注质量才算是过了自己这关。
3. 标注过程中海事场景特有的坑:遮挡、反光、目标尺寸与命名规则
3.1 船只互相遮挡时的标注原则
这是海事场景和通用场景差异最大的地方。行人检测里,人被柱子挡了一半,通常是可以不标的,因为训练一个半身人意义不大。但船不一样,船与船之间的遮挡在海事画面里是常态,港口停泊时一艘船挡在另一艘前面,航道里小船从大船前面穿过,这些都是高频情况。
我的标注原则是:只要目标可见部分超过40%,就标注完整船体(含被遮挡部分的估算范围);低于40%则不标注。40%这个阈值不是拍脑袋定的,我对比过不同阈值下的训练效果:50%会导致大量“半遮船”不参与训练,推理时模型对遮挡场景极其敏感;30%会引入太多只露一个船头的样本,模型学到一堆噪声。40%左右时,mAP和漏检率能取得一个较好的平衡。
这里面有一个人工标注的细节:标被遮挡船只时,框的边界如果贴不到真实船体轮廓,宁可比实际可见范围略大,也不要略小。因为YOLO的框是轴对齐矩形,如果目标被遮挡后实际可见形状不规则,框框稍微外扩可以包容形状误差,训练更稳定。如果框比可见范围还小,模型学到的就是一个残缺形状,推理时反而容易把目标切碎。
3.2 水面反光与浪花虚影要不要框
水面的白浪、船尾的尾流、阳光在波纹上的反射,这些对新手标注员来说是巨大的干扰。我的规则很简单:浪花、尾流、倒影一律不标,即使是紧贴船体的倒影也不标。倒影本质上是光学伪影,如果把它框进来,模型学到的特征是“船加倒影”的组合,一旦测试场景的水面条件变了,检测稳定性就会下降。
但有一个例外:小型艇在高速行驶时,艇体和尾流在视觉上连成一片,人的肉眼都很难分清楚哪里是艇体哪里是水花。这种情况下,我选择框到艇体可见轮廓的末端为止,不带尾流,即使这样看起来框不够“饱满”。宁缺毋滥,训练数据里的边界噪声越少,模型的泛化能力越好。
3.3 远小目标的标注下限
海事场景里,远小目标是一个绕不开的问题。同一艘船,在2海里外可能只有20×20像素,到100米外可能变成300×300像素。YOLO系列对小目标的检测能力本来就偏弱,如果标注时把大量10像素以下的框也标进去,训练时损失函数会被这些小目标带偏。
我的标注下限是目标长边不小于16像素,小于这个尺寸的船体不标注。16像素这个值也是有依据的——在1920×1080分辨率下,16像素的船约等于人眼在屏幕上刚能辨认出“那是条船”的极限。比这更小的目标,人眼都无法确认类别,标注准确率没法保证,标了反而引入噪声。
这带来一个问题:小目标不标注,模型不就永远学不会小目标检测了吗?我的做法是单独处理。对于长边在16到32像素之间的目标,正常标注;对小于16像素的目标集中的图片,要么重新裁切放大后进行二次标注,要么干脆从训练集中剔除。实测下来,与其保留一堆质量不可控的微小目标标注,不如剔除后让模型把注意力集中在可靠目标上,推理阶段再通过图像金字塔或多尺度推理去补足小目标检测能力。
3.4 一个数据集的隐性价值:命名规范与版本管理
我见过很多团队的数据集整理得一团乱:文件夹叫“final_v2”,里面有“标注_最终版”,还有一堆“检测结果(1)”.zip。自己做的数据集,命名规范一定要从一开始就定好,因为数据集的迭代是长期的,模型训练是从不同版本的数据快照里随机抽样的,命名混乱会导致追溯困难。
我的建议格式很简单:
ship_dataset/ images/ train/ # 2400张 val/ # 400张 test/ # 200张 labels/ train/ val/ test/ classes.txt dataset.yaml数据集文件名统一为“来源标识_日期_编号.jpg”,比如camera_20240612_0001.jpg,对应的标注文件名是camera_20240612_0001.txt。这个看起来是个小事,但实际做下来,当你要回查“这批图片是哪个相机拍摄的”“这套模型的验证集里有没有无人机视角的图”时,命名规则能帮你节省大量排查时间。
4. 训练集、验证集、测试集的划分策略:最容易被低估的一个环节
4.1 按场景来源划分而不是随机划分
很多人在划分数据集时直接random split,这个对通用数据集问题不大,但对海事场景会埋下很大的坑。原因在于,同一时间段、同一机位拍摄的连续帧,画面高度相似,如果这些相似帧同时出现在训练集和验证集里,验证集失掉了“验证”的意义,模型在训练阶段就把验证集的答案背下来了。
我的划分方式是:先按视频片段分组,确保同一段视频的帧要么全部进训练集,要么全部进验证集,绝不混合。具体做法是,每个拍摄片段给一个来源编号,划分时以来源编号为最小单位做随机抽样。这样验证集中出现的画面,和训练集中的画面在时间、机位、光照条件上都有实质差异,评估结果才真实。
另一个原则是按场景比例分层抽样。港口停泊场景、航道多船场景、单船开阔水域场景、恶劣天气场景,这四个子集在训练集和验证集中的比例保持一致,防止出现验证集里全是港口密集场景而训练集里几乎没有的失衡情况。
4.2 测试集的保留:评估“没见过”的场景
测试集我单独保留了200张,来源是模型迭代过程中完全没参与过训练和验证的批次。这200张里面还包括一小部分后期补充的、跟早期分布有差异的图像(比如换了一处水域的拍摄画面),专门用于检验模型的泛化能力。
这里我吃了不少亏。最开始我没有单独保留测试集,每次迭代都用同一份验证集评估,结果模型mAP一路涨到0.87,感觉已经很好。后来把模型放到新水域的实拍画面里测试,mAP直接掉到0.61,才发现之前一直在用“考试题目”评估模型。从那之后,测试集固定封存,任何版本的模型在发布前都要跑一遍这200张图,用这个分数作为真实的对外指标。
4.3 目录结构与数据加载的细节
YOLO系列训练时要求images和labels的目录结构一一对应。常用做法是在根目录下建images文件夹和labels文件夹,每个文件夹内部再分train/val/test子目录。注意dataset.yaml里路径要写绝对路径还是相对路径——我建议写绝对路径,因为换机器训练时YOLO会自动创建数据集软链接,相对路径在某些版本上会出奇怪的问题。
dataset.yaml内容示例:
path: /home/yourname/ship_dataset train: images/train val: images/val test: images/test names: 0: cargo 1: fishing 2: passenger 3: engineering_barge 4: small_boat 5: other_vesselheaders不需要,YOLO读yaml时只需要path、train、val、names这四个字段。test字段在有些版本里不读,这不影响训练,只要train和val正确就行。
4.4 类别不平衡的处理思路
这个数据集里,small_boat类别占比只有13%,而cargo占了28%。如果不做任何处理,训练时模型天然会对cargo学得更好。我在训练时开了YOLO自带的类别频率平衡策略,同时给small_boat类别设置了略高的loss权重。另外还有一招很有用:对包含small_boat的图片做mosaic增强时尽量保留小块目标不被裁掉,因为mosaic操作容易在拼接时把小目标随机裁切出画面,导致小目标样本被无意识削弱。
5. 用这套数据集跑YOLOv8的训练配置与实测效果
5.1 硬件环境与训练参数
训练机配置供参考:CPU是Intel Xeon,GPU是单张NVIDIA RTX 4090 24GB,内存32GB,系统Ubuntu 22.04,CUDA 12.1,PyTorch 2.1.0。如果显存小于24GB,建议调低batch size,后续我会给出参考参数。
我用的模型型号是YOLOv8l,输入分辨率设置为1280×1280。为什么是1280而不是默认的640?因为海事场景的目标普遍偏小,640的输入下,一条64像素的船在特征图里只占几个网格,小目标检测非常吃力。提升到1280后,小目标的mAP提升约9个百分点,训练时间大约增加80%。如果你的设备跑不动1280,建议用640训练后用1280做推理,推理时的高分辨率能补一部分小目标检测能力。
核心训练参数:
| 参数 | 值 | 说明 |
|---|---|---|
| model | yolov8l.yaml | 使用large版本 |
| imgsz | 1280 | 输入分辨率 |
| batch | 16 | 24GB显存下的安全值 |
| epochs | 150 | 配合early stopping |
| optimizer | SGD | 对目标检测更稳定 |
| lr0 | 0.01 | 初始学习率 |
| mosaic | 1.0 | 前100轮开启 |
| close_mosaic | 10 | 最后10轮关闭mosaic |
| amp | True | 混合精度训练 |
5.2 从零训练还是迁移学习
海运数据集虽然已经有3000张,但要训练一个l模型,从随机初始化开始跑150轮的话,初期收敛很慢,而且很容易过拟合。我的做法是加载YOLOv8l在COCO上的预训练权重做迁移学习。很多人觉得反正只是做船类检测,COCO里有boat类别,预训练权重迁移过来应该效果不错。实测确实如此:迁移学习起点高,收敛快,最终mAP比从零训练高出2到3个点。
但也有一个反向的坑:COCO预训练权重把boat类别的特征学得比较“泛”,迁移到我的数据集后,前几个epoch模型容易把渔船误检成货船,因为渔船和货船在COCO里大概率都被归到了boat这一个类。我的做法是训练时把单类别的置信度阈值调高一些,等模型在自定义数据集上多跑几十轮之后,置信度分布会自然校正。
5.3 实测指标:检测精度与类别的优劣差异
经过150轮训练,测试集上的指标如下:
| 类别 | mAP@0.5 | mAP@0.5:0.95 | 备注 |
|---|---|---|---|
| cargo(货船) | 0.941 | 0.783 | 结构规整,识别率最高 |
| fishing(渔船) | 0.897 | 0.702 | 甲板杂乱导致少量漏检 |
| passenger(客船) | 0.915 | 0.731 | 上层建筑特征明显 |
| engineering_barge(工程船/驳船) | 0.906 | 0.718 | 平甲板目标区分度一般 |
| small_boat(小型艇) | 0.782 | 0.511 | 尺寸小、速度快,识别率最低 |
| other_vessel(其他船只) | 0.734 | 0.456 | 类别本身定义宽泛 |
整体mAP@0.5在0.88左右,mAP@0.5:0.95在0.68左右。作为参考,我用COCO预训练模型直接推理同一份测试集,mAP@0.5只有0.63,差距主要出现在渔船的误检和远小目标的漏检上。
small_boat和other_vessel这两个类别的指标偏低,我复盘后认为是两类目标的定义边界问题——小型艇和渔船在远距离上人眼都很难区分,other_vessel则是各种杂船都有,类内差异太大。如果业务上不太关心这两类,可以在训练时把它们合并成一个“小型船只”类别,mAP能拉高不少。
5.4 训练过程中的两个常见炸点
炸点一:mosaic增强导致的标注框漂移。YOLOv8训练时mosaic会把4张图拼在一起,如果某个标注框恰好落在拼接缝附近,模型加载时可能读取到坐标越界的情况。这个问题在训练日志里表现为loss突然异常升高。处理办法是在hyp.yaml里把mosaic的剪切比例调小一点,或者检查数据增强阶段是否有目标框面积为负的样本被记录。我做了一次全量检查,发现有几十个标注框的width或height为0(某些标注工具手滑会导出这种脏数据),训练前把它们全部清洗掉了。
炸点二:验证集的conf阈值设置有误导致mAP虚高。YOLO训练时的验证阶段,mAP的计算依赖置信度阈值。如果阈值设得太低,大量低置信度的预测会被当作正样本参与计算,mAP会虚高。建议把验证时的conf设为0.001,这才是mAP计算的合理下界。有些教程喜欢用0.25甚至0.5去算mAP,那不是在算mAP,是在算精确率。
6. 从“检测出船”到“能落地用”:推理部署阶段的工程化改造
6.1 NMS参数和置信度阈值需要按场景调
训练完模型,直接拿去推理,很可能发现结果跟测试集上差距挺大。原因在于推理时的预处理和后处理和训练时不完全一致,尤其是NMS参数和置信度阈值。
我推荐的推理配置参考:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| conf | 0.25 | 低于这个分数都算漏检,可根据场景调 |
| iou | 0.45 | NMS的IoU阈值 |
| max_det | 300 | 单图最大检测数 |
| imgsz | 1280 | 与训练一致 |
在港口密集停泊场景里,船与船紧密相邻,建议把iou调高到0.5甚至0.55,避免NMS把两艘贴着停的船误合并成一个框;在开阔水域单船场景,可以适当降低iou,没什么影响。
6.2 多尺度推理对远小目标的收益
前面提到训练用小目标检测能力偏弱,推理阶段可以做多尺度推理来弥补。YOLO的augment=True参数会自动做多尺度推理,但代价是推理时间翻倍。我实测下来的经验是:在边缘设备上部署时不需要开多尺度;在服务器上做离线分析时建议开启。无人机航拍视角下,开启多尺度推理后小目标检测准确率提升约6个百分点,对实时性要求不高的场景收益明显。
6.3 模型导出:ONNX与TensorRT部署细节
如果你用的是YOLOv8或YOLOv11,导出ONNX后部署到服务端的流程很成熟。我在这里分享三个容易踩的坑:
- ONNX导出的opset版本建议选12以上,否则一些算子兼容性有问题。
- 导出时
simplify=True可以减小模型体积,但必须验证导出前后的推理结果是否一致,个别简化操作会导致数值精度下降。 - TensorRT部署时,如果GPU是Ampere或更新的架构,建议开启FP16精度,推理速度能提升一倍左右,精度损失在可接受范围内。但如果是老架构的卡(比如Tesla P4),FP16支持不完整,强行开启会出现未知错误。
7. 海事监测数据集的后续迭代方向与个人总结
7.1 下一阶段数据补强的三个方向
这套数据集目前已经能满足基本的船舶检测需求,但从实际项目反馈来看,还有三个明显的短板值得补强:
- 夜间红外图像。夜间的船只在普通RGB相机里几乎不可见,目前这块几乎是空白。
- 更多无人机俯拍视角的高密度场景。港口锚地停泊时,上百艘船密密麻麻,现有的52个单图最大目标数还不够极端。
- 雨雪天气下的镜头污渍干扰。摄像头镜片沾水后,画面局部模糊,这个和雾天不完全一样,模型需要单独适应。
7.2 这套数据集还能怎么扩展
除了纯检测任务,海事监测还能往几个方向扩展:第一个是加上多目标跟踪(比如ByteTrack或DeepSORT),把检测结果串成航迹,做船舶流量统计和异常行为识别;第二个是在检测框的基础上加分类分支,识别船的运行状态(航行、锚泊、靠泊);第三个是结合AIS数据做传感器融合,用视觉检测结果去校验AIS上报的船位信息,发现“幽灵船”或者AIS关闭的违规目标。
我自己的下一步计划是把这个数据集扩展成带跟踪标注的视频版本。毕竟单帧检测只能回答“这里有什么船”,而海事监管更需要回答“这条船从哪里来,要往哪里去,它有没有异常行为”。这需要从数据层面提前布局。
7.3 一些掏心窝的经验
最后说几点我实操过程中最深切的体会。第一,数据集的质量优先级永远高于数量,3000张高质量的图,胜过10000张随手抓的图,清洗数据花的时间会在模型训练阶段几十倍地赚回来。第二,海事场景没有“放之四海皆准”的模型,不同水域、不同机型、不同架设高度都会让数据分布发生漂移,有条件时尽量在部署现场采集小批量数据做fine-tune,哪怕只有两三百张,效果都天差地别。第三,别迷信某个框架或某个模型的“官方精度”,一切以你自己的测试集分数为准,因为只有你才知道真实场景长什么样。
这套数据集和完整的YOLO训练配置,我目前整理成了一个标准目录结构。有做海事监测相关项目的朋友,完全可以直接按这个思路去搭建自己的数据管线。做数据集从来不是最光鲜的活,但它决定了你的模型天花板在哪。希望这里面关于标注规则、训练参数、部署细节的经验,能帮你少走一些我已经踩平的弯路。