Python案例如何用OpenCV做立体视觉

wen python案例 2

本文目录导读:

Python案例如何用OpenCV做立体视觉

  1. 立体视觉基础流程
  2. 完整实现案例
  3. 关键参数调优建议
  4. 使用建议

我来详细介绍如何使用OpenCV实现立体视觉,包括完整的案例步骤和代码。

立体视觉基础流程

立体视觉的核心是通过两个相机(或一个相机移动)拍摄同一场景,计算视差图,然后恢复深度信息。

完整实现案例

相机标定

import cv2
import numpy as np
import glob
def stereo_calibration():
    """
    双目相机标定
    """
    # 准备标定棋盘格参数
    CHECKERBOARD = (6, 9)  # 内角点数量
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
    # 世界坐标系中的棋盘格点
    objp = np.zeros((CHECKERBOARD[0] * CHECKERBOARD[1], 3), np.float32)
    objp[:, :2] = np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2)
    # 存储左/右图像的点
    objpoints = []  # 3D点
    imgpoints_left = []  # 左图像2D点
    imgpoints_right = []  # 右图像2D点
    # 读入标定图像
    left_images = glob.glob('calibration/left/*.jpg')
    right_images = glob.glob('calibration/right/*.jpg')
    for left_img, right_img in zip(left_images, right_images):
        img_left = cv2.imread(left_img, cv2.IMREAD_GRAYSCALE)
        img_right = cv2.imread(right_img, cv2.IMREAD_GRAYSCALE)
        # 查找棋盘格角点
        ret_left, corners_left = cv2.findChessboardCorners(img_left, CHECKERBOARD, None)
        ret_right, corners_right = cv2.findChessboardCorners(img_right, CHECKERBOARD, None)
        if ret_left and ret_right:
            objpoints.append(objp)
            # 角点亚像素优化
            corners_left = cv2.cornerSubPix(img_left, corners_left, (11, 11), (-1, -1), criteria)
            corners_right = cv2.cornerSubPix(img_right, corners_right, (11, 11), (-1, -1), criteria)
            imgpoints_left.append(corners_left)
            imgpoints_right.append(corners_right)
    # 单目标定
    ret_left, mtx_left, dist_left, _, _ = cv2.calibrateCamera(
        objpoints, imgpoints_left, img_left.shape[::-1], None, None)
    ret_right, mtx_right, dist_right, _, _ = cv2.calibrateCamera(
        objpoints, imgpoints_right, img_right.shape[::-1], None, None)
    # 双目标定
    flags = cv2.CALIB_FIX_INTRINSIC
    criteria_stereo = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 1e-5)
    ret, mtx_left, dist_left, mtx_right, dist_right, R, T, E, F = cv2.stereoCalibrate(
        objpoints, imgpoints_left, imgpoints_right,
        mtx_left, dist_left, mtx_right, dist_right,
        img_left.shape[::-1], criteria=criteria_stereo, flags=flags)
    return mtx_left, dist_left, mtx_right, dist_right, R, T

立体校正

def stereo_rectification(mtx_left, dist_left, mtx_right, dist_right, R, T, image_size):
    """
    立体校正 - 使左右图像行对齐
    """
    # 立体校正
    R1, R2, P1, P2, Q, _, _ = cv2.stereoRectify(
        mtx_left, dist_left, mtx_right, dist_right,
        image_size, R, T, alpha=0)
    # 计算映射表
    map1_left, map2_left = cv2.initUndistortRectifyMap(
        mtx_left, dist_left, R1, P1, image_size, cv2.CV_32FC1)
    map1_right, map2_right = cv2.initUndistortRectifyMap(
        mtx_right, dist_right, R2, P2, image_size, cv2.CV_32FC1)
    return map1_left, map2_left, map1_right, map2_right, Q

视差图计算

def compute_disparity(img_left, img_right, method='SGBM'):
    """
    计算视差图
    """
    # 转换为灰度图
    gray_left = cv2.cvtColor(img_left, cv2.COLOR_BGR2GRAY)
    gray_right = cv2.cvtColor(img_right, cv2.COLOR_BGR2GRAY)
    if method == 'SGBM':
        # SGBM算法(更准确)
        stereo = cv2.StereoSGBM_create(
            minDisparity=0,
            numDisparities=16 * 5,  # 视差范围,16的倍数
            blockSize=11,
            P1=8 * 3 * 11 ** 2,
            P2=32 * 3 * 11 ** 2,
            disp12MaxDiff=1,
            uniquenessRatio=10,
            speckleWindowSize=100,
            speckleRange=32
        )
    else:
        # BM算法(更快)
        stereo = cv2.StereoBM_create(
            numDisparities=16 * 5,
            blockSize=15
        )
    # 计算视差
    disparity = stereo.compute(gray_left, gray_right).astype(np.float32) / 16.0
    return disparity

深度恢复

def compute_depth(disparity, Q):
    """
    从视差图恢复深度信息
    """
    # 确保视差有效
    disparity[disparity <= 0] = 0.1
    # 使用reprojectImageTo3D恢复3D坐标
    points_3d = cv2.reprojectImageTo3D(disparity, Q)
    # 深度 = Z坐标
    depth = points_3d[:, :, 2]
    # 过滤无效深度
    depth[depth > 1000] = 0  # 设置最大深度阈值
    return depth, points_3d

完整主程序

def main():
    """
    立体视觉主程序
    """
    # 1. 标定相机(如果有标定参数文件可跳过)
    try:
        # 加载已标定的参数
        calibration_data = np.load('calibration_data.npz')
        mtx_left = calibration_data['mtx_left']
        dist_left = calibration_data['dist_left']
        mtx_right = calibration_data['mtx_right']
        dist_right = calibration_data['dist_right']
        R = calibration_data['R']
        T = calibration_data['T']
        print("已加载标定参数")
    except:
        print("进行相机标定...")
        mtx_left, dist_left, mtx_right, dist_right, R, T = stereo_calibration()
        # 保存标定参数
        np.savez('calibration_data.npz', 
                 mtx_left=mtx_left, dist_left=dist_left,
                 mtx_right=mtx_right, dist_right=dist_right,
                 R=R, T=T)
    # 2. 读取左右图像
    img_left = cv2.imread('left_image.jpg')
    img_right = cv2.imread('right_image.jpg')
    if img_left is None or img_right is None:
        print("无法读取图像")
        return
    h, w = img_left.shape[:2]
    # 3. 立体校正
    map1_left, map2_left, map1_right, map2_right, Q = stereo_rectification(
        mtx_left, dist_left, mtx_right, dist_right, R, T, (w, h))
    # 应用校正
    rectified_left = cv2.remap(img_left, map1_left, map2_left, cv2.INTER_LINEAR)
    rectified_right = cv2.remap(img_right, map1_right, map2_right, cv2.INTER_LINEAR)
    # 4. 计算视差图
    disparity = compute_disparity(rectified_left, rectified_right, method='SGBM')
    # 5. 恢复深度
    depth, points_3d = compute_depth(disparity, Q)
    # 6. 显示结果
    # 归一化视差图用于显示
    disparity_normalized = cv2.normalize(disparity, None, 0, 255, cv2.NORM_MINMAX)
    disparity_normalized = np.uint8(disparity_normalized)
    # 归一化深度图
    depth_normalized = cv2.normalize(depth, None, 0, 255, cv2.NORM_MINMAX)
    depth_normalized = np.uint8(depth_normalized)
    # 显示
    cv2.imshow('Left Image', rectified_left)
    cv2.imshow('Right Image', rectified_right)
    cv2.imshow('Disparity Map', disparity_normalized)
    cv2.imshow('Depth Map', depth_normalized)
    cv2.imshow('Color Depth', cv2.applyColorMap(depth_normalized, cv2.COLORMAP_JET))
    # 7. 保存结果
    cv2.imwrite('disparity_map.jpg', disparity_normalized)
    cv2.imwrite('depth_map.jpg', depth_normalized)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
if __name__ == "__main__":
    main()

实时立体视觉(使用摄像头)

def realtime_stereo():
    """
    实时立体视觉处理
    """
    # 加载标定参数
    calibration_data = np.load('calibration_data.npz')
    mtx_left = calibration_data['mtx_left']
    dist_left = calibration_data['dist_left']
    mtx_right = calibration_data['mtx_right']
    dist_right = calibration_data['dist_right']
    R = calibration_data['R']
    T = calibration_data['T']
    # 打开左/右摄像头
    cap_left = cv2.VideoCapture(0)  # 左相机
    cap_right = cv2.VideoCapture(1)  # 右相机
    h, w = 480, 640
    # 计算校正映射
    map1_left, map2_left, map1_right, map2_right, Q = stereo_rectification(
        mtx_left, dist_left, mtx_right, dist_right, R, T, (w, h))
    while True:
        ret_left, frame_left = cap_left.read()
        ret_right, frame_right = cap_right.read()
        if not ret_left or not ret_right:
            break
        # 校正图像
        rectified_left = cv2.remap(frame_left, map1_left, map2_left, cv2.INTER_LINEAR)
        rectified_right = cv2.remap(frame_right, map1_right, map2_right, cv2.INTER_LINEAR)
        # 计算视差
        disparity = compute_disparity(rectified_left, rectified_right, method='BM')
        # 计算深度
        depth, _ = compute_depth(disparity, Q)
        # 显示
        disparity_vis = cv2.normalize(disparity, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
        depth_vis = cv2.normalize(depth, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
        cv2.imshow('Left', rectified_left)
        cv2.imshow('Right', rectified_right)
        cv2.imshow('Disparity', disparity_vis)
        cv2.imshow('Depth', cv2.applyColorMap(depth_vis, cv2.COLORMAP_JET))
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    cap_left.release()
    cap_right.release()
    cv2.destroyAllWindows()

关键参数调优建议

def optimize_sgbm_parameters(img_left, img_right):
    """
    SGBM参数调优
    """
    # 关键参数说明
    params = {
        'minDisparity': 0,          # 最小视差
        'numDisparities': 16 * 5,   # 视差范围(必须是16的倍数)
        'blockSize': 11,            # 匹配块大小(奇数,3-11)
        'P1': 8 * 3 * 11 ** 2,     # 平滑惩罚参数1
        'P2': 32 * 3 * 11 ** 2,    # 平滑惩罚参数2
        'disp12MaxDiff': 1,         # 左右一致性检查阈值
        'uniquenessRatio': 10,      # 唯一性比例
        'speckleWindowSize': 100,   # 去斑点窗口大小
        'speckleRange': 32         # 去斑点范围
    }
    # 根据场景调整
    # 近景:减小numDisparities,减小blockSize
    # 远景:增大numDisparities
    # 纹理丰富:减小blockSize
    # 纹理稀疏:增大blockSize
    return params

使用建议

  1. 硬件要求:建议使用相同型号的双目相机
  2. 标定质量:标定直接影响结果质量,至少需要20对图像
  3. 光照条件:保持左右图像光照一致
  4. 处理速度:BM算法比SGBM快3-5倍,适合实时应用
  5. 精度权衡:增大视差范围会降低处理速度但提高深度范围

这个案例涵盖了立体视觉的完整流程,你可以根据实际需求调整参数和算法选择。

抱歉,评论功能暂时关闭!