2026/10/21 21:14:16

YOLOv5+双目相机三维测距:标定、视差计算与工程实践

YOLOv5+双目相机三维测距:标定、视差计算与工程实践 简介面向计算机视觉开发者的YOLOV5与双目相机三维测距工程包基于yolov5-6.1版本覆盖目标检测、立体匹配与深度计算完整流程。通过YOLOV5对目标快速定位识别再由双目视差求解三维坐标可满足自动驾驶、机器人导航、无人机避障等场景的实时测距需求。工程源码包含数据预处理、模型推理、立体匹配、深度图生成与结果可视化等模块模块间解耦清晰便于扩展替换。包内共141个文件以37个Python源码和22个pyc编译文件为核心配合40个yaml与11个yml配置、预训练pt权重、Dockerfile容器部署文件、tutorial.ipynb交互式教程以及md/txt说明、样例图片和演示mp4整体约40.38MB目录结构清晰便于按模块复用与二次开发。已有2420人学习下载适合具备一定深度学习基础、希望快速搭建双目测距系统的研究者或工程师。资源提供可运行的完整工程代码、预训练权重与配置文件可基于自有数据直接微调配套文档还能帮助理解检测与测距的集成细节有效缩短环境搭建和算法落地的周期。1. YOLOV5 双目相机实现三维测距这份源码项目到底能做什么我手头这套刚拆完的新版双目测距项目正好踩中了这两年计算机视觉落地里最实在的需求点检测框不仅能告诉你是人、是车、是哪个障碍物还能把目标的真实物理距离算出来。它用 YOLOv5 做目标框检测用双目相机做立体匹配和三维坐标恢复两个模块串成一条完整的测距管线。对有毕设、竞赛、工训或实车项目需求的人来说这套资源最实用的地方在于你不用从零磕立体视觉推导标定、匹配、测距的代码路径是完整的跑起来就能打印出距离数据。这不是什么黑匣子工程也不是单纯贴论文公式的教程包。我拆完之后能明确告诉你里面包括双目相机标定、立体校正、SGBM 视差计算、YOLOv5 目标检测结果对齐、目标框内视差转距离这几段关键代码数据集虽然要自己补但算法链路和参数都写得能直接改。适合三种人新手想完整走一遍双目视觉流程的要快速出毕业设计或课程设计原型的以及之前只用过单目测距、现在想换成双目几何测量的开发者。这篇笔记我会把代码结构、运行顺序、每个参数该怎么调、哪些位置最容易翻车全部拆开讲清楚。2. 为什么用 YOLOv5 双目选型逻辑与技术基础2.1 目标检测选 YOLOv5 的理由部署轻、输出好接、可控性强在这个项目里目标检测组件承担的任务非常具体找到画面里的目标输出一个稳定的矩形框和类别。不会要求它同时回归深度那是双目模块的事。选 YOLOv5 做这件事有几个非常实在的原因。首先它的模型文件体积和显存占用相对轻同样一张 640x640 输入图单帧推理时间在普通消费级显卡上能跑出几十毫秒的量级实车场景或实时监控场景都不会卡顿。其次它的输出格式极其友好检测结果是个 (x1, y1, x2, y2, conf, cls) 的数组x1, y1 是目标框左上角像素坐标x2, y2 是右下角像素坐标。这个格式直接送到测距模块里做目标框中心点计算不需要强行改接口。再一个是可定制性。原版 YOLOv5 提供的模型权重已经覆盖了 COCO 的 80 类人、车、自行车这些常见目标都能直接用但对特定场景比如工厂货车识别、工地安全帽佩戴检测你只需要准备标注数据集做微调这部分群里的教程和源码已经非常成熟。我拆的这套新版本双目测距项目检测端就是基于 YOLOv5 官方仓库的模型结构改的保留了权重文件直接加载能力没有魔改网络结构这意味着你想换成 YOLOv8、YOLOX 或其他检测框架时测距模块完全不依赖检测网络的具体结构只需要把检测输出整理成相同格式即可。这个低耦合设计是我比较欣赏的。2.2 为什么用双目而不是单目几何测距的最大优势单目测距在很多项目里也出现过思路是利用已知物体实际大小通过像素尺寸比值估算距离公式大概是 Z f * W / w其中 W 是真实物体宽度w 是像素宽度f 是焦距像素值。这个方案的问题非常明显你必须预先知道每个类别的真实尺寸而且一旦摄像头俯仰角变化、焦距变化误差就完全失控。人稍微侧身一点宽度从 0.4 米变成 0.2 米测距结果直接翻倍这就是单目方案的天花板。双目视觉走的是另一条路通过两个摄像头同时观察一个目标利用左右图像中的像素视差计算深度。视差公式是 Z f * B / d其中 Z 是目标距离f 是左相机的焦距像素值B 是两个相机光心的距离即基线距离d 是目标像素点在左右图上的视差单位是像素。这个公式里没有任何目标类别先验知识不需要知道你检测的是人还是货车只要视差 d 可靠距离结果就几何成立。这就是我拆这个项目时最认同的选型逻辑检测负责识别目标双目负责几何测量两者各管一段互不干扰。2.3 双目测距的完整链路和各环节的输入输出整个链路可以拆成五段相机标定 → 立体校正 → 立体匹配 → 目标框对齐 → 距离计算。相机标定的核心产物是左右相机的内参矩阵 K1、K2、畸变系数 dist1、dist2以及左右相机之间的旋转矩阵 R 和平移向量 T。没有标定后面全是空谈因此项目中单独准备了标定脚本。立体校正做的事情是让左右图像行对齐把双目模型近似成理想的平行光轴模型这样视差搜索就能从二维降到一维只找同一行上的匹配点。这一步输出的是校正映射表存在一个 .npz 文件里运行时直接加载。立体匹配在这个项目里用的是 OpenCV 的 SGBM 算法输入是校正后的左右灰度图输出是视差图 disp每个点的值表示该像素在左右图上偏移了多少像素。目标框对齐是把 YOLOv5 的检测框坐标从原图上映射到左图坐标系上再在框内提取有效的视差值。最后一步用 Z f * B / d 把视差换算成物理距离。需要特别注意的是YOLOv5 检测的原图是经过 BGR 读入和归一化处理的 RGB 图像而 SGBM 立体匹配输入是单通道灰度图这两者在代码里需要分别取流不能只预处理一份图像就同时喂给两个模块否则会出现左右视差图的尺寸或通道数对不上的问题。新版本项目里已经处理好了这条线你在看代码时重点留意 detect.py 附近图像流的分叉处。3. 项目运行全程环境配置、目录结构与三步跑通3.1 环境依赖版本组合比最新更重要这个项目的环境文件 requirements.txt 不是随便列几个包完事里面有明确的版本区间标注。我按实际拆解结果告诉你Python 版本建议 3.8 左右OpenCV 版本在 4.5 到 4.8 之间PyTorch 建议 1.10 以上的稳定版torchvision 保持跟 PyTorch 对应。为什么不追求最新版本因为 OpenCV 4.5 之后 stereoRectify、StereoSGBM 这些立体视觉接口变化不大但更高版本在 printf 调试和某些图像格式转换上会多出不少坑。PyTorch 版本过高时如果机器上 CUDA 版本不匹配YOLOv5 加载权重会直接崩崩的方式还很隐蔽提示信息都不指向 CUDA。安装命令很简单但我强烈建议你用虚拟环境隔离再装conda create -n stereo_3d python3.8 conda activate stereo_3d pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install opencv-python4.8.0.74 opencv-contrib-python4.8.0.74 pip install numpy scipy matplotlib pyyaml tqdm这里的参数说明torch 和 torchvision 要装在 conda 环境里不要用系统 Python避免和系统自带的 OpenCV 依赖冲突opencv-contrib-python 必须装因为部分立体匹配扩展接口在 opencv-python 主包里缺失你如果只装了 opencv-python后面 import cv2 不出错但调用某些函数会 AttributeError这是最常见的一个环境坑。版本号尽量跟上面一致4.8.0.74 是一个相对稳定且兼容性好的组合。如果你 GPU 显存小于 4G建议 YOLOv5 推理时开启半精度 fp16。3.2 项目目录结构哪个文件对应哪段流程拆完源码包之后目录结构大概是这样的我按实际功能重新整理过project_root/ ├── calibration/ │ ├── capture_images.py │ ├── calibrate_stereo.py │ ├── stereo_map.npz │ └── checkerboards/ ├── detection/ │ ├── weights/ │ │ └── yolov5s.pt │ ├── detect.py │ └── utils/ ├── depth/ │ ├── stereo_matcher.py │ └── sgbm_config.py ├── deploy/ │ └── run_demo.py └── requirements.txtcalibration 目录处理标定capture_images.py 负责实时抓取左右棋盘格图像calibrate_stereo.py 负责计算标定参数并生成 stereo_map.npz。detection 目录是 YOLOv5 的推理封装deploy/run_demo.py 是主入口它会加载标定映射表、初始化 SGBM、启动检测模型然后循环读帧、检测、匹配、输出距离。你第一次跑通项目只需要关心 run_demo.py其他脚本可以稍后再逐个读。这种拆分方式把标定一次性的工作和运行时的工作分开改起来不会有牵一发动全身的顾虑。3.3 三步跑通流程标定、单目验证、双目联调第一步是标定。项目里的标定脚本会采集 20 到 30 张不同姿态的棋盘格图像要求棋盘格至少覆盖画面内不同深度和不同角度尤其要包含近景和远景。标定过程在代码里最核心的逻辑是先用 cv2.findChessboardCorners 找角点然后用 cv2.stereoCalibrate 计算内外参和畸变系数。你可以直接跑cd calibration python capture_images.py --rows 9 --cols 6 --save_dir ./checkerboards python calibrate_stereo.py --rows 9 --cols 6 --grid_size 0.025 --save_path ./stereo_map.npz这里的参数需要说明一下。--rows 9 --cols 6 做具体指棋盘格内部角点数量是 9x6不是格子数量两者差 1这个搞错会导致角点检测经常失败。--grid_size 0.025 是每个棋盘格的实际边长单位是米即 2.5 厘米这决定了标定结果中平移向量 T 的单位必须和真实棋盘格尺寸一致不然后续测距结果会整体偏大或偏小。标定结束后你会看到重投影误差通常小于 0.5 像素就算合格大于 1 像素就需要重新采集。第二步是单目验证。你可以先只加载 stereo_map.npz把左右图像校正一下用 cv2.remap 处理一下看校正后的图像是否能把远处的水平边线对齐成一条直线。这一步不需要 YOLOv5 参与是纯视觉验证。如果这一步没过后面双目测距肯定不准。我一般会用一个带水平窗框的画面作为参考看窗框在左右图同一条水平线上是否对齐。第三步是双目联调直接运行cd deploy python run_demo.py --stereo_map ../calibration/stereo_map.npz --model ../detection/weights/yolov5s.pt --source 0--source 0 表示调用本机默认摄像头。如果你用的是录好的双目视频可以改成视频文件路径。跑通之后你会在画面上看到检测框框上方会打印实时距离单位是米。这个版本运行起来的基础效果是能同时看到左右画面拼接、视差图以及带距离的检测框三窗口展示。4. 核心代码与参数调优测距精度的关键细节4.1 目标框与视差图如何对齐中心点和有效区域提取测距模块能不能测准很大程度上取决于你从目标框里挑哪些像素来算视差。整个图像里不是所有像素的视差值都可靠。远处纹理缺失的地方、重复纹理区域、被遮挡的区域SGBM 都会计算出错误视差错误值通常表现为离群的大值或零值。如果你直接把整个目标框内的所有像素视差取平均相当于把错误值也平均进去了距离会偏。这个项目的做法是这样的检测框输出后先做坐标偏移对齐然后在框内取一个中心区域比如框宽和框高的 40% 作为有效计算区再在这个有效区上做中位数滤波。import cv2 import numpy as np def compute_distance_from_box(box, disp_map, focal_px, baseline_m, center_ratio0.4): x1, y1, x2, y2 [int(v) for v in box] w x2 - x1 h y2 - y1 cw int(w * center_ratio) ch int(h * center_ratio) cx (x1 x2) // 2 cy (y1 y2) // 2 x_start max(cx - cw // 2, 0) y_start max(cy - ch // 2, 0) x_end min(cx cw // 2, disp_map.shape[1] - 1) y_end min(cy ch // 2, disp_map.shape[0] - 1) region disp_map[y_start:y_end, x_start:x_end] valid region[(region 0) (region 192)] if valid.size 0: return None disp np.median(valid) if disp 0: return None distance focal_px * baseline_m / disp return distance这段代码的逻辑说明先根据 YOLOv5 输出的框坐标计算出框的正中心位置和中心区域的边界。然后用 mask 筛选出视差值在 0 到 192 之间的像素。为什么要限制 192因为 SGBM 的 numDisparities 一般设置为 64 或 96超过这个范围的值基本都是无效匹配直接排除。再用 np.median 而不是 mean是因为中位数对离群值不敏感就算框内有一些零星错误匹配点中位数能更准确地反映中心目标的真实视差。最后用 Z f * B / d 换算成米。参数说明center_ratio 取 0.4 是什么意思它表示只取目标框中心 40% 宽和 40% 高的区域来计算视差。取太小遇到目标只有中心一小部分是纯色区域时有效视差点太少可能取不到有效值取太大会把目标边缘的背景像素也包进来背景如果距离更远视差值会偏小距离就会偏大。0.4 是个折中值如果目标表面纹理少可以调到 0.6如果目标形状不规则比如人形可以降到 0.3按场景灵活选。4.2 SGBM 参数配置每个参数影响的是哪部分精度很多初学者拿着 SGBM 的默认参数直接跑结果视差图一片噪声。这个项目里把 SGBM 参数单独放在 sgbm_config.py 里方便统一调参。实际使用中需要关注的核心参数有这么几个numDisparities、blockSize、P1、P2、uniquenessRatio、disp12MaxDiff。# sgbm_config.py sgbm_params dict( minDisparity0, numDisparities96, blockSize9, P18 * 3 * 9 ** 2, P232 * 3 * 9 ** 2, disp12MaxDiff1, uniquenessRatio10, speckleWindowSize100, speckleRange32, )参数说明numDisparities 必须是 16 的倍数它决定了算法搜索的最大视差范围值越大能探测的近处距离越近但计算量和错误匹配也越多。如果你的摄像头基线较宽或目标可能靠得很近这个值需要增加如果场景大多数距离在 3 米以上96 就够用。blockSize 是匹配窗口大小必须是奇数取 9 表示用 9x9 的像素块做匹配。blockSize 太小纹理不足区域匹配不准太大深度边缘会被圆滑目标边界和背景的区分度下降。P1 和 P2 是平滑惩罚系数惩罚相邻像素间视差的突变。经验法则是 P1 取 8 * 通道数 * blockSize²P2 取 32 * 通道数 * blockSize²其中通道数对灰度图是 1彩色图是 3。因为这里输入是灰度图所以要写成 8 * 1 * blockSize²。uniquenessRatio 是唯一性比例它要求最佳匹配视差的代价比次佳匹配低至少这个百分比值越大过滤掉的错误点越多但也会删掉一些正确点。disp12MaxDiff 是左右一致性检查的最大允许差值0 或 1 能有效消除遮挡区域的错误匹配但过小也会让正确区域被过滤。这些参数没有绝对最优组合但有一个调参顺序先把 blockSize 固定为 9调 numDisparities 保证近处目标能出现视差再调 P1、P2 让视差图噪声降低最后调 uniquenessRatio 和 disp12MaxDiff 清理离群点。调完在视差图上用鼠标点选目标区域观察该区域的视差值是否稳定在一个区间内稳定即说明参数合格。4.3 相机内参和基线参数从哪里来焦距和基线的正确读法测距公式里的 f 是焦距的像素单位值不是镜头标称的毫米焦距。它从标定内参矩阵中读取K 矩阵第一行第一列就是 fx。基线 B 是两个相机光心的实际距离单位是米但标定得到的平移向量 T 的单位取决于你棋盘格尺寸标定时单位所以两者必须统一。项目里 calibrate_stereo.py 输出的 stereo_map.npz 里保存了 fx 和 T运行阶段直接加载。有一个坑需要注意立体校正之后左右图像会被校正成理想平行模型此时两个相机的主点可能已经发生了变化所以你要用的是校正后图像对应的新内参而不是标定原图的旧内参。OpenCV 的 stereoRectify 会输出校正后的新投影矩阵 P1P1[0][0] 才是校正后的 fx。如果你发现距离总是整体偏大或偏小一个固定倍数先检查 fx 和基线单位是否一致。比如 fx 是像素单位基线是毫米写成了 120那结果就会偏大一百倍。项目代码里在加载 stereo_map.npz 后有专门的函数从 P1 矩阵中取焦距不要自己去内参矩阵 K 里找。5. 避坑排查双目测距最容易翻车的六个细节5.1 距离整体偏大或偏小先查基线单位不要先调算法现象目标实际距离 2 米测出来老是 2.5 米或 1.5 米误差方向固定不随目标距离变化。原因基线 B 的单位和 fx 不一致或者棋盘格 grid_size 填错了。解决方案打开 stereo_map.npz 检查 T 的模长如果棋盘格边长 2.5 厘米写成了 2.5 米T 会远大于真实基线。确保 T 的长度就是两个摄像头光心之间的米数。固定距离放置一个已知目标反推有效基线值再用推出来的值改代码里的基线常量。5.2 远处目标测距结果跳变剧烈现象人站在 4 米外测量结果在 3 米到 6 米之间大幅度跳动。原因远处视差值很小只占几个像素SGBM 匹配误差 1 个像素就会造成距离跳动 30% 以上。解决方法增大 numDisparities 并不会改善远处精度真正有效的是提高图像分辨率用更大分辨率图像做匹配或者增加 blockSize 以提高信噪比。如果还是跳考虑对连续帧的距离结果做滑动平均滤波把窗口设成 5 到 10 帧能明显平滑抖动。5.3 目标检测到了但距离一直显示 None现象YOLOv5 框正常显示但距离数值不出现在框上。原因目标框中心区域在视差图中被判定为无效点可能目标表面是纯色无纹理或者目标处于图像边缘SGBM 没有有效匹配。解决方案把 center_ratio 从 0.4 改到 0.6扩大提取范围观察视差图上目标对应区域是否为黑色如果确认为纯色区域可以做纹理投影辅助匹配或者改用小 blockSize 如 5。还有一个常见原因是目标太近超出了当前 numDisparities 能覆盖的最小距离范围此时需要增大 numDisparities 到 128 或 192。5.4 标定重投影误差很低但立体校正后图像依然没对齐现象标定误差在 0.3 像素以内但 cv2.remap 后左右图垂直线没有平行对齐水平边线错位。原因采集标定板时图像覆盖范围太小角点都集中在画面中央或者棋盘格姿态缺少旋转变化。解决方案重新采集标定时保证标定板在画面四个角落都出现且包含前倾、后仰、左右旋转多种姿态。用 calibrate_stereo.py 输出的校正映射表重跑 remap 验证对齐效果这比看误差值更可靠。5.5 换了一台电脑原来可以运行的项目报错现象在原来机器上正常运行的代码换机器后直接在 import 阶段报错或者运行中途崩溃。原因OpenCV 和 PyTorch 的版本不一致尤其 OpenCV 主版本不同接口有差异。解决方案严格按照 requirements.txt 中的版本号安装不要用 pip install opencv-python 直接装最新版。另外一个常见坑是 CUDA 不一致导致 YOLOv5 权重加载失败可以先在 CPU 模式下跑一遍确认检测模块正常再排查 GPU 加速的配置。5.6 左目和右目画面亮度差异大导致视差图大范围黑洞现象视差图整体偏黑有效匹配点很少。原因双目摄像头自动曝光没关左右相机根据各自视野亮度自动调节了曝光参数导致同一场景左右图亮度不一致。解决方法在摄像头初始化代码中强制关闭自动曝光把曝光时间设为固定值增益也固定。项目代码里在 VideoCapture 初始化后要设置 CAP_PROP_AUTO_EXPOSURE 为 0。这步一般在代码里被很多人忽略不处理会严重影响视差图质量。6. 进阶从单目标测距到批量标定与点云输出这一章我给你三个值得加进去的进阶做法也是把这个项目再往前推一步的实用技巧。第一个是做距离校准曲线。双目测距公式是理想模型但实际镜头畸变校正残差、立体校正误差、SGBM 匹配偏差会导致测距结果存在少量非线性误差。我一般会在地面上做标记固定几个距离点比如 1 米、1.5 米、2 米、3 米分别测十次取平均得到一组实际距离和测量距离的对应关系。然后用这些点做线性拟合得到一个缩放系数 s 和偏置 b最终距离 拟合值 * s b。这个校准技术并不能直接塞进代码但你需要明白标定是几何基础拟合是工程修正两个都做才能让测距结果从“大致可用”变成“可信”。在项目的可执行代码中实际距离和测量距离的拟合函数可写为import numpy as np def calibrate_distance_curve(measure_distances, real_distances): p np.polyfit(measure_distances, real_distances, 1) return p def apply_curve(raw_distance, p): return raw_distance * p[0] p[1]第二个是把视差图转成三维点云。OpenCV 自带 reprojectImageTo3D 函数输入视差图和 Q 矩阵输出每个像素对应的三维坐标数组。把这组坐标导出成 ply 格式后可以在可视化软件里查看目标的点云轮廓。它对你验证测距结果特别有用点云中目标的尺寸应该和目标实际尺寸接近如果人的头肩宽度在点云里显示 80 厘米那说明视差被高估了需要回头检查基线。第三个技巧是批量测距模式。项目代码中有命令行参数 --batch测完之后把每帧的检测框中心坐标和对应距离写到一个 CSV 文件。这个功能适合离线跑一段视频批量统计目标和距离的关系不需要边跑边手工记录。输出的每行内容包括帧号、目标类别、目标框坐标、检测置信度、估计距离。做算法对比实验时这种结构化输出比截图保存方便得多。关于镜头畸变校正我必须再强调一次标定的作用是把像素坐标和物理世界建立准确关系畸变越大视差计算对校正的依赖就越强。如果你用的是低畸变的工业相机还好如果是普通网络摄像头鱼眼效果很强一定不要跳过标定。另外一定要在部署环境中用自己的摄像头重新做一次标定不要直接拿项目自带的 stereo_map.npz 用那两个摄像头的相对位置、镜头焦距都和你手上的不一样不重新标定等于测出来的距离来自一套陌生的几何参数。从那以后我每次接手双目测距项目都会强制性地从标定重投影误差、立体校正对齐目测、固定目标距离实测这三件套做完整验证再做后面的检测和测距联调。这套项目的框架本身没有太大问题但你要是跳过验证直接上实际场景大概率会被各种误差绕进去。路要一步步走先把几何搞对再让检测模块发光发热。希望帮到你。本文还有配套的精品资源点击获取