OpenCV计算机视觉实战(25)------立体视觉详解
0. 前言
双目立体视觉通过模拟人眼视觉原理,以两台相机获取同一场景的左右视图,进而计算视差并转换为深度信息,实现对三维空间的感知。无论是机器人导航、增强现实,还是工业测量与无人驾驶,准确而实时的深度估计都是核心需求。本文从最基础的视差图计算切入,结合相机标定与立体校正,用半全局匹配 (StereoSGBM
) 并辅以 WLS
滤波优化性能;随后借助重投影矩阵生成三维点云与深度图;最后构建交互式测距系统,支持点击任意像素获取精确世界坐标。
1. 视差图计算
在双目视觉系统中,视差图是立体匹配的第一步。它反映了同一物体在左右图像中的位置偏移(视差)。视差越小,物体越远。OpenCV
提供了高质量的 StereoSGBM
算法,它通过局部代价聚合生成稠密视差图,并支持多参数调整来优化性能。
1.1 实现过程
StereoSGBM
参数minDisparity
:最小视差值,通常设为0
numDisparities
:视差搜索范围(必须为16
的倍数)blockSize
:匹配块大小,越大抗噪声越好但精度下降P1/P2
:平滑处理参数,控制视差变化的惩罚力度mode
:三路SGBM
模式提高速度和质量
- 视差计算
stereo.compute
返回带有四倍放大(以整数形式存储)的视差,需要除以16
转为真实值
- 可视化
- 用
normalize
将视差映射到0--255
灰度范围,再转为uint8
显示
- 用
python
import cv2
import numpy as np
# 功能:读取左右图像,计算并显示视差图
imgL = cv2.imread('left.jpeg', cv2.IMREAD_GRAYSCALE)
imgR = cv2.imread('right.jpeg', cv2.IMREAD_GRAYSCALE)
# 1. 创建 StereoSGBM 对象
min_disp = 0
num_disp = 16*5 # 必须是16的倍数
block_size = 5
stereo = cv2.StereoSGBM_create(
minDisparity=min_disp,
numDisparities=num_disp,
blockSize=block_size,
P1=8*3*block_size**2,
P2=32*3*block_size**2,
mode=cv2.STEREO_SGBM_MODE_SGBM_3WAY
)
# 2. 计算视差
disp = stereo.compute(imgL, imgR).astype(np.float32) / 16.0
# 3. 归一化并显示
disp_vis = cv2.normalize(disp, None, 0, 255, cv2.NORM_MINMAX)
disp_vis = np.uint8(disp_vis)
cv2.imshow('Disparity', disp_vis)
cv2.waitKey(0)
cv2.destroyAllWindows()

关键函数解析:
cv2.StereoSGBM_create(...)
:创建半全局立体匹配器stereo.compute(left, right)
:计算视差图(以fixed-point
形式)cv2.normalize(src, dst, alpha, beta, norm_type)
:归一化图像便于显示
1.2 优化思路
- 相机标定和立体校正:
先用 stereoRectify
得到校正映射,将左右图像对齐,再计算视差,消除极线倾斜产生的误差 - 子像素视差精度:在
SGBM
参数中启用disp12MaxDiff
和更细的numDisparities
,获取更平滑的视差 WLS
滤波:用cv2.ximgproc.createDisparityWLSFilter
对原始视差做后处理,抑制噪点并保留边缘
python
import cv2
import numpy as np
# 功能:标定后校正、SGBM + WLS 滤波生成高质量视差图
# 1. 加载左右图及标定数据
imgL = cv2.imread('left.jpg', cv2.IMREAD_GRAYSCALE)
imgR = cv2.imread('right.jpg', cv2.IMREAD_GRAYSCALE)
# 假设已用 cv2.stereoCalibrate 得到如下参数
# 相机矩阵、畸变系数、旋转R和平移T
K1, D1, K2, D2 = ... # Nx3x3, Nx1
R, T = ..., ...
h, w = imgL.shape
# 2. 立体校正与映射
R1, R2, P1, P2, Q, _, _ = cv2.stereoRectify(K1, D1, K2, D2, (w,h), R, T)
map1x, map1y = cv2.initUndistortRectifyMap(K1, D1, R1, P1, (w,h), cv2.CV_32FC1)
map2x, map2y = cv2.initUndistortRectifyMap(K2, D2, R2, P2, (w,h), cv2.CV_32FC1)
rectL = cv2.remap(imgL, map1x, map1y, cv2.INTER_LINEAR)
rectR = cv2.remap(imgR, map2x, map2y, cv2.INTER_LINEAR)
# 3. SGBM 参数调优
min_disp = 0; num_disp = 16*8
stereo = cv2.StereoSGBM_create(
minDisparity=min_disp,
numDisparities=num_disp,
blockSize=5,
P1=8*3*5**2,
P2=32*3*5**2,
disp12MaxDiff=1,
uniquenessRatio=10,
speckleWindowSize=100,
speckleRange=32,
mode=cv2.STEREO_SGBM_MODE_SGBM_3WAY
)
dispL = stereo.compute(rectL, rectR).astype(np.float32)/16.0
# 同时计算右视差用于 WLS
stereoR = cv2.ximgproc.createRightMatcher(stereo)
dispR = stereoR.compute(rectR, rectL).astype(np.float32)/16.0
# 4. WLS 滤波
wls = cv2.ximgproc.createDisparityWLSFilter(matcher_left=stereo)
wls.setLambda(8000)
wls.setSigmaColor(1.5)
dispWLS = wls.filter(dispL, rectL, None, dispR)
# 5. 可视化
disp_vis = cv2.normalize(dispWLS, None, 0, 255, cv2.NORM_MINMAX)
disp_vis = np.uint8(disp_vis)
cv2.imshow('Disparity WLS', disp_vis)
cv2.waitKey(0)
cv2.destroyAllWindows()
关键函数解析:
cv2.stereoRectify(...)
:计算校正映射所需的R1
,R2
,P1
,P2
和重投影矩阵Q
cv2.initUndistortRectifyMap(...) + remap
:生成并应用校正映射,消除畸变并对准极线disp12MaxDiff
,uniquenessRatio
,speckleWindowSize
:控制视差一致性与噪点过滤cv2.ximgproc.createRightMatcher
:生成右视差匹配器,供WLS
使用cv2.ximgproc.createDisparityWLSFilter
:WLS
滤波器,setLambda & setSigmaColor
调节平滑强度与边缘保留
2. 深度图生成
获取了视差图后,我们可以借助相机重投影矩阵 Q
,将每个像素的视差信息转换为世界坐标系下的深度值( Z
值)。这一步是从图像空间走向真实三维的关键。
2.1 实现过程
- 相机内参与基线
focal_length
:相机焦距(像素)baseline
:左右摄像头间物理距离(米)
- 深度计算公式
Z = f × B d Z=\frac{f×B}d Z=df×B
其中 Z Z Z 为深度, f f f 为焦距, B B B 为基线, d d d 为视差 - 防止除零
将非正视差阈值后置为小正值,避免无穷深度

关键函数解析:
NumPy
数组直接运算:depth = (f*B)/disp
disp[disp <= 0] = ε
:用小常数替代无效视差cv2.normalize
:深度到灰度映射。
2.2 优化思路
Q
矩阵重投影:直接用cv2.reprojectImageTo3D
一步获得三维点云,无需手工除以视差- 按有效视差掩码处理:只对有效 (
>0
) 的视差像素进行重投影,剔除噪点 - 点云滤波:使用
NumPy
或Open3D
对生成的点云做统计滤波,移除远/近异常值
python
import cv2
import numpy as np
# 功能:利用 Q 矩阵重投影,直接生成深度及点云
# 1. 重投影到 3D
points_3D = cv2.reprojectImageTo3D(dispWLS, Q) # 返回 (h,w,3) 浮点坐标
# 2. 创建有效掩码,去除无效视差
mask = (dispWLS > dispWLS.min()) & (dispWLS < num_disp)
valid_points = points_3D[mask]
# 3. 简单统计滤波:剔除 z 距离异常的点
z = valid_points[:, 2]
z_med = np.median(z)
z_std = z.std()
keep = np.abs(z - z_med) < 2*z_std
filtered_points = valid_points[keep]
# 4. 可视化深度图与点云示意
depth = points_3D[:,:,2]
depth_vis = cv2.normalize(depth, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
cv2.imshow('Depth from Q', depth_vis)
cv2.waitKey(0)
cv2.destroyAllWindows()
关键函数解析:
cv2.reprojectImageTo3D(disp, Q)
:使用重投影矩阵Q
,一步将视差图转为三维点云NumPy
掩码mask
:仅对有效视差像素生成3D
点,去除噪声与无效区域- 统计滤波:用中值与标准差剔除远近异常点,保证点云质量
3. 双目测距系统
在工程应用中,我们常常需要获取某一点到相机的实际距离。借助前面生成的三维点云,我们可以轻松实现一个交互式系统,只需鼠标点击即可获取目标坐标与深度。
3.1 实现过程
- 鼠标回调
setMouseCallback
捕获鼠标左键点击,记录坐标 - 点击定位与距离显示
在显示窗口用红点标出点击位置,并在其旁绘制深度数值(米) - 循环显示
每帧更新圈点与文字,直观交互测距体验
python
import cv2
import numpy as np
# 功能:点击深度图任意点,输出该点到摄像头的距离
clicked_point = None
def on_mouse(event, x, y, flags, param):
global clicked_point
if event == cv2.EVENT_LBUTTONDOWN:
clicked_point = (x, y)
# 1. 读取并准备视差与深度
focal_length = 0.8 # 以像素为单位(fx)
baseline = 0.1 # 基线,单位:米
imgL = cv2.imread('left.jpeg', cv2.IMREAD_GRAYSCALE)
imgR = cv2.imread('right.jpeg', cv2.IMREAD_GRAYSCALE)
# 1. 创建 StereoSGBM 对象
min_disp = 0
num_disp = 16*5 # 必须是16的倍数
block_size = 5
stereo = cv2.StereoSGBM_create(
minDisparity=min_disp,
numDisparities=num_disp,
blockSize=block_size,
P1=8*3*block_size**2,
P2=32*3*block_size**2,
mode=cv2.STEREO_SGBM_MODE_SGBM_3WAY
)
# 2. 计算视差
disp = stereo.compute(imgL, imgR).astype(np.float32) / 16.0
# 1. 避免除零
disp[disp <= 0] = 0.1
# 2. 计算深度
depth = (focal_length * baseline) / disp # 深度单位:米
# 2. 可视化深度图
depth_vis = cv2.normalize(depth, None, 0, 255, cv2.NORM_MINMAX)
depth_vis = np.uint8(depth_vis)
cv2.namedWindow('Depth')
cv2.setMouseCallback('Depth', on_mouse)
while True:
display = cv2.cvtColor(depth_vis, cv2.COLOR_GRAY2BGR)
if clicked_point:
x, y = clicked_point
Z = depth[y, x]
cv2.circle(display, (x, y), 5, (0,0,255), -1)
cv2.putText(display, f"{Z:.2f} m", (x+10, y-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,0,255), 2)
cv2.imshow('Depth', display)
if cv2.waitKey(30) & 0xFF == 27:
break
cv2.destroyAllWindows()

关键函数解析:
cv2.setMouseCallback(win, func)
:为窗口绑定鼠标事件cv2.EVENT_LBUTTONDOWN
:鼠标左键按下事件cv2.putText(img, text, org, font, fontScale, color, thickness)
:在图像上绘制文字
3.2 优化思路
- 三维坐标输出:点击深度图时,不仅显示
z
深度,还输出x
、y
、z
世界坐标 - 子像素插值:在深度图上点击浮点坐标时,用双线性插值获取更细腻的深度值
python
import cv2
import numpy as np
clicked_pt = None
pt3d = None
def on_mouse(event, x, y, flags, param):
global clicked_pt, pt3d
if event == cv2.EVENT_LBUTTONDOWN:
clicked_pt = (x, y)
# 双线性插值深度
fx, fy = x, y
if 0 < fx < w-1 and 0 < fy < h-1:
x0, y0 = int(fx), int(fy)
dx, dy = fx-x0, fy-y0
# 插值
z00 = points_3D[y0, x0, 2]
z01 = points_3D[y0, x0+1, 2]
z10 = points_3D[y0+1, x0, 2]
z11 = points_3D[y0+1, x0+1, 2]
z = (z00*(1-dx)*(1-dy)+z01*dx*(1-dy)+z10*(1-dx)*dy+z11*dx*dy)
# 插值 x,y
x_world = (points_3D[y0, x0, 0]*(1-dx)*(1-dy) + ... )
y_world = (points_3D[y0, x0, 1]*(1-dx)*(1-dy) + ... )
pt3d = (x_world, y_world, z)
# 假设 dispWLS, Q 已计算,points_3D 也已重投影
h, w = dispWLS.shape
depth_vis = cv2.normalize(points_3D[:,:,2], None, 0,255,cv2.NORM_MINMAX).astype(np.uint8)
cv2.namedWindow('Measure')
cv2.setMouseCallback('Measure', on_mouse)
while True:
frame = cv2.cvtColor(depth_vis, cv2.COLOR_GRAY2BGR)
# 绘制信息面板
if pt3d:
info += f" X:{pt3d[0]:.2f} Y:{pt3d[1]:.2f} Z:{pt3d[2]:.2f}m"
cv2.circle(frame, clicked_pt, 5, (0,0,255), -1)
cv2.rectangle(frame, (5,5),(300,30),(0,0,0),-1)
cv2.putText(frame, info, (10,25), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,255,0),2)
cv2.imshow('Measure', frame)
if cv2.waitKey(1)&0xFF==27: break
cv2.destroyAllWindows()
关键函数解析:
- 双线性插值:在非整点位置准确取深度与世界坐标,提高点击测距精度
- 信息面板:实现半透明叠加,增强交互体验
小结
在本文中,介绍了双目立体视觉的完整流程:相机标定与图像校正确保左右图像的极线对准;StereoSGBM
与 WLS
滤波为视差图提供高质量输入;重投影矩阵 Q
让三维坐标重构变得简单易行;交互式测距系统则将这些算法集成于一体,实现了对场景中任意点的实时距离查询。
系列链接
OpenCV计算机视觉实战(1)------计算机视觉简介
OpenCV计算机视觉实战(2)------环境搭建与OpenCV简介
OpenCV计算机视觉实战(3)------计算机图像处理基础
OpenCV计算机视觉实战(4)------计算机视觉核心技术全解析
OpenCV计算机视觉实战(5)------图像基础操作全解析
OpenCV计算机视觉实战(6)------经典计算机视觉算法
OpenCV计算机视觉实战(7)------色彩空间详解
OpenCV计算机视觉实战(8)------图像滤波详解
OpenCV计算机视觉实战(9)------阈值化技术详解
OpenCV计算机视觉实战(10)------形态学操作详解
OpenCV计算机视觉实战(11)------边缘检测详解
OpenCV计算机视觉实战(12)------图像金字塔与特征缩放
OpenCV计算机视觉实战(13)------轮廓检测详解
OpenCV计算机视觉实战(14)------直方图均衡化
OpenCV计算机视觉实战(15)------霍夫变换详解
OpenCV计算机视觉实战(16)------图像分割技术
OpenCV计算机视觉实战(17)------特征点检测详解
OpenCV计算机视觉实战(18)------视频处理详解
OpenCV计算机视觉实战(19)------特征描述符详解
OpenCV计算机视觉实战(20)------光流法运动分析
OpenCV计算机视觉实战(21)------模板匹配详解
OpenCV计算机视觉实战(22)------图像拼接详解
OpenCV计算机视觉实战(23)------目标检测详解
OpenCV计算机视觉实战(24)------目标追踪算法