干了小半年的港口测量项目,我把激光雷达和摄像头搬到码头边上,做了一个不靠人读的船舶吃水深度识别算法。初期跑通方案到稳定出数,踩的坑比预想多,但这套融合思路基本成熟了:雷达负责几何和距离,摄像头负责纹理和水尺刻度细节,融合后精度能做到厘米级,夜间加补光也能干活。这篇文章把整个系统从头拆到尾:为什么选这两种传感器、标定和同步怎么做、吃水深度到底怎么算、实测中了哪些埋伏,一次性讲清楚。想搞多传感器融合测量类项目的朋友,可以直接参考。
1. 为什么吃水深度需要“雷达+相机”双模态来解决
1.1 传统人工看水尺的3个致命伤
船舶吃水深度,简单说就是船体在水中下沉的深度,通常通过读取船体外板上标刻的“水尺标志”来获得。水尺一般以米为单位,用不同颜色标出刻度数字,人工读数就是站在码头/岸边,目视水面线在船壳上压着哪条刻度。
这个办法问题很明显。一是受视线距离限制,船大了或者码头结构复杂了,根本看不到船侧下半段;二是波浪一来水线是上下晃的,肉眼只能在“平均值”附近估读,误差动辄十几厘米;三是夜间或雨雾天气,刻度看不清,测量直接瘫痪。可港口调度、装载平衡计算都依赖吃水数据,误差大就容易出装载偏差和安全隐患。
我接触过一些港口配载现场,老师傅读数和引航员报告之间可能差出半英尺,这在船舶大型化之后已经不能忍了。行业里需要的是远程、自动、全天候、可复现的测量方式。
1.2 单传感器各自解决不了什么
自动测量这条路,业内试过不少传感器。单用摄像头是最先想到的方案:装个长焦相机,让算法识别水面线和刻度线。但视觉方案的天花板很快就撞上了——相机本质上是把三维世界压缩成二维像素,缺少距离信息,你很难判断水线到底是离相机10米还是15米处的水面,换算成真实吃水数值时比例因子不定。
单用激光雷达也很难做。激光雷达能精确测距,扫描到水面和船体表面能形成点云,但水面受风浪扰动,对激光的反射率又不稳定,很多区域会直接丢点。更关键的是,雷达点云只有空间坐标没有纹理信息,想在点云里识别“水尺刻度上写的是4米还是4.2米”,基本不可能。
所以核心矛盾是:能给出几何结构的东西看不清文字细节,能看清文字细节的东西没有准确的几何尺度。两个传感器互补性非常强,顺理成章想到融合。
1.3 融合方案的总体打法和衡量指标
这套系统的整体逻辑可以概括为三句话:
- 激光雷达负责建立“水面在哪里”“船体侧面在哪里”“船离我们多远”的几何框架;
- 摄像头负责在雷达锁定的局部区域中找到水尺刻度和水面线的精确像素位置;
- 融合模块把二维像素映射到三维空间,结合水尺刻度的先验关系,换算出吃水深度。
系统最终要给出一个实时吃水数值,并附带可信度估计。项目验收时的硬指标是静态水面条件下误差不超过±5cm,动态波浪环境下误差控制在±10cm以内。这个精度范围对于非接触式船舶吃水测量来说是说得过去的。
2. 传感器选型与系统架构决策
2.1 激光雷达选型:线数、精度和安装高度怎么平衡
激光雷达我选的是16线机械式雷达,型号是速腾聚创RS-LiDAR-16。测距范围100米,测距精度±2cm,垂直视场角30°(角度分辨率2°),水平360°(角度分辨率0.09°)。为什么是16线而不是单线或更高线数?
单线雷达只在水平面内出点,最多画出一个断面,想拟合水面平面和船体侧面平面,几何约束严重不足。64线或128线雷达精度更高点云更密,但成本和工控机载荷都上来了,项目中除非船型非常多样,否则用不上。16线在这个场景里刚好:水面可以打出若干条扫描环,船体侧面也有足够点来拟合法向量,同时数据量小,嵌入式平台完全带得动。
安装位置也很关键。我们把雷达安装在码头固定墩柱上,距水面高度约3~4米,向下倾斜20°~35°。这样既能扫到平静的水面回波,又能扫到船体水线附近的外板。雷达有一个需要接受的现实:入射角大于75°时水面基本呈镜面反射,点云会大面积丢失,所以倾斜角和扫描方向的匹配非常讲究,我在后面踩坑部分会展开。
2.2 摄像头选型:全局快门、偏振片和夜晚补光是三个硬指标
相机我选了500万像素的工业相机,配8mm定焦镜头,最关键的一点是必须用全局快门而不是卷帘快门。原因很直接:船在水上永远是晃的,卷帘快门逐行曝光时图像会变形,船体侧面轮廓和水线位置都会跟着扭曲,标定好的投影关系就全乱了。全局快门一次抓完整帧,虽然存在滚动伪影一般没有,但至少不会引入行间错位。
镜头前加装了一片可旋转的线偏振片。水面反射强烈时,反光通常是偏振光,旋转偏振片到合适角度能明显压掉水面耀光,让水尺刻度和水线更清晰。这个经验是第一个版本的算法在水面反光时频频漏检后才得出的,成本低但收益极大,强烈建议任何做水面视觉项目的朋友都上一片。
夜间作业不能指望环境光。我们在相机旁装了LED白光补光灯,红外补光也试过,但水尺刻度是靠颜色对比来实现的,白光照明比红外的字符识别率稳定。实测在10米以内的拍摄距离,120W左右的补光灯完全足够。
2.3 系统拓扑与同步触发逻辑
整个系统的硬件拓扑是:激光雷达通过网口接工控机,工业相机通过USB3.0接口接入,雷达和相机由工控机上的同步触发板卡产生统一上升沿信号触发。雷达SDK支持外部触发模式,相机也支持硬件外部触发,这样每一帧雷达数据和每一帧图像都严格对应相同的时间戳。
有同行问过为什么不做软件软同步、靠网络校时?理论可行,但雷达帧率10Hz、相机帧率15Hz,两者在时间域上错开半帧就直接引入约50ms的时间对齐误差。船体和水面都在动态变化,50ms对应波浪移动都可能有好几厘米的差异。硬件触发是最简单可靠的答案,买一块几十块钱的触发板卡就解决,完全没有必要在时间同步上引入软件复杂度。
软件层面,工控机上跑一个基于ROS2的节点框架:雷达驱动节点、相机驱动节点、标定管理模块、点云处理模块、图像处理模块和融合计算模块。节点间通过共享时间戳队列做数据关联。工控机选择的是i7处理器+16GB内存的无风扇工业主机,实际运行过程中CPU占用率控制在60%以下,没有出现丢帧问题。
3. 空间标定与时间同步,融合作业前最难啃的两块骨
3.1 相机内参标定和激光雷达-相机联合外参标定
融合的第一个前提,是把雷达坐标系和相机图像坐标系对应起来。相机内参我用经典的张正友标定法,用10×7的棋盘格从不同角度拍了大概20张,用OpenCV的标定工具算出焦距、主点和畸变系数。这些步骤网上教程很多,不多讲,只提醒一句:标定棋盘格的时候不要放在水面上,水波会让棋盘格产生局部变形,角的坐标就不准了。
真正麻烦的是雷达和相机之间的外参标定,也就是求解从雷达坐标系到相机坐标系的旋转矩阵R和平移向量t。标准做法是找一个在两者中都可见的目标物,比如一个平面标定板,雷达看到标定板点云,相机看到标定板图像,然后建立3D点与2D点的对应关系,用PnP求解外参。
实操步骤:
- 将标定板立在码头平坦区域,雷达采集标定板点云,从点云中提取标定板平面的三维点,拟合出平面边界;
- 相机同步拍摄同一标定板,提取棋盘格角点的像素坐标;
- 将标定板角点的3D坐标(以标定板中心为原点的自定义坐标系)传入PnP求解,得到外参初值;
- 再用多组不同位姿下的点云—图像对应点做联合优化,最小化重投影误差,得到精确的外参。
一个很关键的经验:标定板不能太小,也不能离雷达太远。雷达点云密度有限,标定板太小时平面上的激光点太少,拟合出来的平面误差很大。我们的标定板尺寸是1m×1m,距离雷达约5~10米,点云数量和工作距离都比较合适。
标定做完后必须做一次可视化验证:把雷达点云投影到图像上,看船体轮廓、水面边界是否与图像边缘重合。如果投影线整体错开一两厘米,说明外参有常数偏差,需要重新精化;如果错开很多,那大概率是标定板位姿提取时方向搞错了,回去检查旋转矩阵的奇异性。
3.2 时间同步:硬件触发为主,时间戳校验兜底
前面讲拓扑时提到硬件触发,这里说具体做法。同步触发板卡产生一个10Hz的方波信号,同时送雷达外部触发引脚和相机触发线。雷达收到触发后开始一帧扫描,相机收到触发后瞬间曝光,这样雷达帧和图像帧天然是同一时刻。每帧数据里写入触发序号,ROS2的receiver模块按序号做关联,从根本上绕开了“哪一帧配哪一帧”的难题。
但硬件触发也不是一劳永逸,雷达的处理链路本身有延迟。RS-LiDAR-16从触发到点云数据通过网络完全到达主机,大约有3~5ms延迟;相机曝光到图像传输完成,也有2~4ms延迟。总的固定延迟是常数,只要两路延迟之差是稳定值,我们就在融合之前补偿这个固定时间偏移。具体做法是做一个用于标定的旋转目标,记录雷达检测目标角度与图像中目标侧面的像素位移差,不断调整时间偏移参数,直到两者一致。
时间同步这块还有一个兜底逻辑:每帧图像的时间戳和雷达帧的时间戳如果相差超过20ms,就丢弃这一对,不参与融合计算。保证进入计算管线的数据都是时间对齐的,宁缺毋滥。
3.3 标定漂移的日常检查套路
项目上线后外参不是一成不变的。码头墩柱的热胀冷缩、设备的轻微震动、雷达支架的螺丝松动,都会导致外参逐渐漂移。我们专门做了一个快速检查工具:在岸边固定位置贴一个醒目的标靶,每天开机后自动把雷达点云投影到图像上,看标靶中心的重投影误差。误差超过2cm就报警提示人工重新标定。这个几行代码就能实现的检查机制,省去了现场大量排查时间。
4. 吃水深度识别的核心算法流程拆解
4.1 水面平面检测和船体侧面提取(点云侧)
融合算法的第一步,是让雷达“找出水面”。激光雷达扫描水面时,平静水面会形成相对规整的稀疏点集,船体表面则是另一个密集的平面结构。使用RANSAC平面拟合算法,对雷达点云中属于水面的点进行平面参数估计,得到水面平面方程:
a·x + b·y + c·z + d = 0
为什么用RANSAC而不是普通最小二乘?因为点云里杂点太多——飞鸟、浪花、漂浮垃圾、岸边栏杆,这些离群点会严重干扰最小二乘结果。RANSAC通过随机选最小点子集拟合平面,再统计内点数量迭代出最优模型,对离群点的鲁棒性要高得多。
但水面点云有个天然难题:静止水面反射率低,整个水面能打入雷达的有效点可能只有几十个,而且很多点是镜面反射出来的“虚假点”,出现在水面下方或水面上方。镜面反射的虚假点是一种典型的系统误差,如果不对其进行过滤,拟合出的水面平面会抬高或压低真实水面位置。
我的处理方法是两层过滤:先用反射强度阈值过滤掉明显是玻璃钢船壳和水面的弱回波点;然后对“候选水面点”做统计滤波,剔除与邻域平均高度偏差过大的孤立点。做完这两步,再用剩余点拟合水面平面。
船体侧面的提取思路类似。船体外板在水线附近是一个近乎垂直的平面,点云密度要比水面大得多。用RANSAC再拟合一个与水面平面接近垂直的平面,同时约束“平面法向量与水面法向量的夹角在75°~105°之间”,就能把船体外板单独拎出来。此外,还可以利用雷达点云到安装位的距离区间,把岸上固定结构物的点一并滤掉,船侧点就会非常干净。
4.2 水尺刻度和水面线识别(视觉侧)
雷达把“船在哪、水面在哪”这个核心问题解决后,摄像头要干的事就纯粹多了:在雷达锁定的ROI里找到水尺刻度,找出水线在刻度上的位置。
水尺区域有一个非常强的视觉先验:刻度和数字是白色/黄色底上涂深色标记,或者是黑色底上涂白字,总之在水线附近是一条高对比度横条带。我先用色彩空间变换和亮度阈值对图像做分割,把水尺区域块提取出来。再用形态学开运算去除细小噪点,然后做连通域分析,筛选出尺寸接近真实水尺的区域。
水尺刻度线的像素位置,用Canny边缘检测加Hough直线检测来提取。水尺的短横线是高度方向水平方向都有的小线段,需要按长度和方向做过滤,只保留接近水平的刻度线。同时,水线本身在图像中也是一条水平边界线,下方是水的纹理,上方是船壳。水线的检测有一个很实用的技巧:把水尺区域拉直成一条纵向条带,在垂直方向上做滑窗,计算每个窗口的纹理强度变化,纹理强度突变处就是水线。
刻度数字的识别我用了自训练的OCR模型,输入是从ROI中裁出的固定高度刻度图像,输出是数字字符串。训练数据是自己在现场采集的几百张切割图像,加了几种数据增强方式,包括旋转、缩放和亮度变化。识别准确率白天能达到98%以上,夜间配合补光也能到92%左右。
4.3 吃水深度融合换算和姿态补偿(融合侧)
有了三个关键信息:水面平面方程、水尺刻度线的像素位置、水线在图像中的像素位置,就可以把这些信息统一到三维空间计算吃水深度了。整个过程按以下步骤进行:
- 从图像中提取水线像素坐标(u, v);
- 利用相机内参数,将水线像素坐标反投影为一条从相机光心出发的三维射线;
- 利用标定好的外参,将该射线转换到雷达坐标系;
- 求这条射线与水面平面的交点,得到水面线上点的三维坐标P_water;
- 从同一副图像中提取相邻两条刻度线(如4.0m和4.2m)的像素行位置,同样反投影到三维空间,求解刻度线在船体坐标系下的三维坐标;
- 根据水线像素在两条刻度线之间的相对位置,线性插值得到吃水数值: draft = 4.0 + (v_4.0 - v_water) / (v_4.0 - v_4.2) × (4.2 - 4.0)
这个插值是简化的二维做法。更精确的做法是在三维空间中计算:把水线三维点P_water与刻度线三维点P_4.0、P_4.2都投影到船体纵中平面上,在垂直方向上比较距离。船体有横倾角和纵倾角时,二维图像插值会引入误差,必须做姿态补偿。
姿态补偿依赖IMU数据。船在波浪作用下有横摇(roll)和纵摇(pitch)两个角速度,IMU安装在船的桅杆处,可以获取实时姿态角。吃水深度定义是从水线到龙骨基线的垂直距离,而视觉测量得到的是沿视觉射线方向的距离,二者存在三角函数关系。
# 姿态补偿简化逻辑(伪代码) draft_measured = draft_raw * cos(roll) * cos(pitch)实际项目中还叠加了多采样平均:因为水面每时每刻都在波动,单帧的水线位置会忽高忽低。我在时间窗口里取了10秒内所有帧的计算结果,去掉最大和最小各10%的野值后再平均。实测这个操作能把波浪导致的随机噪声从±6cm压低到±2cm以内。
4.4 置信度评估和异常检测
港口环境不可能永远干净,水尺可能被油漆覆盖、被泡沫板挡住、被波浪打得花白。所以算法一定要输出置信度,否则错误测量结果会误导调度。
我在系统里设计了几个校验逻辑:第一,水线像素位置和雷达水面投影位置距离过大(超过2cm),说明时间同步或外参有误,判定该帧无效;第二,水尺刻度识别结果与上一个稳定周期的差值超过30cm,认为检测异常,触发重新识别或丢弃;第三,水线位于所有已识别刻度线的范围之外,不做外推,直接判为无有效结果。
这套置信度框架的意义很大。港口方最终看的是“能不能在几分钟内给出一个可信的吃水值”,而不是“每秒钟都要出一个值”。支持漏报、反对误报,宁可没有结果也不要错的结果,这个产品逻辑一定要想清楚。
5. 实测效果、误差来源和没人写进论文的坑
5.1 实测数据能到什么程度
我们在某工程水域做了三轮测试,以人工用测量杆/潜水员报出的吃水作为对照基准,数据统计如下:
| 测试工况 | 测量次数 | 平均绝对误差 | 最大误差 | 成功率 |
|---|---|---|---|---|
| 白天静水 | 120 | 2.4cm | 4.8cm | 96.7% |
| 白天小浪 | 95 | 4.1cm | 8.6cm | 93.7% |
| 夜间补光小浪 | 80 | 5.2cm | 9.4cm | 90.0% |
白天静水条件下,综合精度完全达到±5cm的项目目标。夜间误差上升主要来自补光灯的光照范围限制和远处水尺清晰度下降,但仍在±10cm的容忍范围内。
这套数据和人工读数对比优势很明显。人工读数的典型精度是±10cm起步,而且连续读多小时眼睛就花了;算法在没有疲劳、没有主观偏差的情况下持续输出,稳定性远超人工。
5.2 水面镜面反射和低反射率:点云大面积丢点怎么办
这是我在项目前期最头疼的问题。水面平静得像镜子一样的时候,激光雷达的点反而最少,因为镜面反射把信号弹到别处去了,雷达收不到回波。最极端的情况下,原始点云在水面上几乎形成“黑洞”,RANSAC拟合出来的水面平面都用不了。
应对这个问题的思路有两条。一是“不依赖点云本身,而是依赖约束传播”:把水面平面拟合问题拆成近岸和远水两部分,近岸点云通常质量好,拟合出高置信度的水面平面,然后把该平面作为远水区域的先验平面约束,再在远水区拟合时用先验限制搜索范围。二是“多帧累积”:水面点云的分布每帧都不同,把连续5帧(0.5秒)的点云累积到一起,大大补充单帧点云的密度。这个方法对静态水面和缓慢流动的水面都奏效,实测点云有效率从40%提升到80%以上。
5.3 波浪和水面晃动怎么压下来
波浪是所有水面测量的天敌。理想情况是水面静止,水线高度是一个固定值;实际的水线则在平均水位上下周期性波动。早期版本以单帧水线像素直接换算吃水,输出的数值在波浪周期内剧烈跳变,现场人员根本没法用。
后来引入了两个对策。一是IMU姿态补偿早起作用:船随波浪晃动时,水线在图像上下移动一部分是波浪,另一部分是船的姿态变化。用IMU横摇角修正后,只保留真正的波浪影响。二是时间窗口滑动平均:取一个完整波浪周期约8~10秒的数据做中位数滤波,直接输出“有效半周期平均水位”。
这里有一个反直觉的结果:单看某一帧,测量误差可能很大,但滤波后反而比任何单帧都准。核心原因是随机误差在大样本下被系统性平均掉了。实测滤波前后的标准差从5cm降到了1.8cm左右。
5.4 雨雾天、污损水尺和远距离拍摄的另外几个坑
雨天是另一个头疼场景。雨滴会在激光雷达点云中产生大量噪点,这些离散点在水面平面拟合时极容易被误判为水面点。我的做法是先用点云密度特征剔除雨滴噪点——雨滴的回波是孤立的稀疏点,而真实目标是连续面状分布,两者在邻域密度上差异明显。做一步基于半径的点云密度滤波,单帧处理耗时增加约8ms,但水面平面拟合的稳定性提升非常显著。
雾天主要影响视觉侧。水尺刻度在雾中对比度下降,OCR识别率会跌到60%左右。我用了一个轻量级的去雾处理:暗通道先验方法,把图像去雾后再进OCR。虽然不是专门为水尺优化过的去雾算法,但效果比直接硬识别好了不少。
水尺被污损或者被海生物附着也是常见问题。遇到这种船,算法只能在自己能识别的范围内工作,这时置信度评估就发挥作用了——宁可给出“数据不可用”,也不要硬编一个读数。港口方面了解后也能理解,这就像人工遇到看不清的刻度也会选择不上报而不是瞎猜。
5.5 总结里边最有价值的几个经验
最后分享几点此次项目实际体验中最有价值的沉淀。
第一,不要迷信高线数雷达。16线雷达在合理角度规划下完全够用,更高线数除了增加成本,更多是心理安慰。
第二,偏振片是水边视觉的“白菜价救星”。十几块钱的一片偏振片,装上就可以把水面的镜面耀光消掉大半,水尺的可见性大幅提升,非常值得。
第三,硬件同步触发完胜软同步。多花几十块硬件成本和一天部署时间,省掉的却是长期排查时间同步问题的大量精力。
第四,算法输出的置信度比数值本身更重要。交付给港口这种对安全极端重视的场景,一个带置信度的系统,远比一个偶尔给错数但速度快的系统更容易被接受。
这套激光雷达与摄像头融合的船舶吃水识别方案,目前已经在测试船上连续运行了一个多月。真要说有什么遗憾,就是现场环境比论文里的仿真数据“脏”太多,逼得我们把大量时间花在了抗干扰和置信度上。但恰恰是这些没人写进论文的边界情况,决定了项目是停留在Demo还是真正能被工程接受。机械结构本身也有不少需要改良的地方,比如轻量化设计和小型化集成,后续可以进一步推进,方向已经比较明朗了。