YOLOV5与双目测距实战:标定、视差、深度提取全流程解析

发布时间:2026/10/12 11:47:54
YOLOV5与双目测距实战:标定、视差、深度提取全流程解析 简介面向自动驾驶、机器人导航与无人机避障等场景的三维视觉开发者这款资源将 YOLOV5 目标检测与双目相机视差测距相结合提供一套可运行的新版本实现覆盖目标识别、立体匹配、深度图生成与三维坐标计算等关键环节。压缩包共 141 个文件约 40.38MB主要包括 Python 源码与编译缓存、yaml/yml 配置、模型权重 pt、使用说明与样例文件等便于直接搭建环境或二次开发。其中 yolov5-6.1 目录结构清晰配置文件和训练脚本齐全适合刚入门双目测距或需要快速集成检测与深度估计的研究者。已有 2419 人浏览学习。通过阅读代码与配置可了解如何用检测框引导立体匹配、降低误匹配并提升深度估计精度配套的模型权重、样例图片与视频也能帮助验证效果是三维测距项目实践与算法复现的有用参考。1. 这 300 行代码到底能做什么先看清 YOLOV5 双目测距的真实价值某安防项目里客户要求在 20 米内对闯入车辆做距离告警现场只有一台普通 IPC 摄像头。单目方案调了两个月换过三种模型误差始终在 15% 上下浮动——因为单目测距本质上是靠目标在图像中的尺寸反推距离一旦目标实际尺寸无法预知这个误差就无解。后来换成分体式双目相机同一个 YOLOV5 检测模型距离误差直接压到 5% 以内。这就是标题里这套方案的核心价值用双目视差拿到像素级深度再让 YOLOV5 告诉你深度图的哪个区域属于目标两个成熟技术拼在一起得到的不再是“大概多远”而是“那个目标的距离”。这套方案适合三类人做机器人避障的开发者需要把障碍物距离从激光雷达换成视觉方案做安防或交通管理的工程师要在已有检测模型上加上距离维度以及正在做毕设或课程项目的学生想用最小成本复现一个能演示的测距系统。它不需要昂贵的硬件两颗普通工业相机加一台带 GPU 的电脑就能跑通但前提是——标定要做准视差参数要调好检测框和深度图的对齐逻辑要写对。下文就按「相机标定 → 视差计算 → 检测融合 → 精度调优 → 踩坑记录」这条完整链路拆开讲每一段都给可直接抄走的代码和参数经验。2. 先让两张图变成一台虚拟相机双目标定的完整流程与参数设定2.1 为什么非标定不可基线、焦距和视差之间的数学关系双目测距的物理基础很简单同一点在左右相机成像位置的横向差异叫作视差 (d)深度 (Z) 与视差成反比(Z f \times b / d)其中 (f) 是焦距像素单位(b) 是基线长度两台相机光心的距离。这条公式看起来很干净但实际算的时候每一步都在依赖标定结果——(f) 不能用镜头标称焦距代替因为 CMOS 像元尺寸不一定准(b) 也不能用尺子量两个镜头中心的距离因为光心和镜头圆心有偏差左右相机的旋转和平移更不可能手动配平。标定的本质是解出左右相机的内参矩阵、畸变系数和相对位姿然后用这些参数把左右图校正成行对齐的“理想双目”状态。选型时有个经验值要注意基线越长同等视差下的可测距离越远但近距离盲区越大基线越短近处精度高远处视差小到难以分辨。我做分体式方案时给过一组参照基线 6 cm配 6 mm 镜头适合 18 米范围基线 12 cm配 12 mm 镜头适合 320 米范围。你这套方案如果主要测 5 米以内的机器人避障6 cm 基线足够如果做道路车辆测距建议 12 cm 起步。焦距同理长焦能把远处的视差拉大但视场角变窄检测模型能看到的范围就小了这是个需要和应用场景反复权衡的点。2.2 左右目标独立标定与双目标定的操作步骤先拍标定板照片。棋盘格用 10×7 或 9×6 的常见规格打印后贴在绝对平整的硬板上——这句不是废话后面避坑章节会专门讲。拍摄时注意三件事棋盘格要同时出现在左右画面里左右图都要拍满 20 组以上角度要覆盖倾斜、俯仰、旋转不能只拍正对相机的姿势距离要覆盖实际工作范围比如测距范围是 28 米标定板至少要出现在 1.5 米和 8 米两个极端附近。下面是单目标定加双目标定的核心脚本用 Python 和 OpenCV 实现import cv2 import numpy as np import glob # 棋盘格内角点数量和实际物理尺寸 pattern_size (9, 6) # 内角点数量不是棋盘格总数 square_size 0.025 # 每个格子的边长单位米 # 生成所有内角点的物理坐标z0 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 left_imgs glob.glob(calib/left/*.png) right_imgs glob.glob(calib/right/*.png) objpoints, imgpoints_left, imgpoints_right [], [], [] for lf, rf in zip(sorted(left_imgs), sorted(right_imgs)): left cv2.imread(lf, cv2.IMREAD_GRAYSCALE) right cv2.imread(rf, cv2.IMREAD_GRAYSCALE) ret_l, corners_l cv2.findChessboardCorners(left, pattern_size) ret_r, corners_r cv2.findChessboardCorners(right, pattern_size) if ret_l and ret_r: # 亚像素级角点精化提高标定精度 criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corners_l cv2.cornerSubPix(left, corners_l, (5, 5), (-1, -1), criteria) corners_r cv2.cornerSubPix(right, corners_r, (5, 5), (-1, -1), criteria) objpoints.append(objp) imgpoints_left.append(corners_l) imgpoints_right.append(corners_r) # 双目标定同时解出左右内参、畸变和相对位姿 ret, K1, D1, K2, D2, R, T, E, F cv2.stereoCalibrate( objpoints, imgpoints_left, imgpoints_right, None, None, None, None, (left.shape[1], left.shape[0]), criteriacriteria, flagscv2.CALIB_FIX_INTRINSIC ) print(fRMS 重投影误差: {ret:.4f})这段代码里最关键的两个参数是pattern_size和square_size。square_size填错会导致测距结果整体偏移——比如实际是 25 mm 写成 30 mm所有距离都会被放大 1.2 倍这种系统性误差最难排查因为曲线形态完全正常。flagscv2.CALIB_FIX_INTRINSIC的含义是先用单目标定结果固定内参双目标定只优化旋转 R 和平移 T这样收敛更快也更稳定。最后打印的 RMS 重投影误差我一般要求小于 0.15超过 0.3 基本可以判定照片有问题或者棋盘格不平。2.3 立体校正与重投影把左右图彻底拉平标定得到的 R 和 T 还不能直接用。要真正让左右图行对齐需要做 Bouguet 校正——把右相机相对左相机的旋转矩阵拆成两部分左右各转一半同时让光心连线与成像平面平行。这一步 OpenCV 封装在stereoRectify里其中alpha参数决定校正后图像保留多少原始像素alpha0 时裁剪最多画面可能缩小一圈alpha1 时保留全部像素但会出现黑色边框。我做项目时通常设 0 或 -1-1 表示自动裁剪到无黑边。校正后要生成查找表并应用到每一帧图像上# R 是右相对左的旋转矩阵T 是平移向量 R1, R2, P1, P2, Q, roi1, roi2 cv2.stereoRectify( K1, D1, K2, D2, (left.shape[1], left.shape[0]), R, T, alpha0 ) # 生成映射表用于 remap 快速校正 map1_l, map2_l cv2.initUndistortRectifyMap(K1, D1, R1, P1, (left.shape[1], left.shape[0]), cv2.CV_32FC1) map1_r, map2_r cv2.initUndistortRectifyMap(K2, D2, R2, P2, (left.shape[1], left.shape[0]), cv2.CV_32FC1) rect_left cv2.remap(left_img, map1_l, map2_l, cv2.INTER_LINEAR) rect_right cv2.remap(right_img, map1_r, map2_r, cv2.INTER_LINEAR)Q矩阵是重投影矩阵它把校正后的像素坐标转换到三维空间后续如果用cv2.reprojectImageTo3D直接取三维坐标就要靠它。很多教程把这步略过直接跳到视差计算结果深度图全是条纹噪声。我的习惯是校正完先做一次人工验证在两张校正图上画同一行水平线观察左右图同一物体的行坐标是否一致不一致就回去查标定参数或者R / T的符号方向。3. 从视差图到深度图SGBM 参数调优是关键中的关键3.1 SGBM 的核心参数和它们对测距精度的实际影响立体匹配算法里OpenCV 提供的 SGBM半全局块匹配是这个项目里最值得花时间调的部分没有之一。它比 BM 精度高、比 GC 快是工程上最常见的折中方案。直接上我的默认参数模板注意numDisparities和blockSize是基础但真正决定成败的是P1、P2和uniquenessRatiostereo cv2.StereoSGBM_create( minDisparity0, numDisparities64, # 视差搜索范围必须是 16 的倍数 blockSize7, # 匹配块大小奇数通常 5~11 P18 * 3 * 7 * 7, # 平滑惩罚项 P1通常取 8 * 通道数 * blockSize^2 P232 * 3 * 7 * 7, # 平滑惩罚项 P2通常取 4 倍 P1 disp12MaxDiff1, preFilterCap32, uniquenessRatio10, # 唯一性检测阈值过滤弱纹理误匹配 speckleWindowSize100, speckleRange32, modecv2.STEREO_SGBM_MODE_SGBM ) disparity stereo.compute(rect_left, rect_right).astype(np.float32) / 16.0P2决定算法对深度不连续区域的容忍度。P2 太小物体边缘的视差会“糊成一片”渐变P2 太大细小物体的深度会被背景平滑掉。我调参的顺序是固定blockSize7先把numDisparities设为 64用一张典型场景图反复给uniquenessRatio不同取值观察视差图上的空洞和飞点。uniquenessRatio的作用是要求最优匹配的代价比次优匹配低至少 10%否则判为不可靠——这个值设 5 时视差图很密但噪声大设 15 时干净但边缘缩水10 是多数场景的折中。disp12MaxDiff做左右一致性检查超过 1 个像素的视差直接置为无效这是排除遮挡区域误匹配最有效的手段。另外两个容易忽略的参数preFilterCap控制视差计算前的梯度截断默认 32如果画面过曝可以调到 16 试试speckleWindowSize用来滤除小块的孤立噪声区域超过 100 个像素的连通区域才保留小于这个规模的飞点全部置无效——这一步对后面 YOLOV5 取目标深度时的稳定性非常有帮助。3.2 深度图后处理中值滤波和 WLS 滤波的效果差别SGBM 输出的视差图直接转深度图结果通常是“表面布满颗粒”的。原因在于 SGBM 是逐像素的局部匹配加全局平滑弱纹理区域白色墙面、光滑地面的视差本身就不可靠再加上噪点直接测距就会得到跳变的值。两步后处理是标配第一步用中值滤波去掉椒盐噪声第二步用 WLS 滤波做边缘保持平滑。中值滤波的核大小我固定在 5×5核太大会把真实边缘也抹平目标边缘距离会出现“过渡带”核太小等于没滤。WLS 滤波需要左右两幅图做引导效果比双边滤波好一个档次# 中值滤波滤除孤立噪点 depth_filtered cv2.medianBlur(disparity, 5) # WLS 滤波用左图引导视差图平滑保留边缘 from cv2.ximgproc import createDisparityWLSFilter wls createDisparityWLSFilter(stereo) wls.setLambda(8000.0) # 平滑强度越大越平滑 wls.setSigmaColor(1.5) # 引导图颜色差异的权重 filtered_disp wls.filter(disparity, rect_left, None, rect_right) depth np.zeros_like(filtered_disp) valid filtered_disp 0.5 # 视差转深度Z f * b / d depth[valid] (fx * baseline) / filtered_disp[valid]fx和baseline在你的工程里应该来自标定结果fx是左相机内参 K1[0][0]baseline是 T[0] 的绝对值。我这里写成变量是示意真实代码不要写死。setLambda我见过有人调到 50000 的结果所有目标都变成一块平板边缘也没了——这个参数和场景纹理丰富度强相关纹理越少越需要大 lambda但超过 20000 就要留意边缘是否被过度平滑。调 WLS 时同样用一张典型图反复试看目标轮廓和背景之间是否有一条清晰的“缝”有就说明保留住了。3.3 视差空洞的常规处理为什么不建议用邻域填充弱纹理区域会产生大量无效视差点在深度图上表现为黑色空洞。初学者最容易犯的错误是直接把空洞用周围像素均值填上理由是“反正后面只取目标框里的深度”。这种做法在目标恰好位于弱纹理区域时会拿到一个完全虚构的距离值——看似有数字实际是背景的平均值。正确做法是分场景处理如果空洞面积小于目标框面积的 20%直接用框内有效点的中位数空洞不管如果空洞面积过大说明该目标所在区域根本不适合双目测距要么调整相机曝光很多弱纹理其实是过曝或欠曝造成的要么改回单目高度估计兜底。4. YOLOV5 检测结果与深度图对齐从分类坐标到物理距离的完整链路4.1 新版本 YOLOV5 在推理输出上的变化与对齐逻辑YOLOV5 的新版本 release 里推理脚本的输出格式基本稳定为(x1, y1, x2, y2, conf, cls)其中x1, y1, x2, y2是归一化或绝对像素坐标。这个输出和深度图对齐的关键在于深度图的坐标系和左相机校正图的坐标系必须完全一致。也就是说深度图的原始数据来源是rect_left和rect_right而 YOLOV5 推理时输入图像也必须是rect_left而不是原始左图。新版本里我一般直接导入官方仓库的detect函数但会改一行把输入图像的缩放方式从letterbox改为不缩放。原因是letterbox会在图像四周补灰边补边后的坐标和原始图像的偏移关系需要额外换算虽然官方代码里有ratio和pad参数可以做逆运算但多一环就多一个出错的可能。我的做法是直接在 YOLOV5 的推理代码里把输入分辨率固定为校正图的分辨率不缩放、不填充。如果你的模型在训练时用了 640×640 的输入那就把校正图直接 resize 到 640×640同时记录scale_x和scale_y检测框坐标再乘回去。4.2 目标深度提取算法为什么取框底边中心而不是框中心拿到检测框后最核心的问题是从框内深度图提取那个目标“真正的距离”。常见做法有三种取框中心点的深度、取框内所有深度点的均值、取框内有效深度点的中位数。我用的是第四种也是在实际项目中验证最稳的——取检测框底边中心附近一个小区域内的深度中位数。原因在于检测框的语义YOLO 的框是包含整个目标外接矩形的框中心对行人来说大概率在躯干中部对车辆来说可能在车窗或车顶这些位置的深度不一定能代表目标距相机的距离而框底边中心点通常对应目标与地面的接触点这个点如果没被遮挡深度就是目标的真实距离。取 3×3 或 5×5 区域的中位数比单点采样抗噪能力强很多def get_target_depth(depth_map, box, roi_size5): x1, y1, x2, y2 [int(v) for v in box] # 底边中心点物体与地面接触位置 cx (x1 x2) // 2 cy y2 - 2 # 向上收缩几个像素避开底部边缘噪声 half roi_size // 2 # 边界检查防止越界 y_min max(0, cy - half) y_max min(depth_map.shape[0], cy half 1) x_min max(0, cx - half) x_max min(depth_map.shape[1], cx half 1) region depth_map[y_min:y_max, x_min:x_max] valid region[region 0] # 0 视为无效深度 if len(valid) 0: return 0.0 # 该区域无有效深度交给上层处理 return float(np.median(valid))这函数里有两个细节值得解释。cy y2 - 2是为了避开检测框最底部那一两行像素因为目标底边往往是前景和背景的交界SGBM 在边缘位置的视差容易出错取中位数而不是均值是因为即使在一个 5×5 的小框里也可能混入一两个背景噪点均值会被拉偏中位数对这种离群值免疫。最后返回 0 时的处理策略是如果目标中心区域没有有效深度就扩大搜索范围再试一次还不行就把该目标标记为“测距失败”宁缺毋滥。4.3 多目标场景下的深度分配与 ID 追踪多目标检测时不同目标的检测框不能共用同一个深度区域。一个常见错误是拿整张深度图的全局最小深度当所有目标的距离这在只有一个目标时成立多目标就全错了。正确做法是每个检测框独立调用get_target_depth。但多目标时还有一个隐藏问题两个目标前后遮挡后面的目标框底边可能落在前面目标的深度上。实际项目里我的处理方法是先按深度排序取最近的 N 个目标的框做非极大值抑制联合判断或者引入一个轻量的目标追踪器用帧间连续深度做滤波单帧跳动超过 20% 就认为是异常值丢弃。如果是实时视频流强烈建议加一个简单的滑动窗口滤波# 简单的一阶低通滤波平滑深度突变 depth_smooth alpha * depth_current (1 - alpha) * depth_lastalpha设 0.30.5太高跟踪反应慢太低会跟着噪声跳。注意这套方案里depth_current必须是有效深度无效时不要滤波直接沿用上一帧值否则会把 0 值慢慢“混”进真实距离里。5. 避坑双目测距项目里我踩过的 5 个最典型的坑5.1 标定板贴在软纸板或弯曲面上RMS 误差假性偏低现象双目标定 RMS 只有 0.08看起来完美但实测距离系统性偏差 10% 以上。原因软纸板在拍摄时轻微弯曲棋盘格的物理坐标假设所有角点 z0 且共面被破坏标定算法为了拟合这些畸变点会把焦距和畸变系数“扭曲”来补偿重投影误差看起来很小但真实几何已经错了。解决用铝板或亚克力板贴棋盘格拍摄时不要用手扶着板子边缘用支架固定如果 RMS 低于 0.05反而要反思是不是拍的姿态太单一把标定退化成了一次平面拟合。5.2 视差图在目标边缘出现“膨胀”框底边离真正接触点差好几个像素现象检测框底边正好压在物体和地面的交界但该位置的深度值是物体表面的深度偏近导致距离偏小。原因SGBM 的代价聚合在深度不连续区域会向前景“扩展”尤其在暗色物体贴亮色地面时最明显。解决检测框底边向上收缩 510 个像素再取深度就是上面代码里cy y2 - 2的放大版如果收缩后取到的深度出现空值再逐步往下试探找一个既是有效深度又离底边最近的点。5.3 高反光目标车漆、玻璃幕墙深度图大片空洞现象黑色车漆在后视图中呈现一块“黑洞”检测框底边中心正好落在洞里深度返回 0。原因SGBM 假设同名点颜色相近高反光区域左右相机的成像亮度差异极大一个视角看到高光另一个是暗面匹配代价值全部超标被判为无效。解决这不是调参能解决的是物理层面的问题。我的处理是加曝光控制的偏振片成本高或者做兜底策略——如果底边区域无效就向上搜索到框内第一个有效深度区域但要在结果里标注“该深度来自目标上部可能有偏差”。至少不要让系统返回 0 米去触发错误的避障刹车。5.4 YOLOV5 和深度图分辨率不一致导致框位偏移现象深度图上目标的深度位置是对的但 YOLOV5 检测框画上去后发现框整体向 x 方向偏移了十几个像素目标边缘深度取样取到了背景上。原因YOLO 推理前对图像做了 resize 或 letterbox而深度图没做同样的变换两个坐标系错位或者 YOLO 输入是原始左图深度图是校正后的左图而校正过程本身有约几像素的平移。解决把 YOLO 的推理图像直接指定为rect_left并且关闭 letterbox或者完全复用深度图的处理管线每次改图像尺寸后都要做一次可视化叠加检查在图上叠出检测框和深度值人眼扫一遍比任何自动化测试都管用。5.5numDisparities设置太大导致 GPU 显存爆掉和帧率骤降现象把numDisparities从 64 调到 128 后帧率从 20 掉到 8显存增加三分之一。原因SGBM 的代价空间体积与视差范围线性相关视差范围翻倍内存和计算量同步翻倍CPU 版本更明显。解决先根据最大测距距离反算最大视差——最大视差 f * b / 最小测距距离。比如 f700、b0.06、最小距离 0.5 米最大视差就是 700×0.06/0.5 84所以numDisparities96就够不需要盲目设 128。设太大除了浪费算力还会让更远处的噪声进入视差搜索范围反而降低近处精度。6. 落地验证把测距误差从“感觉准”变成“可交付的指标”最后一章不是讲更多算法而是给你一个可以直接上生产线验收的方法——误差标定测试。我在每个双目项目里都会写一个独立的验证脚本流程是固定相机位置把一个标准尺寸的标靶比如 30 cm×30 cm 的棋盘格板依次放到已知距离上1m、2m、3m、5m、8m每个位置记录 100 帧算出算法输出的距离中位数与真实距离做差得到绝对误差和相对误差。最后画一张距离误差曲线表这张表是项目验收的核心交付物。# 误差标定测试脚本核心逻辑 true_distances [1.0, 2.0, 3.0, 5.0, 8.0] # 皮尺或激光测距仪实测 for d in true_distances: for _ in range(100): frame_l get_left_frame() frame_r get_right_frame() rect_l, rect_r rectify(frame_l, frame_r) disparity compute_disparity(rect_l, rect_r) depth_map disparity_to_depth(disparity) boxes yolo_detect(rect_l) target_depth get_target_depth(depth_map, boxes[0]) # 单目标时取第一个框 pred_distances[d].append(target_depth) median np.median(pred_distances[d]) err_abs abs(median - d) err_rel err_abs / d print(f{d:.1f}m: 测距中位数{median:.2f}m, 绝对误差{err_abs:.3f}m, 相对误差{err_rel:.2%})跑完这份脚本你会得到一张类似下面的误差表在项目报告里直接使用真实距离 (m)测距中位数 (m)绝对误差 (m)相对误差1.01.030.033.0%2.01.960.042.0%3.03.120.124.0%5.04.850.153.0%8.08.400.405.0%看到这组数据的第一个反应不要急着调参去把 8 米处的误差从 5% 压到 3%——先算一下理论极限在 8 米处视差 f×b/Z 700×0.06/8 5.25 像素。SGBM 的视差精度通常只能到 0.51 个像素也就是说这个距离上的理论最小误差就是 0.5/5.25 ≈ 9.5%你的实测 5% 已经跑赢了理论值说明视差超分辨率插值起了作用。这时候再往上提精度的正确方向是加长基线或换更高分辨率相机而不是继续调 SGBM 参数。最后说一个我的使用习惯无论项目多急第一版测距代码跑通后我都会先做一次专门的误差标定测试而不是直接上实景演示。因为在真实场景里目标的颜色、光照、遮挡都会引入额外误差如果连标定条件下的基线误差都不知道实景里的异常根本无法归因。这样做还有一个额外收益——当客户问“精度到底多少”时你拿出来的是表格和实测数不是“感觉挺准”。这套方法我用了好几个项目每次都帮我提前暴露了相机安装松动或镜头畸变异常的问题。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询