☰
水下管道智能巡检视觉实战:图像增强、分割拟合与缺陷检测
2026/11/3 12:05:59 网站建设 项目流程

水下管道智能巡检这类项目,视觉部分往往是最先被寄予厚望、也最容易在现场"掉链子"的一环。实验室水池里跑得好好的检测模型,一下真实水体就开始漏检;明明肉眼能看见的裂缝,算法给出的框却飘到管道外面去了。我在做这套水下管道智能巡检系统的视觉模块时,前前后后返工了三次,最后一次几乎把预处理链路推翻重写。这篇就把视觉这条线上踩过的坑、做过的取舍、以及那些文档里不会写的调参顺序摊开讲一遍,给同样在做水中机器人工程训练、尤其是负责图像这一块的朋友省点时间。内容覆盖水下图像退化建模、增强链路的搭建顺序、管道本体的分割与轴线拟合、缺陷检测通道、像素到物理量的换算标定,以及数据集构建中最容易被低估的工作量。不要求你已经有深度学习基础,但至少要能看懂 Python 和 OpenCV 的基本调用。

1. 水下视觉为什么不等于把陆地上的方案沉下去

1.1 光在水里走一趟,图像就已经不是原来那张图了

很多人第一次拿到水下相机的图,第一反应是"怎么这么绿/这么蓝",然后就想着加个饱和度或者拉一下对比度完事。这个思路在清澈水池里确实能糊弄过去,但只要水体稍微浑一点,问题就会暴露得非常彻底。

水对光的吸收是有强烈波长选择性的。清水里红光衰减系数大概在 0.3~0.4 /m 量级,蓝绿光只有 0.02~0.05 /m,差了将近一个数量级。这意味着只要拍摄距离超过一米,红色通道的信息基本就已经所剩无几,图像整体会向青蓝色偏移。更麻烦的是散射:悬浮颗粒把光线乱反射回来,形成一层均匀的"雾",这叫后向散射,它直接把整幅图的对比度压下去;而前向散射会让边缘变糊,相当于给镜头加了一个随距离变化的高斯模糊。

这两个效应叠加的结果是,同一段管道在 0.5 米处和 2 米处拍出来,颜色分布、边缘锐度、甚至目标的表观尺寸都会发生非线性变化。我最初的检测模型就是栽在这上面——训练集里绝大多数样本拍摄距离在 0.8 米以内,模型实际上学到的是"近距离管道纹理",一旦机器人拉开距离,召回率直接掉到六成以下。

所以第一件想清楚的事是:水下图像退化不是噪声问题,是成像模型问题。你要处理的是 J(x) = I(x)·t(x) + A·(1−t(x)) 这个物理过程,其中 t(x) 是随距离指数衰减的透射率,A 是水体背景光。理解这一点之后,你就不会指望单纯调个 gamma 就能解决问题,而会去考虑分通道补偿和透射率估计。

1.2 巡检任务到底要视觉回答哪几个问题

工程训练项目最容易犯的毛病是"什么功能都想加",最后每个都做不深。视觉模块在整条链路上真正需要交付的输出,其实就那么几项,我在动手写第一行代码之前先把它们列清楚了:

输出项用途精度要求更新频率
管道区域 mask判断机器人是否偏离管路像素级,允许 5% 误差10 Hz
管道中心线偏移量横向纠偏控制的输入±2 cm10 Hz
缺陷目标框(裂缝/附着物/法兰)记录与报警位置误差 < 5 cm5 Hz 即可
前方障碍物距离避障减速±10 cm15 Hz
关键帧图像事后人工复核原图事件触发

这张表定下来之后,整篇文章后面的所有技术选型都围绕它展开。比如"缺陷目标框 5 Hz 就够",直接决定了我不需要为了帧率去牺牲输入分辨率;而"中心线偏移 ±2 cm",反过来要求我必须做严格的像素-物理换算标定,不能拍脑袋给个系数。

提示:如果你的项目还没定这张表,先别急着训模型。输出的精度要求和更新频率会反向决定网络大小、输入尺寸、部署平台,甚至决定你要不要上双目。这些都是改起来代价很高的决策。

2. 预处理链路:先把"看得清"做到及格线

2.1 红通道补偿与对比度增强,顺序不能反

水下增强的算法一大堆,从灰度世界白平衡、伽马校正,到暗通道先验去雾、Retinex 系列,再到近两年各种基于生成模型的方案。我在选型时挨个试过,最后落地的是一条很朴素的链路,因为它足够快、足够可解释、参数的物理含义清楚,出问题能定位。

链路顺序是:红通道补偿 → 灰度世界白平衡 → CLAHE 局部对比度增强 → 轻度锐化。这个顺序是有讲究的。红通道补偿必须放最前面,因为它是一个基于通道间相关性的线性修正,你如果先做了 CLAHE,局部对比度被拉伸过,通道间的线性关系就被破坏了,补偿系数会估得很离谱。

红通道补偿用的公式很经典:

import cv2 import numpy as np def red_channel_compensate(img, alpha=1.0): # img: BGR uint8 b, g, r = cv2.split(img.astype(np.float32)) # 用绿通道的均值作为参照,补偿红通道 r_new = r + alpha * (g.mean() - r.mean()) r_new = np.clip(r_new, 0, 255) return cv2.merge([b, g, r_new]).astype(np.uint8)

alpha这个系数我实测下来在 0.8~1.2 之间比较合适。清澈水池取 0.8 就够,浑水取 1.2 甚至 1.4。它本质上是在"补多少红"和"引入多少假色"之间做权衡:补过头了管道表面会泛出不自然的品红色,反而干扰后续分割。

然后是灰度世界白平衡,假设整幅图的平均色应该是灰的,用它去估一个通道增益。这个假设在水下其实并不总是成立——如果画面里大半是蓝绿色的水,平均色偏蓝是物理事实,强行拉灰反而会让水体区域过曝。所以我加了个保护:只在管道 mask 覆盖率超过 30% 的帧上启用完整白平衡,否则只做半强度补偿。

CLAHE 是这条链路里最见效也最危险的一步。参数上,clipLimit我最终定在 2.5,tileGridSize用 8×8。这两个值的确定过程在下一节细说。

2.2 增强过头会反噬检测,我的参数调试顺序

这里必须讲一个很多人忽视的问题:增强效果的目视评价和检测效果的评价,方向经常是相反的。

我最开始调 CLAHE 的clipLimit,从 1.0 一路加到 6.0,用肉眼看,6.0 的时候管道表面的锈斑、划痕、附着物纹理全都"跳"出来了,特别清晰,当时觉得这下稳了。结果拿去做分割评测,mIoU 反而比 2.0 的时候低了 4 个点。原因很直接:clipLimit太大的时候,均匀的水体背景也被拉出了大量噪声纹理,分割网络把这些纹理误判成管道边缘,mask 边界开始毛刺化,进而把后面拟合出来的中心线带偏。

所以我的调试顺序是这样的,建议你照抄:

  1. 先固定增强参数,只调网络。用一组保守参数(clipLimit=2.0)训一版基线,记住它的 mIoU 和中心线偏移的 RMS。
  2. 再做增强参数的网格扫描,但评测指标不看 mIoU,看下游任务指标——也就是中心线偏移的 RMS 和缺陷框的 IoU。这才是真正关心的东西。
  3. 最后做鲁棒性检查:把同一段视频分别用清水、微浑、浑水三档水体拍摄,看参数在哪一档开始崩。

扫描结果里有个很有意思的现象:clipLimit在 2.0~3.5 之间时,中心线 RMS 基本持平,在 3.5 以上急剧变差;但缺陷框 IoU 是在 2.5 左右达到峰值,再往上因为噪声反而下降。两个指标的最优点不重合,最后取了 2.5 这个折中值。你看,如果不分开测,光看 mIoU 你根本发现不了这个问题。

2.3 增强链路的实测对比

为了让你有个直观参照,我把几种常见方案在我们自采的验证集上跑了一遍。测试集包含 300 帧,覆盖三档水体、两个拍摄距离档位(0.6 m 和 1.5 m),指标是分割 mIoU 和中心线偏移 RMS。

方案推理耗时(ms,640×480)分割 mIoU中心线 RMS(cm)浑水下的表现
原图直接输入00.6124.8严重退化,几乎不可用
仅 CLAHE3.10.6883.5背景噪声放大,边缘毛刺
仅暗通道去雾28.60.7013.2蓝色通道被当暗通道,偏色严重
红补偿 + 白平衡 + CLAHE5.40.7642.1稳定,可控
生成模型增强方案210+0.7712.4效果好但时延不可接受

暗通道去雾那一行值得多说两句。理论上它很适合处理散射造成的雾,但水下场景有个坑:暗通道先验假设"无雾图像中总存在某个通道的局部最小值接近 0",而水下图像的蓝通道本身全局就偏亮,红通道又几乎全黑,这个假设直接被违反,估出来的透射率图会全图偏小,去雾之后画面反而更暗更偏红。我试过加颜色空间转换(转 Lab 只在 L 通道做)能缓解一些,但收益不值那个复杂度,最后放弃了。

生成模型方案效果确实好,但 210 ms 的耗时意味着整条链路只能跑到 4~5 fps,控制环根本吃不上。在算力有限的机载平台上,这个方案目前只能作为离线的图像美化工具,不能进实时闭环。

3. 管道本体提取:从像素分割到轴线拟合

3.1 分割模型选型:轻量优先,类别定义比网络结构更重要

管道本体提取我走了"分割 + 几何拟合"这条两段式路线,而不是直接用一个检测框回归。原因很简单:管道在图像里是一条贯穿画面的长条状目标,检测框会把大量背景框进来,中心点估计误差极大;而分割出来的 mask 可以逐列取中点,得到亚像素级的轴线。

网络用的什么?试过 DeepLabv3+、UNet、以及 YOLO 的实例分割版本。在 640×480 输入、机载边缘板卡上跑,UNet(以轻量主干为编码器)的性价比最高,mIoU 0.764,单帧耗时 22 ms。DeepLabv3+ 精度高一点点(0.771),但耗时翻倍,不划算。实例分割版本速度快但 mask 边界精度不如专门的语义分割网络,管道边缘会有 1~2 像素的锯齿,对拟合不利。

真正决定成败的其实是类别定义。我第一版定义了 6 类:管道、法兰、支架、附着物、裂缝、背景。训出来之后发现"裂缝"这一类几乎学不会,召回率不到 30%。分析下来是两个原因:裂缝像素占比太小(全图不到 0.1%),以及裂缝的视觉特征和管道表面的划痕、水渍高度相似。

后来拆成两个独立通道:分割网络只负责分 3 类(管道、背景、其他结构件),裂缝单独用一个检测网络去抓。这一拆,分割的 mIoU 从 0.71 涨到 0.764(类别少了,类间混淆也少了),裂缝的召回率从 30% 提到 68%。这个经验我觉得挺通用:当某一类的像素占比低一个数量级以上时,别硬塞进分割网络,换检测通道。

3.2 轴线拟合:为什么最后用了 RANSAC 而不是最小二乘

拿到 mask 之后,常规做法是逐列找 mask 的上下边界,取中点作为该列的中心点,然后用最小二乘拟合一条曲线。我一开始就是这么干的,结果在有两处失效:一是 mask 上有小孔洞(比如被附着物遮挡),中点直接跳到孔洞边缘;二是机器人转弯时管道在画面里是弯的,二次曲线拟合对局部异常点非常敏感,一个坏点就能把整条曲线拽歪。

换成 RANSAC 之后情况好了很多。具体做法:

import numpy as np from sklearn.linear_model import RANSACRegressor from sklearn.preprocessing import PolynomialFeatures from sklearn.pipeline import make_pipeline def fit_pipe_axis(mask, min_col_pixels=8): h, w = mask.shape cx_list, cy_list = [], [] for x in range(w): col = np.where(mask[:, x] > 0)[0] if col.size < min_col_pixels: continue # 该列有效像素太少,跳过 # 检查该列是否为连续段,断开则跳过 if col[-1] - col[0] + 1 != col.size: continue cx_list.append(x) cy_list.append(col.mean()) if len(cx_list) < 30: return None # 有效列不足,本帧不输出 X = np.array(cx_list).reshape(-1, 1) y = np.array(cy_list) model = make_pipeline( PolynomialFeatures(degree=2), RANSACRegressor(min_samples=0.6, residual_threshold=3.0, max_trials=200) ) model.fit(X, y) return model

几个参数的含义和我的取值理由:residual_threshold=3.0是像素单位,意思是偏离拟合曲线 3 像素以上的点判为外点。这个值不能太小,因为 mask 边界本身就有 1~2 像素的抖动;也不能太大,否则真正的弯曲特征会被当噪声抹平。「该列是否连续」这个检查非常关键,它把被遮挡产生的孔洞列直接剔掉了,比任何后处理都有效。

拟合出中心线之后,取图像中心列 x = w/2 处的曲线值 y_c,与图像中心 y = h/2 求差,就得到横向偏移量(像素)。这个值再经过标定换算成物理量,喂给控制。

注意:管道在画面里不是水平的时候,你用"图像中心列"去取点其实取的是斜线上的一个点,横向偏移的物理含义会打折。我的处理是先根据拟合曲线的首尾点算出一个旋转角,把图像做一次仿射旋转到管道水平,再做偏移计算。这一步不做的话,机器人转弯时会持续向一侧偏。

3.3 缺陷检测通道:小目标策略和置信度阈值

缺陷检测用的是 YOLO 系列的单阶段检测器,输入 640×640,模型选的是 n 或 s 级别的轻量版本。在边缘板卡上跑到 25~35 fps,够用。

这里真正的功夫不在模型,在数据增强和锚框尺度。裂缝这类目标在 640 输入下可能只有 4×40 像素,标准的 Mosaic 增强会把它们缩得更小,训出来效果很差。我的做法是:

  • 关闭 Mosaic,改用大比例的随机裁剪+缩放(scale jitter 0.6~1.4),保证小目标在训练时尺寸分布和推理时接近;
  • 提高小目标的采样权重,对面积小于 32×32 的标注框做 2 倍重复采样;
  • 置信度阈值不能按常规来定。默认 0.25 的阈值在我们这个场景下误报率约 15%(把锈斑判成裂缝),我调到 0.45 之后误报降到 4%,漏检上升 3 个百分点。考虑到巡检场景"宁可少报也别乱报",因为每一条报警都要人工复核,这个取舍是划算的。

另外补充一点现场经验:补光灯的开关状态对检测结果影响极大,几乎可以认为是两个不同的域。我的做法是在训练集里强制掺入 30% 的无补光样本,让模型对这个变量不敏感。如果在部署时才发现这个问题,返工成本会很高。

4. 从像素到动作:视觉与控制的对接细节

4.1 单目测距的标定流程,以及为什么必须装配后标定

管道直径已知(比如 200 mm),那么用相似三角形就能反推距离:

def estimate_distance(pipe_width_px, focal_px, pipe_real_width_m): # 距离 = 焦距 * 实际宽度 / 像素宽度 return focal_px * pipe_real_width_m / max(pipe_width_px, 1.0)

公式三行就写完了,难点全在focal_px怎么来。它不能直接用相机出厂参数算,因为相机装进了防水舱,隔着一层厚玻璃看外面,平板玻璃在水下会引入额外的折射,等效焦距和空气中不一样,而且视场角会被压缩(典型的压缩幅度在 5%~15%,取决于玻璃厚度和舱内空气腔的尺寸)。

我的标定流程是这样的:

  1. 相机装进舱体、密封、下水,在最终装配状态下拍摄标定板。标定板要防水或者封装在透明袋里,放在水里。
  2. 用棋盘格角点检测算内参,得到focal_px。
  3. 用已知宽度的标尺在不同距离(0.4 / 0.8 / 1.2 / 1.6 / 2.0 m)各拍 10 张,测像素宽度,反算距离,画距离-误差曲线。
  4. 如果误差随距离呈系统性偏大或偏小,说明存在折射导致的比例误差,做一个线性修正系数k,让focal_px_eff = k * focal_px。

我测出来的k是 1.07,也就是说如果不做这个修正,2 米处测距会偏大 14 cm 左右。这个量级对于管道巡检的避障判断来说是致命的——你会以为还有距离,实际上已经贴上去了。

提示:标定务必在最终装配、最终密封、最终下水状态下做。我见过有人用空气中的标定参数直接下水跑,误差大到自己都不信。装配状态一变(换个玻璃、调个镜头位置),标定全部作废。

4.2 帧率、延迟与流水线拆分

视觉链路的总延迟不是各环节耗时简单相加,得看流水线怎么排。测过一次完整链路的时间分布(640×480,边缘板卡):

环节耗时
相机曝光 + 传输33 ms(30 fps 相机)
预处理(红补偿+白平衡+CLAHE)6 ms
分割推理22 ms
后处理 + 轴线拟合4 ms
检测推理(隔帧执行)30 ms
结果打包发布2 ms

不隔帧的情况下总计约 97 ms。这个延迟意味着如果机器人以 0.4 m/s 前进,从拍到图到输出结果,位置已经前移了将近 4 cm。对于缺陷定位,这个量要补偿掉:在输出缺陷坐标时,用当前速度乘以延迟时间做前向补偿。

更关键的是控制环根本不能等视觉。我的架构是视觉节点以 10 Hz 发布中心线偏移和距离,控制节点以 50 Hz 运行,中间用一个轻量卡尔曼滤波做状态外推。视觉效果好的时候滤波主要做平滑,视觉丢帧的时候滤波做短时预测(最多支撑 300 ms),超过这个时间就降级到定深直行 + 减速。这个兜底策略在实际测试中救过好几次——水草遮挡导致连续几帧分割失败,如果没有外推,机器人会直接朝一侧冲出去。

4.3 光照突变、遮挡与丢帧的兜底策略

真实水体里,光照是会突变的。水面波纹聚焦阳光、机器人自身的补光灯在转弯时扫过舱壁、悬浮物突然飘过镜头,都会造成画面亮度在几帧内跳变几十个灰度级。

我的处理是在预处理之后加一个质量门控:

  • 计算图像的平均梯度和有效分割面积的比值,如果分割出的管道面积占全图比例小于 8%,或者轴线拟合的有效列数少于 30,本帧结果标记为"不可信",不发布;
  • 连续 3 帧不可信,触发状态机降级;
  • 如果检测到整图平均亮度比滑动均值偏离超过 40%,跳过当前帧的增强参数,沿用上一帧的参数,避免参数跟着噪声乱跳。

这套门控写起来不到 50 行,但它是整个系统从"能跑"到"敢让它自己跑"的分水岭。实验室里演示的时候你可以人工盯着,出了问题手动接管;真让它自己跑十几分钟,没有兜底是绝对不行的。

5. 数据集:最花时间、最容易翻车的一环

5.1 水池数据和真实水体的域差异有多大

我们自采的数据集构成大致是这样:水池数据 1800 张(清水、瓷砖背景、中心补光),真实水域数据 700 张(微浑、泥沙底、自然光),另外还有 300 张是故意在最恶劣条件下拍的难例。标注全部用多边形分割加框,双人交叉复核。

域差异有多夸张?我做过一个对照实验:只用池数据训,在真实水域测,分割 mIoU 从 0.78 掉到 0.51。掉得最狠的是管道边缘——水池里管道是深色圆柱贴着浅色瓷砖,边缘极其锐利;真实水体里管道表面附着一层生物膜,颜色和背景水体接近,边缘几乎消失。

缩小差距的手段,按性价比排序:

  1. 补真实数据,哪怕只有两三百张,效果也比任何域适应算法都好。这是第一优先级。
  2. 在池数据上做合成退化:按物理模型给池数据加上随机的蓝绿偏移、对比度衰减、雾状叠加,模拟不同浑浊度。这一步把 mIoU 拉回到 0.66,成本几乎为零。
  3. 强增强:颜色抖动、随机伽马、模糊、噪声,这些常规手段也有用,但边际收益递减很快。

5.2 标注规范:一致性比数量重要

标注环节我踩过一个很典型的坑:两个人标同一批图,一个人把管道表面所有的深色斑点都标成缺陷,另一个人只标长度超过 3 cm 的。结果训练出来的模型学了个中间态,谁也不像。

后来定了一份硬性规范,关键几条:

  • 缺陷最小标注尺寸:在标准拍摄距离(0.8 m)下,长度小于 15 像素的不标;
  • 附着物只要覆盖面积超过管道投影面积的 10% 才标整体,否则只标显著部分;
  • 管道与结构件的分界以几何连接处为准,不按材质颜色分;
  • 模糊到人眼无法判断的样本,直接剔除,不要勉强标。

规范定下来之后,标注速度反而快了,因为不用每张图纠结。我建议在开标之前,先让所有参与的人一起标同样的 30 张,然后交叉比对,把分歧点逐条讨论清楚写进规范。这半天时间能省掉后面几天的返工。

还有个提效技巧:先训一个粗糙模型,用它预标注,人工只做修正。当模型 mIoU 到 0.6 左右的时候,预标注能省掉大概 40% 的人工时间。但要注意,模型本身的系统性错误会被标注者继承下来,所以每批预标注里要掺 10% 的纯手工标注做对照,一旦发现偏差扩大就停用预标注重训。

6. 返工三次才想明白的几个细节

6.1 防水舱玻璃带来的视野畸变,比想象中严重

前面提过折射影响测距,其实它对分割的影响同样大。平板玻璃在水下相当于一个负透镜,会让画面边缘产生桶形畸变,而且这个畸变和相机本身的镜头畸变叠加在一起,不是简单的径向畸变模型能拟合的。

我最初只用相机内参做去畸变,结果画面中心区域很准,边缘的管道轴线拟合出来会向外弯。后来改用多项式拟合的畸变查找表,用一组网格标定板在水下拍摄,逐点建立"畸变坐标 → 理想坐标"的映射,用cv2.remap做查表校正,边缘误差从 6 像素降到 1.5 像素以内。

这一步的代价是每帧多花 3~4 ms,但值得。如果你的画面边缘也用得上(管道常常从画面边缘穿出),这个校正躲不掉。

6.2 补光灯频闪和卷帘快门的组合坑

补光灯如果是 PWM 调光的 LED,在卷帘快门下会拍出横向明暗条纹。这个条纹对人眼几乎不可见(因为频率高),但对分割网络的干扰极大——网络会把明暗交界线当成管道边界,轴线直接跳变。

排查过程挺费劲的。最开始怀疑是水体波动,换了静止水箱还有;怀疑是相机问题,换相机还有;最后用高速摄影看补光灯才发现是频闪。

解决方式两个:一是把补光灯改成恒流驱动,去掉 PWM 调光;二是把曝光时间设成工频周期的整数倍(我们这边是 50 Hz,取 20 ms 的整数倍)。两个一起用,条纹彻底消失。如果只能选一个,优先改驱动方式,因为曝光时间拉长会牺牲动态场景的清晰度。

6.3 评测指标好看但现场表现差,问题出在哪

这个坑我觉得最值得单独说。模型在验证集上 mIoU 0.76、缺陷 mAP 0.72,看着挺体面,实际下水跑的时候,操作的人反馈"老是漏报"。

排查下来是验证集的分布和现场不匹配。验证集是随机从全部数据里切的,而实际运行时,机器人大部分时间在拍"没有缺陷的正常管道",只有少数时间经过缺陷段。也就是说,现场遇到缺陷的样本比例远低于验证集。这种情况下,模型在验证集上的表现被大量简单样本拉高了。

改法很简单:按"缺陷尺寸分档"重新统计。把缺陷按像素面积分成小(<32²)、中(32²~96²)、大(>96²)三档,分别算召回。结果一目了然:大缺陷召回 91%,中缺陷 74%,小缺陷只有 38%。现场反馈的"漏报"全部落在小缺陷档。

定位问题之后对策也清楚了:针对小缺陷单独做一次过采样重训,同时把输入分辨率从 640 提到 800(代价是推理时间从 22 ms 涨到 31 ms,可以接受),小缺陷召回提到 61%。这个数字依然不算好,但至少不再是个黑盒——你知道它的边界在哪,可以在报告里如实写清楚,而不是给个虚高的总指标。

提示:任何一个视觉指标,只要它没有按目标尺寸、拍摄距离、水体浑浊度分档统计,就不能拿来判断系统能不能用。整体指标是给评审看的,分档指标才是给自己看的。

最后分享一个我觉得挺有用的小习惯:每次下水测试,不管成功失败,都把原始视频原封不动存下来,按日期和测试条件建目录。我后期做域适应、调参、复现 bug,全靠这些原始素材。有一次一个诡异的轴线跳变查了两天,最后在三个月前的一段旧视频里找到了同样的现象,一对比就发现是那个批次的补光灯驱动板有问题。这些素材的价值在采集当时是看不出来的,但它是你后面所有分析的唯一依据。存储成本相对于整个项目的投入,真的不值一提。

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

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

立即咨询