news 2026/10/13 9:22:44

基于OpenCV的单目与双目相机测距:标定、视差与精度实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于OpenCV的单目与双目相机测距:标定、视差与精度实践

简介:一套基于Python和OpenCV的普通相机图像测距系统资料包,面向计算机视觉与立体成像方向的开发者和学习者,解决通过普通相机拍摄图像实现精确距离测量的问题。系统涵盖单目相机标定校正、双目相机空间位置关系标定、左右视图视差计算与深度映射,完整覆盖从标定板图像采集到深度图生成的流程。压缩包共41个文件,大小约2.35MB,以JPG标定图像、PNG结果图、Python脚本、说明文档等为主,包含左右相机拍摄序列、标定板图片、立体校正与视差结果图,以及配套的标定、主程序等可运行脚本,便于直接对照实验。资源还附带依赖清单、说明文件和附赠文档,可快速搭建环境,理解各模块的调用关系。已有188人学习下载,适合作为课程设计、竞赛项目或实际工程中图像测距模块的参考实现,能帮助读者掌握相机标定、特征匹配、视差计算和深度图生成等关键环节,从棋盘格采集到深度映射形成完整闭环,利于新手入门与项目二次开发。

1. 单目与双目之争:普通相机测距到底怎么从一张照片里算出距离

“基于Python和OpenCV的普通相机图像测距系统”这个标题说的东西,简单讲就是:不买深度相机、不用激光雷达,只靠一个USB摄像头或手机摄像头,利用标定、校正、视差计算这串OpenCV里现成的工具链,把图像里的像素位置换算成真实距离。这个方向看起来是一条直路,真正落地的人都知道,坑全埋在标定和参数里。

先把结论摆出来:单目测距做的是“已知物体尺寸 → 像素占比 → 距离”的比例换算,它本身并不具备测量未知物体的能力;双目测距靠左右两幅图像的视差,在普通相机硬件上,才算是真正“自己量出距离”的方案。因此选型不是看哪个代码更短,而是看你的场景:固定闸机、限高杆、固定工位这类目标尺寸已知、距离不超过三四米的环境,单目足够;机器人避障、料位监测、连续深度输出这类需求,双目才扛得住。下面按主线拆开,给你能直接照着跑的流程和参数。

2. 相机标定与畸变校正:先把像素坐标变成物理坐标

测距的前提,是把图像坐标真正对应到相机坐标系里。普通相机镜头的畸变如果不处理,远处的点可能在像素上偏出好几个像素,这对测距来说是致命的。标定这一步做得扎实,后面所有测量才有意义。

2.1 张氏标定:棋盘格、内参与畸变系数的最小必要概念

标定要做的事,是找到相机内参矩阵和畸变系数。内参矩阵把相机坐标系下的三维点投影到像素平面,包含焦距(以像素为单位)和主点位置;畸变系数描述镜头变形,径向畸变让直线变弯,切向畸变源于镜片装配误差。两者一起决定了一个三维点会落在图像的哪个像素上。

张氏标定法的思路很直接:拿一块打印出来的棋盘格,在不同角度、不同位置拍多张照片,检测角点,把棋盘的已知物理坐标和图像像素坐标对应起来,求解这组投影参数。棋盘格用A4哑光纸打印,贴在硬质平板上,避免纸张褶皱。角点数量要选对内角点数,比如(10, 7)表示横向10个内角点、纵向7个内角点,印出来实际是11乘8的格子块。

拍摄数量建议20到30张,关键不是多,而是姿势分布:远、近、上、下、左、右、旋转、倾斜都要有,画面四角和中间区域都必须让角点出现过。很多人拍十几张全是画面正中央的正面照,结果畸变参数完全不收敛,后面测距一塌糊涂。

2.2 拍摄标定照片与执行标定的完整脚本

先要一张标定板图片。下面这个脚本生成一张可打印的棋盘格,每格24毫米,300dpi,带白边方便裁切:

import cv2 import numpy as np # 棋盘格参数:内角点数 (10, 7),每格 24 mm pattern_size = (10, 7) square_size = 24 # 单位:毫米 dpi = 300 pixels_per_mm = dpi / 25.4 # 左右留白边,打印后不容易裁到黑格 margin = 20 # 单位:毫米 board_w = pattern_size[0] * square_size board_h = pattern_size[1] * square_size img_w = int((board_w + 2 * margin) * pixels_per_mm) img_h = int((board_h + 2 * margin) * pixels_per_mm) img = np.full((img_h, img_w), 255, dtype=np.uint8) start_x = int(margin * pixels_per_mm) start_y = int(margin * pixels_per_mm) cell_px = int(square_size * pixels_per_mm) for i in range(pattern_size[0]): for j in range(pattern_size[1]): x = start_x + i * cell_px y = start_y + j * cell_px if (i + j) % 2 == 0: img[y:y + cell_px, x:x + cell_px] = 0 cv2.imwrite('chessboard.png', img)

这段的要点是pattern_size是内角点数,不是格子数,square_size以毫米为单位,它决定了后面所有距离输出的单位。如果你打印出来的实际格子不是24毫米,一定要量了再填,否则标定结果全部量纲错误。打印分辨率建议不低于300dpi,太低会让角点检测抖动。

标定主体脚本如下:

import cv2 import numpy as np import glob pattern_size = (10, 7) square_size = 24.0 # 棋盘格平面上的三维点坐标,Z方向为0 objp = np.zeros((pattern_size[0] * pattern_size[1], 3), np.float32) objp[:, :2] = np.mgrid[0:pattern_size[0], 0:pattern_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) # 粗略检测角点 found, corners = cv2.findChessboardCorners(gray, pattern_size, None) if not found: print('skip:', fname) # 没检测到直接跳过 continue # 亚像素细化,把角点精度从整数像素提到亚像素 criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corners_sub = cv2.cornerSubPix(gray, corners, (5, 5), (-1, -1), criteria) obj_points.append(objp) img_points.append(corners_sub) if len(obj_points) >= 5: # flags 里 CALIB_ZERO_TANGENT_DIST 表示只估计径向畸变 rms, mtx, dist, rvecs, tvecs = cv2.calibrateCamera( obj_points, img_points, gray.shape[::-1], None, None, flags=cv2.CALIB_ZERO_TANGENT_DIST) np.savez('camera_params.npz', mtx=mtx, dist=dist, rms=rms, img_size=gray.shape[::-1]) print('RMS(px):', rms)

findChessboardCorners返回的found是布尔值,很多实践里检测不出来的照片会被静默忽略,如果你拍到的可用照片少于15张,建议重拍而不是硬凑。cornerSubPix的窗口(5, 5)对大多数棋盘格足够,如果标定板分辨率很低可以改成(11, 11)。calibrateCamera返回的rms是重投影误差,单位是像素,经验上小于0.5可用,小于0.2说明标定质量不错。这个rms只是同一批照片上的平均误差,不能完全代表实测距离精度。

2.3 标定结果怎么判断:重投影误差与图像校正观察

拿到参数后不要急着测距,先做两件事。第一,打印一张验证棋盘格,单独放在几个位置,用已保存的参数算位姿,再把角点投回去,看平均像素偏差是否小于0.5像素。第二,直接对一张有明显直线边缘的照片做畸变校正,观察画面边缘的直线是否变直,绿色通道比对校正前后的像素位置。

import cv2 import numpy as np data = np.load('camera_params.npz') mtx, dist = data['mtx'], data['dist'] img = cv2.imread('test_rect.jpg') h, w = img.shape[:2] # alpha=0 表示裁掉黑边,保留最多有效像素 new_mtx, roi = cv2.getOptimalNewCameraMatrix( mtx, dist, (w, h), 0, (w, h)) undistorted = cv2.undistort(img, mtx, dist, None, new_mtx) # roi 是去畸变后不含黑边的有效区域 x, y, w2, h2 = roi undistorted = undistorted[y:y + h2, x:x + w2] cv2.imwrite('undistorted.jpg', undistorted)

getOptimalNewCameraMatrix里的alpha取值范围是0到1,alpha=0裁切最大但画面里没有黑边,alpha=1保留所有原始像素但边缘会出现弯曲区域。单目测距建议直接用0,双目极线校正阶段一般取更小的裁剪策略,这个在第4章会具体说。

去畸变之后,画面边缘原本弯曲的门框、墙体边缘线应该变成直线。如果你发现标定rms很小但边缘直线依旧弯曲,多半是拍摄覆盖不全,畸变系数在边缘区域没有被有效约束,回炉重拍是唯一的后悔药。

3. 单目测距落地:相似三角形与 solvePnP 的取舍

单目测距从原理上分两档。第一档是相似三角形,代码最简单但约束最多;第二档是solvePnP,用一组已知的三维点和对应像素点直接求解相机外参,能容忍一定程度的倾斜视角,是实际工程里更值得用的方案。

3.1 相似三角形测距:公式、前提与失效边界

针孔模型下的测距公式是:

[ Z = \frac{f \cdot H}{h} ]

其中 (Z) 是目标距离,(f) 是焦距(单位是像素),(H) 是目标真实高度(单位毫米),(h) 是目标在图像里的像素高度。焦距来自标定内参矩阵的mtx[0, 0],目标高度需要提前量好。

这个公式成立的前提有三个:目标物完全正对相机,没有俯仰和偏航;目标高度已知且测量准确;相机光轴与目标平面垂直。真实场景中这三个条件几乎不可能同时满足。比如一个行人对相机有20度的偏角,像素高度会明显缩水,误差直接到百分之十几。所以这个公式只适合闸机、固定工位这类目标姿态可控的场景,我在这里写出来是为了让你先理解原理,工程上不要直接用。

3.2 用 solvePnP 做单目测距:不依赖正对假设

solvePnP的思路是:知道目标上N个三维点在物体坐标系下的坐标,也知道它们落在图像上的像素位置,就可以反求出物体坐标系相对于相机坐标系的旋转和平移。平移向量tvec的模长就是目标中心到相机光心的距离。

工程上最省事的做法是用ArUco码作为目标物,OpenCV自带的检测器直接返回四个角点,不需要自己写角点提取逻辑。代码:

import cv2 import numpy as np # 4x4 字典,ID数量50个,普通测距够用 aruco_dict = cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_4X4_50) params = cv2.aruco.DetectorParameters() def measure_distance(frame, mtx, dist, marker_size_mm=100.0): corners, ids, _ = cv2.aruco.detectMarkers( frame, aruco_dict, parameters=params) if ids is None: return None # marker 真实尺寸的一半,单位毫米 half = marker_size_mm / 2.0 # 物体坐标系下的四个角点,Z=0 表示贴在平面 objp = np.array([ [-half, half, 0], [ half, half, 0], [ half, -half, 0], [-half, -half, 0]], dtype=np.float32) # corners[0][0] 是第一个marker的四个角点,顺序与 objp 对应 ret, rvec, tvec = cv2.solvePnP( objp, corners[0][0], mtx, dist, flags=cv2.SOLVEPNP_ITERATIVE) if not ret: return None # 斜距:目标中心到相机光心的欧氏距离 dist_mm = float(np.linalg.norm(tvec)) # 前向距离:沿相机光轴方向的距离 z_mm = float(tvec[2][0]) # 重投影误差:评判这一帧外参解算质量 pts2, _ = cv2.projectPoints(objp, rvec, tvec, mtx, dist) reproj_err = float(np.mean( np.linalg.norm(pts2.reshape(4, 2) - corners[0][0], axis=1))) return dist_mm, z_mm, reproj_err

tvec的三个分量分别是目标原点在相机坐标系下的X、Y、Z坐标,单位跟objp一致。因为你传进去的坐标单位是毫米,tvec就是毫米。SOLVEPNP_ITERATIVE适合平面目标,也可以用SOLVEPNP_IPPE,它专门针对平面点做了加速和优化。如果画面里检测到多个marker,可以把各自的tvec取中位数平均,抖动会明显小于单个marker。

代码里我额外算了一步重投影误差,这是很多人忽略的地方。solvePnP返回的ret只是布尔成功标志,不是精度指标;真正看出这一帧外参解得好不好,要看把rvec、tvec投回去的像素误差。实测中如果重投影误差大于1像素,说明角点定位不稳,这一帧测距结果直接丢弃更安全。

3.3 单目测距的参数边界:目标尺寸、安装角与误差放大器

单目测距的误差有一个让人难受的特性:距离估计误差跟目标尺寸误差近似成正比。假设marker实际是100毫米,你量出来是101毫米,所有测距结果都会放大1%。安装角度带来的误差更隐蔽——相机朝下倾斜安装时,solvePnP勉强能解出外参,但对标定误差和角点噪声更敏感。

建议把工作距离控制在:

工作距离预期误差(经验值)说明
0.5米以内2%以内角点像素占比大,稳定性好
1到2米3%到5%像素量化误差开始占主导
3米以上5%到10%以上单目方案不建议超过这个范围

单目的本质是外推,不是测量。距离越远,目标在图像中占据的像素越少,角点亚像素精度带来的相对误差越大。所以单目测距的价值不在高精度,而在固定场景下的低成本粗略测距。如果你需要3米以外稳定的毫米级深度,直接看第4章的双目方案。

4. 双目深度测量:从极线校正到视差计算的完整链路

双目才是普通相机硬件下真正能算出绝对距离的方案。它不需要知道目标物体的任何尺寸,只要左右两幅图像中对应点匹配得上,就能用几何关系推出深度。整套链路里,标定、极线校正、立体匹配、参数调优每一步都可能翻车,下面按顺序拆开。

4.1 视差的物理意义:为什么基线越大测距越远

双目测距的核心公式是:

[ Z = \frac{f \cdot B}{d} ]

(f) 是焦距(像素单位),(B) 是两个相机光心之间的距离,通常叫基线,(d) 是同一个空间点在左右图像中的像素横坐标之差,也就是视差。深度与视差成反比,这意味着近处物体视差大、容易测准,远处物体视差小、一点点匹配误差都会被放大成很大的深度误差。

基线选择直接影响测量范围。基线越大,相同深度下视差越大,对远距离测量越有利;但基线过大会导致近处物体的左右视角差异过大,匹配成功率下降。经验上,工作距离在1到3米之间,基线取15到20厘米比较均衡;如果主要测5米以上,基线要尽量放宽,但要做好近处视野被裁掉的准备。两个相机要固定在一个刚性支架上,安装后不能有丝毫松动,否则标定结果会随时间报废。

4.2 双目联合标定与极线校正

双目标定的基础是左右相机分别完成第2章的单目标定。然后采集同一时刻的左右成对棋盘格照片,进入联合标定。这里的关键是左右摄像头必须拍同一时刻的棋盘格位置,不能左右分开拍,否则棋盘格目标的三维位置不一致,联合标定完全无意义。

import cv2 import numpy as np # 假设已经分别得到左右内参 mtx_l, dist_l = np.load('left_camera.npz'), np.load('left_camera.npz')['dist'] # 实际使用时替换为左右各自的参数文件 obj_points = [] img_points_l = [] img_points_r = [] # pairs = 左右图像路径列表,两两对应 for path_l, path_r in pairs: gray_l = cv2.cvtColor(cv2.imread(path_l), cv2.COLOR_BGR2GRAY) gray_r = cv2.cvtColor(cv2.imread(path_r), cv2.COLOR_BGR2GRAY) found_l, corners_l = cv2.findChessboardCorners(gray_l, pattern_size, None) found_r, corners_r = cv2.findChessboardCorners(gray_r, pattern_size, None) if not (found_l and found_r): continue criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-6) corners_l = cv2.cornerSubPix(gray_l, corners_l, (5, 5), (-1, -1), criteria) corners_r = cv2.cornerSubPix(gray_r, corners_r, (5, 5), (-1, -1), criteria) obj_points.append(objp) img_points_l.append(corners_l) img_points_r.append(corners_r) # 联合标定,固定已知内参只优化外参和基线 flags = cv2.CALIB_FIX_INTRINSIC rms_stereo, M1, d1, M2, d2, R, T, E, F = cv2.stereoCalibrate( obj_points, img_points_l, img_points_r, mtx_l, dist_l, mtx_r, dist_r, gray_l.shape[::-1], criteria=criteria, flags=flags) print('stereo RMS(px):', rms_stereo)

CALIB_FIX_INTRINSIC的意思是沿用单目标定的内参,只优化左右相机之间的旋转矩阵R和平移向量T。这个做法比从头联合优化所有参数更稳,因为单目标定已经在各自照片上充分收敛了。stereo RMS的经验值同样参考小于1像素,如果联合标定后RMS明显大于单目的RMS,通常是左右标定板照片数量不够或者棋盘格检测有误。

极线校正的目的是让左右图像中同一个三维点落在同一行上,这样后续匹配只需要沿水平方向搜索:

# 图像尺寸,左右一致 image_size = (width, height) R1, R2, P1, P2, Q, valid1, valid2 = cv2.stereoRectify( M1, d1, M2, d2, image_size, R, T, alpha=0) map1_l, map2_l = cv2.initUndistortRectifyMap( M1, d1, R1, P1, image_size, cv2.CV_32FC1) map1_r, map2_r = cv2.initUndistortRectifyMap( M2, d2, R2, P2, image_size, cv2.CV_32FC1) rect_l = cv2.remap(gray_l, map1_l, map2_l, cv2.INTER_LINEAR) rect_r = cv2.remap(gray_r, map1_r, map2_r, cv2.INTER_LINEAR)

校正完成后一定要用肉眼验证极线:在左图的某一行上选一个明显特征点,看右图同一点是否在相同行。如果差出好几个像素,优先怀疑左右相机没有严格刚性地固定,或者标定板照片太少。Q矩阵保存了重投影矩阵,后面reprojectImageTo3D要直接用到它。

4.3 SGBM 视差计算与深度转换

极线校正之后,立体匹配用StereoSGBM完成。它通过半全局匹配思想,在多个方向传播匹配代价约束,比简单的块匹配对弱纹理区域效果好一些,但也远不是万能的。

# 创建SGBM匹配器 block_size = 15 stereo = cv2.StereoSGBM_create( minDisparity=0, numDisparities=64, # 必须是16的倍数 blockSize=block_size, # 必须是奇数 P1=8 * 3 * block_size ** 2, P2=32 * 3 * block_size ** 2, disp12MaxDiff=1, preFilterCap=32, uniquenessRatio=10, speckleWindowSize=100, speckleRange=2, mode=cv2.STEREO_SGBM_MODE_SGBM_3WAY) # compute返回的是int16,真实视差需要除以16 disp16 = stereo.compute(rect_l, rect_r) disp = disp16.astype(np.float32) / 16.0 # 深度转换 points3d = cv2.reprojectImageTo3D(disp, Q, handleMissingValues=True) Z = points3d[:, :, 2]

这里有一个高频踩坑点:compute返回的disp16不是浮点视差,必须除以16才是以像素为单位的真实视差。直接拿disp16做归一化或深度计算,距离会缩水16倍,这是双目项目里最常见也最隐蔽的错误。numDisparities越大,能测到的深度范围越近,但搜索范围成倍增加,误匹配概率也随之上升。

SGBM 参数调整方向可以参考这张表:

参数经验范围调参方向
numDisparities64/96/128值越大测距越近,但计算量增大
blockSize9/15/21越大对低纹理越稳,但边缘细节丢失
P1/P283block² / 323block²越大视差图越平滑,过大会抹平物体边界
uniquenessRatio5到15越大误匹配越少,低纹理区域也更容易丢匹配
speckleWindowSize50到200滤除孤立噪点,值越大滤除的斑块越大

preFilterCap控制输入图像的截断值,对光照对比度剧烈的场景影响明显,一般32或63。disp12MaxDiff做左右一致性检查,值越小越严格,1到2比较稳妥。

5. 测距误差与常见问题排查:为什么你的结果总是差十几厘米

这一章把最常见的问题按“现象、原因、解决”列出来。每一条都来自实际项目里的真实翻车现场,不是理论推演。

5.1 现象:标定RMS很小,测距却偏了

标定面板上的重投影误差0.3像素,看着漂亮,实际测距离却差出10%以上。原因往往是标定板拍摄覆盖不全。只拍了画面中央区域,畸变系数在最需要约束的画面边缘没有被数据约束,内参在边缘区域的投影自然不准。还有一种常见情况是标定板本身不平整,打印纸贴在弯曲的纸板上,角点物理坐标系统就有偏差。

解决:回炉重拍,20到30张标定照片必须覆盖画面四角、中心、近处、远处,并包含明显旋转角度。同时拿这些照片去检查单个样本的重投影误差,把明显偏离平均值的照片剔除后重新标定。重投影像素偏差大于1的样本直接删掉,不要心软。

5.2 现象:视差图上目标区域全是黑色空洞

SGBM在纯色、低纹理区域没有可靠的匹配代价峰值,所以白墙、白纸这类目标在视差图上会是黑洞。光线不均匀时,阴影边界也会被误判成视角差异,产生成片错误视差。

解决:先判断场景能否改进。给目标表面增加纹理是最直接的办法,比如贴一张有图案的标签纸;实在不能贴,就调大blockSize到21左右,同时降低uniquenessRatio,让匹配算法对弱代价峰值更宽容。但要注意,这些改动会牺牲边缘精度。如果目标本身就是毫无纹理的墙面,建议放弃SGBM,换成主动投影纹理的方案,这不是参数能解决的问题。

5.3 现象:距离越远误差越大,而且是平方级放大

双目公式 (Z = fB/d),深度误差与距离的关系是:

[ \delta Z = \frac{Z^2 \cdot \delta d}{f \cdot B} ]

距离翻倍,误差大概是原来的四倍。这不是算法调参能解决的物理限制,视差的量化误差在那里摆着。实测2米内还能做到3%以内,放到5米就可能跳到15%。

解决:按这个公式反向推算自己方案的可用距离上限。如果要把5米的误差压到5%,就得增大基线 (B),或者提高图像分辨率。还有一种工程做法是多帧聚合:对同一个静态目标连续取几十帧视差图,取中位数深度,能有效压掉匹配噪声带来的随机误差。

5.4 现象:校正后画面被裁剪得太狠,视野明显变小

去畸变和极线校正都会让图像边缘产生无效区域。stereoRectify的alpha参数设为0时,算法会把所有无效像素裁掉,视野骤减。如果需要在视差图和RGB图上保留更多画面内容,可以适当调大alpha。

解决:把stereoRectify的alpha改成0.1到0.3,然后用cv2.getValidDisparityROI这类函数动态获取有效区域,而不是死板地全图计算。需要注意,alpha调大后视差图边缘会有更多无效值,计算深度时要用掩码把这些点滤掉。

5.5 现象:左右图亮度不一致导致匹配大面积错乱

两个USB摄像头各自启用自动曝光、自动白平衡时,同一帧画面左右亮度会有明显差异,SGBM的匹配代价直接失真。动态场景下这个问题更严重,因为自动曝光是在持续变化的。

解决:用cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 0)关闭自动曝光,手动设置固定曝光值,白平衡也用手动模式。如果一个相机枚举顺序不稳定,可能是USB带宽或驱动的问题,优先给两个摄像头分配独立的USB控制器。实在无法硬件同步时,可以做逐帧直方图匹配来拉近左右亮度,但这是补救手段,不是根治方案。

6. 让精度再进一步:误差标定流程与多帧处理技巧

测距系统做到能跑只是第一步,能用才是目的。这里分享一个我保留了很久的习惯:不管单目还是双目,上线前都做一遍误差标定,然后用多帧采样把随机噪声压下去。

误差标定的流程很简单:在0.5米到3米范围内,每隔0.2到0.3米放一个已知尺寸的目标(标定板或ArUco码都行),记录真实距离和算法测出来的距离,画出误差曲线。如果误差曲线呈现明显的系统性偏移,可以拟合一阶多项式,在输出端做补偿:

import numpy as np ground_truth = np.array([500, 800, 1100, 1400, 1700, 2000], dtype=np.float32) measured = np.array([512, 815, 1118, 1428, 1735, 2080], dtype=np.float32) # 一阶拟合:估计值 = 系数 * 真实值 + 截距 coeff = np.polyfit(ground_truth, measured, 1) compensated = measured - np.polyval(coeff, ground_truth) print('原始误差(mm):', measured - ground_truth) print('补偿后残差(mm):', compensated)

这段代码里的补偿系数只对当前这一套相机、镜头、基线、安装位置有效。换一个相机或者重新拆装过支架,必须重新标定。把参数和当天拍摄环境的缩略图一起存档,这是排查问题时最便宜的后悔药。

多帧采样是另一个低成本提升手段。单帧测距结果会有随机的跳变,尤其是ArUco角点或SGBM匹配在某些帧上突然失效。对静态目标取连续30帧的测量结果,用中位数而不是平均值,能有效滤掉极端跳变:

frames = [] for _ in range(30): frame = grab_frame() result = measure_distance(frame, mtx, dist) if result is not None: frames.append(result[0]) if len(frames) > 0: final_distance = np.median(frames) # 中位数抗跳变能力强于均值

如果要输出每一帧的深度图,建议把reprojectImageTo3D得到的Z通道换成浮点视差有效的掩码区域再做中值滤波,而不是直接对原始disp滤波,否则边缘会被抹平。

这套流程下来,单目在1米内的误差能稳定到2%到3%,双目在2米内做到3%左右是可以预期的。别指望5米外还能有这个精度,视差量化误差在那个距离上是物理上限。看清边界、标好参数,这个方向在你的项目里一定跑得稳。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 9:21:46

Android字体适配全解析:不跟随系统与动态设置字体大小实战

1. 字体适配为什么成了Android开发里的“隐形地雷”做过Android应用的人大概都有过这种经历:在测试机上跑得好好的界面,到了用户手里就变成了“车祸现场”——要么文字大得溢出屏幕,要么小得看不清,要么中英文混排时行高突然塌陷。…

作者头像 李华
网站建设 2026/10/13 9:16:10

领导力内在塑造的四大核心维度与实践方法

1. 领导力的本质与内在塑造领导力从来不是职位赋予的权威,而是一种由内而外散发的能量场。我在管理岗位上摸爬滚打十几年,见过太多手握重权却无法服众的"名义领导",也见证过不少没有正式头衔却能一呼百应的"隐形领袖"。这…

作者头像 李华