水下管道智能巡检机器人实战:从YOLOv5到串级PID的完整方案

📅 发布时间:2026/9/2 20:33:22
水下管道智能巡检机器人实战:从YOLOv5到串级PID的完整方案
简介这是一套2021年中国大学生工程实践与创新能力大赛“智能赛道”水下管道巡检赛项全国第八名/国银获奖作品完整源码面向工训赛参赛队、嵌入式系统开发者及水下机器人爱好者。项目以STM32为控制中枢K210承担AI视觉识别涵盖推进控制、传感器采集、图像处理与通信等模块可帮助读者理解水下管道巡检机器人的整体工程架构和调参思路。资源共410个文件以C/H源码、Keil工程文件uvprojx/uvoptx、编译产物axf/hex/map及初始化配置文件ioc为主整体仅3.41MB结构紧凑、便于按模块研读。已有2204人学习下载对备赛冲刺或入门水下机器人开发均有较高参考价值。 2021年那场全国水下机器人赛事我们做的水下管道智能巡检机器人拿了全国第八名、银牌。名次不算差但真正让我记住的不是领奖台而是决赛第三轮里我在电脑前按下“临时调低置信度阈值”那个键的两秒钟。那一瞬间困扰我们两个月的视觉误检问题彻底暴露比赛分也因此被扣掉不少。也是从那天起我才彻底想明白水下管道巡检这个任务真正的难点不在“造一台能潜水的机器”而在于让它在看不清、站不稳、被水流推着走的环境里稳定完成“找管道、贴管走、找缺陷”这三件事。这篇复盘就把这个项目从硬件架构、感知算法、运动控制到完整源码的组织方式原原本本拆开讲一遍给正在准备水下机器人赛项、或者想找一套完整源码做参考的同学一些能直接落地的经验。1. 赛场上那次临时调参暴露了整个系统的软肋决赛第三轮的任务是在浊度比训练池高出一截的水池里巡检一段模拟管道找到法兰、阀门和泄漏标记。训练时我们的视觉模型在干净水质下的正检率在95%左右误检率不到3%。可那天赛前试潜时我就发现摄像头画面整体发白像隔着一层磨砂玻璃检测框明显变少有的目标框只有0.3左右的置信度。按正常0.45的阈值这些框会被全部过滤掉。倒计时三分钟我做了个决定把置信度阈值从0.45调到0.30。听起来很合理对吧目标分数变低了那就放宽门槛把目标捞出来。结果下水后画面里确实多了一堆检测框但很多是管壁上的水垢、锈斑、甚至气泡的反光。机器人在这些误检框的引导下频繁调整航向最终在几个错误位置按下了“标记泄漏点”的提交键。整轮得分比保守策略还要低。赛后我对着日志反复看问题根本不在阈值本身而在于我们的视觉模块没有任何“场景自适应”能力。它用的是固定预处理参数加固定阈值检测结果没经过上下文过滤就直接送给了控制层。单帧里出现一个0.3置信度的框到底是真的目标还是水底噪声我们完全没有判断机制。后续我在源码里补了两个关键机制。第一个是“时间域确认”同一个目标连续三帧以上都出现、且位置移动符合运动约束才认为是真目标。第二个是“位置先验”管道巡检的检测目标几乎只会出现在管道轮廓附近离管道中线太远的检测框直接丢弃不管分数多高。这套逻辑用伪代码展示大概是这样的if det.conf conf_thresh: continue if det.class_id in [FLANGE, VALVE, LEAK]: if abs(det.cx - last_pipe_cx) 0.35 * frame_w: continue # 远离管道中线的目标直接丢掉从那以后我也养成一个习惯改任何参数前先想想它会影响precision还是recall是要在哪个指标上让步。只用单帧分数来判断目标在水下这种“脏数据”环境里几乎必然翻车。2. 带缆ROV双主控架构为什么是 Jetson Nano STM32先回答最常被问的问题为什么不做成无线水下机器人因为2.4GHz信号在水里衰减极快哪怕是浅水池隔两三米就可能丢包。而管道巡检这个场景本来就强调稳定性和长时作业有线系缆反而是最可靠的选择。我们用的是30米六类网线里面走以太网和12V供电水下端用一个PoE分离模块把电和网络分开。整套系统是典型的带缆ROV遥控潜水器架构。控制架构上我坚持用了“双主控”而不是一块高算力板子全干。Jetson Nano 2GB负责图像采集、YOLOv5推理和路径决策STM32F407负责电机控制、姿态解算和深度闭环。两者之间用串口通信115200波特率50Hz周期上报状态。为什么必须分开视觉任务吃算力但实时性要求不高5到10Hz足够而电机控制需要1kHz左右的快速响应用来做姿态环和深度闭环。如果都塞进Jetson Nano图像推理一卡电机控制也跟着抖动机器人会在水里打摆子。STM32跑RTOS调度把控制周期固定住才能保证稳定。机械和水密方面我们用的是600mm长、直径160mm的亚克力防水舱两端6061铝端盖配合两道O型密封圈实测在5米水深下不漏水。四个推进器左右两个水平推进器负责前进和转向中间一个垂直推进器负责下潜和定深再加一个侧推用于贴管时横向移动。这个布局没有用标准的八推进器矢量方案因为管道巡检是“贴着管慢慢走”的场景四个足够还能省重量和电调成本。每个推进器峰值推力2.5kgf整机水中重量控制在7kg左右舱体留约200g正浮力保证停机时能自动上浮——这个安全策略在赛前检查时很加分。传感器选型也值得说一下。摄像头是普通免驱USB摄像头加两个LED补光灯IMU用MPU9250深度计用MS5837-30BA气压式测深精度能到毫米级别定深控制全靠它。另外我在舱内加了一路漏水检测回路两根裸露导线贴在舱底一旦进水水把电极导通立即触发报警并切断推进器电源。这个设计成本不到十块钱但救过我们一次第二版样机有一回端盖O圈没抹硅脂下水三分钟就报警避免了整套电子设备报废。硬件清单大概是这样给想复现的同学一个预算参考Jetson Nano 2GB开发板约800元STM32F407核心板约80元水下推进器×4带封装约4500元USB摄像头、LED补光灯约200元MPU9250、MS5837模块约150元亚克力舱体、铝端盖、O圈、穿线管约1500元系缆、PoE模块、电源模块、4S锂电约800元不算加工费整套物料在8000到10000元。比买成品水下机器人便宜不少而且坏了能自己修对学习来说价值更大。3. 水下图像增强和目标检测让YOLOv5s在水底下不翻车水下图像和自然图像最大的区别是它本质上就是“低质量图像”。光在水里会被吸收和散射红光衰减最快所以水下画面普遍偏蓝绿悬浮颗粒造成类似雾霾的白蒙蒙效果管道表面的金属或塑料反光又会带来高光区域。直接拿预训练模型跑这种图像效果极差。我们试过OpenCV自带的Haar级联和MobileNet-SSD在水下场景基本是废的。后来改成了“先增强后检测”的管线。预处理分三步都是在推理之前对每一帧图像做的白平衡以灰度世界假设校正色偏把蓝绿拉回来一点。CLAHE限制对比度自适应直方图均衡化把管道边缘和焊缝细节从低对比度背景里提出来。暗通道去雾简化版减轻水中悬浮颗粒造成的雾化感。核心代码很短在源码里就是增强模块def enhance_underwater(image): lab cv2.cvtColor(image, cv2.COLOR_BGR2LAB) l, a, b cv2.split(lab) clahe cv2.createCLAHE(clipLimit3.0, tileGridSize(8, 8)) l clahe.apply(l) enhanced cv2.merge([l, a, b]) return cv2.cvtColor(enhanced, cv2.COLOR_LAB2BGR)检测模型用的是YOLOv5s但模型本身只是其中一环。关键在数据。我们自建了一个水下管道部件数据集总共标注了大约6000帧图像包含四类目标法兰、阀门、泄漏标记红色和黄色色块、管壁裂缝。标注是团队三个人轮流干的花了大半个月。数据增强用了mosaic、mixup、随机亮度、随机色温扰动专门模拟不同水质和光照条件。训练在NVIDIA RTX 3060上大概跑了两个小时batch size为16输入尺寸640。训练完再用TensorRT转成engine格式推理耗时从约50ms降到20ms左右基本能满足实时巡检需求。单帧检测结果仍然不可靠我用一个目标候选列表做时间域滤波。每一帧检测出来的框先和已有的候选框做IOU匹配同一个目标连续三帧都被检测到才确认输出新目标在前两帧只进候选池不进入控制逻辑。这个机制付出的代价是多约三帧延迟但对水下贴管巡检这种慢速任务完全无所谓换来的是误检率大幅下降。再补一个很多人会踩的坑水下距离估计。水的折射率约1.33会让目标看起来比实际位置更近单目相机估算距离本来就有多义性。我们的做法简单粗暴在0.5米、1.0米、1.5米三个距离放标定管记录目标框的像素高度拟合出一条“像素高度-真实距离”的查表曲线。因为控制层贴近管壁后主要看的是管道中线的像素偏差不依赖绝对距离所以这个精度完全够用。4. 运动控制串级PID和贴管行走的调参经验水下机器人运动控制的麻烦在于推进器推力非线性、水流干扰随机、机体重心和浮心不完全重合。如果只用单环PID参数稍微调大一点深度和航向就会来回振荡像在水里抽风。所以我在控制层用了串级PID。先看定深控制。外环是深度环输入目标深度和MS5837当前深度的偏差输出一个期望垂直速度内环是垂直速度环输入期望速度和IMU加速度积分出来的实际垂直速度输出推进器推力。写成公式就是depth_err target_depth - current_depth vel_target Kp_d * depth_err Ki_d * integral_depth vertical_thrust Kp_v * (vel_target - current_vel) Kd_v * accel_z为什么用串级而不是单个深度PID因为水越深静水压力越大推进器需要输出更大的推力才能维持深度单环PID在浅水和深水表现差异会很大。内环先把速度稳住外环只负责“缓慢趋近目标深度”整个系统对扰动的抵抗能力强很多。航向保持也是一样。用MPU9250的yaw角作为反馈左右水平推进器差速控制。这里有个细节螺旋桨高速旋转带来的震动会让yaw数据抖得厉害必须先做低通滤波。我们用的是简单的一阶低通cutoff频率设在5Hz左右够用。贴管行走是视觉和控制联动的地方。视觉模块输出管道中线在图像中的水平偏移量和角度偏差控制层把这两个量换算成期望偏航角float desired_yaw base_yaw k1 * pipe_delta_x k2 * pipe_delta_theta; float yaw_err wrap_angle(desired_yaw - current_yaw); float diff_thrust Kp_yaw * yaw_err Kd_yaw * gyro_z; left_thrust base_thrust diff_thrust; right_thrust base_thrust - diff_thrust;调参顺序是先内环后外环先把深度环和yaw环分别调稳再开视觉引导。视觉引导的Kp不要贪大给一个能让机器人缓慢回中的值就够了之后加一点点D抑制过冲。积分项只在长时间存在稳态误差时加入而且必须限幅——如果入水时初始偏差很大积分很容易饱和那个瞬间推进器会猛推一下把机器人推出航线。这个坑我们实机测试时遇到过好好的直线巡管下水十秒就原地转圈后来查了几个小时才发现是积分饱和。另外还有一个很实用的细节视觉输出频率只有5Hz控制频率是20Hz每来一帧视觉数据期望偏航角都会跳变一下。如果直接把跳变值丢给控制环推进器会急加速反而搅起水流把机器人推开。我在代码里对期望偏航角加了一阶惯性平滑让目标值“软着陆”。如果连续五秒检测不到管道中线机器人会进入搜索状态保持当前深度正转五秒、反转五秒同时用声光提示岸上操作员。这个逻辑在比赛中帮我们救回了好几次因为池壁反光偶尔会让视觉短暂丢失目标。5. 完整源码模块怎么组织状态机驱动一切很多新手拿到“完整源码”第一件事是去读main函数但水下机器人这种系统真正的主干是状态机。我们把整个工作流拆成这几个状态INIT、MANUAL、AUTO_START、FIND_PIPE、ALIGN_PIPE、INSPECT_ALONG、LEAK_MARKING、RETURN、IDLE。每个状态都有entry、update、exit三个回调状态切换只发生在update阶段通过事件触发。这样好处非常明显比赛现场出问题时上位机上一眼就能看到当前停在哪个状态操作员可以直接手动接管。源码目录结构是分模块的大概长这样project/ ├── main.py # 主入口初始化状态机 ├── config/ │ └── params.yaml # 所有PID参数、阈值、标定表 ├── vision/ │ ├── camera.py # 摄像头采集线程 │ ├── enhance.py # 水下图像增强 │ ├── detector.py # YOLOv5推理封装 │ └── pipe_tracker.py # 管道中线提取、目标时间域确认 ├── control/ │ ├── depth_ctrl.py # 深度串级PID │ ├── heading_ctrl.py # yaw保持与视觉引导 │ ├── mixer.py # 推进器混控 │ └── state_machine.py # 状态机定义和切换 ├── comm/ │ ├── protocol.py # 二进制通信协议 │ ├── uplink.py # 上报状态和检测结果 │ └── downlink.py # 接收岸上指令 └── tools/ ├── calibrate_depth.py # 深度计标定 └── replay_dataset.py # 日志回放工具视觉、控制、通信分别跑在不同线程里线程间通过带锁的全局变量共享数据避免大图像在处理线程间反复拷贝。图像采集线程保持30fps推理线程每次取最新一帧控制线程固定20Hz通信线程10Hz。这几个数字不需要太高稳定比高帧率重要。通信协议是自己定的轻量二进制协议帧头0xAA 0x55加报文ID、数据长度、数据体最后加CRC16校验。岸上的Qt上位机把深度、yaw、当前状态、视觉检测框全部实时显示出来。调试的时候几个人蹲在池边看上位机比拿防水电脑蹲在水边看串口日志效率高得多。关于复现我必须说一句大实话这套源码直接拿去用大概率会出问题。因为不同主控板、摄像头、光源、水体浊度都会影响参数。params.yaml里我统一放好了所有需要调的参队伍拿到源码后第一件事不是跑起来而是按tools里的脚本重新标定深度计、重新测量推进器推力曲线、重新采集一段视频做检测效果验证。我在源码里留了比较完整的日志系统每个状态切换、每个控制输出、每帧检测结果都会落盘。复盘时用日志重放工具比在水池边猜原因高效太多。6. 从水池到赛场那些烧钱烧时间才换来的注意事项最后这部分不写代码了全是钱和教训堆出来的东西。第一是密封。每次下水前必须做干舱测试和泡水观察。干舱测试很简单盖好端盖放进清水箱里半小时看有没有连续气泡然后擦干、打开舱盖检查O圈和干燥剂颜色。特别注意螺旋桨轴和穿线管这两个地方是漏水高发区。我们的穿线管后来全部改成环氧灌封而不是只靠热缩管因为热缩管在水下长时间受压会慢慢渗水。第二是电磁干扰。无刷电调和摄像头USB线如果靠太近图像会随机花屏有时候表现为“过几秒黑一下”。第一版我们在这个问题上耗了一周后来把电调信号线尽可能远离USB线、给USB线加磁环、换成带屏蔽层的USB延长线问题才彻底解决。水下机器人内部空间紧张线缆整理不能只图好看布局顺序直接影响信号稳定性。第三是系缆拖拽。带缆ROV一定会被缆绳拽着走缆绳在水中也有自重和阻力机器人偏航时操作员感觉“明明给了修正它还是慢慢偏回去”。我在控制层加了系缆补偿思路当检测到持续的微小偏航误差时让积分项缓慢积累而不是立刻加大比例项否则很容易在水流和缆绳的共同作用下振荡起来。第四是光源角度。水下补光灯正对目标表面反光会掩盖管壁裂缝的阴影细节而裂缝识别恰恰需要阴影来凸显凹凸纹理。后来我们把两个补光灯改成45度侧向照明检测效果明显提升。灯光的角度属于那种“原理上说不出大问题、实测差得离谱”的细节必须试过才懂。第五是现场策略。比赛池和训练池的水质、光线永远不可能完全一样。我们在正式流程里加了一个“首轮诊断”环节开赛后先不急着巡检下潜到目标深度收集当前水体下的图像统计信息比如平均亮度、对比度、以及所有目标类别置信度分布然后根据这些统计数据选择预设参数组。这个步骤大概耗时四十秒但能避免很多临场瞎猜。如果我们当时在决赛里按这个流程走完就不会出现那个临时调阈值的决定了。现在回看2021年这个项目最值钱的不是那枚银牌而是一堆“看起来和书里不一样”的教训。水下机器人这个方向没有标准答案只能靠一次次下水喂出来。如果这篇复盘能帮你在做自己的水下管道智能巡检机器人时少走几个弯路那就很值了。完整源码的模块思路和关键代码我都尽量往通用方向写但请记住别人的源码只是起点真正要复现的是那套“先增强、再看目标、再贴管、再确认”的决策链路以及每次下水前对参数和风险的尊重。本文还有配套的精品资源点击获取