双目三维重建是计算机视觉领域的一项核心技术,它通过模拟人类双眼的视差原理,从两张不同视角的图像中恢复出物体的三维信息。这套系统在机器人导航、工业检测、自动驾驶等领域有着广泛的应用前景。相比单目视觉,双目系统无需提前知道物体尺寸,就能直接计算出深度信息,这使得它在实际应用中更加灵活。
我最初接触双目视觉是在一个机器人避障项目中,当时尝试了各种单目测距方法都不理想,直到改用双目摄像头才解决了问题。经过多次迭代优化,现在这套Python实现的双目系统在1米距离内测量误差可以控制在1厘米以内,完全能满足大多数应用场景的需求。
市面上常见的双目摄像头主要分为三种类型:分体式双USB摄像头、一体式单USB摄像头和工业级同步摄像头。对于初学者,我推荐使用价格在300-800元的一体式单USB摄像头,这类设备左右镜头已经固定在同一基板上,省去了机械校准的麻烦。
实测发现,基线距离(两个镜头的间距)对测量精度影响很大。基线越长,远距离测量越准,但近距离盲区也越大。一般室内场景选择3-6cm基线的摄像头比较合适。我目前使用的是一款基线6cm的摄像头,在0.5-3米范围内表现良好。
推荐使用Anaconda创建独立的Python环境,避免包冲突。关键依赖库包括:
python复制opencv-contrib-python==4.5.4.60 # 必须包含contrib模块
numpy==1.19.5
matplotlib==3.1.0
open3d==0.7.0.0 # 用于点云可视化
安装时常见的问题是OpenCV版本不匹配,如果遇到SGBM算法报错,可以尝试以下命令强制安装指定版本:
bash复制pip install opencv-contrib-python==4.5.4.60 --force-reinstall
标定板的质量直接影响标定精度。我强烈建议购买专业的金属标定板,A4纸打印的标定板在边缘处会产生明显畸变。采集时要注意:
采集脚本的核心代码如下:
python复制def capture_images():
cap = cv2.VideoCapture(0)
count = 0
while True:
ret, frame = cap.read()
# 分离左右图像(一体式摄像头)
left = frame[:, :frame.shape[1]//2]
right = frame[:, frame.shape[1]//2:]
# 显示并保存
cv2.imshow('left', left)
cv2.imshow('right', right)
key = cv2.waitKey(1)
if key == ord('s'):
cv2.imwrite(f'left_{count}.jpg', left)
cv2.imwrite(f'right_{count}.jpg', right)
count += 1
cap.release()
双目标定前必须先完成单目标定,这一步很多新手容易忽略。单目标定的核心是获取相机的内参矩阵和畸变系数:
python复制ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
obj_points, img_points, img_size, None, None)
实际项目中我发现两个常见问题:
双目标定在单目标定基础上增加了相机间的相对位置关系计算。关键参数包括:
标定结果的验证非常重要。我通常会检查两个指标:
立体校正的目的是使左右图像的极线水平对齐,这是后续立体匹配的基础。OpenCV提供了两种校正方式:
实测Bouguet方法效果更好,核心代码如下:
python复制R1, R2, P1, P2, Q, _, _ = cv2.stereoRectify(
cameraMatrix1, distCoeffs1,
cameraMatrix2, distCoeffs2,
image_size, R, T)
map1x, map1y = cv2.initUndistortRectifyMap(
cameraMatrix1, distCoeffs1, R1, P1, image_size, cv2.CV_32FC1)
SGBM(Semi-Global Block Matching)是目前效果较好的立体匹配算法。经过多次实验,我总结出以下参数组合在大多数场景下表现良好:
python复制window_size = 3
min_disp = 0
num_disp = 160 - min_disp
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=15,
speckleWindowSize=100,
speckleRange=32
)
特别提醒:numDisparities必须是16的整数倍,否则会报错。这个参数决定了最大可检测的视差范围,值越大检测距离越远,但计算量也越大。
WLS(Weighted Least Squares)滤波能显著改善视差图质量,尤其是弱纹理区域。下图展示了滤波前后的对比效果:
| 滤波前 | 滤波后 |
|---|---|
| 噪声多,边缘模糊 | 噪声少,边缘清晰 |
实现代码:
python复制wls_filter = cv2.ximgproc.createDisparityWLSFilter(matcher_left=left_matcher)
wls_filter.setLambda(8000)
wls_filter.setSigmaColor(1.5)
filtered_disp = wls_filter.filter(disp, left_img)
通过重投影矩阵Q可以将视差图转换为3D点云:
python复制points_3d = cv2.reprojectImageTo3D(disparity, Q)
这里有个关键细节:OpenCV返回的Z值单位是毫米,但X/Y坐标单位与标定时使用的棋盘格单位一致。如果标定时棋盘格单位是毫米,那么所有坐标都是毫米。
Open3D比PCL更容易安装和使用,适合快速验证效果。我的点云可视化流程如下:
python复制def show_point_cloud(color, depth):
rgbd = o3d.geometry.RGBDImage.create_from_color_and_depth(
color, depth, depth_scale=1000.0, convert_rgb_to_intensity=False)
pcd = o3d.geometry.PointCloud.create_from_rgbd_image(
rgbd, intrinsic)
# 点云下采样
pcd = pcd.voxel_down_sample(voxel_size=0.01)
# 移除离群点
pcd, _ = pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0)
o3d.visualization.draw_geometries([pcd])
在标准测试环境下(光照充足,物体纹理丰富),系统测量误差如下表:
| 距离(m) | 平均误差(mm) | 最大误差(mm) |
|---|---|---|
| 0.5 | 2.1 | 5.3 |
| 1.0 | 4.7 | 9.8 |
| 2.0 | 12.4 | 25.6 |
影响精度的主要因素包括:
如果标定误差过大,可以按照以下步骤排查:
视差图中常见的空洞问题可以通过以下方法改善:
python复制disp = cv2.ximgproc.weightedMedianFilter(
left_img, disp, 15, 5)
在树莓派等嵌入式设备上运行时,可以采取这些优化措施:
完成基础版本后,可以考虑以下扩展:
我在实际项目中发现,将传统双目视觉与深度学习结合能取得更好的效果。比如使用神经网络预测初始视差图,再用传统方法优化,可以大幅提升弱纹理区域的检测效果。