简介面向计算机视觉学习者与工程实践者的三维测距实战资源将YOLOV5目标检测与双目相机深度感知结合解决复杂场景下目标定位与空间测距的精度和实时性问题适用于自动驾驶、机器人导航、无人机避障等方向的中级进阶练习。压缩包共141个文件约40.38MB以37个py源码、40个yaml配置、22个pyc编译文件为主辅以yml环境配置、sh脚本、md文档及少量xml、jpg样例与pt权重、mp4演示覆盖数据预处理、模型训练、目标检测与深度计算全流程。已有2420人学习下载。资源内含整合YOLOV5与双目算法的源代码、预训练权重、相机与模型配置文件、双目图像对样例及使用说明可帮助读者理解视差计算与立体匹配的衔接方式快速复现检测加测距的完整链路并在此基础上做二次训练与鲁棒性优化。1. 双目测距落地为什么 YOLOv5 加两个镜头就能出三维坐标单目画面里一个检测框只能告诉你“那里有东西”给不了“离我多远”。双目测距补的就是这一维左右两个镜头同时拍同一个目标在两张图上的像素位置有差异这个差异叫视差视差越大目标越近用焦距、基线距离和视差一套三角关系就能反推深度。YOLOv5 负责在左图上把目标框出来双目几何负责把框中心换算成米。这套组合在分拣、避障、体积粗测这类场景里很常见因为它不需要昂贵的深度相机两个标定好的普通镜头加一块能跑推理的板子就能起步。这份资源把检测、标定、立体匹配、坐标反投影串成一条可复现的链路适合已经能跑通 YOLOv5 推理、想往三维空间再走一步的从业者。下面按“先立住几何、再动手复现、最后盯住坑”的顺序拆。2. 双目几何与标定把像素视差换成真实米数2.1 三角测量公式到底在算什么双目测距的核心只有一个式子Z f × B / d。Z 是目标到相机平面的深度f 是焦距像素单位B 是两个镜头光心之间的距离叫基线d 是同一个点在左右图上的横坐标差即视差。这个式子成立的前提是左右图已经过极线校正两个镜头的成像平面共面、光轴平行此时同一个目标点只在横坐标上有位移纵坐标基本一致找对应点就退化成沿同一行搜索工程上才好做。焦距用像素单位而不是毫米是因为视差 d 本身就是像素差单位要统一。f 的像素值等于物理焦距除以像元尺寸标定出来的内参矩阵里 fx 就是它。基线 B 用米或毫米都行只要和最终想要的 Z 单位一致。举个能抄去验算的数fx 800 像素B 0.12 米某目标视差 d 40 像素则 Z 800 × 0.12 / 40 2.4 米。视差掉到 20 像素Z 就翻到 4.8 米。这就是双目测距的脾气——远处一点点视差误差会被放大成很大的距离误差所以这套方案在近中距离靠谱几十米外精度会明显下滑。还有两个容易忽略的量。一是主点 cx、cy反投影到相机坐标系时要用X (u - cx) × Z / fxY (v - cy) × Z / fyu、v 是目标在左图的像素坐标。二是畸变系数广角镜头畸变不校正视差匹配会系统性偏移深度就整体偏。所以标定不是走形式它直接决定后面所有数字的可信度。2.2 标定流程棋盘格、参数与验收标准标定分两步单目标定拿内参和畸变双目标定拿右相机相对左相机的旋转平移平移向量的模长就是基线 B。常见做法是用棋盘格左右相机同时拍同一块板子的多组姿态每组两张图一般 15 到 25 组比较稳板子要覆盖画面四角和中心倾斜角度也要有变化全平着拍标出来的参数会偏。下面这段是 OpenCV 双目标定的骨架参数含义写在注释里可以直接改成自己的图路径跑import cv2 import numpy as np import glob # 棋盘格内角点数注意是内角点不是格子数10x7 的板子填 (9, 6) pattern (9, 6) square_size 0.025 # 每格边长单位米必须和实际板子一致 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_size objpoints, imgpoints_l, imgpoints_r [], [], [] left_imgs sorted(glob.glob(calib/left/*.png)) right_imgs sorted(glob.glob(calib/right/*.png)) for lp, rp in zip(left_imgs, right_imgs): gl cv2.imread(lp, cv2.IMREAD_GRAYSCALE) gr cv2.imread(rp, cv2.IMREAD_GRAYSCALE) ok_l, cl cv2.findChessboardCorners(gl, pattern, None) ok_r, cr cv2.findChessboardCorners(gr, pattern, None) if not (ok_l and ok_r): continue # 任一侧没找到就整组丢弃不能只留单边 crit (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) cl cv2.cornerSubPix(gl, cl, (11, 11), (-1, -1), crit) cr cv2.cornerSubPix(gr, cr, (11, 11), (-1, -1), crit) objpoints.append(objp) imgpoints_l.append(cl) imgpoints_r.append(cr) # 单目标定flags 里关掉不稳定的高阶项样本少时更稳 ret_l, K1, D1, _, _ cv2.calibrateCamera(objpoints, imgpoints_l, gl.shape[::-1], None, None, flagscv2.CALIB_FIX_K3) ret_r, K2, D2, _, _ cv2.calibrateCamera(objpoints, imgpoints_r, gr.shape[::-1], None, None, flagscv2.CALIB_FIX_K3) # 双目标定输出 R、TT 的模长就是基线 ret, K1, D1, K2, D2, R, T, E, F cv2.stereoCalibrate( objpoints, imgpoints_l, imgpoints_r, K1, D1, K2, D2, gl.shape[::-1], flagscv2.CALIB_FIX_INTRINSIC, criteria(cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 100, 1e-5)) print(baseline(m):, np.linalg.norm(T)) print(reprojection error:, ret) # 重投影误差单位像素逻辑说明先对每组图找角点并做亚像素细化左右都成功才收进样本避免单边样本污染单目标定拿各自内参双目标定用CALIB_FIX_INTRINSIC固定内参只优化外参这样内外参不会互相拉扯。参数上square_size必须和实物一致填错会让基线整体缩放深度全错pattern填内角点填成格子数是最常见的翻车点。验收看两个数重投影误差一般要压到 0.5 像素以内超过 1 像素说明样本质量差或板子不平基线norm(T)要和卷尺量的两镜头光心距离对得上差太多说明标定姿态不够丰富。标完还要用stereoRectify生成校正映射后面所有图都先过一遍remap保证极线水平这一步不做视差搜索就会错行。3. YOLOv5 检测与视差匹配把框中心变成深度值3.1 检测框怎么和视差图对齐YOLOv5 在左图上出框拿到框中心 (u, v)。视差图是整幅图逐像素算出来的直接取 (u, v) 处的视差值有两个风险一是框中心可能落在目标内部无纹理区域视差是空洞二是遮挡边缘视差不可靠。稳妥做法是在框内取一个内缩区域比如宽高各缩 20%统计该区域内有效视差的中位数空洞像素值为 0 或负先剔除。中位数比均值抗离群边缘跳变不会把结果带偏。这里有个顺序问题必须说清视差匹配用的是校正后的左右图检测用的是左图两者必须是同一帧、同一校正参数。如果检测跑在原图上、视差跑在校正图上坐标就对不上深度会系统性偏移。我一般把校正后的左图同时喂给检测和匹配保证像素坐标系一致。3.2 视差计算SGBM 参数怎么调立体匹配常用 SGBM参数多但真正影响结果的就几个。下面这段把校正、匹配、取框内中位数串起来import cv2 import numpy as np # 校正映射标定阶段算好后存下来复用 map1x, map1y, map2x, map2y, Q ... # 由 stereoRectify initUndistortRectifyMap 得到 # SGBM 参数numDisparities 必须是 16 的倍数blockSize 必须是奇数 num_disp 128 # 最大视差搜索范围按最近目标距离估太小近处会截断 block_size 5 # 匹配块大小纹理弱调大边缘糊调小 sgbm cv2.StereoSGBM_create( minDisparity0, numDisparitiesnum_disp, blockSizeblock_size, P18 * block_size ** 2, P232 * block_size ** 2, disp12MaxDiff1, # 左右一致性检查阈值压噪声 uniquenessRatio10, # 唯一性太低会出大量误匹配 speckleWindowSize100, # 斑点滤波窗口去孤立小块 speckleRange2, modecv2.STEREO_SGBM_MODE_SGBM_3WAY ) def depth_of_box(left_rect, right_rect, box, fx, baseline): disp sgbm.compute(left_rect, right_rect).astype(np.float32) / 16.0 # SGBM 输出放大 16 倍 x1, y1, x2, y2 box w, h x2 - x1, y2 - y1 # 内缩 20%避开框边和遮挡边缘 roi disp[int(y1 0.2 * h):int(y2 - 0.2 * h), int(x1 0.2 * w):int(x2 - 0.2 * w)] valid roi[roi 0] if valid.size 10: return None # 有效视差太少判为不可测别硬给数 d float(np.median(valid)) return fx * baseline / d # 反投影到相机坐标系得到三维点 def to_3d(u, v, Z, fx, fy, cx, cy): X (u - cx) * Z / fx Y (v - cy) * Z / fy return X, Y, Z逻辑说明numDisparities决定能测多近视差超过这个范围的目标会被截断按最近工作距离反推——最近 0.5 米、fx 800、B 0.12 时视差约 192就得把numDisparities设到 208 以上。blockSize小则边缘锐但噪声多大则平滑但小目标糊5 到 11 之间试。uniquenessRatio调低会冒出大量误匹配调高则弱纹理区大片空洞10 到 15 是常用区间。SGBM 输出是定点数放大了 16 倍忘了除 16 深度会小 16 倍这个坑很经典。to_3d里 u、v 用框中心Z 用中位数深度得到的是目标中心在左相机坐标系下的三维点。要转到世界坐标或机器人坐标再乘一个手眼标定矩阵那属于另一层这份资源聚焦在相机坐标系输出够大多数避障和抓取粗定位用。4. 避坑与排查双目测距最容易翻车的五处4.1 深度整体偏大或偏小现象所有目标测出来都比实际远一截或近一截比例大致固定。原因多半是基线或焦距单位不一致比如标定square_size填的是毫米、代码里当米用或者norm(T)出来是毫米却直接当米代进公式。解决打印norm(T)和卷尺实测对比确认单位square_size统一用米全链路不再换单位。4.2 视差图大片空洞、框内取不到值现象纹理弱的目标纯色箱体、白墙视差几乎全 0valid.size不够返回 None。原因SGBM 靠局部纹理找对应点无纹理区域本就无解。解决调大blockSize、降uniquenessRatio能缓解但治标工程上更稳的是给目标表面加纹理或改用带结构光的方案代码里必须保留“有效点不足就判不可测”的分支别用 0 视差硬算出一个无穷大深度。4.3 检测框和视差图错位现象深度值看着合理但明显对应到背景上。原因检测跑原图、视差跑校正图或两路用了不同分辨率。解决统一先remap再同时喂检测和匹配检测输入尺寸和视差图尺寸对齐框坐标按同一缩放系数还原。4.4 远处目标深度跳变严重现象5 米内挺稳10 米外数字乱跳。原因视差与深度成反比远处视差只有几个像素1 像素误差就放大成几十厘米。解决这是原理性边界不是 bug按工作距离选基线要测远就加大基线或换长焦同时接受近处盲区变大二者不可兼得。4.5 标定重投影误差合格但测距仍偏现象stereoCalibrate返回 0.3 像素实测深度还是差。原因标定板不平、样本姿态单一或标定后镜头被碰过。解决标定板贴平硬板样本覆盖远近和倾斜标定完立刻用已知距离的物体做一次实测校验别只看重投影误差就收工。5. 精度验证与工程化收尾一套可复用的自检习惯资源跑通不等于能用得有一套验证动作。我一般会做一张距离标定表把目标放在 0.5、1、1.5、2、3、4 米处每个距离测 20 帧取中位数和标准差和卷尺值对比算出各距离段的绝对误差和波动。这张表能直接告诉你这套双目在你的工作距离内能不能用、误差带多宽。典型结果是近处误差几厘米、远处涨到十几厘米看到这个曲线心里就有数了。实测距离测量中位数绝对误差帧间标准差结论0.5 m0.51 m1 cm0.8 cm可用1.0 m1.03 m3 cm1.5 cm可用2.0 m2.08 m8 cm3 cm可用4.0 m4.3 m30 cm9 cm仅作粗判工程化上还有两个技巧。一是把标定结果K、D、R、T、校正映射序列化存盘启动时加载别每次跑都重标标定参数是这套系统的“黑匣子”丢了就得重来。二是深度输出加时间中值滤波连续 5 帧取中位数再上报能压掉单帧匹配噪声代价是响应慢一两帧避障场景完全能接受。三是给不可测目标留后悔药depth_of_box返回 None 时上层逻辑要有降级策略比如按上一帧深度外推或直接标记未知别让一个空洞把整条链路带崩。血泪经验是标定完一定做一次端到端实测再进现场我见过重投影误差漂亮但基线量错 2 厘米、整批深度偏 15% 的情况现场返工成本远高于实验室多花的那半小时。从那以后我每次改完镜头支架或动过相机都强制重标并跑一遍距离表确认误差带没漂才继续。希望这套流程帮到你少走几段弯路。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?