视差计算基础概念与数学原理

视差计算是计算机视觉中立体视觉的核心技术,它通过分析两个或多个摄像机捕获的图像之间的差异来估计场景的深度信息。在开始深入探讨之前,我们首先需要理解视差的基本概念。

视差的定义与几何意义

视差(Parallax)是指由于观察点位置不同而导致的物体位置差异。在立体视觉中,我们通常使用两个摄像机(左眼和右眼)从略微不同的位置观察同一场景。对于场景中的任意一点P,它在左图像和右图像中的投影位置之差就是视差。

从几何角度来看,假设我们有一个标准的立体视觉系统:两个摄像机的光心分别为O1和O2,它们之间的距离(基线)为b,摄像机焦距为f,场景点P到摄像机平面的距离为Z。根据三角测量原理,我们可以推导出视差d与深度Z的关系:

Z = (b * f) / d

其中:

  • Z:物体到摄像机的距离(深度)
  • b:两个摄像机之间的距离(基线长度)
  • f:摄像机的焦距
  • d:视差值(通常以像素为单位)

这个公式告诉我们:视差与深度成反比。视差越大,物体距离摄像机越近;视差越小,物体距离摄像机越远。当物体位于无穷远处时,视差为零。

立体视觉系统的配置

在实际应用中,立体视觉系统主要有两种配置方式:

  1. 平行立体视觉系统:两个摄像机的图像平面完全平行,且它们的光轴相互平行。这是最简单的配置,也是大多数视差计算算法的基础假设。在这种配置下,极线是水平的,搜索匹配点只需要在同一水平线上进行。

  2. 汇聚立体视觉系统:两个摄像机的图像平面相交于某条线,光轴不平行。这种配置更接近人眼的真实情况,但需要额外的校正步骤(极线校正)将图像转换为平行配置,才能应用标准的视差计算算法。

视差计算的核心算法详解

区域匹配算法(Area-based Matching)

区域匹配是最经典的视差计算方法,它通过比较两个图像中局部区域的相似度来寻找对应点。这种方法不需要检测特征点,直接利用像素的灰度信息。

算法步骤

  1. 选择匹配窗口:在左图像中选择一个以参考像素为中心的矩形窗口(通常大小为3×3、5×5或7×7)。
  2. 定义相似度度量:常用的度量包括:
    • SAD(绝对差值和):Σ|I1(x,y) - I2(x+d,y)|
    • SSD(平方差值和):Σ(I1(x,y) - I2(x+d,y))²
    • NCC(归一化互相关):Σ(I1 - μ1)(I2 - μ2) / (σ1 * σ2)
  3. 搜索匹配位置:在右图像中,沿着极线(通常是水平方向)在一定范围内(视差搜索范围)移动匹配窗口,计算每个位置的相似度得分。
  4. 选择最佳匹配:选择相似度得分最高(或最低,取决于度量)的位置作为匹配点,计算视差。

Python实现示例

下面是一个使用Python和OpenCV实现的简单区域匹配算法:

import cv2
import numpy as np

def compute_disparity_sad(left_img, right_img, window_size=5, disparity_range=50):
    """
    使用SAD方法计算视差图
    :param left_img: 左图像(灰度图)
    :param right_img: 右图像(灰度图)
    :param window_size: 匹配窗口大小
    :param disparity_range: 视差搜索范围
    :return: 视差图
    """
    # 确保图像为float类型以提高计算精度
    left = left_img.astype(np.float32)
    right = right_img.astype(np.float32)
    
    # 获取图像尺寸
    h, w = left.shape
    
    # 初始化视差图
    disparity_map = np.zeros((h, w), dtype=np.uint8)
    
    # 计算窗口半径
    radius = window_size // 2
    
    # 遍历左图像中的每个像素(跳过边界)
    for y in range(radius, h - radius):
        for x in range(radius, w - radius):
            # 在左图像中提取匹配窗口
            left_window = left[y-radius:y+radius+1, x-radius:x+radius+1]
            
            best_cost = float('inf')
            best_d = 0
            
            # 在右图像中搜索匹配位置
            for d in range(disparity_range):
                # 计算右图像中对应位置的x坐标
                x_right = x - d
                
                # 检查边界
                if x_right < radius:
                    break
                
                # 在右图像中提取匹配窗口
                right_window = right[y-radius:y+radius+1, x_right-radius:x_right+radius+1]
                
                # 计算SAD成本
                cost = np.sum(np.abs(left_window - right_window))
                
                # 更新最佳匹配
                if cost < best_cost:
                    best_cost = cost
                    best_d = d
            
            # 将最佳视差值存入视差图
            disparity_map[y, x] = best_d
    
    return disparity_map

# 使用示例
# left = cv2.imread('left.png', cv2.IMREAD_GRAYSCALE)
# right = cv2.imread('right.png', cv2.IMREAD_GRAYSCALE)
# disparity = compute_disparity_sad(left, right, window_size=5, disparity_range=64)
# cv2.imwrite('disparity.png', disparity)

特征匹配算法(Feature-based Matching)

特征匹配算法首先检测图像中的显著特征点(如角点、边缘等),然后在另一幅图像中寻找对应的特征点,最后计算视差。这种方法对光照变化和图像噪声具有更强的鲁棒性。

常用特征检测器

  1. Harris角点检测器:通过计算图像梯度的自相关矩阵来检测角点。
  2. SIFT/SURF:尺度不变特征变换,具有尺度和旋转不变性。
  3. ORB:快速特征点检测和描述算法,结合了FAST角点检测和BRIEF描述子,速度快且性能良好。

ORB特征匹配实现

import cv2
import numpy as np

def compute_disparity_orb(left_img, right_img, disparity_range=64):
    """
    使用ORB特征计算视差图
    :param left_img: 左图像
    |param right_img: 右图像
    :param disparity_range: 视差搜索范围
    :return: 视差图
    """
    # 初始化ORB检测器
    orb = cv2.ORB_create()
    
    # 检测关键点和描述子
    kp1, des1 = orb.detectAndCompute(left_img, None)
    kp2, des2 = orb.detectAndCompute(right_img, None)
    
    # 使用BFMatcher进行匹配
    bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
    matches = bf.match(des1, des2)
    
    # 按距离排序
    matches = sorted(matches, key=lambda x: x.distance)
    
    # 初始化视差图
    h, w = left_img.shape
    disparity_map = np.zeros((h, w), dtype=np.uint8)
    
    # 为每个左图像关键点计算视差
    for match in matches:
        # 获取匹配点的坐标
        pt1 = kp1[match.queryIdx].pt
        pt2 = kp2[match.trainIdx].pt
        
        # 计算视差(x方向差值)
        d = abs(pt1[0] - pt2[0])
        
        # 只考虑在视差范围内的匹配
        if d <= disparity_range:
            # 将视差值分配到左图像对应位置
            x, y = int(pt1[0]), int(pt1[1])
            disparity_map[y, x] = d
    
    return disparity_map

# 使用示例
# left = cv2.imread('left.png', cv2.IMREAD_GRAYSCALE)
# right = cv2.imread('right.png', cv2.IMREAD_GRAYSCALE)
# disparity = compute_disparity_orb(left, right)
# cv2.imwrite('disparity_orb.png', disparity)

半全局匹配算法(Semi-Global Matching, SGM)

SGM是一种性能优异的视差计算方法,它通过聚合多个路径上的匹配成本来提高视差图的密度和准确性。SGM在计算效率和匹配质量之间取得了很好的平衡,是目前工业界应用最广泛的算法之一。

SGM算法原理

SGM的核心思想是:对于每个像素和每个可能的视差值,计算一个匹配成本,然后通过动态规划的方法聚合多个方向(通常是8个或16个路径)上的成本,最终选择聚合成本最小的视差值作为结果。

OpenCV中的SGM实现

OpenCV提供了现成的SGM实现,使用非常简单:

import cv2
import numpy as np

def compute_disparity_sgm(left_img, right_img):
    """
    使用OpenCV的SGM算法计算视差图
    :param left_img: 左图像(灰度图)
    :param right_img: 右图像(灰度图)
    :return: 视差图(16位单通道)
    """
    # 创建SGM对象
    stereo = cv2.StereoSGBM_create(
        minDisparity=0,          # 最小视差
        numDisparities=16*5,     # 视差数量(必须是16的倍数)
        blockSize=5,             # 匹配窗口大小
        P1=8*3*5**2,             # 惩罚系数1(相邻视差变化惩罚)
        P2=32*3*5**2,            # 惩罚系数2(视差不连续惩罚)
        disp12MaxDiff=1,         # 视差一致性检查最大差异
        uniquenessRatio=10,      # 唯一性百分比
        speckleWindowSize=100,   # 斑点滤波窗口大小
        speckleRange=32          # 斑点滤波范围
    )
    
    # 计算视差图
    disparity = stereo.compute(left_img, right_img)
    
    # 归一化以便显示
    disparity_normalized = cv2.normalize(disparity, None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)
    
    return disparity_normalized

# 使用示例
# left = cv2.imread('left.png', cv2.IMREAD_GRAYSCALE)
# right = cv2.imread('right.png', cv2.IMREAD_GRAYSCALE)
# disparity = compute_disparity_sgm(left, right)
# cv2.imwrite('disparity_sgm.png', disparity)

视差计算的实战技巧

图像预处理技巧

1. 图像校正(Rectification)

在实际应用中,立体图像对通常需要进行校正,将图像转换为平行配置,这样可以大大简化匹配过程。OpenCV提供了完整的校正流程:

import cv2
import numpy as np

def stereo_rectification(left_img, right_img, camera_matrix_left, dist_coeffs_left, 
                         camera_matrix_right, dist_coeffs_right, R, T):
    """
    立体图像校正
    :param left_img: 左图像
    :param right_img: 右图像
    :param camera_matrix_left: 左相机内参矩阵
    :param dist_coeffs_left: 左相机畸变系数
    :param camera_matrix_right: 右相机内参矩阵
    |param dist_coeffs_right: 右相机畸变系数
    :param R: 旋转矩阵
    :param T: 平移向量
    :return: 校正后的左右图像
    """
    # 获取图像尺寸
    h, w = left_img.shape[:2]
    
    # 计算立体校正变换
    R1, R2, P1, P2, Q, validPixROI1, validPixROI2 = cv2.stereoRectify(
        cameraMatrix1=camera_matrix_left,
        distCoeffs1=dist_coeffs_left,
        cameraMatrix2=camera_matrix_right,
        distCoeffs2=dist_coeffs_right,
        imageSize=(w, h),
        R=R,
        T=T,
        alpha=-1  # 自动调整缩放因子
    )
    
    # 计算映射表
    map1x, map1y = cv2.initUndistortRectifyMap(
        cameraMatrix=camera_matrix_left,
        distCoeffs=dist_coeffs_left,
        R=R1,
        newCameraMatrix=P1,
        size=(w, h),
        m1type=cv2.CV_32FC1
    )
    
    map2x, map2y = cv2.initUndistortRectifyMap(
        cameraMatrix=camera_matrix_right,
        distCoeffs=dist_coeffs_right,
        R=R2,
        newCameraMatrix=P2,
        size=(w, h),
        m1type=cv2.CV_32FC1
    )
    
    # 应用映射进行校正
    left_rectified = cv2.remap(left_img, map1x, map1y, cv2.INTER_LINEAR)
    right_rectified = cv2.remap(right_img, map2x, map2y, cv2.INTER_LINEAR)
    
    return left_rectified, right_rectified, Q

# 使用示例(需要预先标定相机)
# left = cv2.imread('left.png')
# right = cv2.imread('right.png')
# # 假设已通过标定获得以下参数
# K_left = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]])
# dist_left = np.array([k1, k2, p1, p2, k3])
# K_right = np.array([[fx, 0, cx], [0, fy, cy], [0, 1]])
# dist_right = np.array([k1, k2, p1, p2, k3])
# R = np.eye(3)
# T = np.array([-baseline, 0, 0])
# left_rect, right_rect, Q = stereo_rectification(left, right, K_left, dist_left, K_right, dist_right, R, T)

2. 图像增强与滤波

在匹配前对图像进行预处理可以显著提高匹配质量:

def preprocess_images(left, right):
    """
    图像预处理:灰度化、直方图均衡化、高斯滤波
    """
    # 转换为灰度图
    if len(left.shape) == 3:
        left = cv2.cvtColor(left, cv2.COLOR_BGR2GRAY)
        right = cv2.cvtColor(right, cv2.COLOR_BGR2GRAY)
    
    # 直方图均衡化(增强对比度)
    left = cv2.equalizeHist(left)
    right = cv2.equalizeHist(right)
    
    # 高斯滤波去噪
    left = cv2.GaussianBlur(left, (5, 5), 1.5)
    right = cv2.GaussianBlur(right, (5, 1.5), 1.5)
    
    return left, right

视差图后处理技巧

1. 无效值填充(Inpainting)

视差图中常存在无效值(如遮挡区域、低纹理区域),可以使用图像修复技术填充:

def fill_disparity_gaps(disparity_map):
    """
    使用图像修复填充视差图中的空洞
    """
    # 创建掩码:标记无效值(通常为0或负值)
    mask = (disparity_map == 0).astype(np.uint8)
    
    # 使用OpenCV的修复算法
    filled = cv2.inpaint(disparity_map, mask, inpaintRadius=3, flags=cv2.INPAINT_TELEA)
    
    return filled

2. 中值滤波与双边滤波

去除视差图中的噪声和孤立点:

def postprocess_disparity(disparity_map):
    """
    视差图后处理
    """
    # 中值滤波去除椒盐噪声
    filtered = cv2.medianBlur(disparity_map, 5)
    
    # 双边滤波保留边缘
    filtered = cv2.bilateralFilter(filtered, 9, 75, 75)
    
    return filtered

性能优化技巧

1. 视差范围优化

合理设置视差范围可以显著减少计算量:

def estimate_disparity_range(left_img, right_img, baseline, focal_length, min_depth, max_depth):
    """
    根据场景深度范围估计视差范围
    :param baseline: 相机基线长度
    :param focal_length: 相机焦距
    :param min_depth: 最小深度
    |param max_depth: 最大深度
    :return: 最小视差和最大视差
    """
    # 根据公式 d = (b * f) / Z
    max_disparity = (baseline * focal_length) / min_depth
    min_disparity = (baseline * focal_length) / max_depth
    
    return int(min_disparity), int(max_disparity)

2. 多分辨率策略(Coarse-to-Fine)

使用图像金字塔进行多尺度匹配,先粗后精:

def coarse_to_fine_disparity(left_img, right_img, num_levels=3):
    """
    多分辨率视差计算
    """
    # 构建图像金字塔
    left_pyramid = [left_img]
    right_pyramid = [right_img]
    
    for i in range(num_levels-1):
        left_pyramid.append(cv2.pyrDown(left_pyramid[-1]))
        right_pyramid.append(cv2.pyrDown(right_pyramid[-1]))
    
    # 从最粗糙层开始计算
    disparity = None
    for level in reversed(range(num_levels)):
        # 缩放视差到当前层
        if disparity is not None:
            disparity = cv2.resize(disparity, (left_pyramid[level].shape[1], left_pyramid[level].shape[0]))
            disparity = disparity * 2  # 视差随分辨率增加而增加
        
        # 在当前层计算或优化视差
        # 这里可以调用前面的视差计算函数
        # disparity = compute_disparity_sgm(left_pyramid[level], right_pyramid[level])
        
        # 精细化(可选)
        # disparity = refine_disparity(left_pyramid[level], right_pyramid[level], disparity)
    
    return disparity

常见问题解析

问题1:视差计算结果中存在大量空洞(无效值)

原因分析

  • 遮挡区域:物体被其他物体遮挡,导致在右图像中找不到匹配点
  • 低纹理区域:表面缺乏纹理特征,无法可靠匹配
  • 重复纹理:如白墙、地板等,导致错误匹配
  • 透明或反光表面:反射或透射导致匹配困难

解决方案

  1. 使用左右一致性检查
def left_right_consistency_check(left_disparity, right_disparity):
    """
    左右一致性检查,去除不可靠的视差值
    """
    h, w = left_disparity.shape
    consistent_disparity = np.zeros_like(left_disparity)
    
    for y in range(h):
        for x in range(w):
            d = left_disparity[y, x]
            if d > 0:
                # 在右视差图中找到对应位置
                x_right = x - int(d)
                if x_right >= 0:
                    # 检查是否一致
                    if abs(right_disparity[y, x_right] - d) <= 1:
                        consistent_disparity[y, x] = d
    
    return consistent_disparity
  1. 使用视差图填充技术
def fill_disparity_holes(disparity_map, method='nearest'):
    """
    填充视差图中的空洞
    """
    # 创建掩码
    mask = (disparity_map == 0).astype(np.uint8)
    
    if method == 'nearest':
        # 使用最近邻填充
        filled = cv2.inpaint(disparity_map, mask, 3, cv2.INPAINT_TELEA)
    elif method == 'interpolate':
        # 使用插值填充
        from scipy.interpolate import griddata
        # 获取有效点
        valid_mask = disparity_map > 0
        y, x = np.mgrid[0:disparity_map.shape[0], 0:disparity_map.shape[1]]
        points = np.column_stack((x[valid_mask], y[valid_mask]))
        values = disparity_map[valid_mask]
        # 插值
        grid = griddata(points, values, (x, y), method='linear')
        filled = np.nan_to_num(grid).astype(np.uint8)
    
    return filled

问题2:视差图边缘模糊或不准确

原因分析

  • 匹配窗口大小选择不当:窗口太小导致噪声敏感,窗口太大导致边缘模糊
  • 图像校正不精确:极线不水平导致匹配错误
  • 视差不连续区域处理困难:物体边界处视差突变

解决方案

  1. 自适应窗口大小
var def adaptive_window_size(pixel_x, pixel_y, left_img, right_img, max_window=15):
    """
    根据局部纹理复杂度自适应调整窗口大小
    """
    # 计算局部梯度
    roi = left_img[pixel_y-2:pixel_y+3, pixel_x-2:pixel_x+3]
    if roi.shape[0] < 5 or roi.shape[1] < 5:
        return 5
    
    grad_x = cv2.Sobel(roi, cv2.CV_64F, 1, 0, ksize=3)
    grad_y = cv2.Sobel(roi, cv2.CV_64F, 0, 1, ksize=3)
    gradient_magnitude = np.sqrt(grad_x**2 + grad_y**2)
    
    # 根据梯度大小调整窗口
    avg_gradient = np.mean(gradient_magnitude)
    if avg_gradient > 50:  # 高纹理区域
        return 3  # 小窗口保留边缘
    elif avg_gradient < 10:  # 低纹理区域
        return max_window  # 大窗口提高鲁棒性
    else:
        return 7  # 中等窗口
  1. 多方向成本聚合
def multi_direction_aggregation(cost_volume, directions=8):
    """
    多方向成本聚合(SGM的核心思想)
    """
    h, w, num_disparities = cost_volume.shape
    aggregated_cost = np.zeros_like(cost_volume)
    
    # 定义8个方向
    dirs = [(1,0), (0,1), (-1,0), (0,-1), (1,1), (-1,-1), (1,-1), (-1,1)]
    
    for d in range(num_disparities):
        for dy, dx in dirs:
            path_cost = np.zeros((h, w))
            # 沿路径聚合
            # 这里简化实现,实际需要动态规划
            for y in range(h):
                for x in range(w):
                    prev_y, prev_x = y - dy, x - dx
                    if 0 <= prev_y < h and 0 <= prev_x < w:
                        path_cost[y, x] = cost_volume[y, x, d] + path_cost[prev_y, prev_x]
                    else:
                        path_cost[y, x] = cost_volume[y, x, d]
            aggregated_cost[:, :, d] += path_cost
    
    return aggregated_cost

问题3:计算速度慢,无法实时处理

原因分析

  • 视差搜索范围过大
  • 匹配窗口过大
  • 没有利用硬件加速(GPU)
  • 算法复杂度高(如SGM)

解决方案

  1. 使用GPU加速
import cv2
# OpenCV的CUDA模块需要编译时启用CUDA支持
def compute_disparity_gpu(left_img, right_img):
    """
    使用GPU加速的SGM
    """
    # 上传到GPU
    left_gpu = cv2.cuda_GpuMat()
    right_gpu = cv2.cuda_GpuMat()
    left_gpu.upload(left_img)
    right_gpu.upload(right_img)
    
    # 创建CUDA SGM
    stereo = cv2.cuda_StereoSGBM_create(
        minDisparity=0,
        numDisparities=16*5,
        blockSize=5,
        P1=8*3*5**2,
        P2=32*3*5**2
    )
    
    # 计算视差
    disparity_gpu = stereo.compute(left_gpu, right_gpu)
    
    # 下载结果
    disparity = disparity_gpu.download()
    
    return disparity
  1. 使用优化的算法库
# 使用OpenCV的ximgproc模块中的加速算法
import cv2
import numpy as np

def fast_disparity(left_img, right_img):
    """
    使用优化的快速算法
    """
    # 使用快速SGM(如果可用)
    try:
        stereo = cv2.ximgproc.createFastSGM()
        disparity = stereo.compute(left_img, right_img)
        return disparity
    except:
        # 回退到标准SGM
        stereo = cv2.StereoSGBM_create(numDisparities=16*3, blockSize=5)
        return stereo.compute(left_img, right_img)

问题4:视差图在低纹理区域表现差

原因分析

  • 缺乏足够的纹理信息进行可靠匹配
  • 匹配窗口内像素值变化小,导致多个位置相似度相同
  • 噪声干扰导致匹配错误

解决方案

  1. 使用边缘增强
def enhance_texture(left_img, right_img):
    """
    通过拉普拉斯算子增强纹理
    """
    # 转换为浮点型
    left = left_img.astype(np.float32)
    right = right_img.astype(np32)
    
    # 应用拉普拉斯算子
    left_lap = cv2.Laplacian(left, cv2.CV_32F)
    right_lap = cv2.Laplacian(right, cv2.CV_32F)
    
    # 将增强的纹理信息加到原图像
    alpha = 0.5  # 增强强度
    left_enhanced = cv2.addWeighted(left, 1-alpha, left_lap, alpha, 0)
    right_enhanced = cv2.addWeighted(right, 1-alpha, right_lap, alpha, 0)
    
    # 转换回uint8
    left_enhanced = np.clip(left_enhanced, 0, 255).astype(np.uint8)
    right_enhanced = np.clip(right_enhanced, 0, 255).astype(np.uint8)
    
    return left_enhanced, right_enhanced
  1. 使用颜色信息(如果可用)
def compute_disparity_color(left_img, right_img):
    """
    使用彩色图像的多通道信息进行匹配
    """
    if len(left_img.shape) == 2:
        return compute_disparity_sad(left_img, right_img)
    
    # 分离通道
    left_b, left_g, left_r = cv2.split(left_img)
    right_b, right_g, right_r = cv2.split(right_img)
    
    # 分别计算每个通道的视差
    disparity_b = compute_disparity_sad(left_b, right_b)
    disparity_g = compute_disparity_sad(left_g, right_g)
    disparity_r = compute_disparity_sad(left_r, right_r)
    
    # 融合结果(取中值)
    disparity = np.median(np.stack([disparity_b, disparity_g, disparity_r]), axis=0)
    
    return disparity.astype(np.uint8)

问题5:如何评估视差计算结果的质量

评估指标

  1. Bad Pixel Ratio(错误像素率):视差误差超过阈值的像素比例
  2. 均方根误差(RMSE):视差误差的均方根
  3. 视差图可视化:通过伪彩色图观察质量
def evaluate_disparity(gt_disparity, computed_disparity, threshold=1.0):
    """
    评估视差计算质量
    :param gt_disparity: 真实视差图(Ground Truth)
    :param computed_disparity: 计算得到的视差图
    :param threshold: 误差阈值
    :return: 评估指标字典
    """
    # 只在有效区域计算
    valid_mask = gt_disparity > 0
    
    # 计算误差
    error = np.abs(gt_disparity[valid_mask] - computed_disparity[valid_mask])
    
    # Bad Pixel Ratio
    bad_pixels = np.sum(error > threshold)
    total_pixels = np.sum(valid_mask)
    bad_ratio = bad_pixels / total_pixels
    
    # RMSE
    rmse = np.sqrt(np.mean(error**2))
    
    # 平均误差
    mean_error = np.mean(error)
    
    return {
        'bad_pixel_ratio': bad_ratio,
        'rmse': rmse,
        'mean_error': mean_error
    }

def visualize_disparity(disparity_map, title="Disparity"):
    """
    可视化视差图
    """
    # 归一化到0-255
    disp_normalized = cv2.normalize(disparity_map, None, 0, 255, cv2.NORM_MINMAX)
    
    # 应用伪彩色
    disp_color = cv2.applyColorMap(disp_normalized, cv2.COLORMAP_JET)
    
    return disp_color

考试常见考点总结

理论基础题

  1. 视差与深度的关系:必须熟练掌握公式 Z = (b * f) / d,并能解释各参数含义。
  2. 立体视觉系统配置:平行配置与汇聚配置的区别及校正方法。
  3. 匹配成本函数:SAD、SSD、NCC的计算公式及优缺点对比。

算法实现题

  1. 区域匹配算法:能够手写SAD/SSD算法代码,理解窗口大小和视差范围的影响。
  2. SGM算法原理:理解成本聚合、多路径动态规划的思想。
  3. 特征匹配算法:掌握ORB/SIFT特征提取与匹配流程。

实践应用题

  1. 图像校正:掌握OpenCV的stereoRectify和initUndistortRectifyMap的使用。
  2. 参数调优:能够根据场景调整窗口大小、视差范围、惩罚系数等参数。
  3. 后处理技术:左右一致性检查、空洞填充、滤波等。

常见错误分析

  1. 视差范围设置不当:导致远处物体视差为0或近处物体超出范围。
  2. 未进行图像校正:极线不水平导致匹配失败。
  3. 忽略遮挡问题:未使用左右一致性检查导致错误视差。
  4. 窗口大小选择不当:过小导致噪声敏感,过大导致边缘模糊。

总结

视差计算是计算机视觉中的经典问题,掌握其原理和实现技巧对于理解3D视觉至关重要。在实际应用中,需要根据具体场景选择合适的算法和参数,并通过预处理和后处理技术提高结果质量。对于考试而言,重点在于理解基本原理、掌握核心算法实现、熟悉OpenCV相关函数的使用,并能够分析和解决常见问题。

通过本文的详细讲解和代码示例,相信读者已经对视差计算有了全面深入的理解。在实际项目中,建议从简单的区域匹配开始,逐步过渡到SGM等高级算法,并始终重视图像校正和后处理环节。# 视差计算基础概念与数学原理

视差计算是计算机视觉中立体视觉的核心技术,它通过分析两个或多个摄像机捕获的图像之间的差异来估计场景的深度信息。在开始深入探讨之前,我们首先需要理解视差的基本概念。

视差的定义与几何意义

视差(Parallax)是指由于观察点位置不同而导致的物体位置差异。在立体视觉中,我们通常使用两个摄像机(左眼和右眼)从略微不同的位置观察同一场景。对于场景中的任意一点P,它在左图像和右图像中的投影位置之差就是视差。

从几何角度来看,假设我们有一个标准的立体视觉系统:两个摄像机的光心分别为O1和O2,它们之间的距离(基线)为b,摄像机焦距为f,场景点P到摄像机平面的距离为Z。根据三角测量原理,我们可以推导出视差d与深度Z的关系:

Z = (b * f) / d

其中:

  • Z:物体到摄像机的距离(深度)
  • b:两个摄像机之间的距离(基线长度)
  • f:摄像机的焦距
  • d:视差值(通常以像素为单位)

这个公式告诉我们:视差与深度成反比。视差越大,物体距离摄像机越近;视差越小,物体距离摄像机越远。当物体位于无穷远处时,视差为零。

立体视觉系统的配置

在实际应用中,立体视觉系统主要有两种配置方式:

  1. 平行立体视觉系统:两个摄像机的图像平面完全平行,且它们的光轴相互平行。这是最简单的配置,也是大多数视差计算算法的基础假设。在这种配置下,极线是水平的,搜索匹配点只需要在同一水平线上进行。

  2. 汇聚立体视觉系统:两个摄像机的图像平面相交于某条线,光轴不平行。这种配置更接近人眼的真实情况,但需要额外的校正步骤(极线校正)将图像转换为平行配置,才能应用标准的视差计算算法。

视差计算的核心算法详解

区域匹配算法(Area-based Matching)

区域匹配是最经典的视差计算方法,它通过比较两个图像中局部区域的相似度来寻找对应点。这种方法不需要检测特征点,直接利用像素的灰度信息。

算法步骤

  1. 选择匹配窗口:在左图像中选择一个以参考像素为中心的矩形窗口(通常大小为3×3、5×5或7×7)。
  2. 定义相似度度量:常用的度量包括:
    • SAD(绝对差值和):Σ|I1(x,y) - I2(x+d,y)|
    • SSD(平方差值和):Σ(I1(x,y) - I2(x+d,y))²
    • NCC(归一化互相关):Σ(I1 - μ1)(I2 - μ2) / (σ1 * σ2)
  3. 搜索匹配位置:在右图像中,沿着极线(通常是水平方向)在一定范围内(视差搜索范围)移动匹配窗口,计算每个位置的相似度得分。
  4. 选择最佳匹配:选择相似度得分最高(或最低,取决于度量)的位置作为匹配点,计算视差。

Python实现示例

下面是一个使用Python和OpenCV实现的简单区域匹配算法:

import cv2
import numpy as np

def compute_disparity_sad(left_img, right_img, window_size=5, disparity_range=50):
    """
    使用SAD方法计算视差图
    :param left_img: 左图像(灰度图)
    :param right_img: 右图像(灰度图)
    :param window_size: 匹配窗口大小
    :param disparity_range: 视差搜索范围
    :return: 视差图
    """
    # 确保图像为float类型以提高计算精度
    left = left_img.astype(np.float32)
    right = right_img.astype(np.float32)
    
    # 获取图像尺寸
    h, w = left.shape
    
    # 初始化视差图
    disparity_map = np.zeros((h, w), dtype=np.uint8)
    
    # 计算窗口半径
    radius = window_size // 2
    
    # 遍历左图像中的每个像素(跳过边界)
    for y in range(radius, h - radius):
        for x in range(radius, w - radius):
            # 在左图像中提取匹配窗口
            left_window = left[y-radius:y+radius+1, x-radius:x+radius+1]
            
            best_cost = float('inf')
            best_d = 0
            
            # 在右图像中搜索匹配位置
            for d in range(disparity_range):
                # 计算右图像中对应位置的x坐标
                x_right = x - d
                
                # 检查边界
                if x_right < radius:
                    break
                
                # 在右图像中提取匹配窗口
                right_window = right[y-radius:y+radius+1, x_right-radius:x_right+radius+1]
                
                # 计算SAD成本
                cost = np.sum(np.abs(left_window - right_window))
                
                # 更新最佳匹配
                if cost < best_cost:
                    best_cost = cost
                    best_d = d
            
            # 将最佳视差值存入视差图
            disparity_map[y, x] = best_d
    
    return disparity_map

# 使用示例
# left = cv2.imread('left.png', cv2.IMREAD_GRAYSCALE)
# right = cv2.imread('right.png', cv2.IMREAD_GRAYSCALE)
# disparity = compute_disparity_sad(left, right, window_size=5, disparity_range=64)
# cv2.imwrite('disparity.png', disparity)

特征匹配算法(Feature-based Matching)

特征匹配算法首先检测图像中的显著特征点(如角点、边缘等),然后在另一幅图像中寻找对应的特征点,最后计算视差。这种方法对光照变化和图像噪声具有更强的鲁棒性。

常用特征检测器

  1. Harris角点检测器:通过计算图像梯度的自相关矩阵来检测角点。
  2. SIFT/SURF:尺度不变特征变换,具有尺度和旋转不变性。
  3. ORB:快速特征点检测和描述算法,结合了FAST角点检测和BRIEF描述子,速度快且性能良好。

ORB特征匹配实现

import cv2
import numpy as np

def compute_disparity_orb(left_img, right_img, disparity_range=64):
    """
    使用ORB特征计算视差图
    :param left_img: 左图像
    |param right_img: 右图像
    :param disparity_range: 视差搜索范围
    :return: 视差图
    """
    # 初始化ORB检测器
    orb = cv2.ORB_create()
    
    # 检测关键点和描述子
    kp1, des1 = orb.detectAndCompute(left_img, None)
    kp2, des2 = orb.detectAndCompute(right_img, None)
    
    # 使用BFMatcher进行匹配
    bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
    matches = bf.match(des1, des2)
    
    # 按距离排序
    matches = sorted(matches, key=lambda x: x.distance)
    
    # 初始化视差图
    h, w = left_img.shape
    disparity_map = np.zeros((h, w), dtype=np.uint8)
    
    # 为每个左图像关键点计算视差
    for match in matches:
        # 获取匹配点的坐标
        pt1 = kp1[match.queryIdx].pt
        pt2 = kp2[match.trainIdx].pt
        
        # 计算视差(x方向差值)
        d = abs(pt1[0] - pt2[0])
        
        # 只考虑在视差范围内的匹配
        if d <= disparity_range:
            # 将视差值分配到左图像对应位置
            x, y = int(pt1[0]), int(pt1[1])
            disparity_map[y, x] = d
    
    return disparity_map

# 使用示例
# left = cv2.imread('left.png', cv2.IMREAD_GRAYSCALE)
# right = cv2.imread('right.png', cv2.IMREAD_GRAYSCALE)
# disparity = compute_disparity_orb(left, right)
# cv2.imwrite('disparity_orb.png', disparity)

半全局匹配算法(Semi-Global Matching, SGM)

SGM是一种性能优异的视差计算方法,它通过聚合多个路径上的匹配成本来提高视差图的密度和准确性。SGM在计算效率和匹配质量之间取得了很好的平衡,是目前工业界应用最广泛的算法之一。

SGM算法原理

SGM的核心思想是:对于每个像素和每个可能的视差值,计算一个匹配成本,然后通过动态规划的方法聚合多个方向(通常是8个或16个路径)上的成本,最终选择聚合成本最小的视差值作为结果。

OpenCV中的SGM实现

OpenCV提供了现成的SGM实现,使用非常简单:

import cv2
import numpy as np

def compute_disparity_sgm(left_img, right_img):
    """
    使用OpenCV的SGM算法计算视差图
    :param left_img: 左图像(灰度图)
    :param right_img: 右图像(灰度图)
    :return: 视差图(16位单通道)
    """
    # 创建SGM对象
    stereo = cv2.StereoSGBM_create(
        minDisparity=0,          # 最小视差
        numDisparities=16*5,     # 视差数量(必须是16的倍数)
        blockSize=5,             # 匹配窗口大小
        P1=8*3*5**2,             # 惩罚系数1(相邻视差变化惩罚)
        P2=32*3*5**2,            # 惩罚系数2(视差不连续惩罚)
        disp12MaxDiff=1,         # 视差一致性检查最大差异
        uniquenessRatio=10,      # 唯一性百分比
        speckleWindowSize=100,   # 斑点滤波窗口大小
        speckleRange=32          # 斑点滤波范围
    )
    
    # 计算视差图
    disparity = stereo.compute(left_img, right_img)
    
    # 归一化以便显示
    disparity_normalized = cv2.normalize(disparity, None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)
    
    return disparity_normalized

# 使用示例
# left = cv2.imread('left.png', cv2.IMREAD_GRAYSCALE)
# right = cv2.imread('right.png', cv2.IMREAD_GRAYSCALE)
# disparity = compute_disparity_sgm(left, right)
# cv2.imwrite('disparity_sgm.png', disparity)

视差计算的实战技巧

图像预处理技巧

1. 图像校正(Rectification)

在实际应用中,立体图像对通常需要进行校正,将图像转换为平行配置,这样可以大大简化匹配过程。OpenCV提供了完整的校正流程:

import cv2
import numpy as np

def stereo_rectification(left_img, right_img, camera_matrix_left, dist_coeffs_left, 
                         camera_matrix_right, dist_coeffs_right, R, T):
    """
    立体图像校正
    :param left_img: 左图像
    :param right_img: 右图像
    :param camera_matrix_left: 左相机内参矩阵
    :param dist_coeffs_left: 左相机畸变系数
    :param camera_matrix_right: 右相机内参矩阵
    |param dist_coeffs_right: 右相机畸变系数
    :param R: 旋转矩阵
    :param T: 平移向量
    :return: 校正后的左右图像
    """
    # 获取图像尺寸
    h, w = left_img.shape[:2]
    
    # 计算立体校正变换
    R1, R2, P1, P2, Q, validPixROI1, validPixROI2 = cv2.stereoRectify(
        cameraMatrix1=camera_matrix_left,
        distCoeffs1=dist_coeffs_left,
        cameraMatrix2=camera_matrix_right,
        distCoeffs2=dist_coeffs_right,
        imageSize=(w, h),
        R=R,
        T=T,
        alpha=-1  # 自动调整缩放因子
    )
    
    # 计算映射表
    map1x, map1y = cv2.initUndistortRectifyMap(
        cameraMatrix=camera_matrix_left,
        distCoeffs=dist_coeffs_left,
        R=R1,
        newCameraMatrix=P1,
        size=(w, h),
        m1type=cv2.CV_32FC1
    )
    
    map2x, map2y = cv2.initUndistortRectifyMap(
        cameraMatrix=camera_matrix_right,
        distCoeffs=dist_coeffs_right,
        R=R2,
        newCameraMatrix=P2,
        size=(w, h),
        m1type=cv2.CV_32FC1
    )
    
    # 应用映射进行校正
    left_rectified = cv2.remap(left_img, map1x, map1y, cv2.INTER_LINEAR)
    right_rectified = cv2.remap(right_img, map2x, map2y, cv2.INTER_LINEAR)
    
    return left_rectified, right_rectified, Q

# 使用示例(需要预先标定相机)
# left = cv2.imread('left.png')
# right = cv2.imread('right.png')
# # 假设已通过标定获得以下参数
# K_left = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]])
# dist_left = np.array([k1, k2, p1, p2, k3])
# K_right = np.array([[fx, 0, cx], [0, fy, cy], [0, 1]])
# dist_right = np.array([k1, k2, p1, p2, k3])
# R = np.eye(3)
# T = np.array([-baseline, 0, 0])
# left_rect, right_rect, Q = stereo_rectification(left, right, K_left, dist_left, K_right, dist_right, R, T)

2. 图像增强与滤波

在匹配前对图像进行预处理可以显著提高匹配质量:

def preprocess_images(left, right):
    """
    图像预处理:灰度化、直方图均衡化、高斯滤波
    """
    # 转换为灰度图
    if len(left.shape) == 3:
        left = cv2.cvtColor(left, cv2.COLOR_BGR2GRAY)
        right = cv2.cvtColor(right, cv2.COLOR_BGR2GRAY)
    
    # 直方图均衡化(增强对比度)
    left = cv2.equalizeHist(left)
    right = cv2.equalizeHist(right)
    
    # 高斯滤波去噪
    left = cv2.GaussianBlur(left, (5, 5), 1.5)
    right = cv2.GaussianBlur(right, (5, 5), 1.5)
    
    return left, right

视差图后处理技巧

1. 无效值填充(Inpainting)

视差图中常存在无效值(如遮挡区域、低纹理区域),可以使用图像修复技术填充:

def fill_disparity_gaps(disparity_map):
    """
    使用图像修复填充视差图中的空洞
    """
    # 创建掩码:标记无效值(通常为0或负值)
    mask = (disparity_map == 0).astype(np.uint8)
    
    # 使用OpenCV的修复算法
    filled = cv2.inpaint(disparity_map, mask, inpaintRadius=3, flags=cv2.INPAINT_TELEA)
    
    return filled

2. 中值滤波与双边滤波

去除视差图中的噪声和孤立点:

def postprocess_disparity(disparity_map):
    """
    视差图后处理
    """
    # 中值滤波去除椒盐噪声
    filtered = cv2.medianBlur(disparity_map, 5)
    
    # 双边滤波保留边缘
    filtered = cv2.bilateralFilter(filtered, 9, 75, 75)
    
    return filtered

性能优化技巧

1. 视差范围优化

合理设置视差范围可以显著减少计算量:

def estimate_disparity_range(left_img, right_img, baseline, focal_length, min_depth, max_depth):
    """
    根据场景深度范围估计视差范围
    :param baseline: 相机基线长度
    :param focal_length: 相机焦距
    :param min_depth: 最小深度
    |param max_depth: 最大深度
    :return: 最小视差和最大视差
    """
    # 根据公式 d = (b * f) / Z
    max_disparity = (baseline * focal_length) / min_depth
    min_disparity = (baseline * focal_length) / max_depth
    
    return int(min_disparity), int(max_disparity)

2. 多分辨率策略(Coarse-to-Fine)

使用图像金字塔进行多尺度匹配,先粗后精:

def coarse_to_fine_disparity(left_img, right_img, num_levels=3):
    """
    多分辨率视差计算
    """
    # 构建图像金字塔
    left_pyramid = [left_img]
    right_pyramid = [right_img]
    
    for i in range(num_levels-1):
        left_pyramid.append(cv2.pyrDown(left_pyramid[-1]))
        right_pyramid.append(cv2.pyrDown(right_pyramid[-1]))
    
    # 从最粗糙层开始计算
    disparity = None
    for level in reversed(range(num_levels)):
        # 缩放视差到当前层
        if disparity is not None:
            disparity = cv2.resize(disparity, (left_pyramid[level].shape[1], left_pyramid[level].shape[0]))
            disparity = disparity * 2  # 视差随分辨率增加而增加
        
        # 在当前层计算或优化视差
        # 这里可以调用前面的视差计算函数
        # disparity = compute_disparity_sgm(left_pyramid[level], right_pyramid[level])
        
        # 精细化(可选)
        # disparity = refine_disparity(left_pyramid[level], right_pyramid[level], disparity)
    
    return disparity

常见问题解析

问题1:视差计算结果中存在大量空洞(无效值)

原因分析

  • 遮挡区域:物体被其他物体遮挡,导致在右图像中找不到匹配点
  • 低纹理区域:表面缺乏纹理特征,无法可靠匹配
  • 重复纹理:如白墙、地板等,导致错误匹配
  • 透明或反光表面:反射或透射导致匹配困难

解决方案

  1. 使用左右一致性检查
def left_right_consistency_check(left_disparity, right_disparity):
    """
    左右一致性检查,去除不可靠的视差值
    """
    h, w = left_disparity.shape
    consistent_disparity = np.zeros_like(left_disparity)
    
    for y in range(h):
        for x in range(w):
            d = left_disparity[y, x]
            if d > 0:
                # 在右视差图中找到对应位置
                x_right = x - int(d)
                if x_right >= 0:
                    # 检查是否一致
                    if abs(right_disparity[y, x_right] - d) <= 1:
                        consistent_disparity[y, x] = d
    
    return consistent_disparity
  1. 使用视差图填充技术
def fill_disparity_holes(disparity_map, method='nearest'):
    """
    填充视差图中的空洞
    """
    # 创建掩码
    mask = (disparity_map == 0).astype(np.uint8)
    
    if method == 'nearest':
        # 使用最近邻填充
        filled = cv2.inpaint(disparity_map, mask, 3, cv2.INPAINT_TELEA)
    elif method == 'interpolate':
        # 使用插值填充
        from scipy.interpolate import griddata
        # 获取有效点
        valid_mask = disparity_map > 0
        y, x = np.mgrid[0:disparity_map.shape[0], 0:disparity_map.shape[1]]
        points = np.column_stack((x[valid_mask], y[valid_mask]))
        values = disparity_map[valid_mask]
        # 插值
        grid = griddata(points, values, (x, y), method='linear')
        filled = np.nan_to_num(grid).astype(np.uint8)
    
    return filled

问题2:视差图边缘模糊或不准确

原因分析

  • 匹配窗口大小选择不当:窗口太小导致噪声敏感,窗口太大导致边缘模糊
  • 图像校正不精确:极线不水平导致匹配错误
  • 视差不连续区域处理困难:物体边界处视差突变

解决方案

  1. 自适应窗口大小
var def adaptive_window_size(pixel_x, pixel_y, left_img, right_img, max_window=15):
    """
    根据局部纹理复杂度自适应调整窗口大小
    """
    # 计算局部梯度
    roi = left_img[pixel_y-2:pixel_y+3, pixel_x-2:pixel_x+3]
    if roi.shape[0] < 5 or roi.shape[1] < 5:
        return 5
    
    grad_x = cv2.Sobel(roi, cv2.CV_64F, 1, 0, ksize=3)
    grad_y = cv2.Sobel(roi, cv2.CV_64F, 0, 1, ksize=3)
    gradient_magnitude = np.sqrt(grad_x**2 + grad_y**2)
    
    # 根据梯度大小调整窗口
    avg_gradient = np.mean(gradient_magnitude)
    if avg_gradient > 50:  # 高纹理区域
        return 3  # 小窗口保留边缘
    elif avg_gradient < 10:  # 低纹理区域
        return max_window  # 大窗口提高鲁棒性
    else:
        return 7  # 中等窗口
  1. 多方向成本聚合
def multi_direction_aggregation(cost_volume, directions=8):
    """
    多方向成本聚合(SGM的核心思想)
    """
    h, w, num_disparities = cost_volume.shape
    aggregated_cost = np.zeros_like(cost_volume)
    
    # 定义8个方向
    dirs = [(1,0), (0,1), (-1,0), (0,-1), (1,1), (-1,-1), (1,-1), (-1,1)]
    
    for d in range(num_disparities):
        for dy, dx in dirs:
            path_cost = np.zeros((h, w))
            # 沿路径聚合
            # 这里简化实现,实际需要动态规划
            for y in range(h):
                for x in range(w):
                    prev_y, prev_x = y - dy, x - dx
                    if 0 <= prev_y < h and 0 <= prev_x < w:
                        path_cost[y, x] = cost_volume[y, x, d] + path_cost[prev_y, prev_x]
                    else:
                        path_cost[y, x] = cost_volume[y, x, d]
            aggregated_cost[:, :, d] += path_cost
    
    return aggregated_cost

问题3:计算速度慢,无法实时处理

原因分析

  • 视差搜索范围过大
  • 匹配窗口过大
  • 没有利用硬件加速(GPU)
  • 算法复杂度高(如SGM)

解决方案

  1. 使用GPU加速
import cv2
# OpenCV的CUDA模块需要编译时启用CUDA支持
def compute_disparity_gpu(left_img, right_img):
    """
    使用GPU加速的SGM
    """
    # 上传到GPU
    left_gpu = cv2.cuda_GpuMat()
    right_gpu = cv2.cuda_GpuMat()
    left_gpu.upload(left_img)
    right_gpu.upload(right_img)
    
    # 创建CUDA SGM
    stereo = cv2.cuda_StereoSGBM_create(
        minDisparity=0,
        numDisparities=16*5,
        blockSize=5,
        P1=8*3*5**2,
        P2=32*3*5**2
    )
    
    # 计算视差
    disparity_gpu = stereo.compute(left_gpu, right_gpu)
    
    # 下载结果
    disparity = disparity_gpu.download()
    
    return disparity
  1. 使用优化的算法库
# 使用OpenCV的ximgproc模块中的加速算法
import cv2
import numpy as np

def fast_disparity(left_img, right_img):
    """
    使用优化的快速算法
    """
    # 使用快速SGM(如果可用)
    try:
        stereo = cv2.ximgproc.createFastSGM()
        disparity = stereo.compute(left_img, right_img)
        return disparity
    except:
        # 回退到标准SGM
        stereo = cv2.StereoSGBM_create(numDisparities=16*3, blockSize=5)
        return stereo.compute(left_img, right_img)

问题4:视差图在低纹理区域表现差

原因分析

  • 缺乏足够的纹理信息进行可靠匹配
  • 匹配窗口内像素值变化小,导致多个位置相似度相同
  • 噪声干扰导致匹配错误

解决方案

  1. 使用边缘增强
def enhance_texture(left_img, right_img):
    """
    通过拉普拉斯算子增强纹理
    """
    # 转换为浮点型
    left = left_img.astype(np.float32)
    right = right_img.astype(np32)
    
    # 应用拉普拉斯算子
    left_lap = cv2.Laplacian(left, cv2.CV_32F)
    right_lap = cv2.Laplacian(right, cv2.CV_32F)
    
    # 将增强的纹理信息加到原图像
    alpha = 0.5  # 增强强度
    left_enhanced = cv2.addWeighted(left, 1-alpha, left_lap, alpha, 0)
    right_enhanced = cv2.addWeighted(right, 1-alpha, right_lap, alpha, 0)
    
    # 转换回uint8
    left_enhanced = np.clip(left_enhanced, 0, 255).astype(np.uint8)
    right_enhanced = np.clip(right_enhanced, 0, 255).astype(np.uint8)
    
    return left_enhanced, right_enhanced
  1. 使用颜色信息(如果可用)
def compute_disparity_color(left_img, right_img):
    """
    使用彩色图像的多通道信息进行匹配
    """
    if len(left_img.shape) == 2:
        return compute_disparity_sad(left_img, right_img)
    
    # 分离通道
    left_b, left_g, left_r = cv2.split(left_img)
    right_b, right_g, right_r = cv2.split(right_img)
    
    # 分别计算每个通道的视差
    disparity_b = compute_disparity_sad(left_b, right_b)
    disparity_g = compute_disparity_sad(left_g, right_g)
    disparity_r = compute_disparity_sad(left_r, right_r)
    
    # 融合结果(取中值)
    disparity = np.median(np.stack([disparity_b, disparity_g, disparity_r]), axis=0)
    
    return disparity.astype(np.uint8)

问题5:如何评估视差计算结果的质量

评估指标

  1. Bad Pixel Ratio(错误像素率):视差误差超过阈值的像素比例
  2. 均方根误差(RMSE):视差误差的均方根
  3. 视差图可视化:通过伪彩色图观察质量
def evaluate_disparity(gt_disparity, computed_disparity, threshold=1.0):
    """
    评估视差计算质量
    :param gt_disparity: 真实视差图(Ground Truth)
    :param computed_disparity: 计算得到的视差图
    :param threshold: 误差阈值
    :return: 评估指标字典
    """
    # 只在有效区域计算
    valid_mask = gt_disparity > 0
    
    # 计算误差
    error = np.abs(gt_disparity[valid_mask] - computed_disparity[valid_mask])
    
    # Bad Pixel Ratio
    bad_pixels = np.sum(error > threshold)
    total_pixels = np.sum(valid_mask)
    bad_ratio = bad_pixels / total_pixels
    
    # RMSE
    rmse = np.sqrt(np.mean(error**2))
    
    # 平均误差
    mean_error = np.mean(error)
    
    return {
        'bad_pixel_ratio': bad_ratio,
        'rmse': rmse,
        'mean_error': mean_error
    }

def visualize_disparity(disparity_map, title="Disparity"):
    """
    可视化视差图
    """
    # 归一化到0-255
    disp_normalized = cv2.normalize(disparity_map, None, 0, 255, cv2.NORM_MINMAX)
    
    # 应用伪彩色
    disp_color = cv2.applyColorMap(disp_normalized, cv2.COLORMAP_JET)
    
    return disp_color

考试常见考点总结

理论基础题

  1. 视差与深度的关系:必须熟练掌握公式 Z = (b * f) / d,并能解释各参数含义。
  2. 立体视觉系统配置:平行配置与汇聚配置的区别及校正方法。
  3. 匹配成本函数:SAD、SSD、NCC的计算公式及优缺点对比。

算法实现题

  1. 区域匹配算法:能够手写SAD/SSD算法代码,理解窗口大小和视差范围的影响。
  2. SGM算法原理:理解成本聚合、多路径动态规划的思想。
  3. 特征匹配算法:掌握ORB/SIFT特征提取与匹配流程。

实践应用题

  1. 图像校正:掌握OpenCV的stereoRectify和initUndistortRectifyMap的使用。
  2. 参数调优:能够根据场景调整窗口大小、视差范围、惩罚系数等参数。
  3. 后处理技术:左右一致性检查、空洞填充、滤波等。

常见错误分析

  1. 视差范围设置不当:导致远处物体视差为0或近处物体超出范围。
  2. 未进行图像校正:极线不水平导致匹配失败。
  3. 忽略遮挡问题:未使用左右一致性检查导致错误视差。
  4. 窗口大小选择不当:过小导致噪声敏感,过大导致边缘模糊。

总结

视差计算是计算机视觉中的经典问题,掌握其原理和实现技巧对于理解3D视觉至关重要。在实际应用中,需要根据具体场景选择合适的算法和参数,并通过预处理和后处理技术提高结果质量。对于考试而言,重点在于理解基本原理、掌握核心算法实现、熟悉OpenCV相关函数的使用,并能够分析和解决常见问题。

通过本文的详细讲解和代码示例,相信读者已经对视差计算有了全面深入的理解。在实际项目中,建议从简单的区域匹配开始,逐步过渡到SGM等高级算法,并始终重视图像校正和后处理环节。