ARTICLE DETAIL

资讯详情

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

双目立体视觉实战:从张正友标定到SGBM点云重建全流程

双目立体视觉实战:从张正友标定到SGBM点云重建全流程 1. 双目立体视觉为什么两只眼睛就能看到三维双目立体匹配这个名词听起来很学术但说白了就是模仿人的双眼视差。你左眼和右眼看到的画面并不是完全相同的因为两只眼睛之间有一定距离成年人大概是6.5厘米左右所以同一个物体在两个视网膜上的成像位置会有细微偏移这个偏移量就是视差。大脑就是靠这个视差来判断物体离我们有多远的。双目立体视觉系统做的事情就是用两个固定间距的工业相机或摄像头替代人眼通过计算左右两幅图像上的像素偏移反推出场景中每个点的深度信息。在机器视觉和三维重建领域双目方案一直是很高频的选择。它不像激光雷达那样贵得离谱也不像结构光那样对光照条件要求严苛只要环境纹理足够、基线距离合理就能以非常低的成本获取稠密的三维点云。这套流程在机器人避障、AGV导航、包裹体积测量、三维扫描、地形测绘里都有落地应用。这篇我把整个流程串一遍从标定板准备开始到张正友标定法原理、双目标定实操、Bouguet极线校正、SGBM视差计算最后生成点云并做可视化。适合看这篇的朋友基本是那种刚接触双目视觉、把两个相机架起来之后发现输出的点云全是错乱的初学者或者已经会用OpenCV但始终没把标定、校正、视差、点云这几个环节的底层逻辑打通的人。我会把每一步的“为什么这样做”也讲清楚避免你照着代码抄完却不知道怎么调参。2. 标定前的硬件准备与图像采集规范2.1 双目设备的固定与基线选择很多人第一次做双目实验用的是两颗USB免驱摄像头直接拿胶带绑在木板条上就开始拍了。这种做法不是说不行但如果连基线两个相机光心之间的距离都没有固定后续一切标定结果都是空中楼阁。基线长度直接决定深度测量范围基线越长相同距离下视差变化越显著深度精度越高但近距离物体容易出视场盲区基线越短可测范围越近但远距离深度误差会被放大。室内桌面级场景30到60毫米基线比较常用室外机器人导航一般拉大到120毫米以上。确定好基线之后一定要把两个相机刚性锁死任何微小的转动都会让后续标定参数失效。相机固定好后尽量锁死镜头的对焦环和光圈。如果有自动对焦或者自动曝光请务必关掉。双目标定有一个隐含前提左右相机的内参在采集标定图像和多目标定过程中是恒定不变的。自动调焦意味着焦距一直在漂标定出来的内参毫无意义。这个坑我踩过当时用的是笔记本自带摄像头做实验拍着拍着焦距变了标定重投影误差忽高忽低后来换成定焦工业相机搭配固定光源一次就过了。2.2 棋盘格的选择与打印细节张正友标定法用的靶标是棋盘格角点检测算法对黑白色块交界处的亚像素定位非常稳定。棋盘格的选型有几个关键参数格子边长平方毫米、棋盘内角点数量、打印精度。家里用普通A4纸打印也能算但纸张一旦弯曲或受潮角点坐标就带上了非线性误差。建议贴在平整的硬板上最好是铝塑板或有机玻璃板避免手持时产生折痕。棋盘格的“内角点数量”很容易数错。比如一个10x7的棋盘格内角点是9x6因为边缘那一圈不算角点。用OpenCV的findChessboardCorners时传入的patternSize必须是内角点数写错的话检测会直接失败或者检测出一堆错位点。建议选9x6或11x8的内角点组合格子数太少角点约束不够太多则图像边缘处的角点会被畸变严重扭曲。打印棋盘格时最好把格子边长控制在一个已知精度。你不需要用游标卡尺去量打印店的普通打印精度通常就能满足教学实验需求但如果你要做的项目对深度精度要求很高建议定制玻璃基板或陶瓷基板的标定板那种板子的热膨胀系数小、平整度极高标定结果也稳定得多。2.3 标定图像采集的十条军规采集标定图像是整个流程里最容易被忽略、却最能决定最终效果的一步。我总结了自己的采集规范每条都是实战中摔出来的经验左右相机必须同步采集否则棋盘格稍有移动左右图像就不匹配。每张标定图像中棋盘格应占画面面积的四分之一到三分之一不能太小否则角点提取精度不够。棋盘格要尽量覆盖画面的各个区域特别是四个角和中心附近的组合要足够丰富。相机畸变在图像边缘最明显如果标定板永远只在画面中央畸变参数根本约束不出来。拍摄时让棋盘格倾斜不要总是正对相机。张正友标定法的核心思想是利用棋盘格平面在不同姿态下的单应矩阵来约束相机内参如果所有标定板都平行于成像平面那么平面间的单应关系会退化内参求解就不稳定。在同一光照下拍避免反光和阴影影响角点检测。图像分辨率要和后续实际使用一致不要用低分辨率标定、高分辨率运行。标定板到相机的距离要覆盖实际工作距离范围从近到远分别采集。每移动一次位置后要保持静态拍摄不要手持晃动。相同姿态下可以多拍几张但不要连续自动保存几十张类似的图那对约束求解没帮助。如果拍摄过程中相机或棋盘格被碰动过立即重拍这一组不要心存侥幸。采集数量方面左右各15到20张是够用的。少于10张特别是姿态变化少的时候内参求解会非常脆弱。我见过有人只拍了5张就开始标定结果重投影误差高达1.8像素实际校正后视差图全是裂开的条带那种体验非常糟。3. 张正友标定法原理与OpenCV双目标定实操3.1 针孔模型与你必须认识的五个内参双目立体要从图像坐标算出三维坐标绕不开相机成像模型。在OpenCV里相机被抽象为针孔模型现实中的点经过镜头光心投影到成像平面上。这个投影过程用数学表达就是P_camera R * P_world T其中R是旋转矩阵T是平移向量把世界坐标系的点转换到相机坐标系。然后相机坐标系中的点经过内参矩阵投影到像素坐标s * [u, v, 1]^T K * [X_c, Y_c, Z_c]^T内参矩阵K是3x3矩阵K [fx, 0, cx; 0, fy, cy; 0, 0, 1]fx和fy是焦距在x和y方向上的像素长度cx和cy是主点坐标。在理想模型里cx和cy就是图像中心但实际的感光芯片安装偏差会让主点偏那么一两个到几十个像素。这个偏差如果不矫正三维重建结果就会系统性偏移。除了内参还有畸变系数。镜头是物理透镜不可能完美符合针孔模型。主要的畸变有两类径向畸变和切向畸变。径向畸变是光线经过透镜时弯曲导致的表现为画面边缘的直线会弯曲成弧线用k1、k2、k3三个系数描述切向畸变是镜头和感光芯片不完全平行导致的用p1、p2两个系数描述。OpenCV的标定函数里畸变向量有5个元素(k1, k2, p1, p2, k3)如果你用的是鱼眼镜头模型则需要更多参数那就是另一个体系了这里先按下不表。3.2 单目标定的代码结构与输出解读OpenCV的标定入口是cv2.calibrateCamera它接收世界坐标系下的棋盘格角点坐标、图像中的角点像素坐标、图像尺寸这三个核心输入返回相机内参矩阵、畸变系数、每张图的旋转向量和平移向量以及重投影误差。读入一组标定图像之后先用cv2.findChessboardCorners找到内角点再用cv2.cornerSubPix做亚像素细化。亚像素细化的窗口尺寸通常设为(11,11)终止条件设定为最大迭代次数30次或误差0.001像素。这一步能明显提升角点坐标精度减少重投影误差。随后通过cv2.calibrateCamera得到内参。标定完成后注意看返回的重投影误差RMS这个值代表所有角点的投影误差平均值。正常来说RMS在0.1到0.3像素之间是合理的如果超过0.5就要怀疑某一步出了问题比如棋盘格不平整、自动调焦未关闭或者角点检测错误。单目标定是双目标定的前置步骤别直接跳过它去做双目标定。双目标定是在左右相机各自内参已知的基础上进一步估计两台相机之间的相对位姿R和T。如果单目标定都不过关双目标定就更无从谈起。标准流程是先各自标定左右相机再用sstereoCalibrate联合优化两边的参数。3.3 双目标定与立体标定输出立体标定调用cv2.stereoCalibrate传入左右相机的内参、畸变系数、左右角点列表和棋盘格世界坐标它会返回旋转矩阵R、平移向量T、本质矩阵E和基本矩阵F。这里的R和T描述的是右相机坐标系相对于左相机坐标系的变换也就是把右相机系的点变换到左相机系P_left R * P_right T在OpenCV的实现中stereoCalibrate会以左相机坐标系为基准。如果你把右相机作为主相机注意这里的坐标转换关系要做逆变换。展开讲一下为什么这个R和T这么关键它决定了立体校正后的极线方向也决定了解算深度时用的基线长度。如果R和T有误差后面极线校正再怎么优化左右图像的匹配点也无法严格落在同一行视差计算的准确性就被封印了。标定完一定要做一个验证把棋盘格放在场景里分别投影到左右图像上检查重投影误差。这里建议用stereoRectify之后的校正图来做验证因为这才是实际管线中看到的形态。如果校正后的棋盘格角点在左右视图上不在同一行重新检查标定流程。4. 立体校正与SGBM视差计算4.1 Bouguet极线校正让二维匹配降级为一维搜索双目标定得到左右相机的内外参后接下来的关键操作是把左右图像“拉直”使得同一个三维点在左右图中的投影落在一对极线上并且极线是水平对齐的。这一步用到的算法是Bouguet极线校正OpenCV里通过stereoRectify实现。stereoRectify的输入是左右相机内参、畸变系数、双目之间的R和T。它会计算左右两个新的相机坐标系这两个坐标系共光心且各自旋转后的成像平面共面同时计算出使得重投影畸变最小的投影矩阵。输出包括左右相机的校正映射矩阵R1、R2投影矩阵P1、P2以及深度视差到三维坐标的映射矩阵Q。很多教程讲到Q矩阵就一笔带过但Q矩阵正是后面点云重建的核心。Q矩阵长这样Q [[1, 0, 0, -cx_l], [0, 1, 0, -cy_l], [0, 0, 0, f], [0, 0, -1/Tx, (cx_l - cx_r)/Tx]]其中Tx是双目基线的x方向分量f是焦距cx_l和cx_r是左右相机主点x坐标。reprojectImageTo3D就是直接拿这个Q矩阵把视差图转成三维坐标的后面的代码里你会看到它。校正映射得到之后用cv2.initUndistortRectifyMap生成映射表再用cv2.remap对左右图像各执行一次重采样。重采样插值方式建议选INTER_LINEAR速度与质量的平衡最好。校正的效果怎么看把左右图上下拼接成一幅图然后看相同边缘特征是否处于同一水平线也就是所谓的水平对齐。一些教程会画扫描线我更喜欢用棋盘格的角点来验证角点如果不在同一行说明校正还有问题。如果用了cv2.remap后图像边缘有黑色区域别紧张那是极线校正和去畸变同时作用导致的正常现象。4.2 SGBM视差计算与关键参数逐个拆解立体匹配算法很多从最朴素的块匹配BM到半全局匹配SGBM再到全局匹配Graph Cut。工业界最常见的平衡点是SGBM它在精度和速度之间折中OpenCV里也有成熟的实现cv2.StereoSGBM_create。SGBM的核心思想是对每一个像素在右图中沿着水平极线搜索匹配代价最小的视差值同时加上平滑约束让相邻像素的视差值变化不会太突兀。所谓的“全局”其实是把二维图像的能量函数拆成多条路径的一维动态规划兼顾匹配精度和计算效率。参数方面以我常用的配置为例numDisparities视差搜索范围必须是16的整数倍。数值决定算法最多能查多近的物体。这个值和你的基线以及最近工作距离挂钩可以用公式估算最小深度 f * baseline / numDisparities。比如f是500像素基线60毫米numDisparities设为64那最小深度大概就是0.47米。blockSize匹配窗口大小通常取奇数我一般用5或7。窗口太小弱纹理区域会产生很多噪声窗口太大深度边缘会被抹平。P1和P2相邻像素视差变化的惩罚系数P2应该远大于P1。P2越大视差图越平滑但过度增大会丢失细节。P1建议设为8倍通道数乘以窗口面积P2设为32倍。uniquenessRatio视差唯一性阈值通常设在5到15之间防止产生视差跳变噪点。disp12MaxDiff左右一致性检查的最大容差常设为1能有效剔除遮挡区域的错误匹配。mode建议设为StereoSGBM_MODE_SGBM_3WAY相比默认模式计算量略增但精度更好。视差图输出会偏移一个基准值OpenCV输出的原始视差图里有负值实际使用前要除以16还原成像素单位。常用的一句代码是disp cv2.normalize(disp, None, 0, 255, cv2.NORM_MINMAX)先做可视化但真正送进点云重建的是那个原始的单通道uint8或int16数据不是归一化后的图像。SGBM最怕的是弱纹理。对于白墙、纯色地板这类没有纹理性内容的区域匹配就是靠猜SGBM硬着头皮算出来的视差往往一片噪声。解决办法通常是加一块红外结构光投射器给场景人为增加纹理或者降低对无纹理区域的深度精度要求。这也是为什么很多商用双目相机都自带红外补光。4.3 WLS滤波让视差图更干净SGBM直接输出的视差图细节处会有毛刺、空洞和飞点。在我做包裹体积测量时视差图的干净程度直接决定了测量重复性。一个很有效的后处理是加权最小二乘滤波WLSOpenCV的cv2.ximgproc.createDisparityWLSFilter可以实现。WLS滤波的原理是利用一张彩色引导图通常是左相机原始图在保留深度边缘的前提下平滑视差图的平坦区域。这个思路很巧妙彩色图的颜色边缘通常对应物体的真实边界而视差图的跳变应当和颜色跳变一致。WLS滤波会惩罚“视差值平滑但颜色差异大”的情况从而保持边缘锐利。使用时要注意输入输出格式的匹配。SGBM输出的原始视差要除以16并转成32位浮点然后传给WLS滤波器。滤波后仍有少量不可靠的“无效值”黑洞区域可以用cv2.medianBlur或者形态学闭运算填补但别用太大内核否则会把细长物体的边缘全部抹掉。WLS有三个关键参数lmbda平滑强度和sigma颜色相似度权重。lmbda越大越平滑一般取8000到30000之间sigma取1到2之间效果较好。这两个参数要结合实际效果调试并没有万能固定值。5. 从视差图重建三维点云与可视化5.1 用reprojectImageTo3D一张图算出所有三维点有了正确校正的视差图之后三维重建反而成了整个流程里最简单的一步。OpenCV提供了专门的函数points3D cv2.reprojectImageTo3D(disp, Q)这里面的数学逻辑说起来也简单。对左图像中每个像素(u, v)结合它的视差值d利用Q矩阵做矩阵乘法就能得到左相机坐标系下的三维点坐标。Q矩阵把视差和像素坐标打包在一起一次性完成了从二维像素到三维坐标的转换。有一个每个新手都会踩的坑disp必须是正确的视差格式。如果是SGBM输出的原始值要先除以numDisparities。OpenCV里SGBM返回的视差值之所以要除以16是因为内部为了精度放大到了固定Q3.4的定点格式。如果不做这一步重建出来的三维坐标会直接差20倍点云会怪异地压缩成一坨。另外reprojectImageTo3D输出的三维坐标是有限定义域视差为0或无效值的点计算出的Z可能是无穷或负数需要做过滤。我的做法是直接把视差小于等于0的点全部丢弃再把Z坐标超出实际工作范围的异常点也过滤掉。这一步能省去后面大量点云清洗工作。5.2 点云可视化的两条路线即时调试和离线分析拿到三维点之后可视化方式取决于你的工作阶段。如果你只是想快速看看重建效果用Python matplotlib就够了。把points3D的x、y、z分别抽出来按z值映射成颜色用ax.scatter画散点图。点云量大时matplotlib会卡这时候可以先用每隔N个像素采样的方式降采样。这种方法适合快速验证算法但不适合精细分析。如果你需要交互式查看推荐Open3D。代码核心也就几行import open3d as o3d pcd o3d.geometry.PointCloud() pcd.points o3d.utility.Vector3dVector(points_xyz) o3d.visualization.draw_geometries([pcd])Open3D内置了交互式窗口可以旋转、缩放、框选。它还提供了点云下采样、离群点移除、法线估计、平面分割等后处理工具。我当时做地形重建时就是用Open3D先框选掉重建出的飞点再用统计滤波剔除稀疏噪声效果立竿见影。如果你有PCL经验也可以把点云以ply格式保存然后丢到CloudCompare里和激光雷达点云做配准和分析。CloudCompare处理点云配准是强项我可以给你一个实用建议从双目重建得到的点云在尺度上是厘米量级的和激光雷达点云做ICP配准前先粗略对齐两者坐标再用ICP精细配准否则很容易陷入局部最优。5.3 点云后处理的必要性双目重建得到的原始点云质量远不如激光雷达那么干净。常见问题包括边缘处的飞点、弱纹理区域的散射点、遮挡边界的断裂点。这些噪声如果不处理会导致后续处理步骤出现严重偏差。统计滤波是很常用的去噪手段。Open3D的remove_statistical_outlier会计算每个点到其最近N个邻域点的平均距离如果平均距离超出全局均值的某个标准差倍数就把这个点标记为离群点并移除。我一般设置邻域点数为20标准差倍数为2.0。体素下采样同样很常用voxel_down_sample(voxel_size)按固定尺寸网格化点云每个网格保留一个代表点。它既能降低点云密度又能平滑微小噪声。voxel_size的选择和你的实际分辨率有关比如每像素对应的物理尺寸是0.5毫米体素取1毫米就比较合适。做这些后处理的时候要记住一个原则点云是数据的载体你对它做的每一个操作都在“消耗”原始信息。过度滤波会让细节消失欠滤波会让下游崩溃。需要根据具体任务找到平衡点而不是机械套用参数。6. 从标定到点云全链路排坑指南6.1 标定误差排查思路如果你发现最终点云形变严重先怀疑标定环节不要急着调SGBM参数。最直接的检查方式在标定图里选一两张用标定结果把角点世界坐标投影回图像坐标看与检测到的像素坐标的差值也就是重投影误差。RMS大于0.3像素时先别跳去立体流程回头检查采集的标定图。常见的标定误差来源我列在下面可以对照排查症状可能原因处理手段RMS一直偏高棋盘格不够平整或打印有误差换硬基板标定板重新采集RMS忽高忽低相机自动对焦/自动曝光未关闭锁死镜头模式和参数标定结果不稳定标定板摆动太大静态拍摄用快门线或延迟拍摄校正后图像边缘扭曲畸变模型未充分约束增加棋盘格靠近图像四角的姿态校正图像有严重黑边双目标定R不准检查棋盘格是否覆盖全部视野区域我实测中还遇到过一种诡异情况findChessboardCorners在某一组图上始终检测失败看图像也看不出问题后来发现是棋盘格上的透明胶带反光导致角点局部过曝。处理方法是在采集时避免标定板直射强光。6.2 视差图常见问题与调优次序视差图的调试原则是“先保证极线对齐再谈匹配参数”。如果校正图左右视差在边缘处就相差三五行以上后面调什么都是白搭。确认极线对齐后按下面的顺序排查视差图整体全是黑色或数值极小搜索范围numDisparities太小或者极线校正失败。视差图有大片噪声区域相应区域纹理不足考虑增加纹理投射器或调大blockSize。前景物体边缘出现毛刺或不连续注意P1和P2的比例也可尝试调大uniquenessRatio。视差图有左右倾向性的偏向左右一致性检查参数disp12MaxDiff没打开或设置错误。视差图在大平滑面里出现“断裂带”可能是光照不一致导致左右图亮度差异大考虑做直方图均衡化。直方图均衡化对SGBM确实有帮助但要注意局部直方图均衡会改变图像的绝对亮度关系过度使用也可能引入新的误匹配。我在室内光照稳定的场景里一般只做一次全局直方图均衡。6.3 点云生成与可视化阶段的高频坑点云阶段最典型的错误是坐标尺度不对。当你发现重建出来的物体边长比实物大几倍或小几倍优先检查内参矩阵中的fx、fy单位是否正确。像素焦距应该是焦距物理尺寸除以像素物理尺寸工业相机参数表里常给出2.8mm焦距和3.45um像元尺寸那fx就是2.8/0.00345约等于812像素。用错一个数量级整个点云都会莫名其妙地放大。还有一个高频问题reprojectImageTo3D出来的点云在Open3D里看起来是镜像的。这个很正常因为图像坐标系是x向右、y向下而Open3D使用的是右手坐标系。如果视觉上需要翻转手动交换坐标轴即可不必怀疑算法出错。6.4 一套可以“抄作业”的完整流程组合把核心步骤串起来我建议的流程是固定双相机关闭自动增益和自动白平衡采集15到20组标定图。分别对左右图像做角点检测和亚像素细化。分别用单目标定得到左右内参和畸变系数。用双目标定得到R和T。用stereoRectify计算R1、R2、P1、P2、Q。用initUndistortRectifyMap生成映射表用remap逐帧校正图像。用StereoSGBM计算视差图。可选WLS滤波平滑视差图。用reprojectImageTo3D生成三维点云。可选统计滤波、体素下采样后保存或可视化。这套流程里第2步和第3步之间如果RMS不达标不要进入第4步。第6步完成时通过左右棋盘格角点做一次极线对齐验证。第7步到第8步之间如果视差质量太差调参之前先检查第6步的校正图。每一步的验证都不能省不然最后堆放误差才手忙脚乱。7. 一个真实项目的经验总结拿我之前做的一个快递包裹体积测量项目来说当时用两颗500万像素工业相机基线80毫米工作距离0.8到1.5米。标定流程走完SGBM参数调了两周才稳定下来。过程中踩过最大的坑是我一度觉得视差图噪声大而疯狂调P1、P2和blockSize但其实问题出在相机增益太大导致图像噪声把增益降下来、开了一盏均匀的补光灯之后SGBM参数几乎没有改动视差质量直接上了一整个台阶。这件事给我最大的启发是图像采集的物理条件决定算法上限后处理只能在下限附近打补丁。在最后的项目调试阶段我还做了一件非常有用的事把标定结果的RMS、每一帧校正图的极线偏移量、SGBM在特定区域的视差方差都记录下来做成一张监控表。后续稳定性测试时只要某个环节的指标出现异常波动我就能立刻定位是硬件松动、光照变化还是算法参数漂移。这类自建监控机制虽然简单但比事后来回调参数要高效得多。从标定到点云看起来只是几个OpenCV函数的串联但真正做好每一步都有大量值得深挖的细节。希望这篇能帮你把其中的关键节点理顺。如果你按这套流程跑通了自己的双目系统再回头去看那些云里雾里的OpenCV源码和论文会发现视野完全不一样了。
返回列表