33 激光三角测量
结构光(structured light,章节 32)把一整片编码纹理投到物体上,一次曝光就拿到全场高度。激光三角测量(laser triangulation)走的是另一条路:它只投出一条激光线,单次曝光只测得物体被这条线切过的那一道剖面(profile)。看似信息少得多,但配合物体或相机的匀速运动,把一条条剖面沿扫描方向堆叠起来,照样能拼出整面三维形貌——而且单线方案光能集中、提取简单、速度极快,是工业在线三维检测的主力:锂电极片的毛刺测高、焊缝的余高与咬边、钢轨的廓形磨耗,背后多半是一台台激光轮廓仪在产线上以千赫兹的剖面率扫描。本章用一幅合成的”剖面靶”场景贯穿全部实验(图 33.1):一条平输送带上放着一个三棱柱和一段圆弧凸起,线激光自上方斜射,相机从另一角度俯看那条被物体抬高、压弯的亮线。
33.1 三角测量原理
激光器射出的不是一束光,而是经柱面镜展开的一张激光平面(laser plane),它与被测表面相交成一条空间曲线。相机从与激光平面成一个固定夹角的方向观察这条线:当表面某点被抬高,交点沿激光平面平移,在相机像面上就表现为激光线在该列沿行方向的位移。高度与位移之间是一一对应的几何关系——这正是”三角”二字的来历:激光平面、相机光轴与被测点构成一个三角形,已知基线(相机与激光的相对位姿)和观测角,就能解出第三个顶点的深度。
经标定后,这层几何被压缩成一个简单的标定常数。本章合成数据取激光基线行 \(\text{BASEROW}=360\)(高度为零的输送带对应的激光行),并令行位移与高度成正比,比例系数 \(k=0.8\ \text{px/mm}\)。于是某列激光线落在第 \(r\) 行时,该列高度为
\[ h = \frac{\text{BASEROW} - r}{k}. \]
真实系统的标定曲线未必如此线性,但形式不变:提取出激光线的行位置,就等价于测得了高度。所以激光三角测量的精度,归根结底取决于一件事——能多准地找到每一列激光线的中心。
斜射的激光平面与相机像面不平行,按常规对焦只有一小段激光线清晰、两端离焦。Scheimpflug 原理通过让镜头平面、像面与激光平面三者交于一线,使整条激光线同时落在景深内——这是激光轮廓仪镜头普遍倾斜安装的原因。
与结构光相比,激光三角是”单线 vs 全场”的取舍:结构光一次曝光测全场,但要投多帧编码图样、解相位、防串扰;激光三角每帧只测一条线,靠扫描换取面,却把每条线的信噪比和提取精度做到极致。两者同属主动三角测量(章节 30),只是把”信息量”与”采集速度”放在了天平的两端。
33.2 激光线提取
激光线提取(laser line extraction)是逐列独立的一维亚像素峰值定位问题:在每一列灰度剖面上,激光线是一个近高斯的亮峰,要估的是这个峰的亚像素行坐标。这与 章节 14 的边缘亚像素插值、章节 20 的卡尺定位是同一种思想——把昂贵的二维定位拆成一串廉价的一维估计,再用亚像素插值兑现远超像素栅格的精度。两种主流算法如下。
重心法(center of gravity,CoG) 把激光峰看作一段”质量分布”,取其灰度加权质心为线中心。设第 \(r\) 行灰度为 \(I_r\)、背景估计为 \(b\),在峰附近一个窗口内
\[ \hat r_{\text{CoG}} = \frac{\sum_r (I_r - b)\, r}{\sum_r (I_r - b)}. \]
它无须任何模型假设、计算量极小,是工业轮廓仪的默认实现。
高斯拟合(Gaussian fit) 假设激光截面是高斯峰 \(I_r - b \approx a\,\exp[-(r-\mu)^2/2\sigma^2]\)。两边取对数后是关于 \(r\) 的二次式,于是对 \(\ln(I_r-b)\) 做加权抛物线拟合 \(\ln(I_r-b)=A+B(r-c_0)+C(r-c_0)^2\),峰位由顶点给出
\[ \hat r_{\text{fit}} = c_0 - \frac{B}{2C}. \]
拟合多一层模型先验,理论上对噪声更稳,代价是计算量和对”峰确实是高斯”这一假设的依赖。
本章合成激光截面为高斯(\(\sigma=2\ \text{px}\)、峰 220、背景 30),叠加乘性散斑(speckle)噪声——相干激光照到粗糙表面产生的随机干涉斑,强度正比于信号本身。这一点对下文 CoG 与拟合的胜负至关重要:乘性噪声在峰顶最强,恰是拟合最看重的样本。
直觉上高斯拟合”更高级”,实测却给出一个反直觉的结果。在 图 33.1 的正常段(未饱和、未遮挡的列)逐列比对真值:CoG 平均 \(|\text{误差}|=0.072\ \text{px}\),高斯拟合 \(0.111\ \text{px}\)——重心法反而略胜。原因正在于散斑的乘性特征:拟合的对数变换会放大弱信号样本的权重、又最看重峰顶附近的高灰度点,而那里恰是乘性散斑最猛的地方;CoG 对整个窗口求加权平均,把随机斑点平均掉了,反而更稳。这是一个诚实的细节:没有放之四海皆准的最优提取算法,得看噪声的统计结构。下一节会看到,拟合的真正价值要到 CoG 彻底失效时才显现。
33.3 饱和:CoG 的阿喀琉斯之踵
工程上常以为”激光越亮越好”,因为亮意味着信噪比高。但灰度有上限:8 位传感器到 255 就削顶(clipping)。当激光峰被削平,灾难就来了。图 33.2 对比了两列截面:正常列是一个完整对称的高斯峰,而棱柱顶因镜面反光过曝的列,峰顶被齐刷刷削成一段平台——峰形携带的对称性信息被砍掉了。
CoG 在削顶剖面上系统性偏置。它以最大像素为中心取窗口加权——但平台让”最大像素”退化成一整段并列的最高点,质心被这段平台拽向平台一侧,于是整条提取线被往下拉。逐列统计触目惊心:饱和段 49 列,CoG 平均 \(|\text{误差}|=0.387\ \text{px}\)、偏置 \(-0.387\ \text{px}\)——是正常段的 \(5.4\) 倍,且全是单向偏置(不是随机抖动,而是系统性偏低)。换算到高度,棱柱顶的 CoG 估计比真值高估 \(+0.585\ \text{mm}\)。而高斯拟合在拟合时剔除了 \(I\ge250\) 的截断样本,只用平台两侧未饱和的”高斯肩部”反推峰位:饱和段平均 \(|\text{误差}|\) 降到 \(0.078\ \text{px}\)、偏置仅 \(+0.005\ \text{px}\),棱柱顶高度误差 \(-0.046\ \text{mm}\)。图 33.3 把这一逆转画得很清楚:在饱和带(黄)内,CoG(红)整体塌向负误差区,而拟合(蓝)始终贴着零误差线。
这里有一个必须诚实交代的反转。上一节正常段 CoG(0.072)还略优于拟合(0.111);拟合只在截断毁掉 CoG 时才赢。换言之,拟合不是”更准的算法”,而是”对饱和更稳健的算法”。教训有两层:其一,曝光的首要红线是绝不让激光峰饱和——过曝带来的不是更高信噪比,而是无法事后补救的系统偏置;其二,当剖面可能饱和时,CoG 会沉默地高估高度,对镜面、亮漆等易反光表面必须改用能剔除截断样本的拟合,或对饱和列单独标记。
33.4 扫描与距离图
把提取出的激光线行位置逐列代入 \(h=(\text{BASEROW}-r)/k\),就把一帧图像还原成一条高度剖面。图 33.4 把这条重建剖面(黑点)叠在真值(灰线)上:三棱柱是一个尖顶三角、圆弧凸起是一道弓形,两者都精准贴住真值;而被顶脊遮挡的那段,重建剖面直接留空——没有激光就没有数据,绝不用一段虚构曲线把缺口连起来。
单条剖面只是一道切片。让物体随输送带匀速通过激光平面,相机以固定帧率连续拍摄,每帧提取一条剖面,按扫描方向(行)依次堆叠,就得到一幅距离图(range image)——每个像素的”灰度”不再是亮度,而是该位置的高度。它是介于二维图像与完整三维点云之间的 2.5D 表示:有规则的像素栅格,但每格携带一个 \(z\) 值。图 33.5 是本章 200 条扫描线 \(\times\) 640 列拼成的距离图,高度越高越亮:棱柱呈一道带斜坡的亮脊,圆弧凸起是一片柔和的亮斑。
注意棱柱右侧那条纯黑竖带:棱柱的下坡面被自身顶脊挡住,激光根本照不到,相机也就看不到亮线。这一帧里有 37 列检测不到激光峰,提取算法如实地把这 37 列全部标为无效(\(37/37\)),距离图在此留下诚实的空洞,绝不插值填补。这与 章节 26 里”静默失败”恰成反面:宁可留一个看得见的洞,也不编造一段看不出真假的高度。被遮挡是物理事实,伪造的平滑曲面会让下游误判出一个根本不存在的表面。整幅距离图共 \(3700\) 个无效像素,峰值高度 \(50.268\ \text{mm}\),全都集中在阴影区。两条剖面的测量结果也经得起检验:棱柱测得 \(49.954\ \text{mm}\)(真值 50)、圆弧凸起 \(29.918\ \text{mm}\)(真值 30),亚毫米级吻合。
SciVision 用 SciRangeImage 承载距离图:底层是 ushort 原始计数,配 resolutionX/Y/Z 三轴分辨率与 offsetZ 偏置;GetValue 返回原始计数(非 \(z\) 缩放值),物理高度需自行乘 resolutionZ 再加 offsetZ;Save 输出 16 位 PNG 以保住高度精度。本例 GetValue 在棱柱顶读出 \(49.940\ \text{mm}\)。它与 SciPointCloud、SciTriangleMesh 同属 Sci3DData 的核心 3D 数据类型。
距离图是通往本书第 IX 部分点云处理的桥:给每个有效像素配上 \((x,y)\) 物理坐标和 \(z\) 高度,距离图就直接展开成一片有组织点云,后续的配准、分割、测量都在 章节 37 展开。
33.5 重复性与曝光
测高系统好不好,不只看一次测得准不准,更看重复测同一处的离散度——这正是 章节 20 强调的重复性(repeatability)方法论。我们在纯平坦区取 100 列,固定几何、只变随机噪声种子,重复渲染并提取 50 次,统计每列提取位置的标准差,再对全部列取平均。
激光亮度(曝光)在这里是把双刃剑。峰值 220(正常曝光)时,CoG 标准差 \(0.091\ \text{px}\)(折合 \(0.114\ \text{mm}\))、拟合 \(0.139\ \text{px}\);把激光调暗到峰值 100,CoG 升到 \(0.112\ \text{px}\)(\(0.140\ \text{mm}\))、拟合 \(0.163\ \text{px}\)。规律很清楚:激光越暗,信噪比越低,重复性越差——欠曝是随机误差的来源。但上一节又告诉我们,过曝(饱和)带来的是更可怕的系统偏置。两头夹击,曝光只能取折中:亮到信噪比够、又不让任何一列饱和。实践中常以”最高的真实表面恰好不削顶”为整定目标,并对反光异常的列保留饱和标记作为兜底。
33.6 SciVision 实现
必须如实记录一个 SDK 缺陷。本书 3D 激光三角模块 SciSv3DLaserTriangle(v26.1)在本机所有入口点都以访问违例(0xC0000005)崩溃:FindLaserLine(两个重载、各参数组合)、AnalysisLaserline、ReconstructeImage 共 20/20 次崩溃,无论喂干净无噪图、8U 还是 16U、何种 ROI(含默认 UNDEF)。为不让一个崩溃拖垮整个示例,工程把 SDK 调用隔进**子进程探针**——主进程system(“demo.exe sdkprobe”)` 拉起子进程去碰 SDK,崩溃只让子进程异常退出、被主进程捕获记录;一旦某版 SDK 修复,对照结果会自动重新出现。激光线提取则全部改为手写的 CoG 与高斯拟合(即前文全部数字的来源)。
// 子进程探针:尝试 SDK 入口(本机 20/20 崩溃,留作 SDK 修复后的对照)
SCIMV::SciSv3DLaserTriangle lt;
SciPointArray pts; SciVarArray grays;
long rc = lt.FindLaserLine(img, roi, /*thresh*/60, /*width*/20, /*sigma*/3, &pts, &grays);
// rc!=0 即记录失败;子进程崩溃则由主进程 system() 的返回码捕获距离图侧的 SciRangeImage 构造与保存则工作正常,可放心使用:
// u16:每像素高度的原始计数(mm×100);分辨率/偏置随构造传入
SciRangeImage ri(u16.data(), W, NSCAN, W * (int)sizeof(unsigned short), SCI_DATA_USHORT,
1.0, 5002.0, /*resZ*/0.01, /*offZ*/-0.01, /*resX*/XRES, 0.0, /*resY*/YSTEP, 0.0);
double v = ri.GetValue(100, 220); // 原始计数,非物理高度
double z = v * ri.ResolutionZ() + ri.OffsetZ(); // → 物理高度 mm
ri.Save("out\\range_image_sci.png"); // 16 位 PNG,保住高度精度FindLaserLine 的预期接口在此一并列出,供 SDK 修复后参考:输入激光图与搜索 ROI,给定峰强阈值、激光线宽与平滑 \(\sigma\),输出一串亚像素激光点 pts 与对应灰度 grays;本例手写提取正是按这一语义实现的逐列 CoG/高斯拟合。生成本章全部图像与数字的完整工程位于 code/laser_triangulation/。
工业案例:电池极片毛刺的高度判定
锂电池极片分切后,边缘常残留几十微米高的毛刺(burr),过高会刺穿隔膜引发内短路,必须在线测出毛刺高度并剔废。线激光轮廓仪以千赫兹剖面率高速扫过极片边缘,本该胜任 µm 级测高——但极片表面是镜面般的金属箔与涂层,激光打上去强反光,峰值轻易冲到饱和。一旦削顶,CoG 就按本章揭示的机理系统性高估高度:本不超标的边缘被测出虚高的”毛刺”,造成大批误剔(过杀)。对策有三:其一,从硬件压住反光——降低激光功率、加装偏振片抑制镜面分量,让激光峰回到未饱和区间;其二,把提取算法从 CoG 换成能剔除截断样本的高斯拟合;其三,对仍然饱和的列单独打标,交由复检而非直接判废。教训很硬:µm 级激光测高,提取算法与曝光控制和镜头同等重要——再贵的轮廓仪,配上会被饱和带偏的提取算法,测出的也是假高度。
33.7 小结
- 激光三角用一条线换取极致的单剖面精度,靠扫描拼出整面:激光平面与相机的三角几何把高度编码为激光线的行位移(\(h=(\text{BASEROW}-r)/k\));它与结构光是”单线 vs 全场、速度 vs 信息量”的取舍。
- 提取精度决定一切,但没有万能算法:正常段乘性散斑下重心法(CoG,0.072 px)反而略优于高斯拟合(0.111 px)——最优提取取决于噪声的统计结构,得实测而非想当然。
- 饱和是 CoG 的阿喀琉斯之踵:削顶剖面让 CoG 系统性偏置(饱和段 \(-0.387\) px、高度高估 \(+0.585\) mm,是正常段的 5.4 倍),高斯拟合剔除截断样本后降到 0.078 px。曝光的首要红线是绝不让激光峰饱和。
- 遮挡要留诚实空洞,绝不插值:距离图中 37 列被遮挡列全部标为无效——宁缺毋造,伪造的平滑表面比看得见的洞危险得多。
- 曝光是欠曝随机误差与过曝系统偏置之间的折中:峰值 220 时 CoG 重复性 0.091 px,调暗到 100 则升到 0.112 px;整定目标是”最高真实表面恰不削顶”。
亚像素激光线提取的理论基础是 Steger 提出的无偏曲线结构检测算子 (Steger 1998),它给出了带宽度估计的亚像素中心线定位;面向工业现场反光与噪声的快速鲁棒激光条纹提取方法见 Usamentiaga 等人的工作 (Usamentiaga, Molleda, 和 García 2012)。激光三角测量与三维重建更系统的论述另可参阅 Steger 等人的著作 (Steger, Ulrich, 和 Wiedemann 2018)。




