双目测距5个坑与Python完整示例
刚把Github上抄来的OpenCV双目测距代码丢进VSCode,终端直接报cv2.error。调参调了三天,相机标定参数全对,就是算不出距离。别慌,这行代码我踩过的坑比你喝的水还多。
双目测距的核心不是调相机,而是图像对齐和坐标映射。90%的报错都卡在这两步。今天给你一份能跑通的完整示例,从标定到实时测距,代码注释拉满。
为什么你复制的代码跑不通
先说个扎心事实:网上80%的双目测距教程,用的都是理想相机模型。但你的USB摄像头、工业相机,哪个是理想的?
双目测距分三步:
- 相机标定:求内参矩阵、畸变系数、外参矩阵
- 图像校正:消除畸变,让两幅图水平对齐
- 立体匹配:找对应点,算视差,反推距离
你报错,大概率卡在第一步。标定板没拍够15组?光照不均?角点检测失败?内参矩阵算出来是nan?
OpenCV的cv2.calibrateCamera函数文档写得再清楚,你也不会一次拍成功。我见过最离谱的案例:标定板印在A4纸上,打印分辨率不够,角点检测直接飘了。
关键细节:标定板必须用高对比度、无纹理背景,拍摄时覆盖整个画面,包含旋转、平移、不同距离。至少20组,最好30组。
三种主流方案横向对比
别迷信单一方案。双目测距的技术栈,分纯视觉、视觉+硬件、深度学习三类。各自定位不同,别乱选。
| 维度 | OpenCV纯视觉 | 硬件同步双摄 | 深度学习(StereoNet等) |
|---|---|---|---|
| 精度 | ±1-2cm(视距离) | ±0.5cm | ±0.1-0.5cm |
| 帧率 | 30-60fps | 120fps+ | 15-30fps |
| 算力需求 | CPU即可 | 需触发信号线 | GPU必需(T4起步) |
| 部署难度 | 低 | 中(接线) | 高(模型训练) |
| 成本 | ¥0-500 | ¥2000-5000 | ¥50000+(显卡) |
| 适用场景 | 原型验证、低端机器人 | 工业检测、AR | 自动驾驶、高精度测量 |
核心差异在时间同步和标定精度。
纯OpenCV方案,两路USB摄像头独立采集,帧率不对齐。你算出来的视差,可能左图是t时刻,右图是t+0.03s时刻。物体动一下,距离直接飘5cm。
硬件同步方案,用触发信号强制两相机同时曝光。工业相机标配,消费级摄像头没这功能。
深度学习方案,直接端到端输出视差图。精度最高,但需要大量标注数据训练。StereoNet在KITTI数据集上误差0.43m,但你自己拍的数据,误差可能翻三倍。
代码写法对比与逐行讲解
方案一:OpenCV纯视觉(推荐入门)
import cv2
import numpy as np# 1. 加载标定参数
mtx_l, dist_l = np.load("left_calib.npy")
mtx_r, dist_r = np.load("right_calib.npy")
R, T, mask = np.load("stereo_params.npy")# 2. 读取图像
left_img = cv2.imread("left.jpg", cv2.IMREAD_GRAYSCALE)
right_img = cv2.imread("right.jpg", cv2.IMREAD_GRAYSCALE)# 3. 图像校正(关键步骤)
left_undist, left_map1, left_map2 = cv2.getOptimalNewCameraMatrix(mtx_l, dist_l, left_img.shape[:2], 1, left_img.shape[:2])
right_undist, right_map1, right_map2 = cv2.getOptimalNewCameraMatrix(mtx_r, dist_r, right_img.shape[:2], 1, right_img.shape[:2])left_rectified = cv2.undistort(left_img, left_undist, dist_l)
right_rectified = cv2.undistort(right_img, right_undist, dist_r)# 4. 立体匹配
stereo = cv2.StereoSGBM_create(minDisparity=0,numDisparities=160, # 必须2的幂blockSize=7,P1=8*3*7*7,P2=32*3*7*7
)
disparity = stereo.compute(left_rectified, right_rectified).astype(np.float32)/16# 5. 反投影算距离
# 基线b=0.12m, 焦距f=600px
b = 0.12
f = 600.0
depth = f * b / disparity
逐行避坑:
numDisparities必须是2的幂,OpenCV内部用汉明距离,不是2的幂会报Assertion failedP1和P2是正则化参数,公式P1=8*C1*blockSize²,C1通常取3- 反投影公式
Z=f*B/Q,Q是视差,单位必须是像素,别混成毫米
方案二:硬件同步双摄(工业级)
// C++ + OpenCV + GigE Vision SDK
#include <opencv2/opencv.hpp>
#include "GigE_Vision.h"// 1. 初始化双相机, 强制同步触发
Camera cam_left, cam_right;
cam_left.Init("192.168.1.100");
cam_right.Init("192.168.1.101");
cam_left.SetTriggerMode(1); // 硬件触发
cam_right.SetTriggerMode(1);// 2. 同步采集
cv::Mat left_img, right_img;
cam_left.Grab(left_img);
cam_right.Grab(right_img);// 3. 后续处理同方案一
关键差异:
- 用GigE Vision或Camera Link接口,帧率稳定120fps+
- 触发信号线接同一GPIO,保证微秒级同步
- 标定参数必须用高精度标定板(20mm角间距,误差±0.02mm)
方案三:深度学习StereoNet(高精度)
import torch
import torch.nn as nnclass StereoNet(nn.Module):def __init__(self):super().__init__()self.encoder = ResNet50(pretrained=True)self.feature_extractor = FeatureExtractor(128)self.disparity_head = DisparityHead(max_disp=192)def forward(self, left_img, right_img):feat_l = self.encoder(left_img)feat_r = self.encoder(right_img)# 特征匹配cost_volume = self.feature_extractor(feat_l, feat_r)disparity = self.disparity_head(cost_volume)return disparity# 推理
model = StereoNet().cuda()
model.load_state_dict(torch.load("stereo_net.pth"))with torch.no_grad():left = torch.from_numpy(left_img).float().cuda().unsqueeze(0)right = torch.from_numpy(right_img).float().cuda().unsqueeze(0)disparity = model(left, right)depth = focal * baseline / disparity
核心差异:
- 输入是RGB图,不是灰度图
- 视差图是浮点数,精度更高
- 需要GPU推理,T4显卡单帧30ms
- 训练数据必须自采,KITTI数据域差距太大
适用场景与选型建议
选OpenCV纯视觉:
- 机器人原型验证,预算<1000元
- 距离测量精度±2cm够用
- 不需要实时性,离线处理
- 典型案例:AGV避障、手势识别
选硬件同步双摄:
- 工业检测,精度要求±0.5cm
- 帧率>60fps
- 物体运动速度快(>1m/s)
- 典型案例:产线缺陷检测、AR眼镜
选深度学习:
- 自动驾驶,精度±0.1cm
- 光照变化大(隧道、逆光)
- 纹理稀疏区域(白墙、天空)
- 典型案例:L4级自动驾驶、三维重建
避坑指南:
- 标定板别用打印纸,用亚克力板,角点精度±0.02mm
- 光照必须均匀,用无影灯,别用自然光
- 基线别太短,<5cm视差分辨率不够,>30cm遮挡严重
- 图像对齐别省,
cv2.undistort必须调,畸变不校正,视差全错 - 视差图去噪,用
cv2.fastNlMeansDenoising,不然噪声点距离全飘
一个真实案例:某机器人公司用USB摄像头做双目测距,精度±5cm。换硬件同步相机+高精度标定板,精度直接到±0.3cm。成本增加3000元,但故障率降了80%。
这个知识点你面试被问过吗
双目测距的视差-距离反投影公式Z=f*B/Q,我面过3个候选人,2个写错了。一个把Q当成视差差值,一个把B当成基线长度但单位搞混。
还有cv2.StereoSGBM_create的numDisparities为什么必须是2的幂?答不上来的,基本没调过OpenCV立体匹配。
这个知识点你面试被问过吗?留言说说你被问倒过哪道题。