用Python和OpenCV从零搭建一个完整的双目视觉系统(四)

发布于:2025-07-09 ⋅ 阅读:(29) ⋅ 点赞:(0)

本系列文章旨在系统性地阐述如何利用 Python 与 OpenCV 库,从零开始构建一个完整的双目立体视觉系统。

本项目github地址:https://github.com/present-cjn/stereo-vision-python.git

在上一篇文章中,我们完成了相机标定这一最关键的基础步骤,并得到了一份高精度的相机参数文件。现在,我们将利用这份参数文件,来执行双目视觉的核心任务——立体匹配 (Stereo Matching)

我们的目标是,模仿人类大脑处理左右眼图像差异的方式,为左图中的每一个像素,在右图中找到它的对应点。通过计算它们之间的像素位置差异,即视差 (Disparity),我们就能得到一张灰色的、包含深度信息的图像——视差图 (Disparity Map)。这张图上的每一个像素点的强度值,都直接对应着真实世界的深度信息。

本文将详细讲解立体匹配的全过程,从准备工作“立体校正”,到核心算法“SGBM”,再到决定最终效果的“参数调优”。

1. 匹配前的准备:立体校正 (Rectification)

在原始的双目图像中,由于相机安装可能存在微小的角度偏差,左图中的一个点,其在右图中的对应点可能位于一条倾斜的直线上(我们称之为“对极线”)。在整张图上沿着成千上万条斜线去搜索匹配点,计算量巨大且效率低下。

立体校正 (Stereo Rectification) 的目的,就是通过数学变换,将这种复杂的二维搜索问题,简化为高效的一维搜索。

  • 目标: 利用标定得到的旋转矩阵 R 和平移向量 T,对左右图像进行虚拟的旋转,使得两个相机的成像平面完全共面,并且像素行严格对齐。
  • 效果: 校正后,对于左图上的任意一个像素点 (x, y),其在右图上的对应点必定位于同一水平线 y。现在,算法只需要在这一行上进行一维搜索即可,效率和可靠性都大大提升。

原左右图像为

极线校正后左右图像为

代码实现 (utils/image_utils.py)

我们的 rectify_stereo_pair 函数封装了整个校正过程。

# utils/image_utils.py
def rectify_stereo_pair(left_img, right_img, stereo_params, calib_image_size):
    # 从标定参数中提取 K, D, R, T 矩阵
    K1, D1, K2, D2, R, T = (stereo_params[k] for k in ('K1', 'D1', 'K2', 'D2', 'R', 'T'))

    # 1. 计算校正变换和投影矩阵
    # 这一步是核心,它计算出将相机坐标系旋转到理想平行状态所需的旋转矩阵 R1, R2
    # 以及将3D点投影到校正后图像平面上的投影矩阵 P1, P2
    # 最重要的是,它还输出了用于三维重建的 Q 矩阵
    R1, R2, P1, P2, Q, _, _ = cv2.stereoRectify(
        K1, D1, K2, D2, calib_image_size, R, T, alpha=0
    )

    # 2. 计算从原始图像到校正图像的像素映射表
    # `initUndistortRectifyMap` 会为每个像素计算出它在校正后图像中的新位置
    left_map1, left_map2 = cv2.initUndistortRectifyMap(K1, D1, R1, P1, left_img.shape[1::-1], cv2.CV_16SC2)
    right_map1, right_map2 = cv2.initUndistortRectifyMap(K2, D2, R2, P2, right_img.shape[1::-1], cv2.CV_16SC2)

    # 3. 应用映射表,执行校正
    left_rectified = cv2.remap(left_img, left_map1, left_map2, cv2.INTER_LINEAR)
    right_rectified = cv2.remap(right_img, right_map1, right_map2, cv2.INTER_LINEAR)
    
    return left_rectified, right_rectified, Q

这个函数的输出,就是我们进行立体匹配所需要的、高质量的校正图像和Q矩阵。

2. 核心算法:SGBM (Semi-Global Block Matching)

现在我们有了对齐的左右图像,接下来就要计算视差了。本项目采用的是 SGBM (半全局块匹配) 算法,它是对传统 BM (块匹配) 算法的巨大改进。

  • 基本思想: 它不仅像 BM 算法那样,通过比较小像素块的相似度(SAD)来寻找匹配,更引入了一个“全局”的视角。它会惩罚那些导致视差剧烈跳变的匹配,鼓励生成一条平滑、连续的视差路径。
  • 优势: 相比 BM,SGBM 能更好地处理弱纹理区域(如白墙)和重复纹理区域,生成的视差图更完整、噪声更少。
代码实现 (processing/stereo_matcher.py)

我们将 SGBM 的所有逻辑都封装在 StereoMatcher 类中。

# processing/stereo_matcher.py
import cv2
import config

class StereoMatcher:
    def __init__(self):
        # 在构造函数中,从 config.py 文件一次性加载所有 SGBM 参数
        # 这使得调参变得非常方便
        self.matcher = cv2.StereoSGBM_create(
            minDisparity=config.SGBM_MIN_DISPARITY,
            numDisparities=config.SGBM_NUM_DISPARITIES,
            blockSize=config.SGBM_BLOCK_SIZE,
            P1=config.SGBM_P1,
            P2=config.SGBM_P2,
            disp12MaxDiff=config.SGBM_DISP12_MAX_DIFF,
            preFilterCap=config.SGBM_PRE_FILTER_CAP,
            uniquenessRatio=config.SGBM_UNIQUENESS_RATIO,
            speckleWindowSize=config.SGBM_SPECKLE_WINDOW_SIZE,
            speckleRange=config.SGBM_SPECKLE_RANGE,
            mode=config.SGBM_MODE
        )

    def compute_disparity(self, left_rectified_img, right_rectified_img):
        # SGBM 算法要求输入单通道的灰度图
        gray_left = cv2.cvtColor(left_rectified_img, cv2.COLOR_BGR2GRAY)
        gray_right = cv2.cvtColor(right_rectified_img, cv2.COLOR_BGR2GRAY)
        
        # 计算视差图
        disparity_map = self.matcher.compute(gray_left, gray_right)
        
        return disparity_map

4. 视差图的秘密:*16 的含义

当你拿到 compute 函数返回的 disparity_map 时,需要注意一个关键细节:它的数据类型是 CV_16S(16位有符号整数),并且里面的值是真实视差的16倍

这是 OpenCV 为了在内部用高效的整数运算来表示亚像素精度而做的设计。一个值为 800 的点,其真实的、以像素为单位的视差是 800 / 16.0 = 50.0

因此,当我们需要使用或显示真实的视差值时,必须记得将它除以16.0

用项目的中的测试图片可以得到以下视差图

总结

通过立体校正的预处理和强大的 SGBM 算法,我们成功地从两张2D图像中提取出了包含深度信息的视差图。更重要的是,我们学会了如何通过交互式调参来优化匹配结果,这是所有立体视觉应用开发者的核心技能之一。

在下一篇文章中,我们将进入一个核心步骤:利用这张高质量的视差图和 Q 矩阵,计算出深度图并重建出真正的三维点云。


网站公告

今日签到

点亮在社区的每一天
去签到