☰
船舶检测数据集实战:YOLOv8训练与边缘部署
2026/10/14 14:30:28 网站建设 项目流程

做海事目标检测的人应该都有同感:搞模型最怕的不是算法调参,而是没有一套像样的数据。海上场景和普通道路不一样,光照变化剧烈、海面反光强烈、目标尺度跨度大,远处的船可能只有几个像素,近处的船能把整个画面占满,还经常遇到雾天、逆光、浪花干扰。我在做港口智能监控和海上交通管理系统时,拿到一套3000张的YOLO船舶检测数据集,从数据清洗、标注质检到YOLOv8训练、ONNX导出、AGX Orin部署,这条链路走下来发现很多坑是海事场景独有的。我把整理的细节和踩过的坑一条条写清楚,想自己准备船舶检测数据集,或者正在训练YOLO却卡在精度上不去的朋友,可以参考。

1. 项目解读:一套海事船舶检测数据集的核心构成

1.1 为什么海事检测比公路检测更折磨人

目标检测在公路、园区场景已经非常成熟了,但一换到海上,情况完全不一样。首先是背景干扰。海面不是平滑的,波浪会产生大量纹理,阳光照射下还有高光反射,这些在检测器眼里全是噪声。其次是目标的尺度跨度问题。同样一艘货船,在近岸监控画面里能占几百个像素,在远距离航拍画面里可能只有十几个像素,甚至可以混在波纹里看不出来。最后是类别混淆——货船和客船从正上方看轮廓相似,渔船和拖网在画面里形态多变,如果标注的边界框不规范,模型很容易学偏。

这就解释了一个现象:同样的YOLO权重,在COCO数据集上跑得挺欢,一拿到船舶数据就崩。不是模型不行,是数据和场景适配的问题。专门为海事场景收集的数据集,价值就体现在这里:它所包含的目标形态、背景分布、光照条件都是贴着真实监控场景来的,比通用数据集里随便抽几张船的照片要可靠得多。

在这里要特别强调一点,船舶检测不仅仅是“把船框出来”这么简单。在实际工程里,下游还要接目标跟踪、航迹预测、船型识别、违规行为分析等任务。检测框的稳定性、置信度的可信度、小目标的召回率,每一项都直接影响整个系统的表现。所以数据集不是“有框就行”,而是要在标注规范、场景分布、难例覆盖上都做到位,才有可能训练出真正能落地的模型。

1.2 数据集类别构成与标注规范

这套3000张的数据集,我在项目里主要用它做船舶识别与轨迹追踪。按照常见海事场景,我把目标划分为6个类别:cargo(货船)、fishing(渔船)、passenger(客船)、tugboat(拖船)、sailboat(帆船)、yacht(游艇)。需要说明的是,这个类别划分是基于我项目中常见实践的合理补充,不同渠道拿到的数据集类别定义可能不一样,用之前最好先打开标注文件确认一下,别一上来就按自己的类别数配置data.yaml,否则训练会出现严重的类别错位。

标注格式是YOLO标准格式,每张图片对应一个同名txt文件,每行由5个数值组成:类别id、归一化中心点x、归一化中心点y、归一化宽度w、归一化高度h。举个例子,一行标注:

0 0.511719 0.412049 0.058594 0.115342

含义是第0类(cargo),目标中心点位于图片宽度的51.17%和高度的41.20%位置,边界框宽度占图片宽度的5.86%,高度占图片高度的11.53%。这种归一化的好处是训练时不管输入图片缩放到640还是1280,标注都能直接使用,不需要转换。

从场景分布看,我这份数据大致是:港口区域占40%,开阔海域占35%,近岸和河流入海口占25%。天气方面,晴天占60%,多云和阴天占30%,雾天和雨天合计约10%。视角上包含水平视角和俯视视角两类,水平视角来自岸基监控,俯视视角来自无人机或高点摄像头。图像分辨率大部分是1920x1080,也有一部分是3840x2160,训练时可以统一缩放到640或者1280。这些比例是我拿到数据后自己统计出来的,不同版本可能略有差异,但总体上这套分布结构符合真实海事监控数据的常见情况。

1.3 拿到数据集后第一件事:标注质量核验

很多人拿到数据集直接开始训练,这是最大的坑。YOLO格式的训练非常依赖标注质量,框偏了、类别标错了、漏标了,模型学出来就是歪的。我的习惯是先用可视化脚本把标注框画回图片上,随机抽200张人工看一遍,重点检查三件事:

  1. 边界框是否贴合目标边缘,有没有框得过大或过小,尤其是船头和船尾这种容易切错的位置。
  2. 类别是否准确,货船和客船这种外观接近的目标最容易标错。
  3. 有没有漏标的小目标,远处的小船不起眼,标注人员容易漏,但这对模型召回率影响很大。

可视化脚本不用写得多复杂,用OpenCV把txt标注解析出来,画矩形框,保存成带框图片逐张翻就行。我实测下来,这一步至少能发现10%~20%的问题样本。把问题样本挑出来重新修正,比训练完再调半天参数效率高得多。

另外要检查图片文件和txt文件是否一一对应。有些数据集在压缩传输过程中会丢文件,导致训练时出现“no labels found”报错。排查方法很简单,写个小脚本对比images目录和labels目录下的文件名和数量,多一个少一个都找出来。

2. 数据预处理与增强策略:3000张图怎么用出3万张的效果

2.1 格式检查与坐标归一化的坑

YOLO标注的坐标是归一化到0~1之间的。但部分第三方标注工具导出的是像素坐标,需要自己转换。公式很简单:

x_center = (x1 + x2) / 2 / image_width
y_center = (y1 + y2) / 2 / image_height
width = (x2 - x1) / image_width
height = (y2 - y1) / image_height

我踩过一个很典型的坑:某批数据在标注时把1920x1080的图片当成了1280x720来计算归一化坐标,结果所有标注数值都偏大,有些甚至大于1。YOLO训练时遇到越界的坐标会直接报错,不报错的话也会让损失函数异常波动。所以凡是从外部拿的数据,第一步统一用脚本转换格式,并且打印出min和max值确认范围都在0~1之间,这一步别省。

还有一个细节:检查标注坐标有没有出现“框中心点正确但宽高为0”的脏数据。这种情况通常是在标注过程中误操作生成了空框或点框,训练时会导致边界框回归损失异常。写脚本时顺手把宽度或高度小于1个像素的标注过滤掉即可。

2.2 海事专属数据增强组合

3000张图说多不多、说少不少,直接训练大概能到0.7的mAP50,但要稳定上0.85,必须把数据增强用好。YOLOv8内置了Mosaic、MixUp、HSV变换等增强策略,但对海事场景,我觉得最有效的几个是这样搭配的。

Mosaic拼接是重中之重。它把4张图拼成一张再训练,相当于在一张图里塞进了4个不同场景的目标,大幅增加了小目标的数量和背景多样性。船舶检测的目标尺度变化极大,Mosaic对小目标的提升非常明显。我在自己的项目里对比过,开启Mosaic和MixUp之后,验证集的mAP50大概能提升3到5个百分点,而训练时间只增加不到20%,这笔投资相当划算。

HSV扰动也值得开启。海面颜色其实很单一,大面积是蓝色或灰色的水体。如果不做颜色扰动,模型很容易记住某一海域的水色,换一个海域就失效。适度调整色度和饱和度,能让模型更关注船体本身的纹理特征,而不是把背景颜色当特征。

随机旋转和透视变换可以适当加大角度。水面上的船姿态变化多,尤其是俯视视角下,船头的朝向各不相同。我一般把旋转角度开到±30度,对航拍视角的泛化很有帮助。但要特别注意,垂直翻转要慎用。船的图像翻转后形态基本没变化,但如果你下游要接航向判断或者轨迹预测,翻转会破坏语义方向,导致模型输出的角度信息错乱。水平翻转是安全且有效的,垂直翻转按需开启。

2.3 类别不平衡与难例挖掘

海事数据集很常见的一个问题:货船样本多,帆船、游艇样本少。像我这份数据里,货船可能占了近一半,帆船可能只有两三百张。直接训练的话,帆船的精度会掉得很难看,验证集里偶尔还会出现全部漏检的情况。

处理方法有两种,我建议一起用。第一是类别权重,给样本少的类别加大损失权重。YOLOv8的class weights参数可以配置,设置之后模型在反向传播时会对少数类别的损失更敏感,从而提升对这些类别的拟合能力。第二是难例挖掘。训练完第一轮之后,把验证集里预测错误的目标挑出来——比如漏检的帆船、误检的浪花——把这些图片集中起来,加入训练集再训一轮。这种“第二轮定向补课”的思路,比盲目加训练轮数有效得多。

还有一个经验:不要把类别权重视为固定不变的超参。类别权重调得过大,会导致样本多的类别反而下降。我一般先不加权重训练一轮做基线,再根据各类别的单独AP,给明显掉队的类别加权重,一点一点试。

3. YOLO训练全流程实操:从PyCharm环境搭建到模型收敛

3.1 环境准备:PyCharm + Conda + YOLOv8

关于在PyCharm中使用YOLO这件事,网上的教程很杂,我稳定复现过的流程是这样:先用Conda创建一个独立的Python环境,版本选3.10或3.11,然后通过pip安装PyTorch和ultralytics。

conda create -n yolo python=3.11 conda activate yolo pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 pip install ultralytics

这里有个很值得注意的坑:GPU版本的PyTorch必须安装CUDA对应的wheel包,直接用pip install torch,默认装上的往往是CPU版本,训练速度会让你怀疑电脑是不是坏了。装完之后跑一下torch.cuda.is_available(),返回True再继续。很多新手卡在“训练特别慢”这一步,十有八九是这里出了问题。

PyCharm里配置解释器时选择刚才创建好的Conda环境。如果你习惯用ultralytics的CLI,直接在Terminal里敲yolo命令即可,不需要额外写训练脚本。CLI支持的参数很全,训练、验证、导出都覆盖了,对大多数项目来说够用。

补充一个经验:显卡驱动和CUDA版本之间是有对应关系的。如果电脑上已经装了新版本驱动,但PyTorch装的是旧的cu121,两者不匹配时会出现CUDA initialization error。稳妥的做法是先查一下驱动支持的CUDA版本,nvidia-smi右上角会显示,再选择对应的PyTorch wheel。

3.2 数据集配置与训练启动

YOLO训练需要一份data.yaml,内容大概是:

path: ./datasets/ship train: images/train val: images/val test: images/test nc: 6 names: ['cargo', 'fishing', 'passenger', 'tugboat', 'sailboat', 'yacht']

划分数据集时我按8:1:1的比例随机划分训练集、验证集、测试集。这里有一个很容易被忽略的细节:同一个场景、同一艘船的连续帧,必须尽量只出现在一个集合里,否则会造成数据泄露——模型在验证时其实已经见过“同一艘船”的其他角度,验证指标会虚高,上线后真实表现打折扣。

划分逻辑要按文件夹甚至按视频段处理。我自己写了一套脚本,先给每张图片打上“场景ID”标记,再按场景分组切分,确保同一个场景的图片不会横跨两个集合。从实际效果看,这样做之后的验证集指标虽然数值上比随机划分略低,但更真实,更能反映模型的真实泛化能力。

训练启动命令:

yolo detect train model=yolov8m.pt data=ship.yaml epochs=150 imgsz=640 batch=16 device=0

从yolov8m.pt预训练权重开始训练是常规做法,因为COCO预训练权重已经学到了通用的视觉特征。船舶虽然不是COCO里的常见类别,但底层特征(边缘、纹理、形状结构)是可以迁移的,收敛速度比从头训练快很多,效果也更稳定。imgsz选择640是速度与精度的平衡默认值,如果你的显卡显存足够,可以试试1280,对小目标检测有明显提升,但训练时间也会涨不少。

3.3 损失函数与关键超参数调优

说YOLO损失函数之前先说结论:YOLOv8的损失分三块,分类损失用BCE(二元交叉熵),边界框回归用CIoU配合DFL(分布焦点损失),还有一部分用于处理目标存在性判断的置信度损失。很多人不需要修改损失函数本身的结构,但必须学会看训练日志里各类loss曲线的趋势。

训练时重点盯两个指标:box_loss和cls_loss。box_loss下降缓慢,一般意味着定位精度上不去,这时候考虑增加epoch数或者把imgsz调大;cls_loss反向升高,则有可能是标签错了,回去查数据集。如果box_loss在后期出现震荡,说明学习率太高了,这时候降低lr0或者改用余弦退火调度器会有帮助。

超参数方面,我调过最有效的几个:

  • lr0初始学习率,默认0.01基本够用,数据量少的时候可以降到0.005,防止前期震荡。
  • batch在显存允许的前提下尽量往大调,batch过小会导致BN层统计量不稳定,mAP会有明显的抖动。
  • epochs从150开始,观察loss曲线是否还有下降空间,没有就提前早停。

还要提一嘴,在AGX Orin这类边缘设备上做训练,要特别注意功耗和散热。Orin的核心优势是低功耗推理,训练算力相比桌面GPU来说弱很多,我自己的经验是用Orin做训练效率不高,更推荐把训练放到台式机或云服务器上,Orin专注做部署推理。但如果条件受限必须在Orin上跑,建议选yolov8n或yolov8s这种轻量模型,batch降到4,分辨率降到640。

3.4 模型验证:mAP50与mAP50-95怎么读

训练结束后,ultralytics会生成confusion_matrix.png、results.png、PR曲线等一系列结果图。我最常看的是PR曲线和mAP指标。

mAP50指的是IoU阈值0.5下的平均精度,对目标定位要求不太严格,适合粗看模型行不行。mAP50-95是IoU从0.5到0.95逐档计算再取平均,更能反映边界框的贴合度。海事场景如果只做船舶监控报警,mAP50达到0.85以上基本可以上线;如果要做精确的靠泊测量或者船型识别,mAP50-95至少要到0.6,否则框的位置偏差会在下游算法里被放大。

还有一个细节值得关注:在results.png里看val/box_loss曲线,如果验证loss在训练后段反弹,说明过拟合了。这时候应该增加数据增强比例,减少训练轮数,或者推行早停。反过来,如果验证loss和训练loss都还同步下降,说明还可以继续训练,不用急于停止。

4. 模型导出与边缘部署:从ONNX到AGX Orin

4.1 ONNX导出

训练好的模型最终要落到设备上跑。导出成ONNX是通用性最好的一步,后续无论是转TensorRT、OpenVINO还是ONNX Runtime推理,都以ONNX为中间格式。YOLOv8导出命令:

yolo export model=best.pt format=onnx dynamic=True opset=12

导出时有两个点容易踩。第一是dynamic参数,如果只针对固定分辨率部署,不建议开dynamic,导出的模型更小、推理更快;如果需要处理不同分辨率的输入,那必须开启,但要注意后续转TensorRT的时候动态维度配置比较繁琐。第二是opset版本,设得过老可能不支持某些新算子,在设备上转engine会报错。一般opset设11或12足够,太新反而可能遇到兼容性问题。

导出之后千万不要直接拿去部署,先用onnxruntime跑一张图验证输出,和PyTorch推理的结果对比一下框的位置和置信度是否一致。这一步可以排除自定义算子带来的隐患——有些模型是能导出,但推理结果完全乱了,多半是某些算子在导出时被重写成了不兼容的版本。

4.2 AGX Orin上的TensorRT加速

在AGX Orin上搭建YOLO环境,核心工作是把JetPack配套的CUDA和TensorRT装好,然后把ONNX模型转成TensorRT的engine文件。我的转换命令是这样:

trtexec --onnx=ship.onnx --saveEngine=ship.engine --fp16 --workspace=4096

FP16精度下推理速度能比FP32快一倍左右,对船舶检测来说精度损失基本可以忽略。注意workspace要给足,不然转大模型时会直接内存不足退出。如果你的模型输入分辨率很大,比如1280,转engine时建议把workspace设到8192以上。

转换完成后,engine文件是跟GPU型号强绑定的。在Orin上生成的engine不能直接拷到另一台不同型号的设备上跑,必须重新转换。这是部署时最容易踩的坑,我之前有次图省事直接拷贝engine文件到新设备,结果加载时报错,排查了大半天才发现是这个问题。

如果担心FP16掉精度,可以先拿几张典型场景图对比FP32和FP16的检测结果,如果mAP掉太多,就退回FP32。还有一种做法是在训练时给数据加入噪声,增强模型对量化误差的鲁棒性。

4.3 一键部署脚本的思路

网上流传的各种“一键部署脚本”,核心其实就是把部署流程打包成自动化步骤。我在项目里写过一个bash脚本,大致流程是:

  1. 检查环境:确认Python版本、CUDA版本、TensorRT版本是否满足要求。
  2. 创建Conda环境并安装requirements依赖。
  3. 从模型仓库拉取训练好的权重文件。
  4. 将PyTorch权重转ONNX,再用trtexec转成TensorRT engine。
  5. 启动FastAPI推理服务,加载engine并监听端口。

脚本本身的逻辑不复杂,难点在于要把环境检测写全。比如新设备上没有安装TensorRT时脚本直接报错退出,而不是等到转换那一步才失败。我在脚本里加了自动检测和提示安装的步骤,部署新设备时能省不少时间。

顺便提一句,ultralytics近期更新了不少功能,比如内置的模型格式检查、更完善的导出异常提示。如果你用的是较新版本,导出时遇到问题先看一下版本release note,很多坑官方已经标出来了。

5. 常见问题与海事场景排坑实录

5.1 小目标船舶漏检严重

我自己训练过程中最头疼的就是远处的小船漏检。排查思路是从分辨率入手:如果训练用的imgsz是640,而目标在图中只有二三十像素,模型确实很难学到有效特征。解决方式有两个,一个是用1280分辨率训练,把小目标放大;另一个是在数据增强上强化Mosaic,把小目标的数量密度提上来。

如果这两种方法都试过还是不行,就得考虑Tiling策略——把大图切成小块,分块推理后再合并检测结果。虽然推理耗时增加,但对小目标的召回提升是最明显的。在港口这类需要看远距离船只的场景,Tiling几乎是必配方案。注意切块时要有重叠区域,否则目标正好在切开线附近时会被截断,导致漏检。

5.2 海面误检、浪花被当成船

海面波纹和白色浪花在YOLO看来非常像渔船,这是海事场景最容易出现的误检。我试过几个方案,最优解是给训练数据加负样本。具体做法是专门准备几百张完全没有船的纯海面图片,标注文件用空的txt文件,让模型学会“这里没有目标”。在YOLO训练中,空标签会生成纯背景样本,能显著降低误检率。

这个方法效果好,实现也简单,只需要把无船图片和空txt放进训练集即可。我实测下来,加入200张纯海面负样本之后,误检数量大概降了一半。要注意负样本的分布要和部署场景匹配,如果你部署的海域经常有大浪,负样本里就要多准备大浪的图片。

5.3 换一个港口就失效

在一组港口训练好的模型,换到另一个港口,检测效果直线下降。这种问题通常不是模型的锅,而是数据分布变了:不同海域的船舶类型不同、航道走向不同、光照角度不同。我采取的应对策略是多场景混合训练,把多个港口的图片混在一起训练,让模型见到更多变的特征,同时在验证时按场景单独计算指标,清楚了解每个场景的具体表现。

另外还有一种做法,是在部署端做“场景自学习”。把置信度低于阈值的检测结果人工复核后回流到训练集,每周做一次增量训练。这个方法我实测下来,对长期运行的监控系统提升非常明显。模型会慢慢适应当前海域的船型、光照、水位变化,精度越跑越高。当然前提是回流的数据必须经过人工审核,否则会把误检学进去,越学越歪。

5.4 边缘设备上的推理性能优化

在AGX Orin上推理船舶检测模型,除了TensorRT,还有几个性能优化细节可以分享。第一,输入分辨率要和现场摄像头匹配,不用盲目追求1280。如果部署现场的摄像头都是1080p,设置成640x640或1280x736,可以测试一个最优切分方式,有时能换来接近两倍的帧率。第二,开启CUDA Stream,配合多路视频线程,让多路摄像头解码和推理并行,能大幅提升整体吞吐量。Orin本身支持多路视频流解码,这个特性一定要用起来。第三,输出端做过滤,NMS的conf_thres和iou_thres根据现场调试。我一般conf设0.4,iou设0.45,既能压住误检,又不至于漏船。

最后分享一点个人体会。这套3000张的数据集走完整个流程之后,最值钱的不是模型本身,而是对海事场景的理解:数据质量比模型结构更重要,标注精度比网络深度更重要。我后面再做类似项目,第一件事就是花时间把数据摸透,拿放大镜看标注,把所有异常样本挖出来。也建议如果你打算用船舶检测数据集做实验,先不急着追求复杂的网络结构,把YOLOv8m训练收敛,评估指标和可视化结果都看过一轮,后面的调优方向会清晰很多。这个流程完整走一遍,比看十篇教程都管用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询