保姆级教程:用OpenCV和Python从零搭建双目测距系统(附完整代码)
·
从零搭建双目测距系统:OpenCV+Python实战指南
1. 双目视觉基础与环境准备
双目视觉技术通过模拟人类双眼的视差原理,利用两个摄像头从不同角度拍摄同一场景,通过计算图像中对应点的位置差异来获取深度信息。这种技术广泛应用于机器人导航、自动驾驶、工业检测等领域。
1.1 硬件选择与配置
推荐硬件配置方案 :
| 组件类型 | 入门级配置 | 专业级配置 | 说明 |
|---|---|---|---|
| 摄像头 | 普通USB摄像头×2 | 工业级全局快门相机 | 建议选择固定焦距镜头 |
| 支架 | 3D打印/自制支架 | 铝合金可调基线支架 | 基线距离建议50-200mm |
| 计算设备 | 树莓派4B | NVIDIA Jetson系列 | 需考虑实时性要求 |
提示:摄像头安装时需确保光轴平行,可通过机械调整或软件校正实现。
1.2 开发环境搭建
安装必要的Python库:
pip install opencv-contrib-python numpy matplotlib
验证OpenCV安装:
import cv2
print(cv2.__version__) # 应显示4.x版本
2. 相机标定与立体校正
2.1 棋盘格标定实战
准备标定板(建议打印A4尺寸的7x9棋盘格),采集15-20组不同角度的图像:
import cv2
import numpy as np
# 定义棋盘格参数
pattern_size = (6, 8) # 内角点数量
square_size = 2.5 # 棋盘格方块实际尺寸(mm)
# 准备对象点
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) * square_size
# 存储对象点和图像点
obj_points = []
img_points_left = []
img_points_right = []
# 标定过程
for i in range(1, 16):
img_l = cv2.imread(f'left_{i}.jpg')
img_r = cv2.imread(f'right_{i}.jpg')
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)
ret_r, corners_r = cv2.findChessboardCorners(gray_r, pattern_size)
if ret_l and ret_r:
obj_points.append(objp)
img_points_left.append(corners_l)
img_points_right.append(corners_r)
2.2 立体相机参数计算
# 单目标定
ret_l, mtx_l, dist_l, rvecs_l, tvecs_l = cv2.calibrateCamera(
obj_points, img_points_left, gray_l.shape[::-1], None, None)
ret_r, mtx_r, dist_r, rvecs_r, tvecs_r = cv2.calibrateCamera(
obj_points, img_points_right, gray_r.shape[::-1], None, None)
# 立体标定
flags = cv2.CALIB_FIX_INTRINSIC
ret, _, _, _, _, R, T, E, F = cv2.stereoCalibrate(
obj_points, img_points_left, img_points_right,
mtx_l, dist_l, mtx_r, dist_r, gray_l.shape[::-1], flags=flags)
2.3 立体校正与极线对齐
# 计算校正变换
R1, R2, P1, P2, Q, _, _ = cv2.stereoRectify(
mtx_l, dist_l, mtx_r, dist_r, gray_l.shape[::-1], R, T)
# 生成校正映射表
left_map1, left_map2 = cv2.initUndistortRectifyMap(
mtx_l, dist_l, R1, P1, gray_l.shape[::-1], cv2.CV_16SC2)
right_map1, right_map2 = cv2.initUndistortRectifyMap(
mtx_r, dist_r, R2, P2, gray_r.shape[::-1], cv2.CV_16SC2)
# 应用校正
img_l_rect = cv2.remap(img_l, left_map1, left_map2, cv2.INTER_LINEAR)
img_r_rect = cv2.remap(img_r, right_map1, right_map2, cv2.INTER_LINEAR)
3. 视差图计算与优化
3.1 SGBM算法参数详解
Semi-Global Block Matching (SGBM)是OpenCV中效果较好的立体匹配算法:
def create_sgbm():
window_size = 5
min_disp = 0
num_disp = 16*5 # 必须是16的整数倍
stereo = cv2.StereoSGBM_create(
minDisparity=min_disp,
numDisparities=num_disp,
blockSize=window_size,
P1=8*3*window_size**2,
P2=32*3*window_size**2,
disp12MaxDiff=1,
uniquenessRatio=10,
speckleWindowSize=100,
speckleRange=32,
mode=cv2.STEREO_SGBM_MODE_SGBM_3WAY)
return stereo
stereo = create_sgbm()
disparity = stereo.compute(img_l_rect, img_r_rect).astype(np.float32)/16.0
关键参数调优指南 :
| 参数 | 作用 | 推荐范围 | 调整策略 |
|---|---|---|---|
| minDisparity | 最小视差 | 0-64 | 根据最近物体距离调整 |
| numDisparities | 视差范围 | 16-256 | 越大检测距离越远 |
| blockSize | 匹配块大小 | 3-11 | 纹理丰富场景用较小值 |
| uniquenessRatio | 唯一性比率 | 5-15 | 值越大匹配越严格 |
3.2 视差图后处理
# 视差归一化显示
disp_vis = cv2.normalize(disparity, None, alpha=0, beta=255,
norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)
disp_vis = cv2.applyColorMap(disp_vis, cv2.COLORMAP_JET)
# 空洞填充(可选)
right_matcher = cv2.ximgproc.createRightMatcher(stereo)
right_disp = right_matcher.compute(img_r_rect, img_l_rect)
wls_filter = cv2.ximgproc.createDisparityWLSFilter(stereo)
filtered_disp = wls_filter.filter(disparity, img_l_rect, disparity_map_right=right_disp)
4. 三维坐标转换与测距
4.1 视差转深度计算
# 使用Q矩阵进行三维重建
points_3d = cv2.reprojectImageTo3D(disparity, Q, handleMissingValues=True)
# 计算指定点的距离
def get_distance(disparity, x, y):
if disparity[y,x] <= 0:
return -1 # 无效点
return (Q[2,3] * Q[3,2]) / (disparity[y,x] * Q[3,2] + Q[2,2])
# 鼠标交互获取距离
def on_mouse(event, x, y, flags, param):
if event == cv2.EVENT_LBUTTONDOWN:
distance = get_distance(disparity, x, y)
print(f"Position: ({x},{y}), Distance: {distance:.2f} mm")
cv2.namedWindow('disparity')
cv2.setMouseCallback('disparity', on_mouse)
4.2 精度提升技巧
常见问题解决方案 :
-
边缘锯齿问题 :
- 使用导向滤波优化视差图
- 后处理阶段应用双边滤波
-
远距离精度不足 :
- 增加基线距离(相机间距)
- 使用更高分辨率摄像头
-
弱纹理区域匹配失败 :
- 尝试不同的特征匹配算法
- 添加人工标记点辅助匹配
# 导向滤波示例
guided_filter = cv2.ximgproc.createGuidedFilter(img_l_rect, radius=10, eps=100)
filtered_disp = guided_filter.filter(disparity)
5. 完整系统集成与性能优化
5.1 实时视频处理流程
cap_left = cv2.VideoCapture(0) # 左摄像头
cap_right = cv2.VideoCapture(1) # 右摄像头
while True:
ret_l, frame_l = cap_left.read()
ret_r, frame_r = cap_right.read()
if not (ret_l and ret_r):
break
# 校正图像
frame_l_rect = cv2.remap(frame_l, left_map1, left_map2, cv2.INTER_LINEAR)
frame_r_rect = cv2.remap(frame_r, right_map1, right_map2, cv2.INTER_LINEAR)
# 计算视差
disparity = stereo.compute(frame_l_rect, frame_r_rect)
# 显示结果
cv2.imshow('Left', frame_l_rect)
cv2.imshow('Disparity', cv2.normalize(disparity, None, 0, 255, cv2.NORM_MINMAX))
if cv2.waitKey(1) & 0xFF == ord('q'):
break
5.2 性能优化策略
多线程处理框架 :
from threading import Thread
import queue
class CameraThread(Thread):
def __init__(self, cam_id, output_queue):
Thread.__init__(self)
self.cam_id = cam_id
self.queue = output_queue
self.running = True
def run(self):
cap = cv2.VideoCapture(self.cam_id)
while self.running:
ret, frame = cap.read()
if ret:
self.queue.put(frame)
cap.release()
# 创建处理线程
left_queue = queue.Queue()
right_queue = queue.Queue()
left_thread = CameraThread(0, left_queue)
right_thread = CameraThread(1, right_queue)
left_thread.start()
right_thread.start()
GPU加速方案 :
# 使用CUDA加速的SGBM
stereo = cv2.cuda.createStereoBM(numDisparities=64, blockSize=21)
gpu_left = cv2.cuda_GpuMat()
gpu_right = cv2.cuda_GpuMat()
gpu_left.upload(img_l_rect)
gpu_right.upload(img_r_rect)
gpu_disp = stereo.compute(gpu_left, gpu_right)
disp = gpu_disp.download()
在实际项目中,我发现基线距离的选择对系统性能影响最大。经过多次测试,对于室内1-3米的测距范围,12cm的基线距离配合720p摄像头能达到最佳平衡。
更多推荐


所有评论(0)