OpenCV相机测距实战:单目与双目标定到深度估计
发布时间:2026/9/28 17:07:36来源:尧图网络
简介基于Python与OpenCV构建的普通相机图像测距系统面向计算机视觉初学者及立体成像项目开发者解决单目与双目相机标定校正、视差计算和深度测量等核心问题。压缩包共41个文件约2.35MB包含26张左右相机标定与视差测试用JPG图像、6张PNG标定结果示意图、4个可直接运行的Python脚本主程序、双目标定、单目校准、绘图辅助、2个说明文档和1份DOCX配套资料目录划分清晰便于按模块对照学习。系统覆盖相机内参求取、双目标定外参确定、立体特征匹配、视差图生成与深度映射的完整技术流程。通过棋盘格标定板计算焦距和畸变系数采用SIFT、SURF或ORB等算法完成立体匹配并将视差转换为实际物理距离。资源配套真实拍摄的左右图像与标定结果适合需要快速上手图像测距实现细节的开发者。已有179人学习使用。1. 普通相机测距的第一步先分清单目和双目谁在干体力活拿着一个普通USB摄像头去做图像测距很多人的第一反应是“OpenCV里是不是有现成的测距函数”。答案是有一条路但没有任何一个函数能让你不标定就直接读出真实距离。所谓普通相机图像测距系统本质上是把相机的成像几何关系还原出来——相机标定负责告诉你感光芯片和镜头之间的真实空间关系视差计算或单目几何假设负责把这个关系投影到真实世界。单目测距的工程路径依赖“目标在已知平面上的几何约束”比如车辆底盘贴地、行人身高近似双目测距则靠左右相机的视差做三角化理论上对任意可见点都能给出深度。选哪条路取决于你的使用场景里有多少“已知条件”可用。适合做这套方案的人是手里只有普通相机、不想上激光雷达和深度相机却需要在2到5米范围内拿到厘米级结果的开发者。无论是毕设、视觉大作业还是小型巡检项目这条路都能走通但前提是你愿意在标定棋盘格和参数调优上花足时间。这往往不是算法玄学而是流程工程。2. 相机标定与校正从棋盘格照片到可用的内参矩阵2.1 为什么标定不能跳过内参、畸变直接写进测距公式普通相机的镜头不是理想小孔。OpenCV里能算的测距公式全部依赖三个量焦距fx/fy、光心位置cx/cy、畸变系数k1/k2/p1/p2/k3。如果不做标定直接使用标称参数或瞎填一组数值误差通常在10%以上极端情况下光心偏了30像素远处目标距离直接偏出几十厘米。我见过太多人写双目代码时把焦距填成传感器尺寸和镜头焦距的换算值结果视差图算出来深度整体平移这就是典型的黑匣子式翻车。先说明标定要处理的两类问题。第一类是内参矩阵mtxfx/sx 0 cx0 fy/sy cy0 0 1fx和fy分别是x、y方向的等效焦距单位是像素cx、cy是主点理论上在图像中心但装配误差会让它偏移几十个像素。第二类是畸变系数dist径向畸变k1/k2/k3和切向畸变p1/p2。对普通USB摄像头k3一般可以忽略但对广角镜头必须保留。OpenCV提供了cv2.calibrateCamera来同时求解这两个量前提是你提供足够的棋盘格角点图像对。如果用的是鱼眼镜头普通标定流程会失效你得改用cv2.fisheye.calibrate数学模型不同。拿普通针孔模型的标定结果去校正鱼眼图像边缘畸变永远消不干净。常见做法是先确认镜头视场角超过120度就按鱼眼流程单独处理。2.2 单目标定的最小可运行流程calibrateCamera标定的输入是至少10到15张左右不同角度、不同距离拍摄的棋盘格照片。我这里说的棋盘格是指内角点为9x6的打印图案每格边长需要提前量准单位用米。这是整个流程里最容易被忽略的坑——边长量错一毫米焦距就错一批像素后续测距全盘偏。下面是单目标定代码直接保存内参和畸变系数到npz文件import cv2 import numpy as np import glob chessboard_size (9, 6) # 内角点数不是格子数 square_size 0.025 # 每个黑白格的物理边长单位米 # 准备世界坐标系中的棋盘格角点坐标z恒为0 objp np.zeros((chessboard_size[0] * chessboard_size[1], 3), np.float32) objp[:, :2] np.mgrid[0:chessboard_size[0], 0:chessboard_size[1]].T.reshape(-1, 2) objp * square_size obj_points [] # 世界坐标 img_points [] # 图像坐标 images sorted(glob.glob(calib_images/*.jpg)) for fname in images: img cv2.imread(fname) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, corners cv2.findChessboardCorners(gray, chessboard_size, None) if ret: # 亚像素精确化 criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 50, 1e-6) corners2 cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) obj_points.append(objp) img_points.append(corners2) ret, mtx, dist, rvecs, tvecs cv2.calibrateCamera( obj_points, img_points, gray.shape[::-1], None, None ) np.savez(camera_params.npz, mtxmtx, distdist) print(重投影误差:, ret) print(内参矩阵:\n, mtx) print(畸变系数:, dist.ravel())cv2.findChessboardCorners的输入是灰度图直接传彩色图会报错返回的corners是形状为(N,1,2)的数组cornerSubPix要求这个维度别自行reshape。calibrateCamera输出五个量其中ret是重投影均方根误差单位像素小于0.5说明标定质量不错mtx是3x3内参矩阵dist是畸变系数默认是5个元素即[k1,k2,p1,p2,k3]。有一个经验值得记住标定照片的角点必须在画面里分布得足够散。全部把棋盘格放在正中央拍畸变参数会非常不稳定因为边缘信息缺失。我一般会保证每张图里棋盘格占比在四分之一到一半之间同时覆盖四个角落和中心区域。关于棋盘格的尺寸我在上面代码里用了0.025米即25毫米。打印后必须用尺子量一下实际边长打印比例伸缩很常见。有条件的把棋盘格贴在硬质平板上贴纸褶皱会在亚像素细化时引入系统性偏差重投影误差再小都救不回来。2.3 双目相机标定与立体校正让左右图“平视”同一个世界单目标定只解决单个相机看到的世界双目测距还需要知道两个相机之间的相对位姿关系即旋转矩阵R和平移向量T。这就是stereoCalibrate要做的事。它输入左右相机各自的内参和畸变系数以及同一个棋盘格在左右相机画面中的角点坐标输出R和T以及本质矩阵E、基础矩阵F。双目标定的本质是优化一个联合代价右相机看到的点经过R和T变换后应该在左相机坐标系里出现在正确的极线上。如果不做立体校正左右图中的对应点不在同一水平线上视差搜索就成了二维搜索计算量和误匹配率都会大幅上升。立体校正有两个阶段。第一个阶段是stereoRectify根据R和T把左右相机的光轴旋转到平行同时让左右图像平面共面且行对准第二个阶段是remap用生成的映射表把原图像重采样成校正图像。下面是完整的校正流程包括保存映射表供后续直接加载import cv2 import numpy as np # 从单目标定结果加载左右相机参数 dataL np.load(left_camera.npz) dataR np.load(right_camera.npz) mtxL, distL dataL[mtx], dataL[dist] mtxR, distR dataR[mtx], dataR[dist] # 双目标定使用同一棋盘格的左右角点 ret, mtxL, distL, mtxR, distR, R, T, E, F cv2.stereoCalibrate( obj_points, img_points_l, img_points_r, mtxL, distL, mtxR, distR, gray_size[::-1], # 图像宽高 criteria(cv2.TERM_CRITERIA_MAX_ITER cv2.TERM_CRITERIA_EPS, 100, 1e-5), flagscv2.CALIB_USE_INTRINSIC_GUESS ) # 立体校正alpha0表示只保留公共区域丢弃遮幅 R1, R2, P1, P2, Q, roi1, roi2 cv2.stereoRectify( mtxL, distL, mtxR, distR, gray_size[::-1], R, T, alpha0 ) # 生成重映射表 map1L, map2L cv2.initUndistortRectifyMap( mtxL, distL, R1, P1, gray_size[::-1], cv2.CV_32FC1 ) map1R, map2R cv2.initUndistortRectifyMap( mtxR, distR, R2, P2, gray_size[::-1], cv2.CV_32FC1 ) np.savez(rectify_maps.npz, map1Lmap1L, map2Lmap2L, map1Rmap1R, map2Rmap2R, QQ, P1P1) # 对左右图做校正 imgL_rect cv2.remap(imgL, map1L, map2L, cv2.INTER_LINEAR) imgR_rect cv2.remap(imgR, map1R, map2R, cv2.INTER_LINEAR)这里的flags使用了cv2.CALIB_USE_INTRINSIC_GUESS意思是把单目标定得到的内参作为初始值再在双目标定中做微调。实际上双目标定并不会重算焦距它是在优化R和T的同时微调内参。如果左右相机本身不同型号或者分辨率不一致需要各自先做单目标定再联合优化不能只标其中一边。保存下来的Q矩阵是一个4x4的投影矩阵在后面的reprojectImageTo3D中会用到它把视差图直接映射成三维坐标。P1矩阵的[0][0]即左相机的焦距fx在手动计算深度时也要用到。这里有个常见错误很多人直接把P1当成mtx去算深度但立体校正之后光轴已经重新定义P1才是校正后左相机的投影矩阵。用原内参去算深度会出现系统性偏移。2.4 标定质量的快速验证画线时间到了做完校正后总得先确认极线是否对齐再进入视差计算。最直观的验证方法是把左右校正图画在同一个窗口里每隔一定行数画一条水平直线。如果角点或物体轮廓上的特征点在水平线附近来回跳动超过1到2个像素说明校正不够好。原因通常是双目标定用的照片太少或者棋盘格在视野中占比太小。还有一个隐蔽原因标定照片里有一个位置的棋盘格被手指按住了边缘cornerSubPix把指尖当成了角点。检查方法很暴力——把每一张用于标定的棋盘格检测结果可视化确认角点顺序没有错位。角点顺序是最容易翻车的地方一旦标定照片左上角被裁掉了棋子整张图的内角点检测顺序全部平移一位世界坐标和图像坐标对不上重投影误差直接飙到几个像素。一个更省事的做法是把标定工具换成OpenCV的cv2.CharucoBoard。ChArUco板由ArUco标定板与棋盘混合构成它允许标定照片中出现部分遮挡只要有足够多的角点就能参与优化对新手友好得多。如果你打算长期做相机标定我建议直接上ChArUco板棋盘格板可以作为备用方案。3. 双目视差计算与深度测量SGBM参数和三角化落地3.1 视差是什么从极线约束到深度公式双目测距的核心物理公式是深度Z f * B / d。其中f是焦距像素单位B是左右相机光心之间的水平距离基线d是同一个三维点在左右图像中的水平像素差叫视差。这个公式成立的先决条件就是上一节的立体校正——校正后左右图像行对齐同一空间点只看水平方向的像素偏移就行。这也就是为什么前面花那么多篇幅做标定和校正一分钱一分货标定质量直接决定深度公式的精度。在OpenCV里d的单位不是整数像素SGBM输出的视差图以1/16像素为最小单位。也就是说计算真实视差时必须把输出值除以16.0否则深度会整体偏小16倍。这个坑我印象很深第一次跑通SGBM时看到视差图上物体边缘亮度正常但reprojectImageTo3D后的深度值小得离谱排查半天才发现是忘了除以16。视差计算有两种典型算法BMBlock Matching更快但精度差SGBMSemi-Global Block Matching在普通CPU上也能跑到实时。这个标题下我们一般选SGBM。SGBM的核心思路是在每行上做块匹配然后用动态规划对整行进行能量平滑既保留边缘又抑制噪声。它适用于纹理适中的场景白墙、天空这种平坦区域是它的天敌。3.2 SGBM的Python调用与全参数调优先给出一段可运行的SGBM计算视差图的代码包含必要的预处理和后处理import cv2 import numpy as np # 加载校正映射表 maps np.load(rectify_maps.npz) imgL_rect cv2.remap(imgL, maps[map1L], maps[map2L], cv2.INTER_LINEAR) imgR_rect cv2.remap(imgR, maps[map1R], maps[map2R], cv2.INTER_LINEAR) # 转灰度并做直方图均衡化提升弱纹理区域匹配率 grayL cv2.cvtColor(imgL_rect, cv2.COLOR_BGR2GRAY) grayR cv2.cvtColor(imgR_rect, cv2.COLOR_BGR2GRAY) clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) grayL clahe.apply(grayL) grayR clahe.apply(grayR) # 创建SGBM实例 sgbm cv2.StereoSGBM_create( minDisparity0, numDisparities64, blockSize11, P18 * 3 * 11 ** 2, P232 * 3 * 11 ** 2, disp12MaxDiff1, uniquenessRatio10, speckleWindowSize100, speckleRange32, modecv2.STEREO_SGBM_MODE_SGBM ) # 计算视差图并转成浮点型注意除以16 disparity sgbm.compute(grayL, grayR).astype(np.float32) / 16.0 # 去掉无效像素 disparity[disparity 0] np.nan随后用reprojectImageTo3D把视差图转成三维点云其中Q矩阵来自stereoRectify的输出points_3d cv2.reprojectImageTo3D(disparity, Q) # points_3d的第三个通道就是该像素对应的深度值Z depth_map points_3d[:, :, 2]参数里最关键的是numDisparities和P1/P2。numDisparities必须是16的倍数它决定了能检测的最大视差范围。基线越短、目标越近需要的视差范围越大。比如基线12厘米、最近目标0.5米在640分辨率下视差可能达到80像素numDisparities要给到96或128。但给太大也不是好事因为大视差范围会让搜索时间变长同时远处的匹配噪声会被放大。blockSize必须是奇数典型值在5到15之间。值太小弱纹理区域匹配噪声大值太大物体边缘的深度会变得圆滑边缘处出现“背景污染”。P1和P2是平滑惩罚项控制视差图的平滑度。P1惩罚小梯度变化P2惩罚大梯度变化P2通常取P1的4到6倍。代码里用的83blockSize^2是OpenCV文档给出的经验参考实际项目中如果深度图边缘断裂严重可以适当增大P2如果整幅图过于平滑丢失细节则减小P2。uniquenessRatio表示在搜索范围内最优匹配值要超过次优匹配值多少百分比才认为是有效匹配。设小了会有大量误匹配设大了有效像素覆盖率会降低通常取5到15。speckleWindowSize和speckleRange用于剔除“斑点”状的小块孤立噪声值越大滤除能力越强但也会误杀细长物体的真实视差。3.3 从视差图到深度值选点与滤波拿到depth_map之后怎么取距离很多人直接取目标区域的均值或最值这不太靠谱。因为SGBM在物体边缘处必然存在几个像素的“绒毛”这些点的视差可能跳向背景直接把深度均值拉偏。常见做法是先在视差图上做中值滤波或双边滤波再取中心区域的中位数# 中值滤波消除零星噪声 depth_filtered cv2.medianBlur(depth_map, 5) # 手动给一个目标ROI取中位数作为最终距离 roi depth_filtered[120:180, 200:260] valid roi[np.isfinite(roi)] # 去掉无效点 final_depth np.median(valid) print(f目标距离: {final_depth:.3f} 米)为什么用中位数而不是均值因为深度图的噪声往往不是高斯噪声而是呈长尾分布的离群点。一个像素如果被错误地匹配到背景深度可能差出几十米均值会被这类点瞬间拉飞中位数则稳如泰山。如果取中位数后标准差依然很大说明这个区域不适合用SGBM测距可以考虑更高分辨率的图像或者引入时序平均。视差与深度的换算关系双目参数含义对精度的影响基线B左右相机光心水平距离B越大近处精度越高但远处匹配难度也越大焦距f校正后P1[0][0]f越大同一距离下视差越大深度分辨率越高视差误差Δd一般在0.5~1像素深度误差与Z²成正比距离越远误差增长越快图像分辨率像素总数分辨率减半视差量化误差翻倍从这张表能推导出一个工程结论双目测距的精度是按距离平方衰减的。在3米以内做到厘米级不难到10米时同样配置下误差可能就超过半米了。这也是为什么很多落地系统只把双目当作近距离避障传感器远距离还是靠激光雷达或单目几何。4. 单目测距的工程化方案已知高度与几何关系的简化实现4.1 小孔成像与相似三角形从一个实物的像素高度反推距离单目测距在所有讲述里都绕不开一个事实单个相机从图像上无法恢复绝对尺度。但如果有先验信息——比如已知物体的实际高度、宽度或者已知相机安装高度和俯仰角——尺度就被约束住了此时单目测距的精度在某些情况下甚至优于双目。最常见的公式来自相似三角形设物体实际高度为H米在图像中占据h像素相机焦距fy像素则距离d fy * H / h。这个公式对正对镜头的竖直目标如行人、标牌、杆子尤其好用。前提是物体底部没有被遮挡且物体姿态与地面垂直。如果你要测一辆汽车的车距对着车尾牌照区域用这个公式比整体车身更稳因为牌照尺寸是标准化的而且不随车型高度波动。4.2 地面投影测距相机固定在车顶或云台时的几何计算巡检机器人和车载系统的情况更典型相机高度h固定光轴相对水平面有一个俯仰角theta。目标在地面上的点对应图像中的某一行y。此时距离公式变成import numpy as np # 从标定得到内参 mtx np.load(camera_params.npz)[mtx] fy mtx[1, 1] cy mtx[1, 2] # 相机安装高度和俯仰角弧度 cam_height 0.35 # 相机离地高度单位米 pitch_angle 0.12 # 光轴俯仰角单位弧度约7度 def ground_distance(y_pixel): # y_pixel是目标接触地面那一行的像素纵坐标 delta (y_pixel - cy) / fy angle pitch_angle np.arctan(delta) return cam_height / np.tan(angle) # 示例目标底部在图像第580行 target_y 580 dist ground_distance(target_y) print(f目标距相机地面投影点: {dist:.2f} 米)这个公式的本质是把每一个像素行映射到地面上的一个距离值。也就是说你可以提前算好一张“距离查找表”行从0到height-1每个y对应一个距离。运行时直接用目标底部像素的y值查表即可不用每帧做三角函数运算速度更快也更适合嵌入式设备。地面投影测距最怕两种场景。第一种是地面不平有坡度。公式假设地面是绝对平面遇到上坡或下坡目标底部的像素位置不变但真实距离变了。解决办法是引入惯性测量单位IMU数据实时修正俯仰角theta或者在多个距离上预先标定校准。第二种是俯仰角测不准。pitch_angle哪怕只有1度的误差在5米外就会累积出几十厘米的偏差。一个实用技巧是用一条5米长的卷尺在地面做参考反推theta的精确值而不是依赖角度传感器读数。4.3 单目平面单应性标定一个地面矩形换更高精度如果目标在同一个地面上移动可以用更稳的单应性方法即cv2.findHomography。它需要至少4组“图像坐标-地面坐标”对应点。你可以在地面上铺一张已知尺寸的标定布把四个角点的像素坐标和以相机为原点的世界坐标配对得到单应矩阵H。之后任意地面点的像素坐标乘H就能得到它相对相机的平面距离。import cv2 import numpy as np # 地面坐标系以相机光轴在地面的投影点为原点 # 图像中四个角点对应的像素坐标 src_pts np.array([[210, 180], [430, 182], [580, 420], [130, 410]], dtypenp.float32) # 对应的真实地面坐标单位米 dst_pts np.array([[0.0, 0.0], [2.0, 0.0], [2.0, 1.5], [0.0, 1.5]], dtypenp.float32) H, _ cv2.findHomography(src_pts, dst_pts) def pixel_to_ground(px, py): pt np.array([[[px, py]]], dtypenp.float32) ground cv2.perspectiveTransform(pt, H) gx, gy ground[0][0] return np.sqrt(gx ** 2 gy ** 2) # 到原点的平面距离单应性矩阵的好处是同时矫正了俯仰角和镜头畸变的影响而且不需要单独测量相机高度。它的误差来源主要是四个标定点的手动选取精度。选点时不要用肉眼在图像上点一下就完事最好放一个ArUco板到地面指定的位置用cv2.estimatePoseSingleMarkers获取精确角点这样标定误差可以从十几个像素压到1个像素以内。另外一种更彻底的做法是直接用地面标定布提取角点自动生成上百个对应点对把这些点对丢给findHomography做最小二乘鲁棒性远超四点法。5. 测距系统的五个典型坑从标定到视差图的排查手册5.1 标定重投影误差很低但测距结果还是系统性偏移现象calibrateCamera返回的ret小于0.3像素看起来一切正常。换到实际场景测距时发现在2米处偏差足有15厘米且误差随距离增大而增大。原因重投影误差低只代表标定板角点的检测点和重投影点重合度好并不直接保证内参正确。最常见的情况是棋盘格边长的物理值量错了。另一个高发原因是打印的棋盘格没有保持完全平整手持时轻微弯曲导致每张图的角点世界坐标与真实位置有偏差。还有一种情况是标定板只出现在画面中央没有得到足够的畸变信息边缘区域的dist系数外推失真。解决用精度高一点的钢尺或游标卡尺多次测量棋盘格边长每个方向的格子量三个位置取平均把棋盘格贴在亚克力板或玻璃板上避免柔性弯曲重新补拍一批棋盘格靠近图像边缘的照片。我这里有一条经验重投影误差低于0.5像素不代表可以收工还必须在实景中对一个已知距离的目标做一次“端到端校验”。标定和数学没有问题但物理世界的量测有误差只有端到端校验能发现。5.2 findChessboardCorners找不到角点或检测结果错位现象标定脚本运行后有一半的照片ret为False或者角点检测成功但可视化时发现角点顺序错乱棋盘边缘的角点被连到了其他位置。原因OpenCV的棋盘格检测要求棋盘格完整出现在画面中且内角点不能被遮挡。打印机如果用了省墨模式黑白格对比度不足也会导致检测失败。另一个常见问题是你生成棋盘格时用的是“10x7格子”而不是“9x6内角点”两者相差一个单位如果用错尺寸配合CALIB_CB_ASYMMETRIC_GRID标志就会导致顺序错位。解决调用findChessboardCorners前先做一次直方图均衡化或CLAHE提升低对比度环境下的检测率。打印棋盘时留足白边确保最外圈黑白格完整。在拍摄时手持棋盘稍微倾斜30度到45度避免正对镜头这样角点的亚像素提纯更容易收敛。出现错位时把img_points打印出来查看前三个点的坐标变化趋势顺序错乱的标定结果宁可弃用也不要混入优化。5.3 SGBM深度图大片空洞目标区域全是NaN现象视差图整体稀疏目标表面散落着黑色无效像素。CLAHE和滤波都做过了覆盖面积还是不到六成。原因弱纹理区域是SGBM的天然盲区。纯色纸箱、墙面、天空这类区域局部窗口内的灰度方差太小匹配代价在搜索范围内拉不开差距容易误匹配。另一个原因可能是左右图亮度不一致比如两个摄像头自动曝光参数不同同一物体在左右图上灰度差异过大匹配代价整体偏高。解决优先把左右相机切换到手动曝光模式锁定同一个曝光时间和增益值。然后是参数层面的调整。一是把numDisparities加大给搜索空间更宽裕的候选范围二是降低uniquenessRatio让次优匹配更容易被接受三是用cv2.ximgproc.createRightMatcher配合WLS滤波器做后处理它能利用左右一致性检验丢掉错误匹配然后用相邻点的深度插值填补空洞。另外一个工程技巧是改分辨率。SGBM在640x480上算不动的地方提高到1280x720往往能匹配上因为局部纹理在更高分辨率下包含更多鉴别信息。但分辨率提高会带来计算时间的明显上涨做实时系统时需要权衡。5.4 双目基线选短了还是选长了深度误差范围完全失控现象基线12厘米时近距离0.5米到2米效果不错但3米外误差开始迅猛增大换成40厘米基线近处反而不准了。原因深度误差δZ Z² / (f × B) × δd。这个公式说明近处目标视差大基线短也能给足够的分辨率远处目标视差小基线越短越容易被像素量化误差淹没。换成大基线的同时左右相机的视角差变大近距离物体在左右图上的外观差异剧烈变化SGBM的匹配窗口无法适应这种透视变形误匹配率上升。解决按你实际关心的距离范围来选基线而不是越长越好或越短越好。经验公式是基线B Z_far × 视差精度(像素) / f。假设焦距800像素、视差精度0.8像素、期望10米处误差不超过0.5米那B大约需要0.8×10/800 0.01米实际上这个精度要求很保守要精确到0.2米B就需要约50毫米以上。反过来基线定了之后能可靠测距的距离上限大约是基线乘以焦距再除以可接受的视差误差。没必要为了远处准就去加长基线因为镜头视角会缩小近处盲区也会变大。另一个容易被忽略的约束是结构刚性。买来的双目摄像头模块如果外壳是塑料且没有固定螺丝在运输或温度变化中左右相机的相对位姿都会漂移刚标定好的R和T就不准了。工业方案通常用铝合金基座锁死两个摄像头前面板还要加防震缓冲。你的这个项目如果用的是普通USB摄像头自制双目支架务必保证两个相机之间没有自由度不然每次开机都要重新双目标定。5.5 换分辨率或裁剪画面后内参矩阵没有同步缩放现象标定时用1280x720之后为了性能把图像resize成640x360其他代码不变测距结果整体偏大或偏小。原因内参矩阵里的fx、fy和cx、cy都是像素单位它们是针对原始分辨率计算的。图像缩放后单一像素对应的物理尺度变了焦距的像素值也必须按比例缩放。如果不缩放相当于用“每毫米10像素”的焦距去解释“每毫米5像素”的图像深度自然偏。解决如果图像是按等比缩放的直接对内参矩阵做缩放处理即可。假设从宽W_old变成W_new缩放比例s W_new / W_old新的fx fx * sfy fy * scx cx * scy cy * s。注意dist畸变系数不需要缩放它是归一化的。如果你在代码里去掉了某些ROI区域比如切掉顶部那就不只是缩放的问题了主点cx/cy的实际位置已经偏移需要重新标定或者对ROI裁剪做明确的主点平移补偿。这个场景下我没有更好的捷径重新标定一份内参是更稳妥的方案。6. 用离线回归验证测距精度一个可复现的实测流程当标定和视差计算都跑通之后最容易被漏掉的一环是“精度验证”。很多人在屏幕上看到深度图上叠加了彩色伪影就觉得项目完成了其实心里完全没有底。我自己的习惯是任何测距系统上线之前必须做一次离线回归把相机架在固定位置用卷尺量出一系列真实距离在每个距离上采集若干张图像用保存好的标定映射和参数批处理计算深度最后画误差曲线。具体流程分三步。第一步固定相机后用卷尺在光轴方向标出1米、1.5米、2米、2.5米、3米、4米、5米七个位置每个位置放一个竖直条纹纸箱固定好后各拍10帧。第二步写一个Python脚本遍历所有图像对每帧提取目标区域深度中位数记录预测值。第三步计算每个真实距离下的均值、标准差和绝对误差百分比。如果标准差超过均值的10%说明深度不稳定优先排查是SGBM参数问题还是标定板物理尺寸问题。如果标准差稳定但均值偏大或偏小则是标定内参或基线的系统性误差。接下来重点观察误差随距离的变化趋势。单目测距误差随距离线性放大双目测距误差随距离平方放大。如果你发现5米处误差突然从厘米级跳到米级这不是算法出了问题而是超出了该基线和分辨率的可靠测距范围。这时候要么缩短使用距离要么加长基线、换更大焦距镜头而不是盲目调SGBM参数。我自己的经验是在项目交付时把最终参数固化成一个YAML配置文件包含内参、畸变、校正映射路径、SGBM参数和距离查找表主程序每次都从配置加载而不是重复标定。这样任何一次参数调整都能追溯也方便同事接手时快速复现。跑回归脚本之前关掉自动白平衡和自动曝光否则左右图亮度不一致会让视差质量时好时坏。十分建议把校准用的棋盘格和卷尺放进项目附件里长期保存每次相机被拆装过都要重新跑一遍回归流程。最后提醒一句不管是单目还是双目方案相机只要被磕碰过、镜头焦距被旋转过、双目支架被拧过螺丝就必须重新走标定流程。没有哪种标定是可以一次做完用一辈子的。把这个习惯固化下来你的测距系统才算真正像个工程产品而不是只在演示时能看的原型。做这一行久了你会发现最值钱的不是那个标定算法而是你肯不肯在每次动过相机之后老实重拍一组棋盘格。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网