
简介一份面向水下光学目标检测竞赛的完整源码与学习说明资料包源自二零二一年和鲸赛项对应A榜零点五六九、B榜零点五六八的可复现方案。面向计算机、数学、电子信息等专业学生与竞赛入门者覆盖数据读取、模型训练、推理验证等环节可作为目标检测方向的项目式学习样板。压缩包共含八百七十七个文件大小约六点五四兆字节以Python源码、Markdown说明文档、YAML配置为主另有Shell部署脚本、Jupyter教程和示例图片便于按文档快速跑通流程。目前已有约一百四十三人学习适合希望复现高分成绩或基于MMDetection改造水下检测任务的读者。除模型核心代码外还附有容器化部署配置、测试图片、环境安装脚本及自动化测试配置可减少环境搭建与结果验证的重复工作教程文档配合细节讲解能帮助理解从标注格式到训练参数调整的完整思路是一份兼顾上手与深挖的资料。1. 拿到A榜0.569的源码包之前先搞懂这是一场什么比赛提到2021和鲸水下光学目标检测智能算法赛项行内人第一反应往往不是某个模型名字而是标题里那两个数字A榜0.569、B榜0.568。这个成绩放在水下视觉任务里属于中上水平——公榜私榜没有倒挂说明验证划分干净、方案没有对公开榜单过拟合。赛后流出的源码包通常把训练脚本、推理脚本、数据配置和学习说明压在一个zip里代码量不会太大真正值钱的是它对水下图像做的针对性处理。它适合两类人看一类是拿到源码想快速复现成绩的参赛者另一类是准备下一届比赛、想把0.569推到0.6以上的研究者。下面按赛题难点→框架选型→复现跑通→避坑排查→赛后提升这条线展开。2. 水下光学目标检测的难点为什么同一套YOLO在陆上0.75水下只有0.562.1 赛项到底在检测什么任务定义、标注格式与评估口径水下光学目标检测赛项主办方通常给出一批由水下机器人或潜水设备拍摄的图像要求参赛者检测图像中的某一类或几类固定目标。目标集合因赛而异有的以海底生物资源为主比如海参、海胆、扇贝、海星有的会加入鱼群、蟹类甚至人工构件。这类目标有几个共同点体积小、颜色与背景接近、分布密集。训练集标注常见的是Pascal VOC的xml格式或COCO的json格式每个目标一个边界框。拿到数据先看类别分布——如果某个类别样本量只有其他类别的十分之一后面所有训练都绕不开不均衡问题。评估口径同样需要先确认因为它决定优化方向。大多数水下检测赛项使用mAP0.5作为主指标也就是IoU阈值固定在0.5每个类别单独算AP再取平均。少数赛项用mAP[0.5:0.95]后者对框的定位精度更敏感。这两者的差别在实践里非常大mAP0.5下框稍微偏一点不影响判定mAP0.5:0.95下边界抖动0.1个IoU可能就把正样本变成误检。源码包里如果写了最终成绩0.569但没有标注评估口径先按mAP0.5理解因为这是水下赛事的绝大多数选择。真要严格对齐看的不是代码而是赛项官方评测说明。还有一个容易被忽略的点是标注噪声。水下图像模糊目标边界不清晰人工标注的框经常比目标实际轮廓大一圈或小一圈。标注噪声会让模型的AP天花板降低——模型输出再准和标注框的IoU也可能只有0.6。处理思路是训练时不要给框回归loss太高权重或者在数据加载阶段过滤掉那些面积异常大或异常小的标注框。很多新手把AP卡在0.5附近归因于模型能力不足实际上先看标注质量往往能找到更直接的原因。2.2 水下图像的退化偏色、低对比度与边缘模糊水下图像和普通自然图像最大的区别在于光的传播。水对光的吸收具有波长选择性红光在几米深度内就衰减大半蓝绿光能传得更远所以水下照片整体偏蓝绿色水中的悬浮颗粒造成散射又让图像整体发雾、对比度下降、细节模糊。常用成像模型可以写成 J(x) I(x)·t(x) B·(1-t(x))其中I(x)是相机实际观测到的图像J(x)是目标在无衰减条件下的理想图像t(x)是透射率B是背景光。检测模型要恢复的是J(x)中的目标位置但它实际看到的只有I(x)而且不同深度、不同水质下的t(x)和B都不一样。这个退化过程对检测有三个直接影响也是后面所有预处理和增强策略的出发点。第一目标与背景的灰度差变小小目标更容易淹没在环境噪声里模型在深层特征上容易丢掉目标边缘。第二水色分布变化大模型可能学到的是蓝绿色背景下的海参样子而不是海参本身的样子训练集和测试集一个偏蓝一个偏绿特征分布就错位了。第三模糊导致标注框本身的置信度低同一个目标在不同图像里标注员框出的范围可能差几个像素这会让回归任务的目标值不稳定。所以水下检测的通用做法一般不是上来就换大模型而是先把图像退化控制住。常见做法有这么几类用白平衡或灰度世界算法消除偏色用CLAHE做局部对比度拉伸用去雾算法估计透射率做复原。这些方法单独拎出来都有效果但要注意它们不是无损操作——CLAHE会在平坦区域放大噪声去雾算法在深水区可能产生伪影。这也是为什么后面讲到数据增强时我不建议把颜色处理做成离线固定的预处理而应该做成训练时随机扰动。2.3 0.569和0.568这两个数字代表了什么A榜和B榜是竞赛常见的两阶段评测方式。A榜通常对应一个公开的验证集或部分测试集参赛者在比赛期间可以反复提交看到每次提交的分数用于调整方案B榜在比赛临近结束时开放对应隐藏的最终测试集提交次数有限或不可见分数。A榜0.569和B榜0.568几乎持平说明这套源码在公开榜上做的所有调参没有被验证集泄漏反噬——模型在未见过的B榜数据上保持了接近的精度。但持平也意味着两件事。一是方案已经进入平台期。如果数据量不大0.56~0.58这个区间往往对应一批难以区分的难样本密集堆叠的小目标、遮挡严重的目标、和背景颜色相近的目标。这时的提升不再来自学习率或epoch而是来自对这些难样本的专门处理。二是源码包里的线下验证划分大概率是合理的。这也是这份源码最大的价值一个可靠的本地验证流程不需要反复提交A榜就能判断模型改动是否有效。对复现者来说我的建议是先不要动网络结构。先把这个0.569的模型跑出来然后用它的验证结果做错误分析把预测错误的图按类别、尺寸、水色分开统计看看哪一类错误占比最大。这一步比直接换Backbone更能帮你定位瓶颈。错误分析结果会直接告诉你下一步应该去做数据增强、调anchor还是换更大的输入尺寸——通常是这三者之一而不是换模型。3. 检测框架与选型为什么YOLOv5是这份源码里最可靠的起点3.1 检测框架对比单阶段、双阶段与anchor-free的取舍水下光学目标检测赛项的往届方案中YOLO系列占据大多数YOLOv5是其中最常见的基底。原因很简单生态完整、单卡可训、改造成本低。官方仓库内置了数据加载、Mosaic增强、自动锚框、多尺度训练、TTA推理等一整套管线竞赛源码包大多在它之上改动网上积累的踩坑经验也最多。相比之下YOLOX在结构上引入了SimOTA标签分配和anchor-free解耦头理论上小目标表现更好但它的训练对超参更敏感同样的数据分配策略在水下脏标注下容易出问题。两阶段的Faster R-CNN在候选区域提取上更精细小目标召回通常优于单阶段但训练速度和显存占用都不友好。如果竞赛允许用预训练权重Faster R-CNN的精度上限更高如果只能从零训单阶段的收敛稳定性更好。在水下赛项里标注噪声大、单卡训练、周期短这些条件叠加后YOLOv5的综合性价比最高。这不是说YOLOv5一定比Faster R-CNN强而是说在要在有限时间内复现0.569这个分数这个目标下YOLOv5是阻力最小的路径。框架精度上限训练成本小目标表现复现难度YOLOv5中上低中可加P2层低YOLOX中上中中上中Faster R-CNN高高高高表格里的判断不是绝对的但可以作为选型参考。我见过有队伍用Faster R-CNN拿到更高分那通常发生在类别少、目标大、标注干净、GPU充足的条件下。如果你拿到的源码包本身是YOLOv5系那就直接在这条线上做优化不要中途换框架——换框架意味着数据加载、增强、调度全部重来竞赛复现场景下时间成本不可接受。3.2 Backbone与预训练ImageNet权重在水下场景要降温YOLOv5默认的CSPDarknet骨干在ImageNet上预训练过。ImageNet是自然图像水下图像偏色、低对比、纹理弱两者分布差距很大。但预训练权重依然建议用因为骨干网络低层学到的是边缘、角点、纹理基元这些特征与颜色分布无关在水下仍然有效。需要降温的是高层的语义特征和颜色相关特征。常见的微调做法是分层设置学习率骨干前几层冻结或用小学习率检测头用正常学习率。YOLOv5官方实现里没有直接给分层学习率的参数常见做法是在自定义训练脚本里对backbone参数和head参数分别设定lr_mult。很多竞赛源码包并没有做这个处理而是全局统一学习率也能跑到0.569。这说明分层学习率不是必要条件但能减少前期震荡。一个更常见的坑是BN层。如果训练集和验证集图像数量少且风格差异大BN统计量的估计会不稳。常见做法是把输入尺寸固定到同一档比如训练和验证都用640不要训练用640、验证用512。另外从零训练时不要随便冻结BN层否则均值和方差的累积会滞后。参数层面的建议是初始学习率0.005~0.01warmup 3个epoch骨干层学习率系数0.1这套组合比一把梭的默认参数稳定得多。3.3 数据增强颜色扰动比几何增强更值得花时间增强是水下检测提分的最大杠杆。通用增强里Mosaic、MixUp、HSV扰动、随机翻转都有价值但对水下场景优先级应该调整。几何增强解决的是目标姿态变化而水下检测更常见的问题是颜色分布漂移和目标对比度低。因此颜色类增强的收益通常高于随机翻转。一个低成本且有效的组合是灰度世界白平衡和CLAHE不再作为固定预处理而是作为随机增强项以一定概率在训练时对图像生效。具体做法是把它们写成一个数据加载流水线里的函数每次迭代以0.5的概率施加一次颜色变换。这样模型见到的水色分布比原始数据集更宽对测试集的水色漂移更有抵抗力。测试时保持同样的增强管线但关闭随机开关只走预处理逻辑。注意训练和推理必须使用同一个预处理函数。很多源码包训练时用了灰度世界推理时却忘了调用预处理结果测试集分布和训练分布不一致分数直接掉。这个坑在5.2节会详细讲。训练时还可以把小目标增强单独做一组在Mosaic裁剪时对包含小目标的图像区域进行2~3倍放大再拼回去等于人为放大了小目标在训练样本里的占比。这种方法不需要额外数据但效果明显因为水下赛项的难样本大部分集中在尺寸小于32×32的目标。这种放大粘贴在YOLOv5的数据增强模块里可以改也可以在自定义dataset里实现。4. 从源码包复现训练环境、命令与超参一条线跑通4.1 zip解压后先看什么源码包结构的阅读顺序拿到这份源码zip先不要急着装环境。解压后先看目录树和根目录的学习说明确认三件事训练集和验证集怎么划分的、训练入口是哪个脚本、推理输出是什么格式。绝大多数竞赛源码包的结构都可以归到下面这个模式只是文件名不同project/ ├── data/ # 图像与标注 │ ├── images/ │ └── labels/ ├── configs/ │ ├── underwater.yaml # 数据配置 │ └── hyp.yaml # 超参配置 ├── models/ ├── utils/ ├── train.py # 训练入口 ├── detect.py # 推理入口 ├── requirements.txt # 依赖清单 └── 学习说明.md # 复现与思路文档我看源码包的习惯是先用编辑器打开学习说明找到它记录的验证集mAP和复现命令然后打开数据yaml确认类别数和类别顺序最后才看train.py。权重文件如果放在exp目录下注意区分best.pt和last.pt——best.pt是按验证集指标保存的B榜提交时选哪个权重通常要重新验证一次不要默认最后一轮就是最好的。4.2 环境配置与最小训练命令环境上这类源码默认是PyTorch。建议用conda建独立环境Python版本选3.8或3.9PyTorch的版本以requirements.txt为约束不要强行装最新版。CUDA版本和显卡驱动要对应这些搞不定可以先跑CPU模式确认代码逻辑没问题再切回GPU。显存低于8G时先把输入尺寸降到512、batch降到8确保训练能跑起来后续再逐步加大。最小训练命令一般是这样的按YOLOv5系源码的通用参数风格conda activate underwater pip install -r requirements.txt python train.py \ --data configs/underwater.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 150 \ --hyp configs/hyp.underwater.yaml这里--data指向数据配置文件--weights用官方COCO预训练权重做迁移起点--img设成640是速度与精度的折中--epochs设成150是因为水下数据量通常不大需要更多轮次让检测头充分收敛。batch 16在16G显存下比较安全如果换到8G卡batch降到8学习率也要相应降低约四分之一。训练跑起来后先看前三个epoch的loss曲线如果loss在正常下降说明环境没问题。4.3 数据配置yaml与关键超参三个必调项数据配置文件是复现最容易出错的地方。以yaml为例需要核对路径是否存在、类别数与标注是否一致、train与val是否重叠。路径问题最常见源码包在作者电脑上用绝对路径解压到你的机器后目录对不上。我会先把所有路径改成相对路径并把数据目录放到yaml同级的相对位置。path: ./data train: images/train # 训练图像相对路径 val: images/val # 验证图像相对路径 nc: 4 # 类别数按实际标注修改 names: [holothurian, echinus, scallop, starfish]这是水下检测里很常见的一组类别示例nc和names必须与标注文件里的类别定义一致差一个字符都不行。train和val不能有交集否则A榜分数虚高B榜现原形。yaml文件保存时最好用UTF-8无BOM编码中文注释在部分Windows老版本PyYAML下会触发解析异常。超参文件决定训练走向我一般只动三个地方。第一个是lr0默认0.01在batch 16时可以接受但如果batch降到8建议同步降到0.005左右第二个是mosaic和mixup的开关比例mosaic设为1.0mixup设为0.15比较常见mixup太高会让水下模糊目标变得更不可分第三个是anchor训练前用数据集的标注框重新做k-means聚类把聚出的9组宽高写回模型配置避免使用COCO的大目标默认anchors。anchor这事很多人忽略但对小目标类别多的水下数据影响经常在0.01~0.02个mAP。4.4 推理与提交格式坐标习惯决定你能不能拿到分训练完成后用detect.py对测试集做推理。水下赛项的提交格式以csv居多包含每张图像、每个预测框的类别、置信度和边界框坐标。最容易踩的坑是坐标定义同一份结果按xyxy写和按xywh写在评测系统里计算出的IoU完全不同。评测说明里如果写x,y,w,h那么x和y是左上角还是中心点必须确认。下面是一个通用的提交生成示例import pandas as pd rows [] for pred in predictions: # predictions 来自 detect.py 输出 img_id pred[image_id] for box in pred[boxes]: x1, y1, x2, y2 box[xyxy] w, h x2 - x1, y2 - y1 rows.append({ image_id: img_id, category_id: box[cls], # 序号从0还是1开始以赛题为准 score: box[conf], bbox: [x1, y1, w, h], # 转成主办方要求的格式 }) pd.DataFrame(rows).to_csv(submit.csv, indexFalse)这段代码的核心是把模型输出的xyxy转成主办方指定的xywh格式。注意三点类别ID的起始值要按赛题说明确认有的平台从0开始有的从1开始错一个类别全错置信度不要做四舍五入保留原始精度同一张图如果推理脚本已经做了NMS就直接输出不要重复过滤。测试集中如果没有目标提交文件里是否要保留空行记录也要看赛题要求。5. 避坑排查A榜B榜那0.001背后的五个问题5.1 A榜B榜几乎一样但分数就是上不去现象完整复现后A榜0.569、B榜0.568和源码包记录一致但无论怎么调增强和超参mAP都突不破0.6。原因进入平台期通常是两种因素叠加一是验证集与训练集同分布验证分数不再反映真实泛化能力二是难样本没有被专门处理——密集小目标、低对比目标占了错误的大头。解决停止全局调参做错误子集分析。把验证集按类别、目标尺寸、水色分成若干个子集逐个算AP。找到最低的那个子集后针对它做定向增强小目标弱就放大裁剪水色分布差异大就加颜色扰动遮挡多就加复制粘贴增强。这套流程比盲目调10组超参有效得多。5.2 加了颜色预处理mAP反而掉了0.03现象在数据加载阶段加了灰度世界或CLAHE训练loss下降更快但验证mAP从0.54掉到0.51。原因典型的两类错误。第一预处理只加在了训练流水线里推理时漏了导致测试分布和训练分布不一致第二CLAHE在低对比区域放大了噪声模型把噪声纹理当成了目标特征。解决把颜色恢复和对比度增强封装成一个函数在训练循环和推理脚本里调用同一个函数由同一个配置文件控制开关。如果发现CLAHE引入噪声把clipLimit调低从2.0降到1.0或者只对亮度通道操作不对色度通道操作。5.3 训练第一个epoch就出现lossnan现象train.py启动正常日志里第一个epoch的loss就是nan验证mAP始终为0。原因最常见是学习率相对batch size过高检测头随机初始化后梯度爆炸其次是混合精度训练在低精度下遇到异常梯度少部分是标注数据里存在空标签或越界坐标导致loss计算出现inf。解决把lr0从0.01降到0.001再试。如果已经用了AMP混合精度先关闭再跑一个epoch确认。仍然nan的话检查labels目录找出坐标小于0或大于图像宽高的标注删除或修复后重跑。nan问题99%可以在前两步解决不要浪费时间改网络结构。5.4 大目标全对小目标漏检一片现象验证集上大目标AP超过0.8小目标AP只有0.2整体mAP在0.56附近拉不动。测试集小目标占比更高B榜分数比A榜更低。原因输入尺寸640下小目标在深层特征图上只占几个像素默认anchors是从COCO数据集聚出来的锚框偏大匹配不到小目标。解决第一把输入尺寸从640提到768或896batch适当减小第二重新聚类anchors用训练集标注框跑k-means输出9组anchor写回模型配置第三如果显存不够考虑在模型配置里增加P2检测层让网络在小目标特征图上直接输出预测。这三步按顺序做小目标AP通常能提升5个点左右。5.5 TTA开满分数反而下降了现象推理时同时开了水平翻转、垂直翻转和多尺度线下验证集mAP涨了0.003提交B榜后分数从0.568掉到0.561。原因TTA的增益在验证集和测试集上并不总是同向的。多尺度TTA让置信度不高的预测产生了更多候选NMS在IoU阈值不匹配时把这些候选压掉或误保留水下目标边界模糊翻转TTA生成的镜像候选框经常和原框重叠度过低反而干扰最终结果。解决TTA只保留水平翻转和一种尺度的放大关掉垂直翻转——水下目标没有上下颠倒的常规出现逻辑。NMS的conf_thres在TTA场景下要适当提高从0.1提到0.25过滤掉低置信度的重复框。每次调整TTA参数后在本地验证集复现分数变化确认是正向再提交。6. 从0.569继续往上推赛后该做的三件验证源码包到你手里时比赛可能已经结束但它最有价值的不是那套权重而是学习说明里记录的决策过程。我复现完一套竞赛源码后会做三件事也算把分数往上推的三条路线。第一件事是伪标签。用训练好的模型在无标注测试集上做预测把置信度高于0.9的框当作伪标注并入训练集重新训练一轮。水下目标类别少、背景固定伪标签的噪声通常可控常见做法是每一轮只保留高置信样本并监控验证集AP的走向如果验证集AP掉了说明伪标签噪声太大下调置信度门槛。这套源码如果验证集划分得干净伪标签一般能带来0.01~0.02的提升。第二件事是交叉验证式的错误定位。源码包通常只给了一套train/val划分我会在完整训练集上做4到5折交叉验证用每折的验证集独立测一遍mAP。交叉验证除了能拿到更稳的分数估计还能暴露方案在某个数据切片上的脆弱性——比如某一折包含大量深水图像时分数骤降就说明模型对水色分布敏感下一步应该加颜色归一化。这个步骤不需要全量重训先测两折就够定位问题。第三件事是抠推理细节参数。最后一轮训练结束后把detect.py的conf_thres和iou_thres扫一遍网格conf从0.1到0.3、iou从0.4到0.6每次都在验证集上出mAP。很多时候0.568到0.575的提升不来自任何训练改动而来自这两个参数的组合。找到最优组合后写回推理脚本和提交csv生成逻辑保证B榜与A榜使用同一套设置。这三件事做完你对这套源码的理解已经超过下载一个zip跑通程序的程度。我自己在类似的水下赛项里吃过亏比赛时为了追A榜反复调超参最后一轮B榜反而掉了0.01原因是验证集和训练集的划分被我调乱了。后来我养成的习惯是每次训练只依赖一套固定线下划分做决策线上分数只用来确认不用来调参。这个习惯帮我省下不少深夜去盯排行榜的时间。希望这些复盘思路也能帮到你。本文还有配套的精品资源点击获取