YOLOv5双目测距:极线校正与SGBM框内中值采样实战 简介面向深度学习与计算机视觉学习者的一套 YOLOv5 双目视觉测距可运行工程重点解决目标检测与立体测距衔接、环境配置和代码跑通问题适合具备 PyTorch、OpenCV 基础、希望复现实时距离估计的中高级开发者也可用于自动驾驶、机器人导航等场景的入门实验。压缩包共 106 个文件约 20.88MB以 25 个 Python 源码和 52 个编译缓存文件为主体配有 18 个 YAML 配置、若干 Shell 与说明文档并包含预训练权重、示例图像/视频及 Dockerfile覆盖数据处理、模型推理、视差计算与结果可视化等环节。目前已有 2849 人学习下载。工程将 YOLOv5 检测与双目三角测量、立体匹配、视差图计算和距离估计整合为完整流程读者可据此理解检测—匹配—测距的代码组织参照配置完成环境搭建与运行验证并在此基础上改造成自己的双目测距实验。1. 双目测距最难的不是检测为什么 YOLOv5 只能算半个方案把 YOLOv5 和双目相机接在一起最常见的翻车现场不是框画歪了而是框画得特别漂亮、距离数字却乱跳同一把人形靶这一帧 1.8 米下一帧 0.6 米再过两帧直接变成 12 米。多数人第一反应是模型不行去换 yolov5m、加大输入分辨率折腾两天发现没有任何改善因为问题从来不在检测头在视差图。这套已经跑通的 YOLOv5 双目测距源码真正有价值的地方不是把检测和测距拼在一起而是把「检测框 → 极线校正 → SGBM 匹配 → 反投影 → 框内采样滤波」这条链路做成了一个能直接出米制数值的工程左边画框带距离右边出伪彩视差图。它适合做前向避障、移动平台测距、课程设计验证这类需要绝对距离而不是「谁离得近」的场景前提是你得愿意先把标定和视差参数啃下来。2. 环境与最小闭环先把框和视差图同时点亮的四步2.1 依赖组合的取舍OpenCV 必须带 contrib这套代码跑不起来的人里一半死在 OpenCV 版本上。源码里用到了cv2.ximgproc下面的 WLS 视差滤波和加权中值这部分只在opencv-contrib-python里装纯opencv-python会在import cv2之后第一次调createDisparityWLSFilter时抛AttributeError: module cv2 has no attribute ximgproc。另一个隐性雷是 numpy。PyTorch 1.x 系列编译时链接的是 numpy 1.x环境里一旦被别的包顶到 numpy 2.ximport torch会直接报A module that was compiled using NumPy 1.x cannot be run in NumPy 2.0.x。所以我在这个组合上一般会显式钉版本不让 pip 自由发挥。# Python 3.9 是这套依赖最稳的组合3.12 上部分 wheel 还没有 conda create -n stereo-yolo python3.9 -y conda activate stereo-yolo # 先 torch 后 opencv顺序反了容易被依赖求解器把 numpy 顶版本 pip install torch1.13.1 torchvision0.14.1 # contrib 版本必须和 opencv-python 完全同号混装会出现符号冲突 pip install opencv-contrib-python4.8.1.78 numpy1.23.5 pip install pyyaml tqdm pandas这里opencv-python不用单独装opencv-contrib-python已经包含主模块两个都装反而容易互相覆盖cv2的.so。numpy 钉 1.23.5 的原因是它同时满足 torch 1.13 的 ABI 和reprojectImageTo3D对float32连续内存的要求再高的版本在部分机器上会触发cv2.error: Layout of the output array。提示装完先跑一句python -c import cv2; print(cv2.__version__, hasattr(cv2, ximgproc))两个值都对再往下走能省掉后面一半的排错时间。2.2 四步跑通最小闭环源码包里的入口脚本通常叫demo_stereo.py或者main.py参数命名各家不同但需要的输入永远是四样左图、右图、权重、标定文件。第一遍不要碰自己的相机先用包里自带的示例图对确认整条链路是通的再换数据。# 第一步确认权重路径yolov5s 够用别一上来就上 x ls weights/ # 第二步用示例图跑一遍--show 会同时弹检测图和视差图两个窗口 python demo_stereo.py \ --left data/left/0001.png \ --right data/right/0001.png \ --weights weights/yolov5s.pt \ --calib calib/stereo_calib.npz \ --show # 第三步批量跑一个目录输出 csv 方便后面统计 python demo_stereo.py --left data/left --right data/right \ --weights weights/yolov5s.pt --calib calib/stereo_calib.npz \ --save-csv out/depth.csv如果第二步窗口能弹出来但距离全是nan先别改代码去看out/下有没有生成disp.png没有说明视差计算这一步就没过问题在标定文件或者numDisparities跟 YOLO 一点关系都没有。2.3 加载模型这一步的三个参数很多人拿到源码第一件事是改conf把阈值从 0.25 调到 0.6觉得这样框更准。实际对测距来说conf调高只会让远处小目标漏检不会让距离更准真正影响距离稳定性的是classes和 NMS 的iou。import cv2 import numpy as np import torch # sourcelocal 表示直接用本地 yolov5 目录避免运行时去拉仓库 model torch.hub.load(./yolov5, custom, pathweights/yolov5s.pt, sourcelocal) model.conf 0.35 # 0.25 会放进大量远处虚检框内有效视差反而更少 model.iou 0.45 # 默认 0.45人群密集场景调到 0.5 减少框粘连 model.classes [0] # 只保留 person类别越少后面采样越干净 left cv2.imread(data/left/0001.png) right cv2.imread(data/right/0001.png) # 左右图分辨率和通道数必须完全一致否则 remap 会静默出错 assert left.shape right.shape, f{left.shape} vs {right.shape} # hub 接口吃 RGBOpenCV 读进来是 BGR这一步不能省 res model(left[..., ::-1]) boxes res.xyxy[0].cpu().numpy() # 每行 [x1, y1, x2, y2, conf, cls] print(boxes.shape, boxes[:1])conf0.35是个经验值低于 0.3 时画面边缘和反光区域会冒出大量几十像素的小框这些小框里有效视差像素常常不到 20 个中值完全不可信高于 0.5 时3 米外的人形只剩半身框采样区会切到背景上。iou保持默认即可除非画面里目标互相遮挡严重。left[..., ::-1]这一步看着不起眼是新手最常见的错误来源之一喂 BGR 进去模型照样能出框位置大体也对但置信度和类别会飘而且不会报错属于典型的黑匣子式错误。注意torch.hub.load第一次调用会在本地 yolov5 目录下生成缓存如果你的 yolov5 目录是只读挂载会直接失败改成sourcelocal并把目录放在可写路径下。3. 标定与极线校正Q 矩阵差一个符号3 米就变 0.3 米3.1 为什么一定要先 rectify 再 match双目测距的数学前提只有一句话同一个物点在左右图里必须落在同一行像素上。真实相机的两个镜头不可能完全平行总有零点几度的夹角和几毫米的轴向偏差导致同一个点在右图里的纵坐标比左图高几个到几十个像素。SGBM 这类匹配算法是在同一行上滑动窗口找最优的纵坐标差超过blockSize的一半匹配就直接失败或者匹配到隔壁物体上。cv2.stereoRectify干的事情就是算出两个旋转矩阵R1、R2和两个新投影矩阵P1、P2把左右图各自「虚拟旋转」一次让极线变成水平线同时输出Q矩阵供后续反投影用。这一步做完左右图的同一物点纵坐标差应该小于 1 像素。不做 rectify 直接匹配视差图会是满屏雪花你会误以为是 SGBM 参数没调好然后在这条岔路上耗掉一整晚。Q矩阵是 4×4 的里面藏着一个关键量Q[2][3] f即校正后的焦距像素单位Q[3][2] -1/TxTx就是基线长度带符号。reprojectImageTo3D输出的 Z 单位跟你标定时棋盘格边长的单位完全一致。棋盘格边长填的是米输出就是米填的是毫米输出就是毫米差 1000 倍这也是后面避坑章节里要单独拎出来说的一条。3.2 双目标定脚本从单目内参到 Q 矩阵标定的输入是一批同步拍摄的棋盘格图对左右目录里文件名必须一一对应因为stereoCalibrate是按列表下标配对objp的配错了不会报错只会让外参算出一个荒谬的基线。import cv2 import glob import numpy as np PATTERN (9, 6) # 内角点数量不是方格数量数错一位整批白标 SQUARE 0.025 # 方格边长单位米这个单位决定后面一切输出的单位 def calibrate_one(files): objp np.zeros((PATTERN[0] * PATTERN[1], 3), np.float32) objp[:, :2] np.mgrid[0:PATTERN[0], 0:PATTERN[1]].T.reshape(-1, 2) * SQUARE obj_pts, img_pts, size [], [], None for f in files: gray cv2.cvtColor(cv2.imread(f), cv2.COLOR_BGR2GRAY) size gray.shape[::-1] # 注意是 (w, h)不是 (h, w) ok, corners cv2.findChessboardCorners( gray, PATTERN, cv2.CALIB_CB_ADAPTIVE_THRESH | cv2.CALIB_CB_NORMALIZE_IMAGE) if not ok: continue # 亚像素精化这一步直接影响重投影误差能不能压到 0.3px 以内 corners cv2.cornerSubPix( gray, corners, (11, 11), (-1, -1), (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-3)) obj_pts.append(objp) img_pts.append(corners) rms, K, D, _, _ cv2.calibrateCamera(obj_pts, img_pts, size, None, None) print(f{len(img_pts)} 张有效, 单目 rms{rms:.3f}px) return K, D, obj_pts, img_pts, size K1, D1, op_l, ip_l, size calibrate_one(sorted(glob.glob(calib/left/*.png))) K2, D2, op_r, ip_r, _ calibrate_one(sorted(glob.glob(calib/right/*.png))) # 内参已经够稳就锁死只优化 R 和 T避免两组参数互相拉扯 rms, K1, D1, K2, D2, R, T, E, F cv2.stereoCalibrate( op_l, ip_l, ip_r, K1, D1, K2, D2, size, flagscv2.CALIB_FIX_INTRINSIC, criteria(cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 100, 1e-6)) print(f双目 rms{rms:.3f}px, 基线{np.linalg.norm(T)*1000:.2f}mm) # alpha0 裁掉校正后的黑边保留有效像素alpha1 保留全部但会引入无效区 R1, R2, P1, P2, Q, roi1, roi2 cv2.stereoRectify( K1, D1, K2, D2, size, R, T, alpha0, flagscv2.CALIB_ZERO_DISPARITY) np.savez(calib/stereo_calib.npz, K1K1, D1D1, K2K2, D2D2, R1R1, R2R2, P1P1, P2P2, QQ, sizesize, baselinefloat(np.linalg.norm(T)))alpha这个参数值得单独说alpha0会把校正后所有黑边裁掉画面只剩两图共有的有效区域好处是不会有「黑边区域视差全 0 被当成有效值」坏处是视野变窄画面边缘的目标可能直接被裁没。做前向避障我一般用alpha0因为边缘本来也不该作为测距依据做全景或者视野完整性优先的场景用alpha1但要在采样时把disp0的像素滤掉。标定完要生成两张映射表并缓存下来每帧都重算initUndistortRectifyMap会白白吃掉几毫秒d np.load(calib/stereo_calib.npz) size tuple(int(v) for v in d[size]) map1x, map1y cv2.initUndistortRectifyMap(d[K1], d[D1], d[R1], d[P1], size, cv2.CV_32FC1) map2x, map2y cv2.initUndistortRectifyMap(d[K2], d[D2], d[R2], d[P2], size, cv2.CV_32FC1) left_rect cv2.remap(left, map1x, map1y, cv2.INTER_LINEAR) right_rect cv2.remap(right, map2x, map2y, cv2.INTER_LINEAR)后面所有视差计算和框坐标都必须基于left_rect/right_rect不能拿原始图上的框去left_rect上抠像素因为 rectify 会改变像素位置alpha0时还会整体平移和缩放。这是移植到自己的相机上时最容易忽略的一处坐标错位现象是距离整体偏大 10%~30%怎么调 SGBM 都调不回来。3.3 三根验收集重投影误差、极线偏差、基线长度标定跑完不能只看脚本没报错就完事我一般会强制过三根尺子任何一根不过就重新采图。指标计算方式合格线不达标说明双目重投影误差stereoCalibrate返回的 rms 0.5 px棋盘姿态太单一或存在模糊帧极线偏差校正后同一角点的 abs(y1-y2) 均值 1 px外参没收敛或左右图不同步基线长度norm(T)与卷尺实测对比偏差 1%棋盘边长单位填错极线偏差的验法很土但有效取一张标定用的棋盘图校正后findChessboardCorners分别找两图角点逐点算纵坐标差。我见过最离谱的一次是均值 47 像素原因是采集时左右相机不是硬同步拍的是两个时刻的棋盘外参自然算出来是乱的。注意T的符号有意义。norm(T)只给你长度但Q矩阵里的Tx是带符号的符号错了reprojectImageTo3D出来的 Z 会是负值。看到距离全是负数先去查Q[3][2]的符号不要去改代码里的取绝对值。4. 从检测框到米SGBM 六个参数与框内中值采样4.1 Z f·B/d 的量程与分辨率边界测距主公式就一行Z f * B / df是校正后焦距像素B是基线米d是视差像素。这个公式同时给出了量程的上下界理解了它参数就不用瞎试。下界由最大可测视差决定也就是numDisparities。d_max numDisparities对应的Z_min f*B/numDisparities。上界由视差的分辨能力决定视差只有 1 像素的量化台阶而按误差传播ΔZ Z²/(f·B) · Δd距离越远同样 1 像素误差放大得越厉害。拿一组具体数感受一下f700px、B0.06m则f·B42。1 米处视差 42 像素3 米处视差只剩 14 像素。如果视差能精确到 0.5 像素3 米处的距离误差是9/42×0.5≈0.107m超过 10 厘米。这就是为什么 3 米开外的双目测距必须靠亚像素插值和滤波光靠整数视差在工程上不可交付。# 量程估算改基线或分辨率前先算一遍别等跑起来才发现不够用 f_px, B, num_disp 700.0, 0.06, 128 print(最近可测, round(f_px * B / num_disp, 3), m) print(3m 处 1px 误差对应, round(9 / (f_px * B), 3), m)要拉远量程有两条路加基线或者加焦距。基线从 60mm 加到 120mmf·B翻倍3 米处的误差减半但代价是近处盲区变大、匹配难度上升左右图透视差异变大。我在移动平台上一般卡在 100~120mm再大近处 0.5 米以内就没法用了。4.2 SGBM 参数逐个拧StereoSGBM_create有十来个参数但真正决定成败的就六个。剩下的保持默认大概率能跑只是边缘会碎。def build_matcher(num_disp128, block5): return cv2.StereoSGBM_create( minDisparity0, numDisparitiesnum_disp, # 必须能被 16 整除否则 create 直接抛错 blockSizeblock, # 必须奇数5/7/9 常用越大越平滑也越糊 P18 * 1 * block ** 2, # 视差小幅变化的惩罚1 是单通道 P232 * 1 * block ** 2, # 视差大幅变化的惩罚P2 越大越倾向平滑 disp12MaxDiff1, # 左右一致性检查0 才能滤掉遮挡区错误 uniquenessRatio10, # 最优/次优比10~15 之间低纹理区靠它兜底 speckleWindowSize100, # 连通域去斑小噪点面积超过它就抹掉 speckleRange2, preFilterCap63, # 预处理截断光照差异大时降到 31 modecv2.STEREO_SGBM_MODE_SGBM_3WAY)逐个说取舍。numDisparities决定最近可测距离128 对应 0.33 米如果应用场景最近只到 0.5 米用 64 就够还能省一半计算量并且减少误匹配。blockSize是清晰度和鲁棒性的旋钮5 保留细节但噪点多11 在弱纹理墙面上更稳代价是物体边缘会长出几像素的「拖尾」这个拖尾在框边界采样时会直接污染距离。P1/P2的经典取法是8*cn*block²和32*cn*block²cn是通道数灰度图为 1彩色 3。如果代码里把彩色图直接喂进 matcher 又按 1 算平滑强度会不够视差图会出现成片的锯齿。uniquenessRatio是低纹理区域的后悔药白墙、地面这种地方匹配代价曲面很平最优和次优几乎一样好把这个值从 5 提到 15能砍掉大量虚假视差代价是本身很弱的有效像素也被砍掉。disp12MaxDiff建议永远给 1遮挡区域左右一致性检查过不去天然被标成无效比事后滤更干净。4.3 框内采样为什么用中值而不是均值拿到视差图之后直接对检测框内所有有效视差取平均是最常见也最容易出错的做法。一个 200×400 的人形框里真正属于人体的像素可能只占 40%剩下的地面、背景、旁边经过的车视差各不相同均值会被这些杂点整体拉偏而且拉偏的方向不确定看上去就是「距离在跳」。我的做法是缩采样区加中值横向只取中间 1/3纵向从框高的一半到八分之七避开头顶常和远处背景混在一起和脚下地面视差和人体差得远。def box_depth(disp_px, box, f_px, baseline, min_valid30): x1, y1, x2, y2 [int(round(v)) for v in box[:4]] w, h x2 - x1, y2 - y1 if w 8 or h 8: return None, 0.0 # 太小的框不参与噪声远大于信号 cx1, cx2 x1 w // 3, x2 - w // 3 # 横向取中间 1/3 cy1, cy2 y1 h // 2, y2 - h // 8 # 纵向取下半段避开头顶背景 roi disp_px[cy1:cy2, cx1:cx2] valid roi[roi 0].astype(np.float32) # 0 是匹配失败占位必须剔掉 if valid.size min_valid: return None, valid.size / max(roi.size, 1) d_med float(np.median(valid)) # 中值抗单侧杂点均值不抗 if d_med 0: return None, 0.0 Z f_px * baseline / d_med # 单位与 baseline 一致这里是米 return round(Z, 3), valid.size / roi.size函数返回两个值距离和有效率。有效率这一项别丢它是判断这次测量值不可信的开关。有效率低于 0.15 通常说明目标在低纹理区域、被遮挡或者超出量程这时候返回的距离即使看起来合理也不要采信。我在实际部署里会把有效率低于 0.2 的框直接标灰只显示框不显示数字比显示一个错的数字安全得多。f_px从标定文件里取用P1[0, 0]不要用原始K1[0, 0]。rectify 之后焦距可能因为裁剪和重采样发生变化用错了会引入百分之几的系统性偏差。也可以直接用Q矩阵走reprojectImageTo3D两种方式等价但要注意reprojectImageTo3D要求输入是float32的连续数组且视差单位是像素如果视差图是CV_16S定点格式真实视差乘 16要先除以 16。5. 避坑实录五个让测距变玄学的现场与处理5.1 距离全是负数或者 0.03 米现象检测框正常视差图看着也有纹理但输出的距离要么是负的要么稳定在 0.0x 米这个量级换什么目标都一样。原因基线单位或棋盘格单位混用。标定时SQUARE填了 25毫米而不是 0.025米T的单位跟着变成毫米f·B被放大 1000 倍Z自然也大 1000 倍如果代码里又做了一次/1000的「修正」两次错误叠加就变成负数或者 0.03 这种离谱值。解决从源头统一。标定脚本里SQUARE一律用米输出文件名里带上单位标注baseline存进npz时也存米。改完之后拿 1 米处的目标验一次数值落在 0.95~1.05 才算这条路走通。5.2 标定误差很小但实测距离整体偏大现象stereoCalibrate的 rms 只有 0.2 像素基线也和实测对得上但拿卷尺量 2 米程序稳定输出 2.3 米左右偏差接近 15%而且重复性很好。原因左右图像不同步或者标定板姿态过于集中在同一个平面。前者会让外参里混进一个虚假的旋转rms 反而因为参数自由度高而显得很小后者让R、T的可观测性不足棋盘只在正前方一个距离上拍焦距和基线会互相补偿形成一组「能拟合标定数据但不能外推」的参数。解决采图时棋盘必须有倾斜绕 x、y 各 ±20°、有远近覆盖 0.5 米到 3 米、有画面四角位置每种姿态至少 8 张总计 25 张以上。同时确认采集是硬触发或者软件同一帧取图不是先后各拍一张。5.3 近处目标距离被「卡住」在某个固定值现象目标从 1.5 米走近到 0.4 米输出距离到 0.66 米就不动了再近还是 0.66视差图上目标区域变成一整片纯色。原因numDisparities不够。目标近到一定程度真实视差超过numDisparities超出部分全部被截断到最大值反算出来的距离自然卡在下界上。这里的下界就是f·B/numDisparities0.66 米正好对应42/64。解决按最近作业距离反算需要的numDisparitiesnum_disp ≥ f·B/Z_min再向上取到 16 的整数倍。要测到 0.3 米就需要42/0.3140取 144 或 160。加大之后计算量线性增长帧率会掉所以别一上来就填 256。5.4 视差图满屏雪花几乎全是噪点现象检测和标定都没问题但视差图看起来像电视雪花框内有效率常年低于 0.1偶尔蹦出一个距离又马上跳走。原因左右图亮度或色彩不一致。双目模组两个镜头的自动曝光、自动增益是各自独立工作的一个画面偏亮一个偏暗SGBM 的 Census 代价直接失效。另一个常见诱因是左右图接反了视差全为负被roi 0全部滤掉表现出来也是「没有有效像素」。解决先关掉相机的自动曝光和自动增益固定曝光时间和增益两个镜头用同一组值如果硬件不支持就在采集端做一次直方图匹配。接反的问题用一张有明显左右位移的图验证手指放在镜头前偏左左图里手指应该更靠右边缘确认无误再接。5.5 静态很准一动就跳现象目标静止时距离读数稳定在 ±1 厘米一旦人或车移动读数开始剧烈跳动幅度能到半米。原因检测框在动采样区跟着动每帧采到的像素集合都不一样再加上运动模糊让匹配质量下降单帧中值本身就有几厘米的抖动两者叠加就放大成大跳变。解决加一层时序滤波但别用简单的滑动平均它会把真实靠近的响应拖慢。用中值加变化率门限缓存最近 5 帧的距离取中值作为输出如果新值和当前输出的相对变化超过 30%先不采信连续 3 帧都超才更新这样既压住了单帧噪声又不会把真实的快速接近误判成噪声。6. 精度对表与工程化把 3 米处的误差压进 3 厘米标定和参数调完之后必须做一次对表否则你永远不知道自己这套东西到底能交付到什么精度。我的习惯是在地面贴五条标记线0.5、1.0、1.5、2.0、3.0 米各一条人形靶依次站过去每个位置采 100 帧取中值然后和卷尺实测对比。某次在 1280×720、基线 60mm 的模组上跑出来的一组数据是这样的实测距离中值视差解算距离绝对误差相对误差0.50 m84.0 px0.500 m0.000 m0.0%1.00 m41.6 px1.010 m0.010 m1.0%1.50 m27.6 px1.522 m0.022 m1.5%2.00 m20.5 px2.049 m0.049 m2.4%3.00 m13.6 px3.088 m0.088 m2.9%误差全是正的说明存在系统性偏差不是随机噪声。这类偏差优先怀疑标定基线略小、焦距略大而不是算法重标一次往往就回来了。误差随距离平方增长这一点和ΔZ Z²/(f·B)·Δd完全吻合所以别指望靠调 SGBM 把 3 米处的误差压到 1 厘米以内物理上就不允许。想再往下压只有两条路。第一条是亚像素StereoSGBM用MODE_SGBM_3WAY并在后处理里做一次抛物线拟合把整数视差插值到 0.1 像素或者直接用cv2.ximgproc.createDisparityWLSFilter做加权最小二乘联合滤波它对边缘的保边效果比中值滤波好一大截代价是多花几毫秒# 需要 opencv-contribWLS 能把视差边缘收得更紧3m 处实测能削掉 2~3cm left_matcher build_matcher(num_disp128, block5) right_matcher cv2.ximgproc.createRightMatcher(left_matcher) wls cv2.ximgproc.createDisparityWLSFilter(left_matcher) wls.setLambda(8000) # 越大越平滑8000~15000 是常用区间 wls.setSigmaColor(1.5) # 边缘敏感度纹理丰富时降到 1.0 disp_l left_matcher.compute(left_rect, right_rect).astype(np.float32) / 16.0 disp_r right_matcher.compute(right_rect, left_rect).astype(np.float32) / 16.0 disp_f wls.filter(disp_l, left_rect, disparity_map_rightdisp_r)第二条是加基线但这个受机械结构限制而且会牺牲近处视野不是能随时动的旋钮。所以工程上更实际的做法是接受远距离精度下降在业务层设一个置信度阈值2 米内输出具体数值2~4 米只输出距离区间超过 4 米只保留检测框不给数字。从那以后我每次换相机或者换支架都会强制走一遍「五点对表 极线偏差检查」哪怕只是把相机从一个铝型材挪到另一个上因为基线的微小变化足以让 3 米处的读数偏出 20 厘米而这种偏差在实时画面里根本看不出来只能靠对表暴露。源码包按简介里的入口取下来之后建议先把示例图跑通再换成自己相机的标定文件重跑那五条线别跳过这一步直接上业务逻辑。希望帮到你。本文还有配套的精品资源点击获取