ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

双目散斑结构光从原理到调试:散斑生成、参数匹配与标定优化

双目散斑结构光从原理到调试:散斑生成、参数匹配与标定优化 前阵子项目里调一套双目散斑结构光模组刚开始以为很简单不就是激光器加衍射片往物体上投一堆散斑再走一遍双目匹配算法吗结果通上电、看着屏幕上密密麻麻的散斑图我被现实狠狠教育了一顿——白墙上全是飞絮状噪点近处桌面上的散斑糊成一片标定重投影误差死活压不到0.5像素以下。后来花了近两周时间从散斑生成原理、系统参数到标定流程全部重新梳理了一遍最后才把深度图调到可用状态。双目散斑结构光简单说就是主动投影散斑图案的双目立体视觉。它是在被动双目基础上增加一个散斑投射器目的不是像条纹结构光那样直接编码相位而是给白墙、纸箱、光滑表面这类低纹理区域主动注入视觉特征让左右相机有东西可匹配。这个方案结合了主动照明和成熟的双目立体几何在三维重建、深度测量、人脸识别、机械臂引导这些场景里非常常见。这篇内容适合准备上手双目散斑模组的同学也适合已经在调但深度图效果不理想的朋友我会从散斑生成原理、系统参数、质量评价、相机标定到图案优化按我实际踩坑的顺序逐步展开。1. 低纹理表面的救场方案散斑给双目匹配“造特征”1.1 被动双目在哪些地方会失效立体匹配的本质是在左右两张图中找同名点然后根据视差恢复深度。找同名点靠的是局部窗口内的灰度或颜色特征。问题在于真实世界里大量表面是低纹理甚至无纹理的办公室的白墙、打印机的光滑外壳、柜门、桌面这些区域灰度方差极小任何窗口看过去都一样算法根本找不到唯一对应点。我测过一个被动双目系统对着一面迎着阳光的白墙深度图上出现大量“瀑布式”伪差边缘位置全是毛刺。把窗口从5x5调到21x21也没用因为窗口内根本没有可辨识的纹理只有环境光造成的一点渐变。这时候大家才想起来与其指望场景里自带纹理不如主动打一束光上去——这就是散斑结构光最朴素的出发点。1.2 散斑不是简单的“补光”散斑投射器做的事情可以理解为给被测表面临时贴上一层高对比度的“图案贴纸”。这层图案有几个好处它是主动注入的不依赖被测物体本身的颜色和材质左右相机能同时看到同一份图案因此立体匹配能稳定收敛散斑通常工作在近红外波段人眼看不到不影响被测物体的外观。这里要特意区分一下散斑结构光和条纹结构光。条纹/编码结构光一般使用单目相机加投影仪需要精确标定投影仪的内参和外参对投影仪的畸变和非线性响应很敏感。双目散斑结构光则保留了双目立体视觉的完整几何模型双目标定、极线校正、立体匹配这一套成熟流程都能复用投射器只是作为“纹理发生器”不参与几何重建的绝对定位。这意味着如果你后续想换更大基线、更高分辨率的相机重建算法的底层逻辑基本不用动改造系统的自由度要大很多。对了有一个结论可能和直觉相反散斑图案并不是越“随机”越好。真正重要的是局部唯一性——任意一个小的匹配窗口在整个参考散斑图中都能找到唯一位置。这跟二维码的定位设计是一个道理太规则会产生周期性歧义太随机又会让匹配窗口丢失可辨识的约束。2. 散斑怎么生成激光衍射与微透镜阵列两条路线2.1 先说清楚“散斑”的两个含义做这个方向的人第一关就会遇到术语混淆。激光散斑speckle是相干激光照射粗糙表面时表面微结构对光的散射形成干涉产生的颗粒状明暗图案而结构光系统里说的“散斑”通常指的是经过扩散器件之后形成的伪随机点阵。两者来源完全不同一个是被动产生的一个是主动设计的。在双侧相机拍到的图像里这两种东西其实经常同时存在DOE投影点阵是主动设计的“信号”激光本身的相干散斑叠加在点阵边缘上则是“噪声”。好的模组设计要尽量压制后者因为它会破坏散斑点边缘的锐利度影响亚像素匹配稳定性。2.2 DOE一束激光变成几万个光点目前消费级双目散斑模组用得最多的方案是激光二极管加衍射光学元件DOEDiffractive Optical Element。DOE本质上是一块带有微纳浮雕相位结构的薄片激光束经过它时不同位置的相位延迟不同经过衍射叠加后光能量会重新分布到特定方向的若干个衍射级上每个衍射级就是一个光斑投影方向。决定点阵扩散角度的核心参数是DOE表面的最小结构周期。简单记忆一句话衍射周期越小扩散角度越大波长越长衍射角度也越大。工程上对于近红外850nm波长要投射出50度以上的扩散角DOE特征尺寸通常要做几微米量级这对加工精度要求很高。点阵的排列可以是矩形栅格、六角网格也可以掺杂伪随机扰动设计自由度在相位分布图上好坏直接决定零级光的比例和整片衍射效率。用DOE的优点很明显体积小、扩散角大、单点能量高。缺点也不难遇到一是激光器波长随温度漂移后衍射角会跟着变化远距离投影时散斑图案可能整体位移几个像素二是DOE加工误差和装配误差会放大零级光在画面中心形成一个亮斑严重时会饱和一片传感器区域。2.3 微透镜阵列可控图案的另一种选择除了DOE也有不少工业级方案用微透镜阵列MLAMicrolens Array。微透镜阵列可以理解为一整片密密麻麻的微小透镜每一颗微透镜都投影出一个光斑光斑的排列取决于透镜阵列的排布。MLA方案最大的优势是图案可控性更强。你可以自由设计每个微透镜的孔径形状、排布位置和倾斜角度做出完全定制化的半随机点阵而不是像DOE那样只能通过相位编码在衍射规则内做文章。散斑均匀性通常也比DOE好因为每个微透镜的透过率可以单独设计边缘暗角问题更容易在源头解决。代价是体积更大、成本更高高密度点阵下微透镜间的串扰也需要额外处理。对比维度DOE方案MLA方案光源要求激光相干光LED或激光均可图案设计自由度受衍射规律限制高可编辑性更强投影均匀性一般边缘能量容易衰减较好可逐透镜设计点阵密度可以做到很高受微透镜尺寸限制体积成本小、适合量产偏大、成本更高温度稳定性波长漂移敏感对波长不敏感典型场景消费级人脸识别、短距离测量工业级大范围测量、定制光场实际选型没有绝对答案。消费级产品追求体积和成本DOE基本是默认选项工业项目里如果对均匀性和图案可定制性要求高MLA可能更合适。如果只是做算法验证其实可以用普通投影仪投一个伪随机点阵到场景里先把算法链路跑通再回来选硬件方案。2.4 选型时容易被忽略的一点选投射器的时候很多人只看“散斑数量”和“投影角度”却忽略了散斑图案在传感器成像面上的实际密度。同样的散斑投射器放在不同距离、不同分辨率的相机下面成像效果完全不一样。这个问题我在下一章专门展开因为它属于典型的“硬件参数打架”问题不是孤立调某一个部件就能解决。3. 双目散斑系统参数之间是互相打架的3.1 基线、焦距和工作距离的三角关系双目系统的深度精度有一个基础公式虽然简单但值得反复默念σz ≈ (z²) / (b · f) · σd其中z是工作距离b是双目基线f是相机焦距像素单位σd是视差估计误差。这个公式给出了三条硬约束精度随距离的平方下降。工作距离从0.5米加到1.5米同样视差误差下深度误差会放大9倍。所以不要指望一套小基线模组能同时覆盖近距离和远距离高精度。基线越大精度越高。但基线拉大近距离公共视野会变小两台相机看到的共同区域变少散斑图案可能只有一侧能照到。散斑系统里σd往往不取决于算法本身而取决于散斑点成像质量。斑点边缘模糊、过曝、密度太高视差估计误差就会变大。我现在的选型习惯是先明确工作距离范围和目标精度反推基线和相机的下限再看视野和体积约束最后才看散斑投射器。顺序反了的话后面大概率要推倒重来。3.2 散斑密度必须和相机分辨率匹配这个坑我踩得最深。拿到一个散斑投射器第一件事是看它在传感器上每个散斑之间的像素距离而不是只看“点数有多少”。假设投射器在某个工作距离上相邻散斑点的物理间距是p相机在该距离上的像素焦距是f单位像素那么图像上相邻散斑之间大约是d_pix p / (f) 稍等准确说物体面上间距p对应的像素间距 p · f / z比如p3毫米f600像素z800毫米得到约2.25像素这比较理想。如果p只有1毫米同样条件下算出来只有0.75像素散斑点之间几乎连成一片匹配窗口里全是纠缠不清的高频噪声NCC代价曲线会非常平缓视差估计误差直线上涨。反过来如果p太大散斑在匹配窗口里只有一两个点特征约束不足误匹配率也会升高。我实际测试的经验范围是散斑点间距在传感器上大约2到4像素单个散斑点的直径大约1.5到2.5像素这时候匹配算法的可靠性最高。这个结论是“匹配算法视角”下的和人眼觉得“看得清楚”完全没有关系。3.3 曝光与同步左右相机必须保持一致双目散斑系统最容易翻车的工程细节是左右相机不是“同一个大脑”。如果左相机自动曝光、右相机也自动曝光两边的亮度和增益往往不一样同一个散斑点在两幅图像里的形态差异变大匹配代价就会失真。做法很直接固定曝光时间、固定增益左右相机使用相同参数。更稳妥的做法是硬件触发同步保证左右两帧是同一时刻拍摄的。否则手稍微动一下左帧拍到的物体位置和右帧不一致视差图边缘就会出现“拖尾”这种错误没法靠后处理消除。此外相机前端要加和投射器波长匹配的窄带滤光片。我见过有人用850nm的投射器相机端不加滤光片就在阳光直射下测试环境光直接把散斑对比度稀释到接近于零深度图全是洞。加了中心波长850nm、半带宽30nm左右的滤光片之后即时在户外散斑依然清晰可辨。4. 散斑好坏不能靠眼睛判断要用匹配指标说话4.1 参考散斑图怎么采集评估散斑质量的第一步是采集一张可靠的参考散斑图。把一块白色漫反射平板垂直于光轴放好平板表面尽量平整在系统设计的额定工作距离附近拍摄。注意几个细节散斑投射器要开机预热一段时间再采集等激光器波长稳定否则图案位置会漂移采集参考图和后续测试图时曝光、增益参数必须完全一致平板要足够大把整个视场覆盖住避免背景干扰。参考图不是采集一次就能永久复用的。每次改动投射器位置、更换激光器、或者环境温度变化明显之后都应该重新采集。我在项目里吃过亏拿了旧的参考图去和新的测试图做匹配结果整幅深度图都偏了排查了半天才发现是参考图过期了。4.2 三个关键指标匹配率、误匹配率、平面RMS评估散斑图案好坏我一般固定三个指标不凭肉眼感觉。第一个是有效匹配率匹配成功且置信度达标的像素数除以有效像素总数。这个指标衡量散斑“够不够吃”。低于90%通常说明散斑过稀、过曝或者环境光干扰太严重。第二个是误匹配率通过左右一致性检查后左右视差差异大于某个阈值比如1像素的像素比例。这个指标衡量散斑“会不会吃错”。如果散斑图案存在周期性歧义或者在倾斜面上图案变形太大误匹配率会明显上升。第三个是平面RMS在几个不同距离处放平面用深度图拟合一个平面看残差的均方根。这是我最喜欢的综合指标因为深度噪声、视差误差、标定误差最终都会体现在平面RMS里。比如0.5米处平面RMS做到0.3毫米以下说明系统噪声控制得不错如果还在1毫米以上就需要回头查散斑成像质量或者其他环节了。4.3 相关窗口的选择散斑质量评估离不开窗口大小。窗口太小局部特征不足匹配歧义增高窗口太大深度边缘被平滑边界处会“糊掉”。对0.3到1.5米的工作距离、百万像素级别分辨率的相机我一般从9x9或者11x11开始试。窗口设置好之后配合散斑密度做一轮网格搜索看哪个组合能同时满足高匹配率和低平面RMS。窗口选择本质上是“尺度”问题——散斑间距大窗口就要大散斑间距小窗口可以适当缩小。两者不匹配时再怎么调参数都救不回来。4.4 一个案例规整点阵的周期性误匹配曾经用微透镜阵列方案做过一套系统点阵非常整齐人眼看很舒服均匀性也好。结果一跑匹配深度图上有周期性横条纹。后来定位到原因点阵过于规整在特定倾斜角度下匹配窗口内出现多个相似结构算法锁定了错误的匹配位置。解决办法是在设计微透镜排布时加入局部扰动把阵列变成“半随机”点阵同时保证局部唯一性。回归之后误匹配率从5%降到0.6%左右。这件事给我的教训是散斑图案的设计不能只盯着“看起来均匀”要和匹配算法联合起来评估定量指标比视觉直觉可靠。5. 标定做不好散斑再好也白搭5.1 标定期间第一件要记住的事关掉散斑这是一个常见的低级错误。有些人觉得散斑能辅助角点检测标定时故意开着投射器结果角点检测器被散斑干扰角点坐标偏移惨不忍睹。正确做法是标定期间关掉散斑投射器只保留均匀的标定光源。如果相机带了红外滤光片要用能被红外照亮的棋盘格比如红外反光油墨打印的图案或者用普通漫反射棋盘格配合红外补光。我在实验室里通常关掉环境光用一个均匀的红外LED板照亮棋盘格角点检测稳定且重复性好。5.2 棋盘格准备与采集姿势棋盘格第一要求是平。不要拿热敏纸、打印纸直接贴纸受潮或者弯曲以后角点世界坐标的误差会直接进入标定结果。我现在会用玻璃板把棋盘格贴在玻璃背面玻璃在前格子在玻璃后面这样标定板表面是光学平面反光问题也少很多。角点尺寸不要太小边长20到30毫米比较合适内部角点数选8x6或9x7。采集姿势上至少拍20到30对左右图覆盖这些情况标定板放在画面中心平面大约正对相机左右倾斜约30度、上下倾斜约30度放在近距离、中间距离、远距离各若干张标定板出现在画面边缘的情况一定要有不能全部居中。5.3 用SB检测角点和亚像素精化OpenCV里传统的findChessboardCorners在低对比度、带噪声的红外图像上经常漏检新版提供了findChessboardCornersSB使用鞍点检测亚像素精度明显更好推荐优先用。import cv2 import numpy as np pattern_size (9, 6) # 内部角点数 criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-6) for fname in image_files: img cv2.imread(fname, cv2.IMREAD_GRAYSCALE) ok, corners cv2.findChessboardCornersSB( img, pattern_size, flagscv2.CALIB_CB_ADAPTIVE_THRESH cv2.CALIB_CB_NORMALIZE_IMAGE ) if not ok: continue # 再做一次亚像素精化进一步稳定 corners cv2.cornerSubPix(img, corners, (5, 5), (-1, -1), criteria)5.4 不合格角点的三层剔除策略很多人标定的问题不是检测不上而是把所有检测到的角点全部送入标定没有剔除不合格的视图。视觉 SLAM 领域的普通做法在这里同样适用先粗标定再迭代剔除异常。我常用的三层剔除流程是这样的第一层检测阶段直接排除。如果某张图的棋盘格被手指挡掉一半、有反光、或者运动模糊导致角点检测置信度低直接丢弃。第二层检测后做合理性检查。相邻角点之间的距离应该符合等比变化如果某一行相邻角点偏移量突变大概率是检测混叠或者棋盘格被遮挡了。第三层整体标定后按重投影误差剔除。先用所有通过前两层检测的图像做一次初始标定然后逐张计算重投影误差把误差较大的视图剔除再用剩余图像重新标定迭代两到三次。def filter_views_by_reprojection_error(objpoints, imgpoints, size, max_err0.3): ret, mtx, dist, rvecs, tvecs cv2.calibrateCamera( objpoints, imgpoints, size, None, None ) keep_idx [] for i in range(len(objpoints)): proj, _ cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist) err np.mean(np.linalg.norm(proj[:, 0, :] - imgpoints[i], axis1)) if err max_err: keep_idx.append(i) else: print(fview {i} reprojection error {err:.3f} px, removing) # 用筛选后的数据再标定一次 obj_keep [objpoints[i] for i in keep_idx] img_keep [imgpoints[i] for i in keep_idx] ret, mtx, dist, rvecs, tvecs cv2.calibrateCamera( obj_keep, img_keep, size, None, None ) return mtx, dist, keep_idx阈值0.3像素不是永恒的。百万像素级传感器上0.3像素算比较严格如果是五百万像素单像素物理尺寸更小可以放宽到0.4到0.5像素。我一般先看整体分布比如所有视图的误差排序剔除明显偏离长尾的部分而不是一刀切。我自己的实际案例第一次标定全部20对图像重投影误差0.42像素怎么调都下不来。用上面方法按误差排序有3张图像误差在0.8像素以上全是标定板靠近图像边缘、且倾斜角度很大的视图。剔除后回归误差降到0.19像素。同一个模组深度精度肉眼可见地变好了。5.5 双目标定之后的最终检查双目立体标定用cv2.stereoCalibrate输入左右相机的角点对最终输出左右相机的相对旋转和平移。这一步要关注两个东西一是整体重投影误差和单目类似越小越好。二是立体校正之后的行对齐程度。校正完成后可以在校正图上画几条水平线或者直接输出视差图看看边缘是否存在明显的行偏移。如果校正后同一位置在左右图中不在同一行说明内外参还是有问题需要回到前面重新检查和剔除。立体校正最怕的是“局部最优”整体重投影误差很小但某个区域对应关系错乱。这种问题通常出现在标定板没有覆盖到画面边缘时。所以我在前面强调边缘视角的图像一定要多拍几张。6. 散斑优化的具体方向密度、点形、均匀性与温漂6.1 密度优化用匹配率曲线找拐点散斑密度不是越密越好也不是越稀越好。最实用的办法是在同一测试场景、同一曝光参数下测试若干种不同密度的散斑投射方案做出“匹配率-密度”曲线找到拐点。我做过的测试中散斑间距从0.8像素一路加宽到3.5像素匹配率是先升后降的。间距太密的时候相邻散斑混叠局部特征高度相似匹配率只有82%间距到2.3像素左右达到峰值96%以上再往3.5像素走窗口里的散斑数量开始不够匹配率缓慢下降边缘区域的孔洞也开始变多。对当时那套系统来说2到2.5像素就是甜区。6.2 点直径和亚像素稳定性散斑点不是越小越锐利越好。过小的点在传感器上只占不到1个像素相当于采样退化亚像素位置信息丢失过大的点相邻点粘连局部对比度下降。我比较看重一个指标同一平面多次采集后视差的帧间抖动。散斑直径在1.0像素时NCC匹配的亚像素抖动经常超过0.2像素优化到2.1像素后抖动可以压到0.08像素左右。这直接决定深度图的平滑程度特别是平面场景抖动大一点深度图上就会看到明显的颗粒感。6.3 均匀性与零级光的处理DOE方案的边缘能量衰减几乎是不可避免的。画面上经常出现中心亮、四角暗。这个问题的优化有两个层面硬件层面选择边缘级次加了权重补偿的DOE设计或者加一枚光场均匀化透镜。软件层面可以在匹配前对图像做局部归一化但这会牺牲一部分对比度对弱特征区域不太友好。最理想的做法还是让硬件均匀性好一些软件端只做微调。中心零级光是DOE方案的“老熟人”。如果画面中央出现一个远亮于其他散斑的亮斑大概率就是零级光。好的DOE会在相位设计上抑制零级但如果是模组装配或者低端DOE引入的可以在光路中加入一个很小的中心挡块把它物理挡住。不要小看这个细节中心区域如果饱和了匹配算法在这一片就完全失效深度图上就是一个大洞。6.4 温漂与参考图更新激光器发热后波长会漂移DOE的衍射角随之变化。这个影响在近距离不算明显但在1米以上工作距离几个纳米的波长漂移足以让散斑图案整体移动好几像素。对策分三层。第一层是硬件层面给激光器做好散热或者选用带温控的激光器封装。第二层是工作流程层面开机后预热几分钟再采集参考图和正式测试不要在冷启动状态下直接做高精度测量。第三层是系统层面如果工作环境温度范围很宽需要建立“参考图-温度”映射在不同温度档位分别采集参考图。这个优化在消费级产品里少见但在工业环境里挺实用。6.5 环境光干扰的滤光方案环境光的问题在室内不明显一旦放到窗边或者户外太阳光里包含大量近红外成分会直接把散斑信号淹没。我给相机配了窄带滤光片之后户外深度图从“基本不可用”提升到“可用但有噪点”。如果再配合算法端的自适应曝光策略效果会更好。滤光片选择要注意三点中心波长要匹配激光器半带宽不要太宽否则环境红外光还是漏进来滤光片要装在镜头的入射光路上装在传感器前面虽然也能用但容易产生反射和鬼影。另外左右两台相机的滤光片透射率一致性也会影响匹配最好成对购买或者同一批次。7. 一次从“满屏噪点”做到可用的调试记录7.1 现象与初判有一段时间我用DOE方案在办公室场景里调一套模组工作距离设定在0.3到1.5米。初始状态深度图表现如下白墙区域全是飞絮状噪点边缘有“毛刺”画面中央有一片完全无法匹配的区域对应散斑投射的零级亮斑近距离放在桌面上的纸箱边缘出现横向条纹0.5米处平面RMS约1.2毫米差得离谱。7.2 逐项排查的顺序我没有直接上手改算法而是按“成像链路”的顺序逐项排查。先关掉散斑投射器拍左右相机的原始图像。结果发现两个问题左右相机自动曝光参数不一致白墙左边亮右边暗增益较高时红外噪声明显放大。改成固定曝光、固定增益之后原始画面的基础帧噪声降下来一截。再打开散斑投射器看散斑原始图像。中心果然饱和用光电测量仪验证是零级光的能量集中在中心。我在DOE前面加了一枚中心遮挡片中心亮斑消除画面中间区域恢复了匹配能力。接着用ImageJ统计散斑图像在传感器上的点间距发现只有约1.2像素明显过密。在保证视场角的要求下调整了投射器的物距比使散斑在目标工作距离上的成像间距达到2.3像素左右。重新采集参考图后匹配率从82%跳到96%。最后回到标定。单目重投影误差0.42像素剔除三张误差最大的边缘视角图后降到0.19像素。双目标定完成后重新立体校正视差图的边缘毛刺明显减少。7.3 最终参数与效果整套调整后的系统参数如下参数项调整前调整后散斑成像间距约1.2像素约2.3像素匹配率82%96%单目重投影误差0.42像素0.19像素0.5米平面RMS1.2毫米0.42毫米1.0米平面RMS无法稳定评估约1.1毫米这次调试的真正收获是深度图质量不是某一项指标单独决定的散斑成像、曝光、标定三大块必须同时达标任何一块有明显短板最终结果就会被那块短板限制。先修成像链再动算法排查效率最高。7.4 一个小经验项目收尾后我自己捋了一下最想提醒后来人的一条每次改动散斑投射器的位置、电流、或者更换激光器都要重新采集参考散斑图。参考图是这套系统一切匹配的基准基准偏了算法怎么调都是白搭。还有一个习惯也值得养成标定和散斑是两个互斥状态标定关投影测试开投影别嫌麻烦去省那一次切换。按这个流程走下来双目散斑系统其实没有玄学每一步都能在原始图像和数值上找到对应关系。
返回列表