本文目录导读:

我来详细介绍如何使用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
使用建议
- 硬件要求:建议使用相同型号的双目相机
- 标定质量:标定直接影响结果质量,至少需要20对图像
- 光照条件:保持左右图像光照一致
- 处理速度:BM算法比SGBM快3-5倍,适合实时应用
- 精度权衡:增大视差范围会降低处理速度但提高深度范围
这个案例涵盖了立体视觉的完整流程,你可以根据实际需求调整参数和算法选择。