YOLOv5双目视觉测距实战:从相机标定到三维坐标的完整技术解析 简介面向自动驾驶、机器人导航与无人机避障等场景YOLOv5与双目相机结合的三维测距新版本为开发者提供一套完整的实时目标检测与深度估计实现。该版本基于YOLOv5-6.1先由YOLOv5快速识别并定位图像中的目标再由双目相机依据视差原理计算对应深度最终输出目标在三维空间中的位置可移植到移动平台或嵌入式设备。资源包共141个文件约40.38MB涵盖yaml/yml配置、py脚本、pyc编译文件、pt预训练权重、ipynb教程、mp4演示、Dockerfile与样例图像等目录结构清晰便于在不同环境中部署与二次开发。包内除完整源码与模型权重外还附带教程文档能帮助用户理解立体匹配、深度图构建与目标框深度映射的完整链路从模型推理到实际测距均可直接复现。目前已有2419人学习下载适合具备一定深度学习与OpenCV基础的开发者用于项目复现、算法调优或课程设计。1. 一个老问题的新解法为什么“新版本”值得重新看一遍双目测距这事网上资料不少但大部分停在“算出视差图”就结束了离真正能用差一口气。这个项目把 YOLOV5 检测和双目视觉测距串成了完整闭环输入是左右目两张图输出直接是目标的类别、置信度和三维坐标不用自己再拼胶水代码。我拆完第一感觉是它解决的痛点很实在——你不需要懂立体匹配的底层原理也能拿到可用的距离数据。适合谁手里有双目相机、想在巡检机器人或安防项目里加测距功能的人以及想把 YOLO 检测结果从像素坐标升级到物理坐标的开发者。新版相比老版主要换了检测框架的调用方式、重写了标定参数加载逻辑还对不同焦距的相机做了适配。对于想二次开发的人来说这个版本的结构更清晰标定和测距两个环节拆得很干净。2. 系统设计与技术选型双目测距的完整拓扑2.1 从像素到米整个系统的坐标链路设计整个项目的核心链路可以概括为双目相机标定 → 畸变校正 → 立体匹配 → 视差图 → 深度图 → YOLOV5 检测框中心点深度提取 → 三维坐标换算。这条链路里最容易出错的是坐标系转换因为 YOLOV5 输出的检测框中心坐标是基于校正后的左目图像的像素坐标系而测距结果需要的是相机坐标系下的三维坐标。这个项目把链路拆成三个独立模块标定模块负责生成相机参数测距模块负责视差和深度计算检测模块负责目标定位。三个模块之间通过参数文件和数据接口解耦这意味着你可以单独替换 YOLOV5 为其他检测模型只要输出格式保持一致即可。从架构上看这种设计是比较聪明的它让测距逻辑不依赖具体检测模型的实现细节。2.2 依赖组件新版本具体用了什么从工程文件来看新版的技术栈是这样的YOLOV5 作为目标检测器模型权重文件为best.pt检测类别是 COCO 的 80 类子集通常实际部署时会重新训练成自己需要的类别OpenCV 负责图像读取、畸变校正、立体匹配cv2.StereoSGBM是默认的立体匹配算法比 BM 的视差图更平滑NumPy 处理矩阵运算和点云计算YOLOV5 本身是基于 PyTorch 的所以环境里还需要 PyTorch。版本要求不算苛刻常见的 1.8 到 2.x 都能跑核心原因是项目只在推理阶段用到了检测模型不涉及训练所以对 CUDA 版本不敏感。2.3 目录结构与模块职责工程解压后核心文件分布大概是这样的结构project/ ├── main.py # 入口负责初始化和主循环 ├── camera.py # 双目相机采集与单目图像读取封装 ├── calibrate.py # 标定脚本生成相机参数文件 ├── stereo_match.py # 立体匹配与深度图计算 ├── distance.py # 目标测距核心逻辑 ├── utils.py # 坐标转换等辅助函数 ├── weights/ │ └── best.pt # 检测模型权重 ├── config/ │ └── camera_params.yaml # 标定结果配置文件 └── requirements.txtmain.py负责串起整个流程camera.py做图像采集stereo_match.py做视差和深度计算distance.py负责把检测结果和深度数据结合。这个拆分方式比较符合嵌入式视觉项目的惯例每个文件都在做一件具体的事排查问题时不用到处翻。3. 核心模块拆解标定、匹配到测距的完整实现3.1 标定脚本相机参数怎么进入系统双目测距的前提是双目相机已被标定。这个项目的标定脚本使用棋盘格图像进行标定输出内参矩阵、畸变系数、左右目相对旋转和平移向量。关键代码如下import cv2 import numpy as np import yaml # 棋盘格尺寸内角点数 pattern_size (9, 6) # 棋盘格每个格子的实际边长单位毫米 square_size 20.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标定完成后脚本会把结果写入camera_params.yaml。注意square_size必须和你打印棋盘格的实际尺寸一致这个值直接决定了测距结果的尺度。有人标定完测距偏了 10%多半是这个参数没改默认 20mm 实际用的是 25mm 的棋盘格。3.2 立体匹配视差图计算的工程实现立体匹配是双目测距里最“玄学”的部分参数调不好视差图就全是噪点。这个项目用的是 OpenCV 的 SGBM 算法参数配置如下import cv2 def create_sgbm(): sgbm cv2.StereoSGBM_create( minDisparity0, numDisparities64, # 视差范围必须是 16 的倍数 blockSize11, # 匹配块大小奇数 P18 * 3 * blockSize ** 2, P232 * 3 * blockSize ** 2, disp12MaxDiff1, uniquenessRatio15, speckleWindowSize100, speckleRange32 ) return sgbmnumDisparities决定了能测到的最近距离数值越大能测的越近但计算量也越大。blockSize影响视差图的细腻程度值太小容易产生噪点太大则边缘模糊。实际调试时建议先把uniquenessRatio降到 10 左右看整体效果再逐步调高。SGBM 输入的是校正后的灰度图因此在进入匹配之前需要先用标定得到的映射表对左右图像做重映射去除镜头畸变并使极线对齐。3.3 测距核心从检测框到三维坐标这是整个项目最核心的一段。YOLOV5 检测到目标后取检测框中心点从 SGBM 算出的视差图中读取该点的视差值再通过三角测量原理计算出距离import numpy as np def calculate_distance(disparity, center_x, center_y, focal_length, baseline): # 中心点视差值单位是像素 d disparity[center_y, center_x] / 16.0 if d 0: return -1 # 双目测距公式Z f * T / d # focal_length 是焦距像素值baseline 是左右相机光心距离 distance_mm focal_length * baseline / d return distance_mm这里的focal_length和baseline都来自标定结果。需要注意 SGBM 输出的视差值在 OpenCV 中默认放大了 16 倍所以必须除以 16。很多人第一次跑出来的距离偏大 16 倍就是忘了这一步。如果只需要距离公式到Z就够了。但项目里还计算了完整的三维坐标这时要把像素坐标转换到相机坐标系def pixel_to_camera(u, v, depth, camera_matrix): fx camera_matrix[0, 0] fy camera_matrix[1, 1] cx camera_matrix[0, 2] cy camera_matrix[1, 2] x (u - cx) * depth / fx y (v - cy) * depth / fy z depth return np.array([x, y, z])这个函数本质上是相机内参矩阵的逆变换把图像上的像素点投射到三维空间。depth就是上一步测出来的distance_mm。输出坐标以左目相机光心为原点单位是毫米对后续做机械臂抓取或路径规划可以直接用。3.4 主循环检测与测距的同步主程序把检测和测距串起来流程是读取左右图 → 校正 → 检测左图 → 计算视差 → 提取中心点视差 → 计算距离。核心代码逻辑import cv2 import torch def run(): # 加载检测模型 model torch.hub.load(ultralytics/yolov5, custom, pathweights/best.pt) model.conf 0.5 # 检测置信度阈值 cap_left cv2.VideoCapture(0) cap_right cv2.VideoCapture(1) # 加载标定参数 with open(config/camera_params.yaml, r) as f: params yaml.safe_load(f) # 主循环 while True: ret_l, frame_l cap_left.read() ret_r, frame_r cap_right.read() # 校正图像 frame_l_rect remap(frame_l) frame_r_rect remap(frame_r) # YOLO 检测 results model(frame_l_rect) detections results.pandas().xyxy[0] for _, det in detections.iterrows(): x1, y1, x2, y2 int(det[xmin]), int(det[ymin]), int(det[xmax]), int(det[ymax]) center_x (x1 x2) // 2 center_y (y1 y2) // 2 depth calculate_distance(disparity, center_x, center_y, fx, baseline) # 在图上标注距离 cv2.putText(frame_l_rect, f{depth}mm, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)这段逻辑已经足够清晰但注意检测框中心点可能落在没有有效视差的区域——比如目标表面反光或纹理较弱SGBM 会给出无效值。针对这种情况建议做一个小滤波取中心点周围 3×3 邻域的视差中值可以在不增加太多计算量的情况下显著提升稳定性。4. 运行指南与避坑手册从环境配置到参数排查4.1 环境搭建与依赖安装这个项目的依赖不算复杂但 PyTorch 和 OpenCV 的版本搭配容易翻车。先看requirements.txt的内容重点依赖项一般包括opencv-python PyYAML numpy torch scipy我的习惯是用 Python 3.8 或 3.9 建独立虚拟环境避免系统环境里的 OpenCV 冲突python -m venv venv source venv/bin/activate pip install -r requirements.txt安装 PyTorch 时要注意如果你只有 CPU去官网选对应的 CPU 版本安装命令不要直接pip install torch否则可能拉下来一个 2GB 多的 CUDA 版本白白浪费磁盘空间。4.2 运行参数说明运行入口有三个关键参数标定配置文件路径、检测模型路径、相机编号。在camera.py里VideoCapture(0)和VideoCapture(1)分别对应左右目相机的设备编号。如果你用的是 USB 双摄像头免驱设备插上后在设备管理器里确认编号顺序把图像分辨率一致的两个编号填到代码里。如果左右目接反了测出来的距离会是负值或者直接计算出 NaN这是最常见的第一坑。参数位置默认值说明model.confmain.py0.5检测置信度阈值误检多就调高漏检多就调低numDisparitiesstereo_match.py64视差范围根据目标距离范围调整square_sizecalibrate.py20.0棋盘格尺寸单位毫米标定前必须确认4.3 避坑双目测距中最常遇到的四个问题现象一测距结果整体偏大或偏小但误差比例固定。原因不是算法问题而是基线或焦距读数不对。我遇到过用标定脚本获取的baseline和实际物理测量值差了 5mm导致所有距离误差约 5%。解决用卷尺直接测量两个镜头光心之间的距离对比标定值如果差异超过 2% 就手动修正配置文件。现象二近距离测距准确远距离误差超过 20%。原因SGBM 的视差离散化导致距离越远视差值越小一个像素的视差误差会被放大。解决要么增大numDisparities提高视差搜索范围要么换更高分辨率的相机。没什么玄学物理极限在那。现象三YOLOV5 检测正常但测距输出显示-1或nan。原因检测框中心点处的视差值为 0 或无效。我一般是先打印disparity[center_y, center_x]看原始值再检查 SGBM 输入图像是否校正干净。多数情况是极线校正那一步没生效检查remap的映射表是否来自同一份标定文件。现象四程序跑起来一卡一卡帧率不到 5。原因SGBM 本身计算量大加上 YOLOV5 推理CPU 环境下双重压力。解决先用torch.hub.load(..., devicecpu)强制 CPU 推理对比SGBM 参数里的numDisparities从 64 降到 32速度能提升一半左右也可以把输入图像先resize到 640 再做匹配代价是远距离精度下降。5. 二次开发把这个项目改成你要的样子5.1 思路一替换成自己的检测类别默认权重是 COCO 80 类如果我要检测自己的目标类别做法是准备数据集、训练 YOLOV5 模型、把输出的best.pt替换进weights/目录。训练完成后的关键步骤是确认模型输出格式是否兼容。新版项目里用的results.pandas().xyxy[0]是 YOLOV5 的标准输出接口只要你的模型权重是 YOLOV5 系列训练的格式天然兼容。5.2 思路二从单点测距升级到全目标测距当前版本只输出检测框中心点的距离。如果要测量目标整体的宽度或高度可以在pixel_to_camera基础上把检测框四个角点都映射到三维坐标然后计算两点之间的欧氏距离# 测量目标在真实空间中的宽度 pt_left pixel_to_camera(x1, center_y, depth, camera_matrix) pt_right pixel_to_camera(x2, center_y, depth, camera_matrix) width_mm np.linalg.norm(pt_right - pt_left)不过这里有个前提假设目标左右边缘处于同一深度平面。如果物体有旋转角度这个宽度会有偏差。对垂直的矩形物体比如货架上的箱子这个测量方式是能用的。5.3 思路三彩色视差图可视化与调试辅助SGBM 输出的视差图是灰度图不方便观察目标边缘是否清晰。在stereo_match.py里加两行代码用cv2.applyColorMap把视差图伪彩色化调试时把左右目图像、视差图、检测结果并用窗口拼接显示。这个小工具能让你一眼看出中心点取到的视差值是否落在目标区域内而不是背景地面上。从那以后我每次改参数都会强制走一遍“校正预览 → 视差预览 → 测距验证”三步不再直接改完就跑到现场测。视觉测距的坑大多不在算法理论上而在图像质量、参数配合这些容易被忽略的细节上。希望这个项目拆解能帮你把测距功能稳定落地。本文还有配套的精品资源点击获取