双目视觉测距的核心思想其实来源于我们人类的双眼。当你闭上一只眼睛时,会发现判断物体距离变得困难;而睁开双眼后,这种能力立刻恢复。这是因为我们的大脑通过比较左右眼看到的图像差异(视差)来计算深度信息。
在技术实现上,这个过程可以分为四个关键步骤:
这里有个生活化的类比:想象你站在路边,用两只手指分别测量远处电线杆的视角。当电线杆距离你较近时,两手指需要张开较大角度才能框住它;距离远时,张开角度就小。这个角度差异就类似于视差,而深度计算就是基于这个原理。
重投影矩阵Q是连接视差图和深度图的关键桥梁。这个4×4的矩阵包含了相机标定和校正阶段获得的所有几何信息。我们可以把它想象成一个"翻译官",负责把二维图像坐标和视差值"翻译"成三维空间坐标。
在OpenCV中,通过stereoRectify函数可以得到这个矩阵。它的结构如下:
python复制Q = [[1, 0, 0, -cx],
[0, 1, 0, -cy],
[0, 0, 0, f],
[0, 0, -1/Tx, (cx-cx')/Tx]]
其中:
cx,cy是左相机主点坐标f是相机焦距(像素单位)Tx是基线长度(两个相机中心的水平距离)cx'是右相机主点坐标实际使用时,OpenCV提供了便捷的reprojectImageTo3D函数:
python复制def get_depth(disparity, Q):
points_3D = cv2.reprojectImageTo3D(disparity, Q)
x, y, depth = cv2.split(points_3D)
return depth
这里有个实用技巧:如果视差图是CV_16S类型(这是SGBM等算法的默认输出),真实视差值需要除以16。我在项目中就曾忽略这点,导致深度计算总是出错,调试了半天才发现问题所在。
从视差到深度的计算公式看似简单:
code复制depth = (f × baseline) / disparity
但这个公式背后蕴含着丰富的几何原理。让我们拆解一下各个参数的影响:
在实际项目中,我发现这个公式有几个优化点:
这里有个实测对比数据:
| 优化方法 | 平均误差(mm) | 处理时间(ms) |
|---|---|---|
| 原始数据 | 52.3 | 15 |
| 高斯滤波 | 38.7 | 22 |
| 双边滤波 | 31.2 | 65 |
双目测距的精度受多种因素影响,通过系统化的参数调优可以显著提升效果。以下是我在多个项目中总结的经验:
相机参数优化:
算法参数调优:
python复制# SGBM算法典型参数设置
stereo = cv2.StereoSGBM_create(
minDisparity=0,
numDisparities=64, # 视差搜索范围
blockSize=11, # 匹配块大小
P1=8*3*11**2, # 平滑度参数
P2=32*3*11**2,
disp12MaxDiff=1,
uniquenessRatio=10,
speckleWindowSize=100,
speckleRange=32
)
环境因素考虑:
一个常见的误区是过度追求高分辨率。实测发现,在相同硬件条件下,1080p图像经过下采样到720p后,由于噪声减少和算法效率提升,实际精度反而提高了15%。
下面给出一个完整的深度计算示例流程:
python复制import cv2
import numpy as np
class StereoDepth:
def __init__(self, config_file):
self.load_config(config_file)
self.init_matchers()
def load_config(self, file):
# 加载相机标定参数
self.cam_config = cv2.FileStorage(file, cv2.FILE_STORAGE_READ)
self.Q = self.cam_config.getNode("Q").mat()
def init_matchers(self):
# 初始化立体匹配器
self.left_matcher = cv2.StereoSGBM_create(
minDisparity=0,
numDisparities=96,
blockSize=7
)
self.right_matcher = cv2.ximgproc.createRightMatcher(self.left_matcher)
def compute_depth(self, left_img, right_img):
# 转换为灰度图
left_gray = cv2.cvtColor(left_img, cv2.COLOR_BGR2GRAY)
right_gray = cv2.cvtColor(right_img, cv2.COLOR_BGR2GRAY)
# 计算视差
disp_left = self.left_matcher.compute(left_gray, right_gray)
disp_right = self.right_matcher.compute(right_gray, left_gray)
# WLS滤波
wls_filter = cv2.ximgproc.createDisparityWLSFilter(self.left_matcher)
disp_filtered = wls_filter.filter(disp_left, left_gray, None, disp_right)
# 转换为深度图
depth = cv2.reprojectImageTo3D(disp_filtered, self.Q)[:,:,2]
return depth
这个实现中加入了WLS(加权最小二乘)滤波,能有效提升视差图的质量。在实际部署时,建议将视差计算部分用C++加速,Python只负责结果可视化,这样能提高3-5倍的运行速度。
获得深度信息后,我们可以创建三维点云进行可视化。Python中常用的库有Open3D和PyVista:
python复制def visualize_point_cloud(color_img, depth_map):
import open3d as o3d
# 创建点云
height, width = depth_map.shape
fx = abs(self.Q[2,3])
fy = fx
cx = -self.Q[0,3]
cy = -self.Q[1,3]
# 生成点云
points = []
colors = []
for v in range(height):
for u in range(width):
z = depth_map[v,u]
if z <= 0: continue
x = (u - cx) * z / fx
y = (v - cy) * z / fy
points.append([x, y, z])
colors.append(color_img[v,u]/255.0)
# 创建Open3D点云对象
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(points)
pcd.colors = o3d.utility.Vector3dVector(colors)
# 可视化
o3d.visualization.draw_geometries([pcd])
在实际项目中,点云处理有几个实用技巧:
在双目视觉项目实践中,会遇到各种"坑"。以下是我遇到的一些典型问题及解决方法:
问题1:近距离物体深度跳动严重
问题2:远处物体深度不准确
问题3:重复纹理区域匹配错误
问题4:算法运行速度慢
一个特别容易忽视的问题是温度漂移。在长时间运行后,相机内部参数会因温度变化而发生微小改变。我在一个工业项目中就遇到过这种情况——系统早上校准后精度很好,但到下午误差就明显增大。最终解决方案是增加温度传感器,建立参数补偿模型。