Skip to content

第十九章 · 立体视觉与深度估计

19.1 双目视觉基础

19.1.1 三角测距原理

     相机1(O1)          相机2(O2)
        \                  /
         \ d1           d2/
          \              /
           ◇------------◇  场景点 P
           |<------ b ---->|
                基线

深度 Z = (f × b) / disparity
  • f: 焦距(像素)
  • b: 基线长度(两相机间距)
  • d: 视差(左图 x1 - 右图 x2)

19.1.2 双目系统要求

要求说明
标定两个相机分别标定 + 双目标定
极线校正两图像共面(行对齐)
匹配同名点在极线上寻找对应

19.2 双目标定与极线校正

python
import cv2
import numpy as np

# ---- 1. 分别标定两个相机 ----
ret_L, K_L, D_L, rvecs_L, tvecs_L = cv2.calibrateCamera(...)
ret_R, K_R, D_R, rvecs_R, tvecs_R = cv2.calibrateCamera(...)

# ---- 2. 双目标定 ----
# stereo_calib_data: (obj_points, img_points_L, img_points_R)
# 每对图像中,两相机看到的角点索引相同

stereo_flags = (cv2.CALIB_FIX_INTRINSIC |  # 固定内参
                cv2.CALIB_USE_INTRINSIC_GUESS)

ret, K1, D1, K2, D2, R, T, E, F = cv2.stereoCalibrate(
    obj_points_list, img_points_L, img_points_R,
    K_L, D_L, K_R, D_R,
    image_size, flags=stereo_flags
)

# 返回值:
# R: 旋转矩阵(相机2相对相机1的旋转)
# T: 平移向量(基线)
# E: 本质矩阵
# F: 基础矩阵

# ---- 3. 极线校正 ----
# 计算校正映射表
R1, R2, P1, P2, Q, roi1, roi2 = cv2.stereoRectify(
    K1, D1, K2, D2, image_size, R, T, flags=cv2.CALIB_ZERO_DISPARITY
)

# P1, P2: 校正后的投影矩阵(3×4)
# Q: 重投影矩阵(深度→3D)
# roi1, roi2: 有效区域

# ---- 4. 初始化校正映射 ----
map1x, map1y = cv2.initUndistortRectifyMap(K1, D1, R1, P1, image_size, cv2.CV_32FC1)
map2x, map2y = cv2.initUndistortRectifyMap(K2, D2, R2, P2, image_size, cv2.CV_32FC1)

# 应用校正
left_rect = cv2.remap(left_img, map1x, map1y, cv2.INTER_LINEAR)
right_rect = cv2.remap(right_img, map2x, map2y, cv2.INTER_LINEAR)

19.2.1 极线校正可视化

python
def visualize_epipolar_lines(left_img, right_img, map1x, map1y, map2x, map2y):
    """可视化极线对齐效果"""
    # 左图随机采样点
    n_points = 100
    h, w = left_img.shape[:2]
    ys = np.random.randint(0, h, n_points)
    xs = np.random.randint(0, w, n_points)

    # 应用校正映射到采样点
    rect_left_x = map1x[ys, xs].astype(int)
    rect_left_y = map1y[ys, xs].astype(int)

    # 绘制极线(水平线)
    img_vis = left_img.copy()
    for x, y in zip(rect_left_x, rect_left_y):
        cv2.line(img_vis, (x, y), (w-1, y), (0, 255, 0), 1)

    cv2.imshow('Epipolar Lines', img_vis)
    # 校正后,同名点应在同一行上

19.3 立体匹配(Stereo Matching)

19.3.1 SGBM(Semi-Global Block Matching)

python
# ---- SGBM 立体匹配 ----
stereo = cv2.StereoSGBM_create(
    minDisparity=0,         # 最小视差
    numDisparities=16 * 16, # 视差范围(必须是 16 的倍数)
    blockSize=5,            # 块大小
    P1=8 * 3 * 5 * 5,      # 平滑项系数1
    P2=32 * 3 * 5 * 5,     # 平滑项系数2(P2 > P1)
    disp12MaxDiff=1,        # 左右一致性检查最大差值
    preFilterCap=63,        # 预处理截断值
    uniquenessRatio=10,     # 唯一性阈值
    speckleWindowSize=100,  # 视差变化窗口
    speckleRange=32         # 视差变化范围
)

disparity = stereo.compute(left_rect, right_rect)
# disparity: CV_16S 格式 — 值 = 真实视差 × 16
disparity_f = disparity.astype(np.float32) / 16.0

# 可视化
disparity_norm = cv2.normalize(disparity_f, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8UC1)
disparity_color = cv2.applyColorMap(disparity_norm.astype(np.uint8), cv2.COLORMAP_JET)

19.3.2 BM(Block Matching)

python
# ---- BM 立体匹配(快速但精度较低)----
stereo_bm = cv2.StereoBM_create(
    numDisparities=16 * 16,
    blockSize=15
)
disparity_bm = stereo_bm.compute(left_rect, right_rect)

19.3.3 SGBM vs BM 对比

特性SGBMBM
精度★★★★★★★★
速度⚡⚡⚡⚡⚡
内存
深度质量高(全局优化)
推荐离线/高精度实时/嵌入式

19.4 视差图 → 深度图

19.4.1 使用 Q 矩阵重投影

python
# Q 矩阵在 stereoRectify 中计算
# Q = | 1  0  0 -cx |
#     | 0  1  0 -cy |
#     | 0  0  0  f  |
#     | 0  0 -1/T  0 |

# 视差图 → 3D 点云
# 方法1: convertMaps + reprojectImageTo3D
xyz = cv2.reprojectImageTo3D(disparity, Q, handleMissingValues=True)
# xyz[y, x] = (X, Y, Z) — 相对于左相机坐标系

# 方法2: 手动计算
f = P1[0, 0]  # 焦距
T = -T[0]     # 基线(取绝对值)
disparity_valid = np.where(disparity_f > 0, disparity_f, 1)  # 避免除零
depth = (f * T) / disparity_valid

19.4.2 3D 点云可视化

python
# 将深度图转为 3D 点云
def disparity_to_pointcloud(disparity, Q):
    """视差图 → 3D 点云"""
    xyz = cv2.reprojectImageTo3D(disparity, Q)
    h, w = disparity.shape[:2]

    points = []
    colors = []
    for y in range(h):
        for x in range(w):
            d = disparity[y, x]
            if d > 0:  # 有效深度
                points.append([xyz[y, x, 0], xyz[y, x, 1], xyz[y, x, 2]])
                # colors.append(img_gray[y, x])  # 可附加颜色
    return np.array(points)

pointcloud = disparity_to_pointcloud(disparity_f, Q)
print(f"点云数量: {len(pointcloud)}")
# 输出: (N, 3) 的数组

19.5 立体深度估计管线总结

左图像 + 右图像(已标定、已校正)

立体匹配 (SGBM/BM) → 视差图

Q 矩阵 → 深度/3D坐标

3D 点云 / 深度图

19.6 双目视觉速查表

步骤函数输出
分别标定calibrateCameraK, D
双目标定stereoCalibrateR, T, E, F
极线校正stereoRectifyR1, R2, P1, P2, Q
初始化映射initUndistortRectifyMapmap1x, map1y
极线校正remaprectified images
立体匹配StereoSGBM_compute视差图
视差→3DreprojectImageTo3DXYZ 点云