从零搭建双目测距系统:OpenCV实战指南
在计算机视觉领域,双目测距技术因其成本低廉、实现简单而广受欢迎。不同于昂贵的激光雷达或深度相机,普通USB摄像头组合就能实现厘米级精度的距离测量。本文将手把手教你用Python和OpenCV构建完整的双目测距系统,从硬件选型到代码实现,避开数学公式的泥沼,直击工程实现的核心要点。
1. 硬件准备与环境配置
1.1 设备选型建议
对于入门级双目系统,推荐以下硬件组合:
- 摄像头:两个罗技C920(或C270)USB摄像头,支持640x480分辨率
- 标定板:A4纸打印的7x9棋盘格(每个方格边长2.5cm)
- 支架:可调节间距的摄像头支架(基线距离10-20cm为佳)
提示:摄像头安装时需确保两者光轴基本平行,可通过观察实时画面调整
1.2 Python环境搭建
创建conda环境并安装必要依赖:
conda create -n stereo python=3.8 conda activate stereo pip install opencv-contrib-python numpy matplotlib验证OpenCV安装:
import cv2 print(cv2.__version__) # 需≥4.5.02. 双目标定实战
2.1 图像采集规范
采集标定图像时需注意:
- 左右相机同步拍摄(间隔<0.5秒)
- 棋盘格需覆盖画面不同区域(前/后/左/右/倾斜)
- 每组图像保存为
left_01.jpg/right_01.jpg格式
采集20组以上有效图像样本,示例采集代码:
import cv2 left_cam = cv2.VideoCapture(0) right_cam = cv2.VideoCapture(1) for i in range(30): _, left_frame = left_cam.read() _, right_frame = right_cam.read() if cv2.waitKey(200) == ord('s'): # 按s保存 cv2.imwrite(f'calib/left_{i:02d}.jpg', left_frame) cv2.imwrite(f'calib/right_{i:02d}.jpg', right_frame)2.2 相机标定核心代码
def calibrate_camera(img_dir, pattern_size=(6,8)): obj_points = [] img_points = [] # 生成3D标定板坐标 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) images = glob.glob(f'{img_dir}/*.jpg') for fname in images: img = cv2.imread(fname) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 查找角点 ret, corners = cv2.findChessboardCorners(gray, pattern_size, None) if ret: obj_points.append(objp) corners_refined = cv2.cornerSubPix( gray, corners, (11,11), (-1,-1), (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)) img_points.append(corners_refined) # 计算内参和畸变系数 ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera( obj_points, img_points, gray.shape[::-1], None, None) return mtx, dist3. 立体校正与匹配
3.1 立体标定关键参数
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 1e-5) flags = cv2.CALIB_FIX_INTRINSIC # 固定已标定的内参 ret, _, _, _, _, R, T, E, F = cv2.stereoCalibrate( obj_points_left, img_points_left, obj_points_right, img_points_right, left_mtx, left_dist, right_mtx, right_dist, image_size, criteria=criteria, flags=flags)3.2 极线校正实现
# 计算校正映射 R1, R2, P1, P2, Q, _, _ = cv2.stereoRectify( left_mtx, left_dist, right_mtx, right_dist, image_size, R, T) # 生成校正映射表 left_map = cv2.initUndistortRectifyMap( left_mtx, left_dist, R1, P1, image_size, cv2.CV_16SC2) right_map = cv2.initUndistortRectifyMap( right_mtx, right_dist, R2, P2, image_size, cv2.CV_16SC2) # 应用校正 left_rect = cv2.remap(left_frame, left_map[0], left_map[1], cv2.INTER_LINEAR) right_rect = cv2.remap(right_frame, right_map[0], right_map[1], cv2.INTER_LINEAR)4. 视差图与深度计算
4.1 SGBM参数调优
立体匹配的SGBM算法参数对结果影响显著,推荐初始设置:
| 参数 | 推荐值 | 作用 |
|---|---|---|
| minDisparity | 0 | 最小视差 |
| numDisparities | 64 | 视差搜索范围 |
| blockSize | 7 | 匹配块大小 |
| P1 | 837^2 | 平滑度惩罚1 |
| P2 | 3237^2 | 平滑度惩罚2 |
| disp12MaxDiff | 1 | 左右一致性检查阈值 |
| uniquenessRatio | 10 | 唯一性检测比率 |
初始化SGBM对象:
stereo = cv2.StereoSGBM_create( minDisparity=0, numDisparities=64, blockSize=7, P1=8*3*7**2, P2=32*3*7**2, disp12MaxDiff=1, uniquenessRatio=10) disparity = stereo.compute(left_rect, right_rect).astype(np.float32)/164.2 深度计算与可视化
# 从Q矩阵获取深度 depth = cv2.reprojectImageTo3D(disparity, Q)[:,:,2] # 可视化 plt.figure(figsize=(12,5)) plt.subplot(121) plt.imshow(cv2.cvtColor(left_rect, cv2.COLOR_BGR2RGB)) plt.subplot(122) plt.imshow(depth, cmap='jet') plt.colorbar(label='Distance (cm)') plt.show()5. 常见问题排查
5.1 标定失败解决方案
棋盘格检测失败:
- 确保光照均匀,避免反光
- 尝试不同尺寸的棋盘格(如5x7或9x11)
- 手动验证角点检测结果
重投影误差过大:
- 删除误差大于0.5像素的图像
- 增加标定图像数量(建议≥30组)
- 检查摄像头是否在采集过程中移动
5.2 视差图质量问题
出现条纹噪声:
# 添加WLS滤波 right_stereo = cv2.ximgproc.createRightMatcher(stereo) wls_filter = cv2.ximgproc.createDisparityWLSFilter(stereo) disparity_right = right_stereo.compute(right_rect, left_rect) filtered_disp = wls_filter.filter(disparity, left_rect, None, disparity_right)前景物体出现空洞:
- 调整SGBM的
uniquenessRatio(15-25) - 尝试BM算法作为替代:
stereo = cv2.StereoBM_create(numDisparities=64, blockSize=21)- 调整SGBM的
在实际测试中,使用两个罗技C920摄像头,基线距离15cm时,对于1米内的物体能达到±2cm的测量精度。关键是要确保标定过程严谨,立体匹配参数根据场景适当调整。完整项目代码已包含标定、校正、测距全流程,可直接集成到机器人或AR应用中。