ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

改进YOLOv5船舶目标检测:小目标召回提升与锚框重聚类实战

改进YOLOv5船舶目标检测:小目标召回提升与锚框重聚类实战 简介这份文档面向计算机视觉方向的研究生、算法工程师及船舶检测领域从业者系统探讨基于改进Yolov5算法的船舶目标检测方法帮助读者理解复杂海况与多目标场景下提升检测精度与鲁棒性的完整思路。内容从研究背景与国内外现状切入梳理图像预处理与特征提取基础并深入剖析Yolov5算法原理与流程重点展开改进设计包括随机旋转、随机裁剪、颜色变换等数据增强策略卷积层与池化层优化、注意力机制应用以及正负样本损失与类别不平衡问题的调整方案。实验部分覆盖数据集准备与标注规范、模型训练验证设置、性能指标与结果对比分析并讨论与其他算法的差异、影响因素及未来研究方向。资源为1个docx文档压缩包约80KB目录结构完整、章节层次清晰已有47人学习。适合作为船舶目标检测课题的理论参考与实验设计范本也可用于快速把握Yolov5改进脉络与评估方法。1. 船舶目标检测为什么总在“小目标”上翻车从 YOLOv5 的先天短板说起做过内河、港口或近海监控的同行大概率都遇到过这种场景白天画面里几百米外的货轮检测框稳如老狗一到傍晚逆光或者船体只占几十个像素时模型要么漏检要么把两艘并排的渔船框成一个。基于 YOLOv5 做船舶目标检测核心矛盾从来不是“能不能检出船”而是小尺度船舶、密集排列、水面反光干扰这三座大山。YOLOv5 本身是个非常成熟的单阶段检测器CSPDarknet 主干加 PANet 颈部再挂三个检测头速度与精度平衡得相当好但它的默认锚框和特征融合方式是为 COCO 那类通用目标调的直接拿来跑船舶数据集小目标召回率往往掉得厉害。这篇笔记就围绕“改进 YOLOv5 做船舶检测”这条线把数据集构建、锚框重聚类、注意力与颈部改造、训练超参、部署验证这几个环节拆开讲目标是让你看完能自己搭一套可复现的流程而不是停在“调包跑通”的层面。适合已经跑过 YOLOv5 官方 demo、想往垂直场景落地的算法工程师和研究生。2. 改进前的基线先把 YOLOv5 在船舶数据上跑出一个可信的对照2.1 为什么必须自己重聚类锚框YOLOv5 官方给的 anchors 是在 COCO 上聚类出来的尺度分布偏通用。船舶目标有个很鲜明的特点长宽比极端。内河货船、集装箱船在俯拍或平视视角下宽高比经常到 4:1 甚至 6:1而 COCO 的锚框里这种极端比例很少。锚框和真实框的 IoU 上不去正样本匹配质量就差回归分支再强也拉不回来。所以改进的第一步不是加模块而是用 K-means 或 K-means 在你自己的船舶数据集上重新聚类出 9 个锚框按面积分给 P3/P4/P5 三个检测头。常见做法是聚类前先把所有标注框的宽高归一化到 0-1距离度量用1 - IoU而不是欧氏距离这样聚类结果更贴合检测任务本身。import numpy as np def kmeans_iou(boxes, k9, iters300): # boxes: (N, 2) 归一化后的 wh n boxes.shape[0] # 用 IoU 距离初始化随机选 k 个框 idx np.random.choice(n, k, replaceFalse) centers boxes[idx].copy() for _ in range(iters): # 计算每个框与每个中心的 IoU # 两两组合交集 min(w), min(h) inter_w np.minimum(boxes[:, None, 0], centers[None, :, 0]) inter_h np.minimum(boxes[:, None, 1], centers[None, :, 1]) inter inter_w * inter_h area_b boxes[:, 0] * boxes[:, 1] area_c centers[:, 0] * centers[:, 1] union area_b[:, None] area_c[None, :] - inter iou inter / (union 1e-9) dist 1 - iou labels dist.argmin(axis1) # 更新中心取该类框的中位数比均值更抗离群点 for j in range(k): if (labels j).sum() 0: centers[j] np.median(boxes[labels j], axis0) return centers # 假设 anns 是 (N,2) 的归一化 wh 数组 anchors kmeans_iou(anns, k9) # 按面积排序后手动分给 P3/P4/P5 anchors anchors[np.argsort(anchors[:, 0] * anchors[:, 1])] print(anchors)这段代码的关键在距离度量和中心更新方式。用1 - IoU做距离聚类目标直接对齐检测的匹配质量用中位数更新中心是因为船舶标注里偶尔会有超大框比如把整个码头标进去均值会被带偏。跑完之后把 9 个锚框按面积从小到大排前 3 个给 P380×80 特征图管小目标中间 3 个给 P4后 3 个给 P5。改完锚框同样的训练配置下小目标召回通常能涨 3 到 5 个点这是性价比最高的一步。2.2 基线训练配置与对照指标在动结构之前先固定一套基线配置后面所有改进都跟它比。数据集按 8:1:1 切分输入分辨率 640batch size 16SGD 动量 0.937初始学习率 0.01余弦退火warmup 3 个 epoch训练 300 epoch。评估用 mAP0.5 和 mAP0.5:0.95另外单独统计小目标面积 32²的召回率这个指标才是船舶场景的胜负手。基线跑完你会看到整体 mAP0.5 可能到 0.85 左右但小目标召回只有 0.6 出头密集场景漏检明显。这个差距就是后面改进的空间。提示训练前务必用--rect关闭矩形推理做一次验证确认数据加载和标注没有错位。船舶数据集常见的一个坑是标注文件里类别索引从 1 开始而 YOLOv5 要求从 0 开始不检查会直接训崩。3. 结构改进注意力、颈部融合与检测头怎么改才不白费3.1 在主干末端加注意力CBAM 与 SE 的取舍船舶检测的干扰主要来自水面反光、波浪纹理和岸边建筑。在主干网络末端SPPF 之前加一个注意力模块让网络自己抑制背景响应是常见做法。SE 模块只做通道注意力计算量小CBAM 同时做通道和空间注意力对“船在哪”这种空间定位更敏感但参数量略大。我的经验是如果小目标漏检是主要矛盾优先上 CBAM因为空间注意力能强化小目标所在区域如果只是背景误检多SE 就够。插入位置建议放在 SPPF 之后、进入 PANet 之前这样注意力作用在最高层语义特征上再往下融合时能带着“哪里重要”的信息走。import torch import torch.nn as nn class CBAM(nn.Module): def __init__(self, channels, reduction16): super().__init__() # 通道注意力 self.mlp nn.Sequential( nn.Linear(channels, channels // reduction), nn.ReLU(inplaceTrue), nn.Linear(channels // reduction, channels) ) # 空间注意力7x7 大核卷积 self.spatial nn.Conv2d(2, 1, kernel_size7, padding3) def forward(self, x): b, c, h, w x.shape # 通道分支 avg self.mlp(x.mean(dim(2, 3))) mx self.mlp(x.amax(dim(2, 3))) ca torch.sigmoid(avg mx).view(b, c, 1, 1) x x * ca # 空间分支 avg_s x.mean(dim1, keepdimTrue) max_s x.amax(dim1, keepdimTrue) sa torch.sigmoid(self.spatial(torch.cat([avg_s, max_s], dim1))) return x * sa参数上reduction16是原论文默认值通道数小的层可以调到 8。空间分支用 7×7 卷积是为了覆盖足够大的感受野别改成 3×3否则空间注意力退化成局部响应。加完 CBAM 后训练时间大概增加 8% 到 12%推理 FPS 掉 5 左右换来小目标召回涨 2 到 4 个点值不值取决于你的实时性要求。3.2 颈部改造用 BiFPN 思路替换 PANet 的简单相加YOLOv5 的 PANet 在做多尺度融合时用的是 concat 加卷积不同尺度的特征权重是隐式的。船舶检测里 P3 小目标特征和 P5 大目标特征差异极大直接 concat 会让小目标特征被稀释。改进方向是引入带权重的双向融合类似 BiFPN 的做法给每个输入特征学一个可学习的权重融合时先加权再相加。落地时不用完全照搬 BiFPN 的复杂结构只在 PANet 的每个融合节点上加一个nn.Parameter权重做归一化后加权求和即可。这样改动小、易训练对船舶这种尺度跨度大的场景收益明显。class WeightedFusion(nn.Module): def __init__(self, num_inputs): super().__init__() # 每个输入一个可学习权重初始为 1 self.w nn.Parameter(torch.ones(num_inputs)) self.eps 1e-4 def forward(self, feats): # feats: list of tensors, 同 shape w torch.relu(self.w) w w / (w.sum() self.eps) out sum(wi * fi for wi, fi in zip(w, feats)) return out注意权重用 ReLU 保证非负再归一化避免训练中出现负权重导致特征相消。这个模块插在 PANet 每个 concat 之前把原来的 concat 换成加权融合再接卷积。实测在船舶数据集上 mAP0.5:0.95 能涨 1.5 到 2 个点小目标召回涨得更明显。3.3 增加 P2 检测头小目标的最后一道保险如果小目标召回还是上不去最直接的办法是加一个 P2 检测头。YOLOv5 默认从 P380×80开始检测对应 640 输入下最小 8 像素步长。船舶在远距离下可能只有 10 到 20 像素P3 的特征图上一个目标就一两个格子很难学。加 P2160×160后步长降到 4小目标有更多像素参与。代价是计算量明显上升推理速度大概掉 20% 到 30%。做法是在主干里引出 P2 特征经过一次下采样后与颈部上采样特征融合再挂一个检测头。锚框也要相应调整P2 那组锚框面积要更小。这一步属于“重武器”建议在前面几步做完、指标还差一口气时再上。4. 训练策略与超参数让改进真正落到指标上4.1 数据增强的组合与船舶场景适配YOLOv5 默认的增强组合是 mosaic mixup HSV 随机缩放平移。船舶场景要针对性调整mosaic 概率可以保持 1.0因为它能显著增加小目标出现频率但mixup 建议关掉或降到 0.1因为 mixup 把两张图线性叠加水面纹理叠加后会产生不真实的伪影模型容易学偏。HSV 增强里饱和度扰动可以加大0.7 左右因为水面反光主要影响亮度饱和度变化能提升鲁棒性。随机缩放范围建议从默认的 0.5 调到 0.3 到 1.5让更多小尺度样本参与训练。# data/hyp.yaml 关键项 mosaic: 1.0 mixup: 0.0 hsv_h: 0.015 hsv_s: 0.7 hsv_v: 0.4 scale: 0.5 # 实际用 0.3~1.5 需改代码或自定义 degrees: 0.0 translate: 0.1scale参数在官方实现里是单边缩放系数想实现 0.3 到 1.5 的非对称范围需要改datasets.py里的random_perspective调用或者用自定义 dataloader。这一步别偷懒船舶小目标对尺度分布非常敏感。4.2 学习率、权重衰减与 warmup 的配合改进后的模型参数量变了学习率不能照搬。加了注意力模块和加权融合后新增参数建议用更大的初始学习率比如主干 0.01新增模块 0.02或者统一用 0.01 但把 warmup 拉长到 5 个 epoch让新模块先稳定下来。权重衰减保持 0.0005别调大船舶数据集通常不大正则太强会欠拟合。余弦退火的最小学习率设成初始的 0.01 倍训练后期精细收敛。如果发现训练 loss 震荡先检查是不是锚框改完后没有重新生成缓存标签YOLOv5 的--cache会缓存旧标签改锚框后必须删掉*.cache文件重跑。4.3 用验证集小目标召回做早停依据官方早停看的是 mAP0.5:0.95但船舶场景更该盯小目标召回。建议在验证脚本里单独统计面积小于 32² 的 GT 被检出的比例把它作为早停和选模的第一指标。具体做法是在test.py里加一段按面积分桶的统计输出 small/medium/large 三档的召回和 AP。这样你能清楚看到改进到底帮到了哪一档而不是被整体 mAP 掩盖。5. 避坑与排查船舶检测改进里最容易翻车的五件事5.1 现象改完锚框后 loss 不降反升原因通常是锚框顺序和检测头没对齐。YOLOv5 的 anchors 是按 P3/P4/P5 顺序写的如果你聚类后按面积排序但忘了对应到正确的特征图层级小锚框被分给 P5匹配全乱。解决打印每个检测头的锚框面积范围确认 P3 最小、P5 最大且和数据集 GT 的面积分布对得上。5.2 现象加了注意力模块后训练前期 loss 爆炸原因多是注意力模块初始化不当sigmoid 输出接近 0.5 时乘上去相当于给特征减半多层叠加后数值不稳定。解决把注意力模块最后一层初始化为接近恒等映射比如空间卷积权重初始化为 0或者把注意力输出改成x * (1 sa)的残差形式让初始状态接近不加注意力。5.3 现象小目标召回涨了但误检暴增这是加 P2 检测头后的典型副作用。P2 特征图分辨率高背景纹理也被放大模型把波浪当成船。解决提高 P2 检测头的正样本匹配阈值或者对 P2 分支单独用更大的分类 loss 权重压制背景响应。另一个办法是在 P2 融合前加一个轻量注意力专门抑制水面高频纹理。5.4 现象推理时 FPS 掉得比预期多除了 P2 检测头本身的开销常见原因是加权融合模块用了 Python 循环逐元素相加没走 GPU 并行。解决把加权融合写成torch.stack后按权重维度做einsum或直接广播乘加确保全在张量层面完成。另外检查 CBAM 的空间卷积是不是用了分组卷积分组数太大也会拖速度。5.5 现象换数据集后指标崩盘船舶数据集之间域差异极大内河的船和远洋的船在颜色、尺度、背景上完全不同。改进模块学到的可能是某个数据集的偏置。解决在新数据集上至少重新聚类锚框并冻结主干前几层做微调再逐步解冻。别直接拿旧权重端到端训容易过拟合旧域。6. 验证与部署把改进模型塞进实际监控链路的一个技巧改进做完最终要落到能跑的地方。验证阶段除了常规 mAP我习惯做一个分距离段的召回曲线把 GT 按框面积分成远、中、近三档分别画召回随置信度阈值变化的曲线。这样能直观看到改进在哪个距离段起作用也方便和业务方对齐“到底够不够用”。部署时如果目标是边缘设备比如在树莓派 5 上跑自己训练的 YOLOv5 模型建议先做通道剪枝再导出 ONNX剪枝率从 0.3 开始试每剪一次都重新验证小目标召回别一次剪太狠。导出 ONNX 时注意把注意力模块里的动态 shape 操作固定住否则某些推理引擎会报错。# 导出 ONNX 的关键参数 torch.onnx.export( model, dummy, ship_yolov5.onnx, opset_version12, input_names[images], output_names[output], dynamic_axesNone # 固定 batch 和尺寸边缘部署更稳 )opset_version用 12 是兼容性和算子支持比较平衡的选择dynamic_axes设 None 是为了让推理引擎能充分优化边缘设备上固定输入尺寸反而更快。导出后用onnxruntime跑一遍和 PyTorch 输出对齐误差超过 1e-3 就要查是哪个算子的问题通常是注意力里的amax或mean在低版本 opset 下行为不一致。最后说个我自己的习惯每次改完结构先别急着跑 300 epoch用 50 epoch 的小实验快速筛一遍只保留小目标召回有提升的改动再上全量训练。船舶检测这个方向花在无效改进上的时间远比调参多快速证伪比盲目堆模块重要得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表