1. 双目视觉测距的基本原理第一次接触双目视觉时我也被那些复杂的数学公式吓到过。但后来发现它的核心原理其实和我们人眼的立体视觉很像。想象一下当你闭上一只眼睛时很难准确判断远处物体的距离但睁开双眼后这个判断就变得轻松很多。双目摄像头就是模拟这个原理工作的。两个摄像头就像我们的左右眼由于位置不同看到的同一物体在图像中的位置会有差异这个差异叫做视差(Disparity)。视差越大物体离摄像头越近视差越小物体就越远。通过精确计算这个视差我们就能推算出物体的实际距离。这里有个简单的公式可以理解这个关系距离 (基线距离 × 焦距) / 视差基线距离就是两个摄像头之间的物理距离焦距是摄像头的内参之一。在实际项目中我常用的是基线距离在6-12cm的双目模组这个范围既能保证足够的视差精度又不会让设备体积过大。2. 相机标定精准测距的第一步2.1 准备标定板和拍摄技巧标定是双目视觉中最关键也最容易出错的一步。我习惯使用9×6的棋盘格标定板每个方格边长建议在2-3cm。太小会影响角点检测精度太大则需要更大的拍摄空间。拍摄时要注意保持标定板平整褶皱会影响角点定位每个摄像头需要15-20张不同角度的照片包含标定板在不同距离、不同倾斜角度的状态确保标定板在两张图像中都能完整显示我常用的拍摄模式是米字形走位先正对拍摄然后上下左右各倾斜30度左右拍摄最后再加几个斜对角方向。这样能确保标定参数在各个方向上都准确。2.2 使用OpenCV进行标定OpenCV提供了完整的标定工具链。这是我常用的Python代码框架import numpy as np import cv2 import glob # 准备对象点 objp np.zeros((6*9,3), np.float32) objp[:,:2] np.mgrid[0:9,0:6].T.reshape(-1,2) # 存储对象点和图像点 objpoints [] # 3D点 imgpoints_l [] # 左摄像头2D点 imgpoints_r [] # 右摄像头2D点 # 读取图像 images_left glob.glob(left/*.jpg) images_right glob.glob(right/*.jpg) for img_l, img_r in zip(images_left, images_right): gray_l cv2.cvtColor(cv2.imread(img_l), cv2.COLOR_BGR2GRAY) gray_r cv2.cvtColor(cv2.imread(img_r), cv2.COLOR_BGR2GRAY) # 查找角点 ret_l, corners_l cv2.findChessboardCorners(gray_l, (9,6), None) ret_r, corners_r cv2.findChessboardCorners(gray_r, (9,6), None) if ret_l and ret_r: objpoints.append(objp) imgpoints_l.append(corners_l) imgpoints_r.append(corners_r) # 标定相机 ret, K1, D1, K2, D2, R, T, E, F cv2.stereoCalibrate( objpoints, imgpoints_l, imgpoints_r, None, None, None, None, gray_l.shape[::-1], flagscv2.CALIB_FIX_INTRINSIC)标定完成后一定要检查重投影误差通常应该小于0.1像素。我遇到过误差过大的情况往往是标定板照片质量不高导致的重新拍摄后问题就解决了。3. SGBM算法详解与参数调优3.1 SGBM算法核心原理Semi-Global Block Matching(SGBM)是双目视觉中最常用的视差计算算法之一。相比简单的BM算法它增加了平滑性约束通过多路径聚合来优化视差图。算法主要步骤预处理使用Sobel算子计算图像梯度代价计算对每个像素点计算匹配代价代价聚合沿16个方向进行代价聚合视差计算使用胜者为王(WTA)策略选择最佳视差后处理包括亚像素增强、左右一致性检查等3.2 关键参数解析与调优经验在OpenCV中SGBM有十几个可调参数但实际使用时主要关注这几个window_size 3 min_disp 0 num_disp 160 stereo cv2.StereoSGBM_create( minDisparity min_disp, numDisparities num_disp, blockSize window_size, P1 8*3*window_size**2, P2 32*3*window_size**2, disp12MaxDiff 1, uniquenessRatio 10, speckleWindowSize 100, speckleRange 32, mode cv2.STEREO_SGBM_MODE_SGBM_3WAY )参数调优经验numDisparities必须是16的整数倍。对于640×480分辨率的图像我通常设置为64-160。太大计算量增加太小测距范围受限blockSize奇数通常3-11。纹理丰富的场景用小窗口平滑区域用大窗口P1/P2控制视差平滑度。P1是相邻像素视差变化1时的惩罚P2是变化大于1时的惩罚。我常用P18×3×window_size²P232×3×window_size²uniquenessRatio通常5-15。值越大匹配越严格但可能增加无效区域实测中发现室内场景需要更精细的参数调整而室外场景对参数变化相对不敏感。一个常见误区是过度追求视差图的视觉效果实际上应该以实际测距精度为准。4. 实战中的常见问题与解决方案4.1 视差图中的典型问题在项目中经常遇到的几种问题视差图大面积空洞通常是由于纹理缺失区域如白墙或遮挡区域。解决方案增加speckleWindowSize和speckleRange参数使用WLS滤波器进行后处理考虑增加纹理投射器视差不连续物体边缘出现锯齿。可以尝试调整P1/P2参数增强平滑性约束使用更小的blockSize启用SGBM的HH模式计算量会增大误匹配噪声表现为散点噪声。解决方法提高uniquenessRatio启用左右一致性检查增加预处理的高斯模糊4.2 精度提升技巧经过多个项目实践我总结了这些提升测距精度的方法温度补偿摄像头运行发热会导致内参变化工业级应用需要考虑温度补偿非标定区域补偿标定通常只在特定距离范围进行实际使用时可以通过多点标定建立补偿模型动态参数调整根据场景深度动态调整SGBM参数近景用高精度参数远景用低精度参数多帧融合对连续帧的视差图进行融合减少瞬时噪声一个容易忽视的问题是镜头畸变。即使标定时校正了畸变边缘区域的测距精度仍然会下降。在实际应用中我会刻意避免使用图像边缘20%区域的测距结果。5. 完整测距流程实现5.1 图像预处理流程好的预处理可以显著提升SGBM的效果。这是我的标准预处理流程def preprocess(img): # 1. 灰度化 gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 2. 直方图均衡化可选 if use_histeq: gray cv2.equalizeHist(gray) # 3. 高斯模糊 gray cv2.GaussianBlur(gray, (3,3), 0) # 4. 伽马校正针对低光照 if gamma ! 1.0: gray np.uint8(np.power(gray/255.0, gamma)*255) return gray对于光照变化大的场景我会额外加入局部对比度增强(CLAHE)。但要注意过度处理反而会引入噪声。5.2 视差图后处理原始视差图通常需要后处理才能使用def postprocess(disp, min_disp, num_disp): # 1. 空洞填充 disp cv2.ximgproc.disparityWLSFilter.filter(disp, left_img, None, right_img) # 2. 亚像素增强 disp np.float32(disp) disp (disp - min_disp)/num_disp # 3. 中值滤波 disp cv2.medianBlur(disp, 3) # 4. 转换为实际距离 depth (baseline * focal_length) / (disp 1e-6) return depth实际项目中我会将视差图分为多个ROI区域对每个区域采用不同的后处理参数。比如中心区域使用更强的滤波边缘区域则减弱处理强度。6. 性能优化与实时实现在树莓派等嵌入式设备上实现实时双目测距需要一些优化技巧分辨率选择实测VGA(640×480)分辨率在大多数场景已经足够相比HD分辨率能提升3-4倍速度ROI处理只计算感兴趣区域的视差。比如自动驾驶中主要关注前方道路区域参数分级预览模式使用快速低精度参数(numDisparities64, blockSize5)精确模式当检测到关键物体时切换高精度参数并行计算利用OpenCL或CUDA加速。使用OpenCV的UMat可以自动启用OpenCLleft_gpu cv2.UMat(left_img) right_gpu cv2.UMat(right_img) disp_gpu stereo.compute(left_gpu, right_gpu) disp cv2.UMat.get(disp_gpu)在Jetson Nano上经过优化后我可以实现30fps的VGA分辨率视差计算。关键是把SGBM参数调整到刚好满足需求的最低精度并合理利用硬件加速。