1. 任务拆解与方案选型:冷水珊瑚检测到底难在哪
Lophelia pertusa是冷水珊瑚里最典型的一种,常常在几百米甚至上千米深的水下形成大面积的珊瑚礁,是深海生态系统的关键栖息地。要监测它的分布和健康状况,靠潜水员不现实,主流做法是让ROV(水下机器人)带着摄像系统在固定航线上跑,回传连续视频流,再由专业人员一帧一帧地看。视频一多,人的眼睛根本顶不住,动辄几十个小时的录像,看久了漏判率会明显上升,而且不同人的判定标准也不一致。这时候把目标检测模型拉到流程里,让它先做粗筛,把疑似Lophelia的帧和位置标出来,再由生态学专家复核,整个监测效率就能提升一个量级。
这次选的检测框架是YOLOX,而不是YOLOv5或者更早的YOLOv3,主要考虑到几点:一是Lophelia属于纹理复杂、边缘不规则的半刚性目标,不是像行人、车辆那样有清晰边界的刚性目标,需要 anchor-free 的回归方式减少框的误导;二是水下图像普遍存在对比度低、颜色偏移的问题,模型需要更强的特征提取能力;三是部署环境可能是甲板服务器,也可能是ROV上的小型计算盒,YOLOX 提供了从MobileNet到DarkNet53一系列可换的骨干网络,能灵活调整精度和速度的平衡。后面我会把每个环节的选择理由和实际参数都展开说,方便你直接参考这个流程去套自己的水下目标数据集。
整个项目可以拆成四个核心模块:数据工程、模型训练、评估迭代、部署推理。看起来是标准的目标检测流程,但每个环节放到水下场景都有坑。数据工程不是光抽帧和打标签,水下视频的帧间重复率高、光照波动大,抽帧策略要专门设计。模型训练的显存和BatchSize、EMA衰减系数、SimOTA的候选框数量这些参数都不是照抄COCO配置就完事的,要结合目标大小分布和类别不平衡去调。评估阶段也不能只看mAP,漏检率对生态监测是硬指标。部署阶段更麻烦,ROV的工控机算力有限,TensorRT或OpenVINO的精度损失怎么控制都是实际问题。
接下来我按这条主线逐个模块拆,把能复现的配置、参数和踩坑记录都写出来。
1.1 核心难点:水下图像退化与目标形态建模
Lophelia在ROV影像里通常呈现成片的白皙色或奶油色分枝状结构,但这不是绝对的——墨蓝色水体把红色通道吃掉了,珊瑚的色调会向灰绿偏移;当ROV的补光灯角度变化时,高光区域和阴影交替出现,同一条珊瑚枝在相邻帧看起来像是两种东西。这就是水下检测和陆地检测最大的区别:域偏移随时存在,不能指望训练集和测试集是同一分布。
形态上,Lophelia的检测框也不像读车牌那样规整。它是一簇一簇的分枝,框的边界怎么定本身就是个主观题。如果标注时不够谨慎,会出现同一个珊瑚簇在相邻帧被标成不一样的框,直接干扰模型的空间一致性。我的经验是标注规范一开始就要定死:框尽量包含完整骨架主枝,细小的末端分支如果超出画面边缘就不强行纳入,宁可框略大一点,不要在框的完整性上反复横跳。
1.2 方案选型:为什么YOLOX是平衡点
YOLOX发布于2021年中,放在今天已经不算新,但在很多工业项目里依然是很好用的选择。它把anchor-based换成anchor-free,减小了正负样本匹配的复杂性;解耦头把分类和回归分开,避免两个任务互相干扰。对Lophelia这种边缘不太规则的目标,anchor-free的focal loss配合SimOTA匹配策略能把低质量预测过滤得比较干净。
相比YOLOv5,YOLOX的部署灵活度更高,官方提供了Python、C++、TensorRT、ONNX、OpenCV DNN等一堆导出路径。相比YOLOv8,YOLOX在端侧推理的速度和内存占用上更可控,这对ROV上不富裕的算力来说很关键。选型时我的判断标准很简单:如果团队没有大量时间做Relabeling和调参,选YOLOX的extra分支,成熟稳定,社区案例多,坑基本上都能搜到。
其实还可以提一嘴YOLOX-PAI或者Nanodet这个方向,但考虑到底层代码可控性和资料密度,项目最终锁定了YOLOX的官方仓库。后面所有训练和评估都跑在PyTorch框架下,骨干网络用了DarkNet53,输入分辨率取640。这个选择在后续实验里证明是合适的:更大分辨率比如960能把小目标召回拉高3到4个百分点,但训练时间几乎翻倍,推理速度也掉得明显,在ROV场景里不算划算。
2. 数据工程:抽帧、标注、增强,每一步都在决定上限
很多项目在模型结构上花大力气,结果精度上不去,回头查全是数据的问题。水下目标检测尤其如此,因为图像质量本身差,数据再不干净,模型就学不到东西。这一节我把实际操作流程写细一点,包括参数和选择逻辑,可以直接照着做。
2.1 ROV视频抽帧策略与质量筛选
ROV视频一般是25fps或30fps的MP4流,连续几分钟内画面几乎静止,如果全部抽帧,数据极度冗余不说,还会让相邻帧之间在训练集和验证集之间泄漏,指标虚高。更麻烦的是视频里经常有手控操作和悬停观察的片断,画面模糊和剧烈抖动占比不小,直接拿去训练就是噪声。
我这边采用的方法是“先抽关键帧,再人工质量筛选”两步走:
- 按固定间隔抽帧,间隔设置为每2秒抽1帧,也就是12.5fps或15fps对应约25到30帧中抽1帧。
- 对抽出的图像计算Laplacian方差,做模糊检测,低于阈值直接淘汰。
- 再把保留帧按时间顺序每N帧划分为一个迷你段,每个段里随机挑1到2帧,避免空间冗余。
模糊检测的阈值要自己看一批数据调,不固定。ROV如果开启了防抖云台,阈值可以设高一点;如果镜头是固定在车体上的,抖动严重,阈值就得放松。这个度没有标准答案,我通常的做法是先输出100张模糊分值的直方图,人工看一下临界区域长什么样再定。
抽帧之后一定要做数据集洗重。把分辨率、采集地点、光照条件等属性记录下来,训练时按地点分组做K折验证,防止同一个视频的帧同时出现在训练和测试里。我一开始偷懒没做分组,直接在全体帧里随机划分,结果mAP显示到了0.87,换成按视频分组重新划分后,掉到0.76,虽然不好看,但真实。
2.2 标注规范与工具选型
标注工具我用的是LabelImg和X-AnyLabeling轮着来。前者轻量简单,适合批量矩形框标注;后者对半自动辅助有帮助,但水下目标本身是稀疏纹理,自动标注的初始框经常歪,还是要手动调整。很多同学喜欢一上来用任意角度旋转框或者多边形实例分割的标注方式,觉得更精确,但要注意后处理复杂度会指数上升,YOLOX的常规训练管线并不直接支持多边形,需要再转mask或旋转框,非常麻烦。所以第一版老老实实矩形框,先把工程跑通,拿到基准确认任务可行性,再考虑是否上实例分割。
标注规范是数据质量的第一道闸门。Lophelia的定义,我在项目里按下述规则执行:
- 具备明显分枝结构、整体呈白色或灰白色的目标,且大小占画面至少12x12像素,才允许标注。
- 分枝稀疏、形态不清晰的,不标。
- 被泥沙半掩埋、只有小部分露出的,不标。
- 多个珊瑚簇挨在一起但主体分枝可区分的,分开标注,否则合并为一个框。
- 画面边缘被截断的主骨架,框的边界截至图像边缘。
定了规范后,至少要两个人分头标同一批图,算一下框的IoU一致性,低于0.7就得重新对齐标准。这个步骤看着费时间,实际能省后面大量返工。
标注量上,第一版只训练单类,Lophelia一共标注了8600个目标框,分布在5300张图上,其中约3000张来自不同海区和不同光照条件。类别单一是刻意的,先保证核心目标不做错,再考虑把海蛇尾、海葵、垃圾类加进来做多类扩展。单类的另一个好处是能精确分析误检来源,比如把岩石误检成珊瑚,问题出在纹理相似还是光照,一目了然。
2.3 增强策略:模拟水下物理环境,而不是盲目堆料
通用的随机翻转、随机裁剪、色彩抖动这些常规增强对水下任务远不够。问题在于模型需要学会忽略偏蓝偏绿的光照、阴影变化和漫射噪声,泛红的珊瑚在不同光照下要能被同一个模型认出。我尝试把增强分成三组对比实验:基础组、强色彩组、物理模拟组。
基础组就是Mosaic+MixUp+RandomHorizontalFlip+RandomAffine,这套YOLOX自带。
强色彩组在基础组之上加了HSV空间的大范围抖动,色调偏移幅度从±10度加大到±25度,饱和度从±10%加到±30%。
物理模拟组更进一步,参考水下成像模型,对图像做通道级衰减和散射模拟。简洁的做法是把RGB三通道各自乘以不同系数,比如红色通道乘0.55,绿色通道乘0.85,蓝色通道乘0.95,再叠加高斯噪声和随机局部模糊。这种方法模拟了不同水深条件下红光的衰减差。
实验结论很有意思:强色彩组对mAP的提升最明显,约涨了2.8个百分点;物理模拟组提升约1.5个百分点,但是对模型在极端蓝绿图像上的鲁棒性帮助很大。基础组是底线,不能省。Mosaic和MixUp对提升泛化能力的作用很关键,尤其在目标尺度变化大、样本分布不均衡的数据集上,Mosaic可以把不同深度的珊瑚丛拼在一张图里,让模型更适应尺度和背景的多样性。
还有一个细节是Copy-Paste增强。把标注出来的珊瑚目标Roi区域随机裁剪,粘贴到不含目标的背景区域上,同时做一点透视变形和色彩扰动。效果很好,因为天然增加了目标的数量和分布多样性。但要注意,如果背景区域真的没有别的生物,粘贴时不要破坏真实标注的语义边界。
3. 训练配置:YOLOX机制与参数设定的底层逻辑
这一节讲训练侧。YOLOX本身不是单纯拿来就训的,几个关键机制和超参数直接影响效果,尤其在水下数据集上,很容易踩到调参的坑。
3.1 从Anchor-based到Anchor-free:为什么这反而稳了
YOLOX的最大改动在于Decoder部分完全去掉了预设的anchor框。传统YOLO系列需要聚类出若干组anchor尺寸,在特征图每个位置生成多组预设框,再去和gt做匹配。问题是Lophelia的尺寸和长宽比跨度极大,近景大珊瑚和远景小珊瑚在同一个batch里出现,预设anchor无论如何都很难照顾周全——anchor数量多了,计算量上去,数量少了,召回率会掉。
Anchor-free的做法简化了这一点:让每个空间位置直接预测目标中心的存在概率和到四条边的距离。两级特征图下采样倍数不同,配合FPN不同level的检测头,分别对应大目标和小目标,天然适应了珊瑚的尺度分布。这个机制在实际数据上的好处非常直接:小目标漏检的比例明显下降,尤其是在远端影像里,Lophelia可能只有20x30像素大小,anchor-based模型需要刚好匹配到合适的尺度层,而anchor-free更容易对上。
不过anchor-free也有代价:中心点附近的所有特征都会参与预测,一个长条形的珊瑚簇,中心区域占的面积不大,如果中心点的定义不准确,回归的loss就容易混乱。YOLOX为此引入了中心采样(center sampling)策略,在SimOTA时会限制正样本候选落在gt框中心附近的一定区域,避免远离中心的特征点参与回归,这算是很实用的设计。
3.2 SimOTA标签分配:让每个目标找最合适的预测
SimOTA是把“目标分配”当做一个最优传输问题来近似求解。流程大致是:
- 对每个gt框,找出它周围所有候选预测点,以及这些点的预测得分和代价。
- 用topk策略选择cost最小的若干预测点作为正样本。
- cost由分类loss和回归loss共同组成,动态平衡多任务。
- 最后在全局范围内逐步调整每个gt的样本数,称为dynamic k。
这个机制在类别多、目标密集的数据集上表现好,但水下珊瑚场景常常是“稀疏大目标”,一张图就0到3个目标。SimOTA没太多候选点可以选,反而容易出现某些gt周围没有合适的正样本,导致该目标完全没参与训练,表现为loss下降正常但recall特别低。
我调试时发现,把SimOTA的center_radius从默认的2.5缩小到1.5,让候选采样范围更贴合小尺寸目标和稀疏分布,recall有明显回升。配合上3.1提到的center sampling,很多边缘目标也能被正确分配。另外,这个参数不能调到过小,否则真实框中心偏移大的样本就完全匹配不上了,反而是反效果。
3.3 训练超参的实测记录
YOLOX仓库的default配置是COCO语义下的产物,直接对它做迁移训练就意味着要改几个关键位置。
我最终用的配置如下(这是实际项目里跑通的一组参数):
| 参数名 | 值 | 调整思路 |
|---|---|---|
| 输入尺寸 | 640x640 | 在召回和速度之间取平衡 |
| BatchSize | 16 | 单卡V100上的上限,可放宽到32但梯度不稳 |
| Optimizer | SGD,momentum=0.9 | 收敛稳定,比Adam在检测上表现好 |
| 初始学习率 | 0.01 / BatchSize x 0.001 | 按线性缩放规则 |
| 学习率调度 | cosine anneal,warmup=5 epochs | 前1/8周期先warmup,防开局发散 |
| Epoch | 150个完整epoch | 深水数据噪声大,收敛偏慢 |
| EMA | 开启,decay=0.9998 | 让模型权重更平滑 |
| weight_decay | 5e-4 | 防止骨干过拟合 |
| 冻结骨干 | 前5个epoch冻结 | 保护预训练语义特征不被破坏 |
| mosaic_prob | 0.85 | 后期逐步降为0.3,避免真值错位 |
其中最重要的经验是初期冻结骨干。骨干用的是ImageNet预训练的DarkNet53,如果一开始就放开所有层,水下图像的大面积色偏会把浅层特征冲击得很厉害,网络会花很长时间去重新适应,而前5个epoch冻结,让检测头和neck先学会把小目标定位出来,骨干权重不被破坏,之后整体微调,收敛速度和最终精度都能改善。
ema_decay这一项也值得专门说一句。0.9998是偏慢的EMA,在150epoch的情况下能跟到很后面,精度比0.999略高一点,但模型拉到推理阶段时,ema权重的表现通常是更好的。训练日志里要同时记录raw model和EMA model两个mAP,很多人只看其中一个,后期踩坑。
4. 训练实录与评估指标:我能看到什么,到了什么水平
这节记录一次实际训练的过程和评估环节,包括Loss曲线陷阱、mAP不够用的地方,以及为什么需要建立一套自研的评估协议。
4.1 Loss曲线的正确打开姿势
训练到第20到30个epoch时,loss曲线整体向下,但会发现分类分支的loss下降明显快于回归分支,而且有轻微的平台期。这是正常的,因为Lophelia的类别单一,分类任务简单,框回归才是难的部分。如果回归loss不断震荡,不要急着调学习率,先看是不是SimOTA的候选样本分配在变化——比如有些目标在前50个epoch被分配给了低层特征图,后50个epoch又换成了高层特征,会导致回归目标不一致。
另一个镜像问题是mAP在训练集上很高,0.94,但验证集上一直上不去,整体在0.74附近徘徊。这种情况基本就是过拟合。我在40epoch左右尝试把mosaic_prob降到0.5,mixup关闭,并加大random_crop的幅度,让模型从小数据集模式切换到大尺度模拟模式,mAP回升到0.81,缓解了过拟合。
Loss曲线只作为过程监控工具,不要试图单看某一项loss高低来评判模型好坏,因为整个训练期间loss的绝对值一直被各种开关(mosaic、MixUp)和标签分配策略影响,横向比较没有意义。关注相对变化才是对的。
4.2 评估指标:为什么mAP要看IoU=0.3而不是0.5
生态监测类项目对漏检率的要求更高——漏掉一丛珊瑚,对资源评估和变化趋势监控的影响,比把一个石头误判成珊瑚要大得多。mAP@0.5的常规threshold对稀疏小目标可能太严格,Lophelia的边界框不确定性较高,哪怕标注者也会有30%的IoU差异,所以依赖0.5的阈值会把大量“检测对了但框不够准”的结果判成假阴性。
我在项目里建立了一个多层次评估协议:
- 第一层:mAP@0.3,用于评估“中心找对了没有”,当作目标存在的指示器。
- 第二层:mAP@0.5,用于评估“目标定位质量”,作为常规精度参考。
- 第三层:F1曲线最佳点的precision和recall,看实际部署时的平衡表现。
- 第四层:按目标尺寸分层,统计小目标(像素面积<32x32)、中目标(32x32到96x96)、大目标的recall,防止“只看总量骗自己”。
实测最好模型在上述协议下:mAP@0.3大概0.86,mAP@0.5大概0.78,recall在小目标段为0.68,大目标段为0.87。另一个关键指标是F1的阈值位置,发现最佳F1在0.52左右,这个值在部署时可以直接作为置信度阈值的前置设定。
4.3 小目标和背景干扰是主要瓶颈
所有误差分析做完,漏检主要集中在小目标和背景底质高度相似的区域。海底的砂石、碎壳和珊瑚死骨骼在灰度纹理上跟活的Lophelia很像,模型经常把小块的死珊瑚骨骼误判为活体,这个问题的本质是训练数据里活体与死骨骼样本不平衡。
处理思路包括引入负样本难例挖掘:把验证集里被误判为目标的非目标图块,抽样加入训练集,并直接标注为背景。同时调整数据集的采样策略,让每批batch里难负例占比保持在15%到20%。最终误检率下降了大概一半。
5. 部署实践与常见问题排查:从PyTorch到ROV工控机的旅程
训练只是前半段,水下项目真正麻烦的是把模型挪到现场环境里跑起来,还要稳定运行。
5.1 推理速度与精度的平衡选择
ROV上能部署的算力通常是一块NVIDIA Xavier或者类似Jetson设备,计算能力非常有限。直接跑PyTorch模型不现实,需要先转成TensorRT或者ONNX。我的步骤是:
- PyTorch权重转ONNX,使用opset=11,输入固定尺寸640x640。
- ONNX转TensorRT,用FP16,遍历一层层检查精度损失。
- 给模型输入加preprocess:水下图像先做直方图均衡化,再归一化到0~1,实测能提升最终精度约1.2个mAP点,代价是额外增加3ms耗时。
- 推理用Engine对象反复调用,不再走ONNX Runtime的接口,减少调度开销。
整个FP16转换后,在Xavier上大约跑到50ms一帧,约20FPS,连续处理720P视频很吃力但能跑。如果再叠加ROV本身的姿态震动和光源变化,抽帧处理控制在10FPS以下,留余量比较稳妥。
5.2 常见问题速查表
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 训练loss持续下降但验证mAP不动 | 过拟合 | 关MixUp,降低mosaic_prob,加大random_crop |
| 小目标全漏检 | 输入分辨率太低 / 小目标样本不够 | 提升到768,加入Copy-Paste增强,检查SimOTA候选范围 |
| 大量把石头误报成珊瑚 | 难例样本不足 | 难例挖掘,添加背景负例 |
| 部署精度比验证集低一大截 | 预处理不一致 / 图像色偏差异 | 统一mean/std和归一化顺序,现场抽帧做在线校准 |
| 检测框整体偏移 | 标注规范不一致 / 中心采样偏移 | 重新对齐标注框,检查SimOTA center_radius |
| 深蓝图像检测变差 | 训练集覆盖不足 | 做水下物理模拟增强,增加深水样本 |
| 关机重启后Engine挂掉 | TensorRT序列化与版本不匹配 | 重新生成engine,避免跨设备序列化 |
5.3 难例挖掘的具体实施方式
难例挖掘是提升部署效果最具性价比的操作。具体做法是:拿训练好的模型在验证视频里跑一遍推理,把被误判为目标的高置信度负样本图块自动截取出来,按置信度降序排列,然后人工扫一遍,确认这些确实都是背景或非目标,再将其裁剪后放入训练数据,标注为negative(空标签),并且给予它们约1.5倍的loss权重。这个过程迭代两到三轮,误检率可以显著下降。
这里有个很隐蔽的坑:难例挖掘做多了,模型会变得过度保守,把真正的珊瑚也压制掉了。所以每做完一轮难例挖掘,就要回归一遍验证集,让mAP和recall都保持正向趋势再继续,否则宁可停下来。
6. 工程扩展方向:从单类检测到长期生态监测
项目到这一步,已经能用YOLOX完成Lophelia的基础识别检测,我从自身的体验来看,整套流程成熟后还能往几个方向扩展。
6.1 多类目标扩展与Joint Detection
冷水珊瑚礁生态系统的监测往往不止关注Lophelia本身。海蛇尾、海葵、海绵、深海鱼类都是重要的指示物种。把这些类别添加到同一个模型里,甚至可以用共享骨干的multi-head结构,比单独为每个物种准备一套模型要节省大量资源。训练时要注意的是类别不均衡——Lophelia样本多,海蛇尾样本少,可以给少数类用重采样或focal gamma调整。
6.2 从2D框到形态分割
矩形框能告诉科学家“这里有Lophelia”,但给不出生物量的有效估计。下一步很自然是把检测升级为实例分割,比如用YOLO-Seg或者Mask R-CNN,直接输出珊瑚簇的像素级轮廓。有了轮廓数据,可以估算珊瑚覆盖面积、分支密度,甚至分析生长速率。不过分割标注成本比检测高很多,如果不是直接需要生物量数据,检测框的输出就足够支撑大部分监测需求了。
6.3 视频时序信息的利用
ROV视频是时间连续的,每一帧的检测结果天然形成了轨迹。利用这一点,可以把单一帧的漏检通过前后帧的检测结果补回来——当前帧没检测到,但前后各5帧都检测到了,那这一帧大概率也有目标,只是被模糊或遮挡干扰了。我采用简单的时间投票机制:维护一个10帧的滑动窗口,目标框在窗口中连续出现8次以上,就认定这个目标存在,并把位置做平滑外插。这套后处理能显著提升视频层面的召回率,尤其面对ROV悬停和小幅晃动时的检测稳定性提升非常明显。
6.4 当前局限与谨慎心态
需要清醒认识到,目前这套系统的输出更适合做“区域监测和变化趋势分析”,而不是“绝对丰度评估”。光照条件和ROV离底高度对检测结果的影响仍然很大,不同航次之间的数据可能会带来精度大幅波动。使用的时候要做好Mindset:模型帮你高效筛选出疑似目标,最后由生态学专家做最终判断,这才是健康的使用方式。任何号称可以直接替代人工计数的系统,在水下场景里目前还很不可靠。
我自己在这个项目里学到的最大一条是:数据的均匀性和标注一致性,直接影响整个项目的天花板,模型结构和调参只能在它的基础上做微调。与其花大量时间在刷网络上各种结构变更,不如先花心思把采像方案定好,让ROV上的补光灯和相机参数保持稳定,这样后期的模型训练难度才会真正降下来。最后再分享一个小技巧:训练前花一天时间对整个数据集做一个颜色统计直方图,看看有没有异常的颜色偏移样本,把它挑出来单独处理,往往能给最终模型带来意想不到的稳定性提升。