OpenCV计算机视觉实战(25)——立体视觉详解

OpenCV计算机视觉实战(25)------立体视觉详解

    • [0. 前言](#0. 前言)
    • [1. 视差图计算](#1. 视差图计算)
      • [1.1 实现过程](#1.1 实现过程)
      • [1.2 优化思路](#1.2 优化思路)
    • [2. 深度图生成](#2. 深度图生成)
      • [2.1 实现过程](#2.1 实现过程)
      • [2.2 优化思路](#2.2 优化思路)
    • [3. 双目测距系统](#3. 双目测距系统)
      • [3.1 实现过程](#3.1 实现过程)
      • [3.2 优化思路](#3.2 优化思路)
    • 小结
    • 系列链接

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.createDisparityWLSFilterWLS 滤波器,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) 的视差像素进行重投影,剔除噪点
  • 点云滤波:使用 NumPyOpen3D 对生成的点云做统计滤波,移除远/近异常值
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 深度,还输出 xyz 世界坐标
  • 子像素插值:在深度图上点击浮点坐标时,用双线性插值获取更细腻的深度值
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()

关键函数解析:

  • 双线性插值:在非整点位置准确取深度与世界坐标,提高点击测距精度
  • 信息面板:实现半透明叠加,增强交互体验

小结

在本文中,介绍了双目立体视觉的完整流程:相机标定与图像校正确保左右图像的极线对准;StereoSGBMWLS 滤波为视差图提供高质量输入;重投影矩阵 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)------目标追踪算法

相关推荐
机器之心2 小时前
大神爆肝一个月,复刻DeepMind世界模型,300万参数就能玩实时交互像素游戏
人工智能·openai
AI规划师-南木2 小时前
学AI需要什么样的电脑配置?(机器学习丨深度学习丨计算机视觉丨自然语言处理)
人工智能·深度学习·神经网络·机器学习·计算机视觉·自然语言处理·零基础入门
CoovallyAIHub2 小时前
全球首个精细梯田地块数据集GTPBD发布:为梯田遥感研究填补空白(附数据地址)
深度学习·算法·计算机视觉
CoovallyAIHub2 小时前
【一周AI风暴】周鸿祎放话“不用AI就裁员”,前谷歌CEO鼓吹对华996血拼!
深度学习·算法·计算机视觉
余衫马2 小时前
实战指南:RVC 语音转换框架
人工智能·深度学习·ubuntu
说私域2 小时前
社交媒体与兴趣电商环境下品类创新机会研究——以“开源AI智能名片链动2+1模式S2B2C商城小程序”为例
人工智能·开源·媒体
top_designer3 小时前
还在手动“磨皮”:用AI降噪+智能蒙版,构建商业摄影的自动化后期管线
图像处理·人工智能·自动化·aigc·photoshop·摄影·lightroom
SelectDB技术团队3 小时前
Apache Doris 4.0 AI 能力揭秘(二):为企业级应用而生的 AI 函数设计与实践
数据库·人工智能·apache·olap·mcp
aneasystone本尊3 小时前
梳理 Dify 应用的会话接口
人工智能