StereoSGBM实战指南:如何用普通摄像头实现高精度视差图生成

张开发
2026/5/10 4:00:20 15 分钟阅读

分享文章

StereoSGBM实战指南:如何用普通摄像头实现高精度视差图生成
1. StereoSGBM算法入门从原理到实践你可能听说过深度摄像头比如微软的Kinect或者英特尔的RealSense它们确实能提供不错的深度信息但价格动辄几千元。其实用普通摄像头也能实现深度估计这就是我们今天要讲的StereoSGBM算法。StereoSGBM是OpenCV中实现的一种半全局块匹配算法专门用于从双目图像中计算视差图。简单来说它通过比较左右两个摄像头拍摄的同一场景的图像找出每个像素点在两幅图像中的位置差异也就是视差然后根据视差计算出物体到摄像头的距离。想象一下当你闭上一只眼睛时用手指交替遮挡左右眼会发现手指的位置似乎在移动。距离越近的物体这种移动视差就越大。StereoSGBM算法就是利用这个原理只不过它用数学方法精确计算每个像素点的视差。这里有个关键点左右图像必须是在同一时间从不同视角拍摄的同一场景。如果拍摄时间不同或者相机移动了计算结果就会出错。这也是为什么我们通常使用双目摄像头或者用单摄像头平行移动拍摄两幅图像。2. 环境搭建与基础代码实现2.1 安装必要的软件包首先确保你安装了Python和OpenCV。如果你用Anaconda可以这样安装OpenCVconda install -c conda-forge opencv或者用pippip install opencv-python opencv-contrib-python2.2 基础代码框架下面是一个最简单的StereoSGBM实现我们先用OpenCV自带的示例图像测试import numpy as np import cv2 # 读取左右图像 imgL cv2.imread(tsukuba_l.png, 0) # 左图灰度模式 imgR cv2.imread(tsukuba_r.png, 0) # 右图灰度模式 # 创建StereoSGBM对象 window_size 3 min_disp 0 num_disp 16*5 # 必须是16的整数倍 stereo cv2.StereoSGBM_create( minDisparitymin_disp, numDisparitiesnum_disp, blockSizewindow_size, uniquenessRatio10, speckleWindowSize100, speckleRange32, disp12MaxDiff1, P18*3*window_size**2, P232*3*window_size**2 ) # 计算视差图 disp stereo.compute(imgL, imgR).astype(np.float32) / 16.0 # 显示结果 cv2.imshow(left, imgL) cv2.imshow(disparity, (disp-min_disp)/num_disp) cv2.waitKey() cv2.destroyAllWindows()这段代码会显示左图和计算出的视差图。在视差图中明亮的区域表示距离近的物体暗的区域表示距离远的物体。3. 参数详解与调优技巧3.1 关键参数解析StereoSGBM有一大堆参数刚开始可能会让人眼花缭乱。下面我解释几个最重要的minDisparity最小视差值通常设为0。如果校正后的图像有偏移可能需要调整。numDisparities视差搜索范围必须是16的整数倍。值越大能检测的深度范围越广但计算量也越大。blockSize匹配块大小必须是奇数。太小会导致噪声多太大会丢失细节。uniquenessRatio唯一性比率用来过滤误匹配。通常5-15效果较好。speckleWindowSize斑点滤波窗口大小用于去除小的噪声区域。speckleRange相邻像素视差变化的最大允许范围。P1, P2控制视差图平滑度的参数。P2通常比P1大3-4倍。3.2 实时参数调优为了找到最佳参数我们可以创建一个带滑动条的界面来实时调整def update(val0): stereo.setBlockSize(cv2.getTrackbarPos(window_size, disparity)) stereo.setUniquenessRatio(cv2.getTrackbarPos(uniquenessRatio, disparity)) stereo.setSpeckleWindowSize(cv2.getTrackbarPos(speckleWindowSize, disparity)) stereo.setSpeckleRange(cv2.getTrackbarPos(speckleRange, disparity)) stereo.setDisp12MaxDiff(cv2.getTrackbarPos(disp12MaxDiff, disparity)) print(computing disparity...) disp stereo.compute(imgL, imgR).astype(np.float32) / 16.0 cv2.imshow(left, imgL) cv2.imshow(disparity, (disp-min_disp)/num_disp) if __name__ __main__: window_size 5 min_disp 16 num_disp 192-min_disp blockSize window_size uniquenessRatio 1 speckleRange 3 speckleWindowSize 3 disp12MaxDiff 200 P1 600 P2 2400 imgL cv2.imread(your_left_image.jpg) imgR cv2.imread(your_right_image.jpg) cv2.namedWindow(disparity) cv2.createTrackbar(speckleRange, disparity, speckleRange, 50, update) cv2.createTrackbar(window_size, disparity, window_size, 21, update) cv2.createTrackbar(speckleWindowSize, disparity, speckleWindowSize, 200, update) cv2.createTrackbar(uniquenessRatio, disparity, uniquenessRatio, 50, update) cv2.createTrackbar(disp12MaxDiff, disparity, disp12MaxDiff, 250, update) stereo cv2.StereoSGBM_create( minDisparitymin_disp, numDisparitiesnum_disp, blockSizewindow_size, uniquenessRatiouniquenessRatio, speckleRangespeckleRange, speckleWindowSizespeckleWindowSize, disp12MaxDiffdisp12MaxDiff, P1P1, P2P2 ) update() cv2.waitKey()这样你就可以实时看到参数变化对视差图的影响找到最适合你场景的参数组合。4. 实战应用从视差图到深度图4.1 视差图转深度图视差图只是中间结果我们最终需要的是深度图。转换公式很简单深度 (焦距 × 基线距离) / 视差焦距可以从相机标定获得基线距离是两个摄像头光心之间的距离。下面是转换代码def disparity_to_depth(disparity, focal_length, baseline): # 避免除以零 disparity[disparity 0] 0.1 depth (focal_length * baseline) / disparity return depth # 假设焦距为700像素基线距离为6.5厘米 focal_length 700 # 像素单位 baseline 6.5 # 厘米 depth_map disparity_to_depth(disp, focal_length, baseline) # 显示深度图 depth_map_normalized cv2.normalize(depth_map, None, 0, 255, cv2.NORM_MINMAX) cv2.imshow(Depth Map, depth_map_normalized.astype(np.uint8)) cv2.waitKey()4.2 深度图的后处理直接计算出的深度图通常会有很多噪声和空洞无效区域。我们可以用一些简单的后处理来改善质量空洞填充用周围有效像素的平均值填充无效区域中值滤波去除小的噪声点双边滤波平滑的同时保留边缘# 空洞填充 def fill_holes(depth_map): invalid_mask depth_map 0 valid_mask ~invalid_mask filled depth_map.copy() # 获取无效像素的坐标 coords np.array(np.nonzero(invalid_mask)).T # 获取有效像素的坐标和值 vals np.array(np.nonzero(valid_mask)).T values depth_map[valid_mask] # 使用最近邻填充 from sklearn.neighbors import NearestNeighbors nbrs NearestNeighbors(n_neighbors1).fit(vals) distances, indices nbrs.kneighbors(coords) filled[invalid_mask] values[indices.flatten()] return filled # 中值滤波 depth_map_filled fill_holes(depth_map) depth_map_filtered cv2.medianBlur(depth_map_filled.astype(np.float32), 5) # 双边滤波 depth_map_smoothed cv2.bilateralFilter(depth_map_filtered.astype(np.float32), 9, 75, 75)经过这些处理后深度图的质量会有明显提升。5. 常见问题与解决方案在实际使用中你可能会遇到各种问题。下面是我总结的一些常见问题及解决方法视差图全是噪声检查左右图像是否正确对齐尝试增大blockSize调整uniquenessRatio增大可以减少误匹配视差图有大片黑色区域检查minDisparity和numDisparities设置是否合适确保物体在视差搜索范围内边缘处的视差不准确这是块匹配算法的固有局限可以尝试后处理或使用更先进的算法计算速度太慢减小图像分辨率减小numDisparities使用CUDA加速版本如cv2.cuda.StereoSGBM_create深度值不准确重新校准相机确保焦距和基线距离准确检查相机是否平行极线是否对齐记住没有一套参数适合所有场景。室内、室外、近景、远景都需要不同的参数设置。这也是为什么我建议先用滑动条找到大致合适的参数范围然后再在代码中固定这些参数。

更多文章