文章目录
- 1. 引言
- 2. 端到端工程流程
- 3. 标定阶段代码
- 3.1 单目标定
- 3.2 立体标定与校正
- 4. 立体匹配 SGBM
- 4.1 参数配置
- 4.2 完整调用
- 5. 三维重建
- 6. 性能优化
- 7. 常见问题排查
- 8. 总结
1. 引言
前面十三篇我们分别讲解了成像模型、标定、畸变、极线几何、校正、匹配、后处理、三维重建等各环节的理论。本篇把这些环节串联成一套端到端可运行的完整工程,用 OpenCV 实现从原始双目图像到三维点云的全流程,并讨论 SGBM 参数调优、实时性能优化和常见问题排查。
本篇是理论走向实践的"合龙"篇,建议对照前面各篇的理论,逐段理解代码对应哪一步、为什么这么写。
2. 端到端工程流程
图 1:OpenCV 双目立体视觉端到端工程流程。从角点提取到点云生成,共八个核心步骤,分为标定、校正、重建三大阶段。
| 阶段 | 步骤 | OpenCV API |
|---|---|---|
| 标定 | 角点提取 | findChessboardCorners+cornerSubPix |
| 单目标定 | calibrateCamera | |
| 立体标定 | stereoCalibrate | |
| 校正 | 立体校正 | stereoRectify |
| 映射表 | initUndistortRectifyMap | |
| 逐帧校正 | remap | |
| 重建 | SGBM 匹配 | StereoSGBM_create |
| 点云生成 | reprojectImageTo3D |
工程结构建议:标定与映射表只需离线计算一次,保存到文件;在线运行只做"校正→匹配→重建"三步,保证实时性。
3. 标定阶段代码
3.1 单目标定
importcv2importnumpyasnpimportglob PATTERN=(9,6)# 棋盘格内角点数SQUARE=25.0# 方格边长 mm# 已知世界坐标点objp=np.zeros((PATTERN[0]*PATTERN[1],3),np.float32)objp[:,:2]=np.mgrid[0:PATTERN[0],0:PATTERN[1]].T.reshape(-1,2)*SQUAREdefcalibrate_one(images,img_size):objpoints,imgpoints=[],[]criteria=(cv2.TERM_CRITERIA_EPS|cv2.TERM_CRITERIA_MAX_ITER,30,0.001)forfnameinimages:gray=cv2.cvtColor(cv2.imread(fname),cv2.COLOR_BGR2GRAY)ret,corners=cv2.findChessboardCorners(gray,PATTERN,None)ifret:corners=cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)objpoints.append(objp)imgpoints.append(corners)ret,K,dist,_,_=cv2.calibrateCamera(objpoints,imgpoints,img_size,None,None)print(f"重投影误差 ={ret:.4f}px")returnK,dist,objpoints,imgpoints3.2 立体标定与校正
# img_size 取自标定图像尺寸,格式为 (宽, 高),如 1280×720img_size=(1280,720)# 立体标定(左右同时拍摄的图像对)Kl,dl,obj_l,img_l=calibrate_one(glob.glob('left_*.jpg'),img_size)Kr,dr,obj_r,img_r=calibrate_one(glob.glob('right_*.jpg'),img_size)flags=cv2.CALIB_FIX_INTRINSIC ret,Kl,dl,Kr,dr,R,T,E,F=cv2.stereoCalibrate(obj_l,img_l,img_r,Kl,dl,Kr,dr,img_size,flags=flags)print(f"基线 B ={np.linalg.norm(T):.2f}mm")# 立体校正,得到校正参数 + 重投影矩阵 QR1,R2,P1,P2,Q,_,_=cv2.stereoRectify(Kl,dl,Kr,dr,img_size,R,T,flags=cv2.CALIB_ZERO_DISPARITY,alpha=0)# 预计算映射表(仅一次)mapL1,mapL2=cv2.initUndistortRectifyMap(Kl,dl,R1,P1,img_size,cv2.CV_32FC1)mapR1,mapR2=cv2.initUndistortRectifyMap(Kr,dr,R2,P2,img_size,cv2.CV_32FC1)# 保存参数供在线使用np.savez('stereo.npz',Kl=Kl,dl=dl,Kr=Kr,dr=dr,R=R,T=T,R1=R1,R2=R2,P1=P1,P2=P2,Q=Q,mapL1=mapL1,mapL2=mapL2,mapR1=mapR1,mapR2=mapR2)4. 立体匹配 SGBM
4.1 参数配置
图 2:SGBM 关键参数调优指南。numDisparities 定搜索范围,blockSize 定匹配尺度,P1/P2 控制平滑度。调参顺序为 numDisparities → blockSize → P1/P2 → speckle。
defcreate_sgbm(num_disp=128,block=9):min_disp=0sgbm=cv2.StereoSGBM_create(minDisparity=min_disp,numDisparities=num_disp,blockSize=block,# P1, P2 随 blockSize 缩放(彩色输入需再乘通道数 cn)P1=8*block*block,P2=32*block*block,disp12MaxDiff=1,# 左右一致性阈值uniquenessRatio=10,# 唯一性约束speckleWindowSize=100,# 斑点窗口speckleRange=32,# 斑点范围(教程界常见宽松取值;官方建议 1~2,内部隐式 ×16)preFilterCap=63,mode=cv2.STEREO_SGBM_MODE_SGBM_3WAY)# 与 MODE_HH 聚合方向相同、把反向方向放在独立一遍计算的省内存变体returnsgbm| 参数 | 含义 | 调参建议 |
|---|---|---|
numDisparities | 视差搜索范围(16 的倍数) | 覆盖最近物体视差,宁大勿小 |
blockSize | 匹配块大小(奇数) | 3~11,纹理弱用大块 |
P1 | 相邻视差差 1 的惩罚 | 8 × b l o c k 2 8 \times block^28×block2(彩色输入再乘通道数 cn) |
P2 | 相邻视差差 >1 的惩罚 | 32 × b l o c k 2 32 \times block^232×block2(彩色输入再乘 cn),必 > P1 |
disp12MaxDiff | 左右一致性阈值 | 1~2,过大失效 |
uniquenessRatio | 唯一性约束 | 5~15,剔除模糊匹配 |
speckleWindowSize/Range | 斑点滤波 | 100/32 常用(speckleRange 官方建议 1~2) |
mode | 代价聚合路径 | MODE_SGBM(默认,单遍 5 方向);MODE_HH(8 方向完整两遍 DP,精度高);MODE_SGBM_3WAY(与 HH 聚合方向相同、反向方向独立一遍的省内存变体) |
4.2 完整调用
# 加载 §3.2 保存的离线标定/校正参数(stereo.npz)data=np.load('stereo.npz')mapL1,mapL2,mapR1,mapR2=data['mapL1'],data['mapL2'],data['mapR1'],data['mapR2']Q=data['Q']# §5 三维重建用sgbm=create_sgbm(num_disp=128,block=9)# 在线处理一帧left_rect=cv2.remap(left_img,mapL1,mapL2,cv2.INTER_LINEAR)right_rect=cv2.remap(right_img,mapR1,mapR2,cv2.INTER_LINEAR)# 转灰度匹配disp=sgbm.compute(cv2.cvtColor(left_rect,cv2.COLOR_BGR2GRAY),cv2.cvtColor(right_rect,cv2.COLOR_BGR2GRAY))disp=disp.astype(np.float32)/16.0# SGBM 输出是 16 倍整数,需除 16# 视差后处理disp[disp<0]=0# 无效值置 0disp_vis=cv2.normalize(disp,None,0,255,cv2.NORM_MINMAX,cv2.CV_8U)cv2.imshow('disparity',cv2.applyColorMap(disp_vis,cv2.COLORMAP_JET))5. 三维重建
点云保存与可视化使用 Open3D,未安装时先执行pip install open3d。
# 视差图 → 三维点云points=cv2.reprojectImageTo3D(disp,Q)# H×W×3mask=disp>0# 有效区域points=points[mask]# 取有效点 N×3# 可选:附颜色(OpenCV 读图默认 BGR 通道序,点云常用 RGB,需先转换再取色)left_rect_rgb=cv2.cvtColor(left_rect,cv2.COLOR_BGR2RGB)colors=left_rect_rgb[mask]# 保存 PLYimportopen3daso3d pcd=o3d.geometry.PointCloud()pcd.points=o3d.utility.Vector3dVector(points)pcd.colors=o3d.utility.Vector3dVector(colors.reshape(-1,3)/255.0)o3d.io.write_point_cloud('cloud.ply',pcd)o3d.visualization.draw_geometries([pcd])6. 性能优化
图 3:实时双目系统的性能优化策略。映射表预计算、GPU 加速、多线程流水线、降分辨率,可叠加获得数倍至数十倍加速。
| 策略 | 加速倍数 | 说明 |
|---|---|---|
| 映射表预计算 | ×10+ | remap查表,避免每帧重算 |
| GPU 加速 | ×5~20 | CUDA StereoSGBM 或 OpenCL |
| 多线程流水线 | ×2~4 | 采集/校正/匹配/重建并行 |
| 降分辨率/ROI | ×2~3 | 缩小图或裁剪有效区 |
实时目标(经验参考值):720p 图像 + SGBM 在中端 CPU 上约 10~20 fps,GPU 加速后可达数十 fps。若仍不够,可降分辨率,或改用默认的
MODE_SGBM(单遍 5 方向,聚合开销更小)。
7. 常见问题排查
图 4:常见问题排查表。覆盖标定误差、匹配条纹、深度抖动、远距离失效、弱纹理空洞五大典型问题。
| 问题 | 现象 | 原因 | 对策 |
|---|---|---|---|
| 标定误差过大 | 重投影误差 > 1px | 标定板不平/图像模糊/角度单一 | 检查平整度+精化角点+多角度采集 |
| 匹配条纹 | 水平带状错误 | P2 过小/路径数不足 | 增大 P2/用完整 8 方向 |
| 深度图抖动 | 帧间不稳定 | 无时序滤波/光照变化 | 加时序滤波/Census 代价 |
| 远距离失效 | 远物无深度 | 视差 < 1px/超搜索范围 | 增大基线+焦距/亚像素精化 |
| 弱纹理空洞 | 白墙/天空无值 | 匹配代价平坦 | 后处理空洞填充+先验 |
8. 总结
本篇用 OpenCV 实现了双目立体视觉的完整工程:
- 端到端流程:标定(角点→单目→立体)→ 校正(rectify→map→remap)→ 重建(SGBM→reproject)
- 离线/在线分离:标定和映射表离线算一次,在线只做校正+匹配+重建
- SGBM 调参:numDisparities→blockSize→P1/P2→speckle,按序调优
- 性能优化:预计算+GPU+多线程+降分辨率可叠加加速,720p 下中端 CPU 约 10~20 fps、GPU 加速后可达数十 fps(经验参考值)
- 问题排查:标定误差、条纹、抖动、远距失效、弱纹理空洞各有对策
在最后一篇中,我们将从工程全局视角,讨论双目系统的精度评估、基线选型、误差分析,以及与结构光、ToF、LiDAR 等方案的对比选型。
💡实践建议:先用 OpenCV 自带的
samples/python/stereo_match.py示例数据跑通全流程,再替换为自采数据。逐步把每个参数的含义和影响"摸"出来,比死记参数表有效得多。
参考资料:
- OpenCV 官方文档:cv::StereoSGBM 类(mode 枚举与各参数口径)
- OpenCV 官方文档:Camera Calibration and 3d Reconstruction(stereoCalibrate / stereoRectify / reprojectImageTo3D)
- OpenCV 官方 Python 示例:samples/python/stereo_match.py