资讯详情

资讯详情

基于YOLOv8的船舶分类识别检测系统搭建实战指南

做船舶检测这个项目其实起因挺朴素的——某天接到一个水运管理方向的需求对方希望用视觉手段自动识别航道内过往船只的类别给调度、监管和统计提供辅助。当时市面上能直接抠来的通用目标检测方案不少但真正落到“船舶分类识别检测”这个具体场景边界条件比想象中复杂得多。这篇文章我打算把基于YOLOv8完整搭建一套船舶分类识别检测系统的过程捋清楚包括数据集怎么准备、模型怎么训练、UI界面怎么接、部署阶段踩了哪些坑。想复现同款项目、或者准备从零做图像识别应用的同学这套流程可以当参考。1. 船舶识别检测任务从需求到技术选型1.1 船舶检测到底解决什么问题船舶识别不是一个新问题但它和通用的行人、车辆检测有明显差异。水面的背景是动态的受光照、波浪、雾霾影响极大船只本身又包含很多种形态从集装箱货轮到小型渔船长度从几米到几百米都有。传统方案靠人工盯监控屏费眼且容易漏尤其在夜间多路视频的场景下人力完全跟不上。当时梳理下来需求方要的核心能力有三块一是区分画面里有没有船二是给船打上类别标签比如货船、渔船、客船、帆船、拖船三是尽量给出置信度方便后续系统根据可信程度做人工复核。这三块需求直接决定了项目的技术路线必须用目标检测模型而且检测和分类要一步到位不能先检测再单独分类那样两套模型串联会引入额外延迟和误差累积。1.2 为什么最终选择YOLOv8目标检测的技术路线其实有好几种经典的两阶段有Faster R-CNN系列单阶段有YOLO系、SSD、还有基于Transformer的DETR之类。两阶段精度高但慢DETR收敛慢且对硬件要求偏高在工业落地场景里大家更看重的是“接近实时的推理速度”和“能直接在普通显卡甚至CPU上跑”。YOLO系在这块的优势一直很突出而选择YOLOv8而不是继续用V5主要看中三点第一YOLOv8是无锚框检测解耦了分类头和回归头对形状差异很大的目标比如细长型船只更友好省去了调锚框尺寸的环节。第二它自带C2f结构梯度流更丰富小目标特征提取能力有提升这对远处小船的检测很关键。第三是生态成熟Ultralytics的仓库把训练、验证、导出封装得相当顺手从拿到环境到跑通第一次训练周期非常短。除了模型本身我也把数据集来源、标注工具、部署方式一起纳入选型判断。如果选纯Transformer方案数据集规模需要非常大才能体现优势而船舶类的公开数据不多前期还得自采自标YOLOv8对中小规模数据集更友好迭代起来也快。1.3 整体架构规划系统的整体架构我一开始就按闭环来设计包含数据层、训练层、推理服务层和展示层。数据层负责图片和视频整理、标注、划分数据集训练层用YOLOv8执行模型训练和验证推理服务层负责加载权重、执行检测、做后处理和格式转换展示层就是用户看得见摸得着的UI界面可以用桌面程序也可以用Web页面。这套架构的优点是每一层都能独立替换。比如训练层以后想换更轻量的模型或者推理层想加一个跟踪模块都不会牵一发而动全身。UI界面我最终选择了桌面端方案因为现场值班人员更习惯本地软件不依赖浏览器和服务器资源部署也简单。2. 数据准备的完整链路采集、清洗、标注与增强2.1 数据集来源与构成模型能不能用60%以上取决于数据质量。船舶识别的公开数据集确实存在但类别分布和真实场景有差距往往偏重某一类船或者分辨率太单一。所以我采取了“公开数据预热 自采数据校准”的组合策略。公开部分我筛选了以水域目标检测为核心的几类数据集主要用来让模型先见到足够多的大样貌变化。自采部分则更贴合实际部署场景在河边、港口、跨河大桥等位置架设相机采集不同时间段、不同天气、不同距离下的船舶画面连续记录一周形成素材库。这个过程看起来枯燥但非常必要因为自采数据能覆盖部署现场的光线角度、水面波纹、船只涂装这些“现场特征”。数据总量大约整理了八千多张图片按8:1:1划分训练集、验证集、测试集。反复提醒自己的一点是划分时必须保证同一艘船在不同帧里的画面不能同时出现在训练集和验证集否则验证指标会虚高。2.2 类别定义与样本平衡类别定义直接决定了系统的实用性。太多类别会让样本数量被稀释太少又达不到“分类识别”的需求。我最终保留了六类集装箱货船、散货船、客船、渔船、帆船、拖船。这六类基本覆盖了内河和近海常见船型也各有辨识特征比如集装箱船甲板整齐、渔船上层建筑偏尾部且常带吊臂。样本平衡是绕不开的坑。拍回来的素材里货船数量明显多于帆船因为货船航速慢、在画面里停留时间长。如果直接用原始比例训练模型对帆船和拖船的召回率会很难看。处理办法是数据加权对于少样本类别在训练时提高其loss权重同时用上下翻转、旋转、尺度变化进行针对性扩充。实际操作中我还在训练轮次中途做了类别分布统计确认少数类参与训练的频率没有低到离谱。2.3 标注规范的细节标注质量直接决定模型上限。这个项目里我明确了几条标注纪律遮挡超过50%的目标不标目标小于32x32像素且肉眼难以判断类别的宁可不标紧挨着的两艘船尤其是并靠的渔船和驳船框边必须紧贴船体轮廓不能为了省事拉一个大框把两艘船框进去。标注格式用的是YOLO的txt格式每行是“类别id 中心点x 中心点y 宽度 高度”所有坐标都归一化到0到1。这个格式看起来简单但有个隐蔽问题如果图片尺寸变了归一化坐标不用改可一旦在标注后对图片做了裁剪坐标体系就全乱了。所以我的流程是先统一把所有图片resize到同尺寸再做标注避免后续转换出错。标注工具我选了一款开源的图形化标注软件这类工具大同小异关键在于支持快捷键连续标注能大幅提升标图效率。八千多张图单人标完大概用了两周。如果想压缩时间可以用大型模型先出一版粗糙标注人工再修正但涉及类别定义比较细致的场景我不建议完全依赖自动标注。2.4 数据增强策略数据增强不能乱加。船舶场景和通用物体不同水面有方向性波纹和倒影的存在导致过度旋转增强会产生不真实样本。我实测下来水平翻转是最安全的因为它不破坏船身结构、只是镜像符合水面场景的实际情况。再往下是轻度随机旋转控制在正负15度以内。超过这个范围船身倾斜得太厉害会让模型学到“斜着的船”这种假规律。HSV色域增强也有用主要是应对清晨和傍晚偏暖色、阴天偏灰的情况实际设置成H偏移较小、S和V偏移中等即可。马赛克增强能提高小目标检测效果因为四张图拼接出一张大图相当于变相缩小了目标尺寸模型被迫去学更精细的特征。但马赛克增强在训练后期要逐渐关掉否则模型容易对拼接边界产生依赖我在最后50个epoch把增强强度降到了原来的30%。3. YOLOv8模型训练配置文件、参数调优与训练监控3.1 模型版本的选择YOLOv8有n、s、m、l、x五个规格分别对应从轻量到超大的参数量。选择哪个规格不能只看精度排行更要看部署环境的算力。项目最终运行在一台配备中等性能显卡的工控机上同时要保证UI界面操作流畅所以一开始就不考虑l和x。我在n和s之间做了对比n版本推理速度极快但小目标召回偏弱s版本精度提升明显、速度损失可接受。最终训练选的是YOLOv8s后续推理时把batch重新置为1实际单帧耗时大约在12到18毫秒完全能满足实时需求。规格选定之后我用官方预训练权重作为初始权重。预训练模型在COCO数据集上见过大量通用物体迁移到船舶场景能省很多训练时间尤其是船和“箱子”“车辆”这类形状相似的物体迁移特征可以直接复用。如果队伍里没有大算力显卡用预训练权重微调是性价比最高的方案。3.2 训练参数逐项解读训练参数的设置这里很多数值是靠测试得到的不建议无脑照搬。我的核心配置如下输入分辨率640x640。这个尺寸兼顾了小目标检测和显存占用。再往上加到960精度有提升但推理耗时上涨明显对实时系统不划算。epoch设置为200。前100轮loss下降快后100轮主要用来收敛细节。到第170轮左右验证集的mAP开始周期性震荡说明学习率需要衰减。batch size设定为16。太大会导致单卡显存溢出太小则梯度不稳定。如果显存不够优先减batch并同步降低学习率。初始学习率0.01使用余弦退火调度最小的学习率设为初始值的十分之一。余弦退火的好处是让loss在训练后期平缓逼近不容易在极小值附近反复横跳。权重衰减设为0.0005用于抑制过拟合。样本有几万张模型参数量几千万不加约束训练后期很容易在训练集上刷分、验证集上翻车。类别权重我在少样本类上上调到了基准的1.5倍。这个值不是越大越好太大会导致模型为了强凑少数类而误判多数类。训练启动的命令其实很简单yolo detect train dataship.yaml modelyolov8s.pt epochs200 imgsz640 batch16 lr00.01 cos_lrTrue weight_decay0.0005训练过程中可以随时中断Ultralytics会保存最近一次的权重文件恢复训练时直接指定最新的pt文件即可。这个机制在调整参数后重新续跑时救了不少次。3.3 训练过程监控与中断恢复训练监控最重要的一项是分辨“欠拟合”和“过拟合”的信号。我每20个epoch手动看一眼验证集上的混淆矩阵而不是只盯着loss曲线。loss曲线下降到一个平台不代表模型真的学会了有可能只是训练集的记忆游戏。有一次训练到第120个epoch时训练loss还在下降验证集mAP反而掉了一个点这就是典型过拟合信号我果断停止了训练回滚到第100轮的权重。监控工具上我用了Ultralytics自带的训练日志和TensorBoard两种方式并行。TensorBoard里主要看train/loss、val/loss、metrics/precision、metrics/recall四条曲线。如果precision和recall在训练后期出现此消彼长的剪刀差通常是置信度阈值没校准或者正负样本不平衡需要回到数据层面找原因。另外一个值得记录的细节是最好把验证集中预测错误的图片单独存到一个文件夹每轮训练结束后看一眼错误样本的变化趋势。刚开始错误集中在“把波浪反光当船”中后期基本消失训练到末尾时错误样本主要是“近距离两船并靠互相遮挡”这个问题靠调参无法根治最终通过后处理NMS逻辑做了缓解。3.4 指标评估与坏例分析模型训练完我只看四个指标Precision、Recall、mAP50、mAP50-95。其中mAP50-95比mAP50更严格它统计了不同IoU阈值下的平均精度能反映出框位置的准确性。船舶检测场景里框位置抖动会影响后续对船舶航向的估算所以mAP50-95一定不能太低。最终我的验证结果是mAP50大约93%mAP50-95大约77%对于六分类的中等规模数据集算是正常偏上水平。坏例分析阶段我重点看了三类错误。一是把航标、浮筒、礁石误判成小船这类错误往往因为标注数据里缺少“负样本”进行约束。解决方法是在背景图片里随机加入部分高相似干扰物并标为背景。二是把远距离货船和客船混淆因为远处像素太少两类船的纹理细节全部丢失只能靠轮廓猜测。解决方向是增加“远距离高分辨率成对数据”让模型看见同一条船在不同距离下对应的特征差异。三是夜间灯光干扰有些船的甲板灯在低照度下显得异常明亮和岸边霓虹灯混杂导致误检这要靠数据扩充和图像预处理联合去解决。4. 从模型权重到可用的UI系统推理链路与界面设计4.1 技术栈选择桌面端还是Web端模型训练好只是第一步交付物必须是一个操作界面友好的系统。技术路线我在Flask Web端和PySide6桌面端之间犹豫过。Web端的好处是多人访问方便服务部署在一台机器上值班室和办公室都能看缺点是视频流回传延迟、浏览器兼容性调整、以及并发访问时的资源管理都需要额外工作量。考虑到实际使用环境通常是值班室里的固定工控机桌面端更直接所以我选了PySide6。PySide6是Qt的Python绑定信号槽机制对多线程界面控制特别合适。用它写一个检测面板左侧放视频画面右侧放参数面板和结果列表底栏显示FPS和检测计数样式调整自由度高。和OpenCV的HighGUI相比PySide6做出来的东西起码像个正经软件而不是调试窗口。4.2 核心功能模块设计UI的功能模块我按照使用流程拆成了四块输入模块、检测模块、结果展示模块、数据管理模块。输入模块支持三种来源单张图片、视频文件、实时摄像头信号。视频文件用OpenCV逐帧读取摄像头信号通过设备的索引或者RTSP流地址接入。检测模块封装了一个Detector类负责加载模型、执行推理、后处理并向外提供每帧的检测列表。结果展示模块负责在画面上绘制边界框、类别标签、置信度并在右侧表格里同步更新记录。数据管理模块支持把检测结果导出为CSV或者带标注的视频文件方便留档。检测逻辑的代码结构需要提前规划不能在界面回调里直接写模型推理。我的Detector类大致长这样from ultralytics import YOLO class ShipDetector: def __init__(self, weights_path, conf_thres0.25, iou_thres0.45): self.model YOLO(weights_path) self.conf_thres conf_thres self.iou_thres iou_thres def detect_frame(self, frame): results self.model.predict( sourceframe, confself.conf_thres, iouself.iou_thres, verboseFalse, devicecuda:0 ) detections [] for box in results[0].boxes: detections.append({ cls: results[0].names[int(box.cls)], conf: float(box.conf), box: [float(v) for v in box.xyxy[0]] }) return detections这样界面层只需要把读到的帧交给detect_frame拿回列表后绘制即可。模型推理和界面绘制完全解耦后面想换成ONNX或者TensorRT的推理后端只需要改Detector内部实现界面代码不动。4.3 实时视频流与线程模型UI卡顿是这类应用最容易翻车的地方。如果直接在界面主线程里做视频读取和模型推理画面必然一卡一卡甚至触发Qt的“无响应”警告。解决办法是把视频读取和模型推理放到两个QThread里界面主线程只负责接收“已经绘制好的帧”并显示。我的线程结构是CaptureThread负责从摄像头或视频文件取帧通过信号发给DetectThreadDetectThread执行模型推理并把推理结果连同原始帧一起发回主线程主线程收到后在Qt的QLabel上绘制边界框并刷新结果表格。这里要注意的是OpenCV的VideoCapture对象不能跨线程传递它必须在CaptureThread内部创建并循环读取。还有一个细节是队列容量。如果检测速度跟不上视频帧率中间队列会无限堆积旧帧导致延迟越来越大。我用了有界队列容量设为5满了就丢最旧的帧。实际体验中牺牲一小部分帧率换来的实时性比攒一堆延迟帧更有价值。4.4 推理加速与模型导出实时视频流跑起来之后我进行了两轮加速优化。第一轮是把模型从PyTorch权重导出成ONNX然后使用ONNX Runtime做推理。ONNX Runtime在保持精度的前提下比原生PyTorch的推理快20%到40%这是因为它做了算子融合和内存复用。导出命令非常简单yolo export modelruns/train/exp/weights/best.pt formatonnx opset12导出后注意检查输出节点YOLOv8的ONNX模型输出的形状是(1, 84, 8400)84的前四位是cx、cy、w、h加上80个COCO类别得分。因为我们是六分类模型会输出(1, 10, 8400)所以写后处理时要根据实际类别数调整解析逻辑。第二轮优化是把ONNX进一步转成TensorRT的engine文件在NVIDIA显卡上跑能快更多但这个环节依赖CUDA和TensorRT版本环境配置麻烦只推荐在正式部署时做。5. 实战中踩过的坑与补救方案5.1 小目标漏检一个长尾问题项目刚跑通时画面里的大船检测效果很棒但稍远一点的船经常漏掉。这个问题不是“模型不够准”而是数据里小目标样本太少了。我统计了训练集的目标框尺寸分布发现小于64x64像素的目标占比不到15%。模型在训练时大概率没见过多少小目标自然学不到对应特征。补救措施从两端入手。数据端我把远处的船只区域单独裁剪出来放大后并入训练集相当于给小目标“多拍了特写”同时把大图中的目标降采样作为小目标样本。模型端训练时开启多尺度训练输入尺寸在480到960之间随机波动让模型适应不同尺度。经过一轮迭代测试集上小目标召回率提高了近20个点。这个坑本身不可怕怕的是不看数据分布盲目调参。5.2 近距离多船互相遮挡港口里船舶并靠是常态检测结果经常出现一个框把两条船框住或者两条船的置信度都偏低。YOLOv8的后处理NMS设计默认是抑制同一区域内的重叠框但两船重叠时真实语义上是两个目标nms反而会杀掉一个正确框。我的处理方案是降低IoU阈值从默认的0.45调低到0.3让重叠区域的多个候选框更容易同时保留。另一个辅助做法是给结果列表增加“是否遮挡”的标记当某个框的置信度在0.4以下且和另一个高置信度框大比例重叠时系统自动提示人工复核。这个问题我认为没有完美解法最终得靠部署策略容忍一定误检再由人工兜底。5.3 夜间与恶劣天气的表现一开始训练数据都是白天晴朗天气部署测试时夜里画面一上检测率直接掉了一大截。原因很直白模型没见过夜间的特征分布。夜间船只在探照灯下是高对比度轮廓背景水面完全黑色这和白天暗背景下的小目标检测完全不是一回事。解决方式分两步。第一晚间专门采集了一部分真实画面加入训练包括靠灯光的、月光下的、完全无光靠红外补光的。第二UI界面上增加了图像预处理选项比如夜间模式自动做直方图均衡化和轻度降噪让画面细节更明显。这两步都上了以后夜间检测率才恢复到白天的八成左右。这个案例给我的教训是数据采集阶段就要把“运行环境的极端情况”当成一等公民来设计而不是事后补课。5.4 类别不平衡导致的误判前面提到过帆船样本少所以训练时给了额外权重。但权重加过头模型又变得过度敏感把一些带白色帆形结构的小艇也误判成帆船。这类问题调试起来很烦因为它在整体指标上几乎看不出变化只有在查看错误图片时才会暴露。我最后的处理技巧是把误判成帆船的图片单独搜集起来作为“难例”放进训练集并在标注时让模型看到这些目标其实是其他类别。难例挖掘在训练这类长尾分布数据的时候非常管用。每训练完一轮就把验证集上的错误样本回灌到训练集进行下一轮微调这种做法比单纯增加权重更有效地纠正模型行为。6. 项目落地后的扩展方向与个人体会6.1 从检测到跟踪为船只轨迹分析做准备单纯检测每一帧里的船只能在逻辑上回答“这一帧里有没有船”。想进一步做航迹分析、船舶计数、区域闯入预警就需要在检测模块之上叠加目标跟踪。现在比较成熟的做法是ByteTrack或DeepSORT。ByteTrack的优势是简单高效对短期遮挡容忍度好很适合部署在实时系统里。我在推理链路里预留了Tracker接口检测结果会先经过跟踪器给每个目标分配一个稳定ID再送到UI展示。这样同一艘船在跨帧时不会因为短暂的检测抖动而丢失身份。做这个扩展的初衷是客户真正关心的是“这艘船从哪里来、往哪里去”而不仅仅是“这艘船是什么”。6.2 模型服务化与多路视频接入现场不只有一路摄像头往往有四五路甚至十几路。桌面端单机模式处理一路视频没问题多路接入就涉及算力分配和画面拼接。一种可行的方向是把模型封装成HTTP服务或者用消息队列管理推理任务各个视频通道把帧推送到服务中心服务中心统一调配GPU资源返回结果后再分发给前端。我建议不要一次性追求过于复杂的服务架构先按“一路一进程”的方式部署用GPU显存占用情况和单卡推理延迟决定最多能并行跑几路。等系统稳定了再考虑引入批处理推理把多路视频帧合并成batch一次推理吞吐量提升非常可观。6.3 写到最后的一些体会这个项目从需求梳理到系统交付前前后后反复迭代了差不多两个月。最大的体会是模型训练在项目里占的时间其实没有想象中那么多真正耗时的是数据采集、数据标注、界面联调和现场部署。YOLOv8把训练门槛压得很低但把“能用”变成“好用”靠的还是围绕场景做的工程工作。如果让我给准备做类似项目的人三句话第一句是先花一周把数据分布摸清楚比先跑十版模型有用得多第二句是UI和推理一定要解耦否则后面加功能会改到怀疑人生第三句是预留难例挖掘的机制因为模型上线之后最让你头疼的问题一定来自那些你没想过的情况。这套流程沉淀下来以后换一个检测对象再做一遍会发现大部分环节都能复用无非是数据变了、类别变了、界面文案变了方法论是通用的。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →