1. 项目背景与核心价值

双目视觉系统在机器人导航、三维重建、自动驾驶等领域有着广泛应用。但想要获得准确的深度信息,相机标定和极线校正是绕不开的两大技术门槛。最近我在开发一个基于双目相机的物体测距系统时,花了整整两周时间才把整套流程跑通。今天就把这些实战经验整理成文,手把手带你用Python+OpenCV实现从标定到校正的全流程。

相比单目相机,双目系统的优势在于可以通过视差计算深度。但前提是必须精确知道两个相机之间的相对位置关系(标定),并确保左右图像的极线水平对齐(校正)。这两个步骤中任何一个出现偏差,都会导致后续的立体匹配和深度计算产生严重误差。

2. 硬件准备与环境配置

2.1 设备选型建议

我使用的是两个罗技C920摄像头搭建的平行双目系统,选择理由如下:

  • 相同型号确保成像特性一致
  • 支持手动对焦和曝光锁定
  • 1080P分辨率满足精度需求
  • USB接口即插即用

实测中发现不同品牌摄像头组合会出现白平衡不一致的问题,建议使用同型号相机。如果预算充足,工业级双目相机(如ZED系列)是更好的选择。

2.2 Python环境搭建

推荐使用conda创建独立环境:

conda create -n stereo python=3.8
conda activate stereo
pip install opencv-contrib-python==4.5.5.64 numpy matplotlib

特别注意:

  • 必须安装opencv-contrib版本(包含额外模块)
  • 4.5.5版本经测试稳定性最佳
  • matplotlib用于可视化标定结果

3. 双目相机标定全流程

3.1 标定板选择与拍摄技巧

使用7x9的棋盘格标定板(OpenCV推荐):

  • 每个方格边长建议2-3cm(根据拍摄距离调整)
  • 打印时用卡纸裱糊保证平整度
  • 拍摄时保持标定板占画面1/3以上面积

拍摄注意事项:

  1. 左右相机同步拍摄(可用多线程控制)
  2. 标定板需呈现不同角度和位置
  3. 至少采集20组有效图像(我用了35组)
  4. 避免强光直射导致反光

3.2 标定参数详解与代码实现

核心参数说明:

  • 相机矩阵(cameraMatrix):包含焦距(fx,fy)和光心(cx,cy)
  • 畸变系数(distCoeffs):k1,k2径向畸变 + p1,p2切向畸变
  • 本征矩阵(E):描述左右相机相对旋转和平移

标定核心代码:

def stereo_calibrate(left_paths, right_paths, pattern_size=(8,6)):
    # 准备对象点 (0,0,0), (1,0,0), ..., (7,5,0)
    objp = np.zeros((pattern_size[0]*pattern_size[1],3), np.float32)
    objp[:,:2] = np.mgrid[0:pattern_size[0],0:pattern_size[1]].T.reshape(-1,2)
    
    # 检测角点
    objpoints = []
    imgpoints_l, imgpoints_r = [], []
    
    for l_path, r_path in zip(left_paths, right_paths):
        img_l = cv2.imread(l_path)
        img_r = cv2.imread(r_path)
        
        gray_l = cv2.cvtColor(img_l, cv2.COLOR_BGR2GRAY)
        gray_r = cv2.cvtColor(img_r, cv2.COLOR_BGR2GRAY)
        
        ret_l, corners_l = cv2.findChessboardCorners(gray_l, pattern_size, None)
        ret_r, corners_r = cv2.findChessboardCorners(gray_r, pattern_size, None)
        
        if ret_l and ret_r:
            objpoints.append(objp)
            imgpoints_l.append(corners_l)
            imgpoints_r.append(corners_r)
    
    # 执行标定
    ret, K1, D1, K2, D2, R, T, E, F = cv2.stereoCalibrate(
        objpoints, imgpoints_l, imgpoints_r,
        None, None, None, None,
        gray_l.shape[::-1], flags=cv2.CALIB_FIX_INTRINSIC)
    
    return K1, D1, K2, D2, R, T

3.3 标定结果验证技巧

验证标定质量的三个关键指标:

  1. 重投影误差应<0.5像素
  2. 旋转矩阵R的行列式应接近1(|det(R)-1|<1e-6)
  3. 平移向量T的单位向量方向应符合实际安装位置

可视化检查方法:

# 绘制校正前的极线
def draw_epilines(img1, img2, lines, pts1, pts2):
    r,c = img1.shape[:2]
    img1 = cv2.cvtColor(img1, cv2.COLOR_GRAY2BGR)
    img2 = cv2.cvtColor(img2, cv2.COLOR_GRAY2BGR)
    
    for r,pt1,pt2 in zip(lines, pts1, pts2):
        color = tuple(np.random.randint(0,255,3).tolist())
        x0,y0 = map(int, [0, -r[2]/r[1]])
        x1,y1 = map(int, [c, -(r[2]+r[0]*c)/r[1]])
        cv2.line(img1, (x0,y0), (x1,y1), color, 1)
        cv2.circle(img1, tuple(pt1), 5, color, -1)
        cv2.circle(img2, tuple(pt2), 5, color, -1)
    return img1, img2

4. 极线校正实战详解

4.1 校正原理剖析

极线校正的目标是:

  1. 使左右图像的极线水平对齐
  2. 保证同一扫描线上的像素行号一致
  3. 最小化图像变形

OpenCV提供两种校正方法:

  • Hartley方法(非标定)
  • Bouguet方法(基于标定参数,效果更好)

4.2 Bouguet校正实现

核心步骤:

  1. 计算左右相机的校正旋转矩阵R1, R2
  2. 计算投影矩阵P1, P2
  3. 生成映射表用于remap

代码实现:

def stereo_rectify(K1, D1, K2, D2, R, T, image_size):
    # 计算校正变换
    R1, R2, P1, P2, Q, _, _ = cv2.stereoRectify(
        K1, D1, K2, D2, image_size, R, T,
        flags=cv2.CALIB_ZERO_DISPARITY, alpha=0.9)
    
    # 生成remap映射
    map1_l, map2_l = cv2.initUndistortRectifyMap(
        K1, D1, R1, P1, image_size, cv2.CV_32FC1)
    map1_r, map2_r = cv2.initUndistortRectifyMap(
        K2, D2, R2, P2, image_size, cv2.CV_32FC1)
    
    return map1_l, map2_l, map1_r, map2_r, Q

# 使用示例
map1_l, map2_l, map1_r, map2_r, Q = stereo_rectify(K1, D1, K2, D2, R, T, (640,480))
img_l_rect = cv2.remap(img_l, map1_l, map2_l, cv2.INTER_LINEAR)
img_r_rect = cv2.remap(img_r, map1_r, map2_r, cv2.INTER_LINEAR)

4.3 校正效果评估

合格校正应满足:

  1. 左右图像的行对齐误差<1像素
  2. 棋盘格水平边缘保持直线
  3. 有效视场损失<15%(alpha参数控制)

常见问题处理:

  • 图像边缘扭曲严重 → 调低alpha值(0.7-0.9)
  • 行对齐不理想 → 检查标定数据质量
  • 出现黑边 → 调整投影矩阵P的平移分量

5. 实战问题排查手册

5.1 标定阶段常见问题

问题1:findChessboardCorners检测失败

  • 检查标定板是否完全可见
  • 尝试调整patternSize参数
  • 使用cornerSubPix提高精度:
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
corners = cv2.cornerSubPix(gray, corners, (11,11), (-1,-1), criteria)

问题2:重投影误差过大

  • 检查标定板是否平整
  • 增加标定图像数量(建议≥20)
  • 剔除误差较大的图像组

5.2 校正阶段典型异常

问题1:校正后图像严重变形

  • 确认输入的标定参数正确
  • 检查相机矩阵K是否单位化
  • 验证旋转矩阵R的正交性

问题2:左右图像行不对齐

  • 重新检查立体标定结果
  • 确保stereoRectify的alpha参数合理
  • 测试不同的校正方法(Hartley/Bouguet)

6. 性能优化技巧

6.1 加速标定过程

  • 使用多线程并行检测角点
  • 缓存检测结果避免重复计算
  • 采用PyPy解释器提升循环速度

6.2 实时校正方案

  • 预计算remap映射表
  • 使用CUDA加速:
img_l_rect = cv2.cuda.remap(gpu_img_l, map1_l, map2_l, cv2.INTER_LINEAR)
  • 降低分辨率处理(640x480→320x240)

6.3 标定参数存储优化

将标定结果保存为JSON:

import json

def save_calibration(filename, K1, D1, K2, D2, R, T):
    data = {
        'K1': K1.tolist(),
        'D1': D1.tolist(),
        'K2': K2.tolist(),
        'D2': D2.tolist(),
        'R': R.tolist(),
        'T': T.tolist()
    }
    with open(filename, 'w') as f:
        json.dump(data, f)

7. 扩展应用方向

完成标定和校正后,可以��一步实现:

  1. 立体匹配生成视差图
stereo = cv2.StereoSGBM_create(
    minDisparity=0,
    numDisparities=64,  
    blockSize=11)
disparity = stereo.compute(img_l, img_r)
  1. 三维点云重建
points = cv2.reprojectImageTo3D(disparity, Q)
  1. 基于深度的物体检测

在实际项目中,我将这套系统用于仓库AGV小车的障碍物检测,测距精度达到了±2cm@3m,完全满足业务需求。关键是要保证标定过程的严谨性和校正质量的稳定性。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐