从不稳定高度图到可靠三维测量:工业 3D 视觉中的图像处理、全局平面校准与融合实践
本文是一篇基于实际工程代码整理的技术复盘。它不逐个解释函数,而是围绕三个问题展开:怎样从复杂场景中找到可靠基面,怎样融合来自不同高度层的深度数据,以及怎样让算法跨过 C# 与 C++ 的边界后仍然稳定运行。下文将承载目标结构的主体表面称为基体,将其上的凸起、器件或待测结构统称为目标结构。Base 表示低量程或基础高度图,Top 表示高层高度图;Raw/Filtered 分别表示原始深度和滤波深度。文中的算法、阈值和实现关系来自现有源码静态分析。由于没有配套的正式基准数据,本文不虚构性能提升百分比;涉及效果与速度的内容,只讨论代码中能够确认的设计意图、复杂度变化和待验证边界。“可靠”是整套管线的工程目标,不是未经测试的性能结论。写在前面:这不是一个“取最大值”问题在工业 3D 检测中,我们经常会得到不止一张高度图。一张图对基体和低矮结构比较稳定,但面对复杂、反光的目标结构或遮挡区域时,可能出现缺失;另一张图对顶部结构更友好,却可能带来帘幕状毛刺、孔洞、孤立点和局部抬高。与此同时,基体本身可能倾斜,目标结构又分布在多个高度层上。最直接的想法是“取最大高度”或“优先使用 Top 图”。实际效果通常并不好:Top 毛刺会直接进入最终结果;Base 与 Top 的接缝处会出现高度跳变;同一个目标结构可能被切成一片 Base、一片 Top;全图大核滤波虽然能压住噪声,却容易抹平真实边缘;每个 ROI 单独拟合平面,会让同一位置在不同调用中得到不同高度。所以,真正的问题不是“哪个点更高”,而是:哪些数据值得相信?发生冲突时应该相信哪一路?昂贵计算应该花在哪些像素上?这也是整套代码从早期方案逐步演进到 Hybrid 全局校准和 Fusion 的主线。先说结论这次代码复盘可以归纳成六条经验:高度图既是图像,也是有组织点云。二维结构判断交给阈值、形态学和连通域,三维几何判断交给物理坐标、平面、法向和高度统计。基体不能简单用全点最小二乘。目标结构面积一大,普通拟合就会被顶面拉偏;更符合当前场景的模型是“共享倾斜、具有不同高度偏移的多个平行层”。Fusion 是数据源选择,不是数值平均。先建立 Top 可信区,再把大差异像素组成冲突区域,用粗糙度、完整性和接缝质量统一裁决。性能优化的重点是减少精算对象。组件 ROI、候选预筛、选择性写回和计算预算,往往比单纯把所有循环并行化更有效。算法边界也是算法的一部分。坐标方向、单位、结构体对齐、Mat 指针类型、缓存 Key 和资源生命周期,任何一个出错都可能让正确公式得到错误结果。串联融合必须先判量程合法性,再叠加 Z 偏移。否则超出当前拉伸量程的残留值会被moveHeightInMm二次抬高,并在后续融合中继续传播。整条处理路线可以先用一张图概括:Base / Top 高度图BGR 与可选 Mask无效值、颜色与结构区域Hybrid / 全局参考平面减平面与统一 Z 基准Top Trust MaskTop 配准前量程门控Top 5.0 mm:height=invalid,trust=0Base / Top 冲突组件裁决毛刺、孔洞、法向与孤立点后处理稳定高度图、点云与测量结果一、从二维高度图走向三维几何1. 高度图为什么适合图像算法项目中的高度图通常是CV_32FC1:每个像素保存一个 Z 值,行列位置隐含 X/Y。它天然保留了邻接关系,因此可以直接使用图像处理方法寻找结构:阈值和颜色过滤用于生成候选 Mask;膨胀、腐蚀、开闭运算用于连接、保护或清理区域;连通域用于计算面积、包围框、长宽比和填充率;中值和局部分位数用于压制高度毛刺;boxFilter 可以一次统计整图的邻域密度。但高度图又不能被当作普通灰度图处理。无效点往往是一个很大的哨兵值,如果让它参与双线性缩放或平均滤波,它会污染周围的有效高度。代码中的一个典型做法是先把哨兵值转换为 NaN,再在处理后恢复:invalid sentinel - NaN ↓ resize / median / interpolation ↓ NaN - invalid sentinel这不是对所有 OpenCV 算子都普遍安全的规则。NaN 在插值、排序、比较和中值操作中的传播行为并不完全相同;工程实现应为每个实际使用的算子验证其 NaN 语义,并同步维护有效性 Mask。对于类别 Mask 或离散高度层回放大,则使用最近邻插值,避免创造原本不存在的类别或高度层。2. 高度图为什么又必须回到物理坐标只在像素空间比较高度还不够。法向、坡度、平面距离都必须考虑像素尺寸:pixelSizeMm = pixelSizeInUm × 0.001 slope = |z_center - z_neighbor| / pixelSizeMm同样是相差 0.1 mm,在 5 μm/pixel 和 20 μm/pixel 的图像上,对应的几何坡度完全不同。有组织点云通过标定参数把像素索引和原始 Z 映射到物理坐标。在本项目采用对角尺度项、平移与耦合项另行处理的约定下,核心关系可简写为:x = M[0] × column y = M[5] × row z = M[10] × rawZ这组简式不是通用相机标定模型;如果矩阵包含旋转、错切或非零平移,应使用完整变换。当前系统采用的是一条“二维找结构、三维做判断”的混合路线,而不是把所有问题都塞进某一种表示中。二、第一道难题:怎样找到真正的基准面1. 普通最小二乘为什么会失败假设基体上没有目标结构,参考面可以写成:z = a·x + b·y + c对全部有效点做最小二乘,通常能得到不错的倾斜平面。问题在于,真实基体上还有目标结构。它们的顶面可能覆盖大量像素。如果直接拟合所有点,面积较大的结构会把参考平面向上拉,最终导致:基体校正后不再接近 0;目标结构高度整体偏低;不同 ROI 中目标结构占比不同,拟合结果随之变化;同一目标结构在整帧和局部检测中得到不同高度。2. Hybrid 的关键假设:多个平行层代码没有把目标结构的点全部当成无用离群点,而是利用了一个更符合当前装配场景的结构假设:z_i(x,y) = a·x + b·y + c_i也就是说,基体和大部分目标结构顶面具有近似相同的倾斜(a,b),区别主要是 Z 偏移c_i。这让问题从“在大量目标结构点中排除离群值”,变成了“先估计公共斜率,再寻找最低且具有空间支撑的平行层”。这个假设有明确边界:如果目标结构存在独立倾斜、明显翘曲或非平面顶面,共享斜率模型可能不再成立。此时质量门和回退链只能降低误用风险,不能替代针对真实场景的验证。完整的分级回退链路如下。可以看到,RANSAC 并不是入口,而是质量门失败后的第三选择;完整帧低分位拟合则是保证确定输出的最后兜底。是且无手动 Mask否