这套船舶检测数据集项目,我自己完整跑过一整套YOLO海事监测识别流程。3000张标注图像不算特别大,但胜在类别和场景分布规整,从港口到开阔海面、从白天到傍晚都有覆盖,拿来训练和评估一个船舶目标检测模型完全够用。很多做海事监测的朋友经常问我有没有能直接用的数据,这次我把整理数据集时踩过的坑也一并总结出来,适合刚接触YOLO的初学者,也适合想把手头船舶检测模型精度再往上提一提的工程师参考。
整篇内容会围绕几个问题展开:这份船舶检测数据集应该怎么设计才合理,YOLO模型为什么适合海事监测场景,在PyCharm里怎么一步步把训练环境搭起来,以及训练部署时最容易遇到哪些障碍、怎么排查。我尽量把实操细节和原理讲透,少说空话。
1. 船舶检测数据集:为什么值得专门做一份
1.1 海事监测场景到底需要什么
港口和航道监控的摄像头动辄几十上百路,靠人工盯屏幕看有没有船只闯入、有没有异常靠近,基本是不可持续的事情。人眼连续盯屏20分钟以上注意力就开始下降,而航道上的小目标船只可能就那几秒出现在画面角落,漏掉就可能出事故。所以需要目标检测模型自动把画面里的船舶框出来,再配合跟踪算法和规则判断,这样才能实现全天候的海事监测。
海事监测的典型应用场景包括船舶交通管理系统(VTS)、智慧港口调度、海上搜救辅助识别、渔政执法、海洋保护区巡查等。这些场景有一个共同点:画面里待检测目标就是船,但船的外观、尺寸、拍摄角度差异很大。从数公里外海上一个小点,到近景靠泊的万吨货轮,完全属于同一个检测语义。再加上水面反光、波浪、雨雾干扰,船和背景的区分度不一定高,这让船舶检测比通用物体检测更有挑战性。
用传统图像处理方法做船舶检测,比如背景建模、边缘检测加形状匹配,遇到复杂海况就非常脆弱——浪花的边缘和船体边缘在梯度图上很难区分。深度学习目标检测出现之后,特别是YOLO系列把实时性和精度平衡得很好,海事监测系统才普遍转向这种方案。现在做海事项目,第一步基本就是准备一份能覆盖足够多场景的船舶检测数据集。
1.2 一份能用的船舶数据集包含哪些内容
我整理的这套3000张YOLO海事监测数据集,不是简单从网上爬下来一堆图片就算了。真正好用的数据集至少要包含四个维度:场景多样性、目标尺寸分布、光照条件覆盖,以及规范统一的标注格式。
场景方面要覆盖港口岸线、锚地、开阔海面、河道入海口;目标尺寸要从占据画面百分之五十的大船,到几十像素的小船都要有;光照条件至少要包含晴天、阴天、逆光、夜晚红外、晨昏低照度。公开发布的一些船舶数据集,往往只覆盖某一种视角,比如全是俯拍遥感图,或者全是岸基摄像头图像。但实际海事监控系统里,无人机俯拍、岸边高点监控、船载摄像头都会用到,视角不覆盖全,模型泛化就会出问题。
标注格式也是关键。这份数据集统一转成了YOLO格式的txt文件,每行是“类别id 中心点x 中心点y 宽度w 高度h”,其中四个坐标值都按图像宽度高度归一化到0到1。为什么用YOLO格式?因为ultralytics框架训练时直接读这个格式,不用写复杂的数据解析代码,而且和数据集配套的训练脚本天然兼容。如果你拿到手的是COCO或VOC格式,后续写转换脚本也能转,但多一步就多一个出错的可能。
2. 3000张船舶图像应该怎么设计与标注
2.1 类别与场景怎么取舍
最开始设计类别的时候,我差点把类别搞成几十类,什么散货船、集装箱船、油轮、渔船、客船、拖轮、帆船全都分开。后来实际标了一批数据发现,这纯粹是给自己挖坑。原因很简单:同一艘散货船在不同距离、不同角度下看起来可能比散货船和集装箱船之间的差异还大,标注标准很容易不一致,模型也会学得稀里糊涂。
所以合理的做法是控制类别数量。我最后用的是6类:货船(含散货船、集装箱船)、客船、渔船、工程船(含拖轮、挖沙船)、帆船和游艇、快艇和其他小艇。如果你只关心船舶定位,甚至可以退化到1类,就是单纯检测“船”。类别少,数据量集中的优势就出来了,每个类别能分到的训练样本更多,整体精度自然上去。等模型基线跑稳了,再考虑细分类别也不迟。
场景分布要刻意做平衡。如果3000张图里有1500张是港口靠泊的大货船,那模型对开阔海域的小目标基本就废了。我最后让近岸港口、开阔海面、远距离小目标、夜晚低照度这几类场景各占一定比例,宁可少收一些重复度极高的港口大船图,也要保证那些难样本存在。数据平衡不只是在数量上平衡,而是在“难度”上平衡。
2.2 标注流程和YOLO格式转换
标注工具我用过LabelImg、Label Studio和X-AnyLabeling,小项目推荐LabelImg,轻量直接;样本量大又需要多人协作,用Label Studio更合适。标注船舶时我给自己定了几条死规矩:
第一,检测框要贴合船体的主要轮廓,不要把船头激起的浪花框进去。浪花和船体一起框进去会造成目标框尺寸偏大,影响iou计算,尤其训练后期mAP指标上不去时,这种系统性偏差是主要嫌疑。
第二,目标之间有遮挡时,按可见部分标注,同时保证框内主要是可见船体。不要凭想象把被遮挡的部分也框出来。
第三,太小的目标要设一个阈值。比如图像中船体最长边小于20个像素,就不标了;否则会造成大量极其难以学习的微小高亮框,拉低整体标注质量。每张图的标注质量比数量重要得多。
标注完成之后,要把VOC或COCO格式转成YOLO格式。这里给一个最简单的Pascal VOC转YOLO的脚本思路,核心就是读取XML里的bndbox坐标,再除以图像宽高做归一化:
import os import xml.etree.ElementTree as ET def convert_voc_to_yolo(xml_path, out_dir, classes): tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) lines = [] for obj in root.iter("object"): cls_name = obj.find("name").text if cls_name not in classes: continue cls_id = classes.index(cls_name) bndbox = obj.find("bndbox") x_min = float(bndbox.find("xmin").text) y_min = float(bndbox.find("ymin").text) x_max = float(bndbox.find("xmax").text) y_max = float(bndbox.find("ymax").text) x_center = (x_min + x_max) / 2.0 / img_w y_center = (y_min + y_max) / 2.0 / img_h w = (x_max - x_min) / img_w h = (y_max - y_min) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") out_name = os.path.splitext(os.path.basename(xml_path))[0] + ".txt" with open(os.path.join(out_dir, out_name), "w") as f: f.write("\n".join(lines)) classes = ["cargo", "passenger", "fishing", "engineering", "sailboat", "speedboat"] # 遍历所有xml文件调用即可转换之后必须做一次反向检查。我会用OpenCV把标注框画回图上,肉眼抽查几百张,重点看有没有坐标越界、宽高为0、类别错乱。这一步偷懒的话,后面训练出来的loss曲线一定会非常难看。
2.3 训练集、验证集、测试集怎么划分
划分数据集不能直接random.shuffle了事。如果同一个视频片段抽出来的连续帧被拆到训练集和验证集,验证集就形同虚设,模型见的“新图”其实和训练图高度相似,评估分数虚高。合理的划分方式是先按视频片段或拍摄点分组,再把组整体按比例划分,确保验证集和测试集里的图像与训练集来自不同的拍摄场景。
我一般按7:2:1划分成训练集、验证集、测试集。验证集负责训练过程中的调参和早停选择,测试集只在最终评估时用一次,不反复用来调参。目录结构建议按ultralytics的习惯组织:
datasets/ ├── ship/ │ ├── images/ │ │ ├── train/ │ │ ├── val/ │ │ └── test/ │ ├── labels/ │ │ ├── train/ │ │ ├── val/ │ │ └── test/ │ └── ship.yaml每个标注txt文件和对应图像文件的主文件名保持一致,只是后缀不同。这个命名对应关系是YOLO训练时内部查找label的依据,一旦对不上就会报“image without label”之类的警告,虽然不会中断,但会白白少训练一部分样本。
3. YOLO为什么是海事目标检测的主力
3.1 单阶段检测与两阶段检测的取舍
在做船舶检测之前,我最早试过Faster R-CNN,模型精度其实不错,两阶段检测器先用区域建议网络生成候选框,再对每个候选框做二次分类和回归,理论上对小目标更友好。但放到海事监控的实际项目里,问题很快暴露:一张1080P图像在GPU上推理要几十毫秒,看起来不慢,可要是同时处理十几路摄像头,帧率立刻不够。而海事监控对实时性的要求又很高,你需要的是每帧都尽快给出结果,不是慢悠悠算出一个高精度框。
YOLO作为单阶段检测器,把目标检测当成一个回归问题端到端直接求解。图像被分成S×S的网格,每个网格负责预测目标中心落在自己区域内的边界框、置信度和类别概率。这个设计天然快,因为整个检测过程只过一遍网络,不需要额外的候选区域生成阶段。在船舶这种目标刚性较强、背景相对固定的场景里,单阶段模型完全够用。
你可能担心YOLO对小目标效果不好,这确实是它的短板之一,但这个短板更多来自输入分辨率、特征金字塔设计,而不是“单阶段”这个思路本身。YOLOv8以后引入了更细粒度的特征融合结构,配合合适的输入尺寸,对开阔海面上的小船检测能力已经足够应付大多数监测场景。
3.2 不同版本和模型大小怎么选
YOLO版本迭代很快,从YOLOv5到YOLOv8,再到YOLOv11,听起来眼花缭乱,其实选型逻辑很简单:先看你的部署设备,再看你对精度的要求。
YOLOv5最大的优势是稳定、生态成熟、资料多,很多嵌入式设备上的TensorRT推理代码还是基于v5写的。YOLOv8在结构上做了不少升级,内置anchor-free检测头,支持实例分割,训练收敛更平稳,ultralytics框架对它支持也最好,适合从零开始做项目。YOLOv11是较新的版本,在一些基准上有提升,但如果你的部署环境还停留在兼容老版本算子上,不建议盲目追新。
模型大小方面,同一系列通常有n/s/m/l/x五个档位。我常用的是s和m:s模型参数量小、推理速度快,放在Jetson Orin这类边缘设备上跑视频流正合适;m模型精度更好,部署到服务器或者有足算力的工控机上用。实际项目中可以先拿m模型训练一版,确认能达到精度目标,再蒸馏或者直接换s模型上边缘设备。可以在本地记录几种模型的对比参数,方便后续参考:
| 模型 | 参数量 | 输入尺寸 | 推理速度(GPU) | 适配设备 |
|---|---|---|---|---|
| YOLOv5s | 约7M | 640 | 较快 | 边缘盒子 |
| YOLOv8s | 约11M | 640 | 较快 | Jetson Orin |
| YOLOv8m | 约25M | 640 | 中等 | 工控机/服务器 |
| YOLOv8x | 约68M | 640 | 较慢 | 服务器 |
版本之间不要总想着换最先进的那个。海事项目最怕模型推倒重来,一次选型之后标注数据、后处理逻辑、告警规则都耦合在一起了,换模型等于重做一遍集成测试。
3.3 损失函数和训练监督信号
YOLO的训练损失由三部分构成:分类损失、边框回归损失、置信度损失。分类损失让模型学会区分船舶类别,边框回归损失让预测框和真实标注框套得更准,置信度损失让模型判断“这里到底有没有目标”。理解这三部分,能帮你训练出问题时快速定位原因。
分类分支用的是二元交叉熵,对每个类别独立判断“是/不是”。边框回归分支在YOLOv8里用CIoU加DFL,CIoU考虑了两个框的重叠面积、中心点距离和长宽比的差异,比普通IoU回归更稳定。简单说,同样让预测框向真实框靠拢,CIoU不仅能缩小重叠误差,还能兼顾框的中心位置和形状比例,目标还没完全对齐之前梯度信号也不会消失。DFL则是把边界框的数值预测变成离散概率分布,提升数值稳定性。
置信度分支负责衡量预测框内是否真的有目标。在实际训练中,如果你发现loss下降很快但mAP一直偏低,往往就是置信度分支出了问题,比如正负样本严重不均衡,导致模型倾向于把大部分区域预测成“无目标”。解决思路是调节正负样本分配策略,比如对anchor匹配阈值做调整,或者减少背景占比。这些细节在训练日志里都有迹可循,关键是别只看总loss,要把三个分支的loss曲线拆开看。
4. 用PyCharm把训练环境跑通,实操记录
4.1 环境安装:从conda到ultralytics
PyCharm装YOLO训练环境,最省心的流程是先用conda建独立虚拟环境,避免把系统Python整乱。打开终端执行:
conda create -n ship-yolo python=3.10 -y conda activate ship-yolo安装PyTorch时要注意CUDA版本。如果我没记错,当前ultralytics稳定版本对PyTorch 2.x支持都很好,只要你的nvidia驱动能支撑对应CUDA就能跑。安装命令按官网生成的为准,大致是:
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 pip install ultralytics装完验证一下:
yolo predict model=yolov8n.pt source=https://ultralytics.com/images/bus.jpg如果这句命令能跑通,说明ultralytics安装没问题,也说明运行流程是通的。在PyCharm里只需要把项目解释器指向刚才建好的conda环境,后面写训练脚本时导入ultralytics就不会出现ModuleNotFoundError。
这里有一个经验:不要图省事用pip直接装在base环境。海事项目经常要同时装opencv、shapely、ao3d、pymongo这些依赖,版本冲突早晚会炸,虚拟环境隔离之后至少不会牵连系统环境。万一环境装坏了,conda里直接删掉重建,两分钟就能恢复。
4.2 数据配置与训练启动
训练之前要写好数据集的yaml配置。网上很多示例把path写绝对路径,换个机器就报错,我习惯用相对当前yaml的位置来写:
path: ./ship train: images/train val: images/val test: images/test nc: 6 names: 0: cargo 1: passenger 2: fishing 3: engineering 4: sailboat 5: speedboat然后就可以启动训练。我通常先加载COCO预训练权重做迁移学习,这样比从头训练收敛更快,也能借ImageNet或COCO上学习到的通用特征提升小船识别能力:
yolo detect train \ data=ship.yaml \ model=yolov8s.pt \ epochs=200 \ imgsz=640 \ batch=16 \ workers=8 \ patience=30 \ project=run/ship_detect \ name=exp1几个参数值得细说。imgsz默认是640,但海事场景里远处的小船在640下可能只有10几个像素,效果必然不好。如果你的显存够,建议直接训练1280甚至1536。yolov8s在1280分辨率下显存占用接近12GB,如果是RTX 3060 12GB以上显卡可以勉强跑,显存不够就先用640训练,后期用更大输入尺寸微调几轮。batch尽量开大,模型收敛更稳,但如果出OOM就减半。patience设30,意思是验证集mAP连续30个epoch没有提升就提前停止,避免白白消耗算力。
训练过程中不要完全放手不看。我一般半小时左右瞄一眼损失曲线,如果发现总loss在前几个epoch剧烈震荡,多半是数据里有标签错误或者学习率过大。这时候停下来修数据,比让训练继续跑完最后发现模型垃圾要省时间得多。
4.3 评估指标与推理验证
训练结束后,直接看验证集上的mAP。注意不要只看最后一行,把验证集中每个类别的Precision、Recall、mAP50、mAP50-95拆开分析。
mAP50是IoU阈值取0.5时的平均精度,mAP50-95是在0.5到0.95之间多个IoU阈值下的平均,后者更严格。船舶检测里,有时候mAP50很高但mAP50-95低,说明模型框得大概对但不够精细,这对海事里做精确测距、尺寸估算的项目会有影响。如果类别不均衡,比如货船样本多、帆船样本少,mAP均值可能被货船带高,这时候要看帆船这一类单独的recall,通常这是最低的那个类别,也是后续数据增强要重点补充的方向。
为了让结果更直观,可以用以下命令对一组测试图做批量推理:
yolo predict model=run/ship_detect/exp1/weights/best.pt source=datasets/ship/images/test save=True输出图片里会画上检测框和置信度。我会特意看一下几个难样本:小目标、逆光、夜晚。如果难样本漏检,先别急着加大epoch,重点排查输入尺寸和数据增强是否覆盖了这些情况。还有一个小技巧:把测试集结果导出成csv,按置信度排序,看那些高置信度却标错的样本,往往能发现标注标签里类别混淆的共性问题。
5. 实操中常见的坑与排查记录
5.1 小船漏检与输入尺寸的关系
做海事船舶检测,最常遇到的问题就是“远处的小船没检测出来”。从物理上讲,小船在图像里占的像素太少,卷积下采样几轮之后,特征图上的响应可能已经消失,模型没有足够的信息做判断。解决思路有几个方向。
最直接的是提升输入分辨率。把imgsz从640提到1280,相当于给模型更多像素去看到那个目标,mAP提升通常非常明显。代价是显存和推理时间同步上升。如果设备算力有限,还可以用切图推理,也就是SAHI的思路:把大图切成若干有重叠的小块分别推理,再把结果合并。船舶检测场景里这招很有效,因为船是稀疏目标,切块不会把一个完整目标截断太多,配合非极大值抑制就能把重合框去掉。
我常用的组合是:训练时用960分辨率,推理时用detector加切图策略。这样训练成本可控,部署时又能针对大图区域精细检测。一旦小船漏检,不要急着换更大的模型,先把输入尺寸和切图参数试一遍,往往收获十倍于换模型的收益。
5.2 损失不收敛怎么排查
训练时loss变成NaN或者验证集mAP一直是0,第一步不要调参,先查数据。最常见的原因是label文件里有坐标越界。YOLO格式要求所有值在0到1之间,但标注或转换时有可能生成大于1或小于0的数字,比如VOC转YOLO时忘了按图像宽高归一化。还有个别标注文件为空,或者类别id超出了nc范围。
快速写一个检查脚本:
python -c " import os for f in os.listdir('datasets/ship/labels/train'): path = os.path.join('datasets/ship/labels/train', f) with open(path) as fh: for line in fh: parts = line.strip().split() if len(parts) != 5: print('格式异常', f) vals = list(map(float, parts[1:])) if any(v < 0 or v > 1 for v in vals): print('越界', f) "如果数据干净,再看损失曲线。总loss在高位震荡不下降,优先调低学习率,比如从0.01降到0.001。YOLOv8默认的lr其实已经比较合理,但如果你换了优化器或者改了衰减策略,就可能失控。batch太小时,梯度噪声大,loss曲线也容易乱跳,这种情况优先加batch而不是调学习率。
如果训练正常但验证集mAP偏低,注意看是不是数据增强太强,让验证集和训练集的分布差距变大。YOLO默认开启不少增强策略,比如随机翻转、马赛克、HSV扰动,训练后期减小增强强度常常能稳定提升精度。
5.3 ONNX导出与部署踩坑
训练好的模型要部署到监控系统,通常先导出ONNX,再转为TensorRT或OpenVINO等推理格式。导出命令很简单:
yolo export model=best.pt format=onnx opset=12 dynamic=True但我第一次导出后就遇到了预处理尺寸不一致的问题。YOLO训练时图像会做letterbox,就是等比缩放加灰边,保证输入尺寸是固定正方形。推理端如果用OpenCV直接resize到正方形,没有letterbox,模型的效果会大打折扣,尤其是对宽屏监控画面影响明显。
正確的做法是在部署代码里实现letterbox逻辑,并在推理后把检测框坐标映射回原图坐标系。这个环节最容易出错。排查方法也很直接,用同样的图像分别在PyTorch和ONNX Runtime上推理,对比输出框差异。如果差异大于1个像素,就要检查预处理、归一化、输入张量的通道顺序是否一致。
导出时还有个常见坑:opset版本设置过高,旧版TensorRT不支持。保险起见先设opset 12,遇到节点不兼容再加简化脚本处理。总体思路上,模型能跑通不是终点,部署端的效果复现才是真正交付的那一刻。所以我会在导出的代码注释里把letterbox参数和坐标映射函数写清楚,留给后续接手的人减少踩坑时间。
6. 从目标检测走向海事智能感知
6.1 结合多模态分析与实例分割
船舶检测数据集解决的是“船在哪、是什么船”的问题,但完整海事感知系统还需要知道“这艘船要干嘛、对航道有没有威胁”。所以现在很多项目会把YOLO目标检测和下游分析模块串联起来。例如检测到船舶之后,再接OCR识别船体上的船名标识,调取AIS报文数据库做身份校验,判断该船是否偏离既定航线。也有人在检测框内继续做实例分割,把船体轮廓抠出来,用于测算船舶吃水线或载重情况,这套流程对港口码头调度特别有价值。
如果要用实例分割,数据集的标注格式就要从矩形框升级为多边形分割掩码。这里提醒一句:如果没有充足的人力去标注密集的多边形轮廓,不要轻易从框检测转分割。实例分割对标注精度要求极高,标注不齐会让模型训练效果比框检测还差。想要平稳过渡,可以先保留已有的框标注,再在少量关键类别上补充多边形标注,配套使用半自动标注工具辅助人工修正。
多模态AI分析是另一个方向。把检测到的船舶类别、位置、速度、轨迹等结构化信息,连同监控画面片段,一起送入大语言模型或多模态模型,可以自动生成更直观的告警描述,比如“北侧航道有一条大型货船正在向左偏航,可能影响后方小船”。这种“YOLO目标检测+多模态分析”的组合,已经开始在智慧海事、智慧交通等场景应用。底层的数据集质量直接影响整个系统的效果上限,基础模型漏检,后面分析模型再聪明也没用。
6.2 数据集版本管理与持续迭代
数据集不是一次性工作。实际部署后会发现新场景不断出现,比如某个港口夏季有大量养殖区渔船聚集,冬天有浮冰,这些都会让原有模型指标下降。所以我建议从一开始就做数据版本管理。每一批新增图像单独放一个目录,标注修正后整体合并,再重新训练。不能用“final_v2”这种命名方式,过两周你就分不清哪个版本是训练主线,哪个是废弃分支。
持续迭代时最省时的方法是半自动标注。用当前最准的模型去预测新采集的图像,生成初步标签,人工只需要修正错误框和补充漏检目标。这个流程比完全手工标注效率高很多,但要注意不要让模型自己的错误变成标注标准的一部分,否则随着迭代,数据里的系统性偏差会不断自我强化。定期抽检已经标注完成的新数据,比对人工标注和模型预测结果,能有效扼制这个问题。
如果数据量持续扩充到5000张甚至10000张,可以训练不同版本模型并存:一个偏向快速推理的轻量版用于边缘设备,一个高精度版用于服务器离线分析。两套模型基于同一个数据版本训练,减少数据不一致带来的对比干扰。整体而言,把数据集当成产品维护,才能保证长期稳定的船舶检测效果。
最后再分享一点我个人的体会:3000张船舶图像的数据集,在别人眼里可能只是一个规模中等的数据资产,但真正把检测模型从mAP50约0.85推到0.93以上,靠的往往不是换更先进的模型结构,而是把数据里的标注误差一点点抠干净,把场景分布测到平衡,把推理时那几个坐标变换细节抠到一致。谁能在这些细节上多花功夫,谁的船检模型就会更稳一点。这套来回调试积累下来的工程经验,比单纯拿到一份现成数据集珍贵得多。