
双目测距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 failed
P1和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立体匹配。
这个知识点你面试被问过吗?留言说说你被问倒过哪道题。