☰
ZED + YOLO 双目测距:从原理到落地链路实战
2026/9/29 1:12:00 网站建设 项目流程

简介:本资源面向计算机视觉初学者与机器人、自动驾驶方向的开发者,提供ZED双目相机结合YOLO目标检测实现测距的完整工程代码,核心运行脚本为zedceju.py。内容涵盖ZED SDK安装、numpy版本兼容处理、虚拟环境配置以及YOLO模型调用与深度距离计算逻辑,可帮助读者快速搭建双目测距实验环境。资源包共42个文件,包含20个Python脚本、5个XML配置、2个cfg与2个weights模型文件,以及Makefile、CUDA源码、说明文档等,压缩包约78.97MB,目录结构清晰,便于按模块查阅与二次开发。目前已有3004人学习下载,适合希望掌握立体视觉与实时目标检测融合应用的读者参考实践。

1. zed + yolo 双目测距:从 zedceju.py 看一条能跑通的落地链路

双目测距这件事,很多人第一次接触是在 ZED 这类双目相机上。它自带深度图输出,看起来开箱即用,但真到项目里,你会发现纯靠相机 SDK 给的深度在远距离、弱纹理、反光面上经常飘,尤其是要测某个具体目标(人、车、零件)的距离时,整张深度图反而不好用。zedceju.py 这个脚本的思路很直接:用 YOLO 先把目标框出来,再在 ZED 提供的深度信息里取目标区域的深度,换算成实际距离。这样做的价值在于,你不需要自己写立体匹配,也不用重新标定,ZED 负责几何,YOLO 负责语义,两者一叠加,就能得到一个「某个物体离我多远」的可用数字。这套方案适合做机器人避障、AGV 测距、工业现场目标定位的工程师,也适合刚上手双目 + 检测、想先跑通一条完整链路再谈优化的人。下面我按自己实际搭这套东西的顺序,把选型、代码、参数和踩过的坑讲清楚。

2. 为什么是 ZED 出深度、YOLO 出框:分工与选型理由

2.1 双目测距的几何原理和 ZED 帮你省掉了什么

双目测距的底层就是三角测量。左右两个相机有固定的基线 B,同一个空间点在左右图像上的横坐标差叫视差 d,深度 Z = f·B / d,f 是焦距。理论上你自己拿两个工业相机、做极线校正、跑 SGBM 或 BM 立体匹配,也能得到视差图,但这条路的工程量在于:标定要准、校正要稳、匹配参数要针对场景调,弱纹理区域还会大面积空洞。

ZED 系列相机把这一整套封装好了,SDK 直接输出深度图(深度以 32 位浮点存储,单位是毫米),你拿到的是已经算好的 Z。常见做法是直接用pyzed.sl里的retrieve_measure取MEASURE.DEPTH。这一步省下来的时间,足够你把精力放在检测和业务逻辑上。所以选型上,ZED 负责「几何正确」,这是它的强项;而「这是什么东西」交给 YOLO,这是检测模型的强项。两者职责不重叠,这是这套方案能成立的根本原因。

需要提醒的是,ZED 的深度精度随距离衰减很快。官方给的参考大致是:0.3~3 米内比较可靠,3 米以外误差开始明显,超过 10 米基本只能当参考。所以如果你的场景是近距离测距,这套组合非常合适;如果是几十米开外,得重新评估。

2.2 YOLO 版本怎么选,权重从哪来

YOLO 这条线现在版本很多,从 v5、v8 到 v11、v12 都有人用。做这套测距,我一般推荐 YOLOv8 或更新版本,原因是 Ultralytics 的 Python 接口足够简单,model.predict()一行就能出框,而且预训练权重覆盖 COCO 80 类,人、车、常见物体都能直接测,不用先训模型就能验证链路。

权重获取上,Ultralytics 会在你第一次调用时自动下载对应的.pt文件,比如yolov8n.pt(n 是 nano,最轻量)。如果你要测的是特定目标(比如某种零件、某类工件),那就得自己准备数据集训练,这一步涉及标注、格式转换、训练配置,属于另一条线,本文不展开,但链路是通的:训练好的权重替换掉预训练权重即可。

选 n 还是 s/m/l,取决于你的算力。测距场景通常要求实时,n 或 s 在普通笔记本 CPU 上也能跑到十几帧,GPU 上更宽裕。先用 n 跑通,再按精度需求往上换,这是比较稳的顺序。

2.3 zedceju.py 的整体数据流

把整个脚本拆开看,数据流是这样的:

  1. 初始化 ZED 相机,打开深度模块;
  2. 循环抓取一帧,同时拿到左目图像(RGB)和对应的深度图;
  3. 把左目图像送进 YOLO,得到若干检测框;
  4. 对每个框,在深度图的对应区域内取深度值(通常取中位数或均值,剔除无效值);
  5. 把深度值从毫米换算成米,叠加到画面上显示。

这里有个关键点:YOLO 检测用的是左目图像,深度图也必须取左目视角的深度,两者像素坐标要对齐。ZED SDK 里MEASURE.DEPTH默认就是左目坐标系,所以只要图像和深度图分辨率一致,框的坐标可以直接用来索引深度图。如果分辨率不一致,就得先做缩放对齐,这是后面避坑章节要讲的一个点。

3. 把 zedceju.py 跑起来:环境、代码与参数

3.1 环境搭建:ZED SDK 和 Python 依赖

ZED 相机要跑起来,第一步是装 ZED SDK,这个必须从官方渠道装,版本要和你的 CUDA 驱动匹配。装完之后,Python 侧需要pyzed,它通常随 SDK 一起提供,路径在 SDK 安装目录下的pyzed文件夹里,需要手动加进PYTHONPATH或者用 SDK 自带的get_python_api.py脚本安装。

YOLO 侧就是 Ultralytics:

# 安装 ultralytics,会自动带上 torch 等依赖 pip install ultralytics # 验证 ZED 的 python 绑定是否可用 python -c "import pyzed.sl as sl; print('pyzed ok')" # 验证 yolo 是否可用 python -c "from ultralytics import YOLO; print('yolo ok')"

逻辑说明:ultralytics是 YOLO 的官方封装,装它等于把推理、后处理、可视化都带上了。pyzed.sl是 ZED 的 Python 接口,import成功说明 SDK 路径配对了。参数上没什么可调的,这一步就是确认两个库都能 import,任何一个报错都先解决环境,别急着往下写代码。

提示:ZED SDK 对 CUDA 版本比较敏感,装之前先确认显卡驱动支持的 CUDA 版本,再选对应的 SDK 版本,否则pyzed能 import 但一开相机就崩。

3.2 打开 ZED 并取深度图的最小代码

先单独把 ZED 这一半跑通,确认能拿到深度,再叠 YOLO。这样出问题好定位。

import pyzed.sl as sl import numpy as np # 创建相机对象并设置初始化参数 zed = sl.Camera() init_params = sl.InitParameters() init_params.depth_mode = sl.DEPTH_MODE.ULTRA # 深度模式,精度优先 init_params.coordinate_units = sl.UNIT.METER # 深度单位设为米 init_params.camera_resolution = sl.RESOLUTION.HD720 # 分辨率 err = zed.open(init_params) if err != sl.ERROR_CODE.SUCCESS: print("相机打开失败:", err) exit(1) # 运行时参数 runtime = sl.RuntimeParameters() image = sl.Mat() depth = sl.Mat() if zed.grab(runtime) == sl.ERROR_CODE.SUCCESS: zed.retrieve_image(image, sl.VIEW.LEFT) # 左目彩色图 zed.retrieve_measure(depth, sl.MEASURE.DEPTH) # 深度图,单位米 depth_np = depth.get_data() # numpy 数组,float32 print("深度图尺寸:", depth_np.shape) print("中心点深度:", depth_np[depth_np.shape[0]//2, depth_np.shape[1]//2]) zed.close()

逻辑说明:DEPTH_MODE.ULTRA是精度最高的模式,代价是算力;如果帧率不够可以降到PERFORMANCE。coordinate_units设成UNIT.METER后,深度图里的数值直接就是米,省得后面再除 1000。retrieve_measure取出来的depth_np是 float32 的二维数组,无效像素是nan或inf,取深度时一定要过滤。

参数说明:camera_resolution影响图像和深度图尺寸,HD720 是 1280×720,比较均衡;要更高精度可以上 HD1080,但帧率会掉。depth_mode在 ULTRA 和 PERFORMANCE 之间权衡,实测 ULTRA 在近距离确实更稳。

3.3 把 YOLO 检测框和深度图对齐取距离

这是 zedceju.py 的核心逻辑。YOLO 出框,框内深度取中位数。

from ultralytics import YOLO import numpy as np model = YOLO("yolov8n.pt") # 预训练权重,首次运行自动下载 def box_distance(depth_np, box, min_valid=0.1, max_valid=20.0): """在检测框内取有效深度的中位数,返回米""" x1, y1, x2, y2 = map(int, box) # 边界裁剪,防止越界 h, w = depth_np.shape x1, y1 = max(0, x1), max(0, y1) x2, y2 = min(w, x2), min(h, y2) if x2 <= x1 or y2 <= y1: return None roi = depth_np[y1:y2, x1:x2] # 过滤 nan / inf 和超出合理范围的深度 valid = roi[np.isfinite(roi)] valid = valid[(valid > min_valid) & (valid < max_valid)] if valid.size == 0: return None return float(np.median(valid)) # 中位数比均值抗噪 # 假设 image_np 是左目 RGB numpy 数组 results = model.predict(image_np, conf=0.5, verbose=False) for r in results: for b in r.boxes: xyxy = b.xyxy[0].cpu().numpy() cls_id = int(b.cls[0]) name = model.names[cls_id] dist = box_distance(depth_np, xyxy) if dist is not None: print(f"{name} 距离 {dist:.2f} 米")

逻辑说明:box_distance先做边界裁剪,因为 YOLO 的框可能贴边甚至略微越界,直接索引会报错。然后过滤nan/inf,再卡一个合理深度区间,最后取中位数。用中位数而不是均值,是因为框内难免混进背景像素,均值容易被远处的异常值拉偏,中位数更稳。

参数说明:conf=0.5是置信度阈值,测距场景宁可漏检也别误检,因为误检会给出一个错误距离,比没有更危险。min_valid/max_valid按你的场景设,室内 0.1~10 米够用,室外可以放宽到 20 米,但要知道 ZED 在 10 米外的深度本身就不太可信。

3.4 关键参数速查表

参数位置推荐值说明
depth_modeInitParametersULTRA精度优先,帧率不够换 PERFORMANCE
coordinate_unitsInitParametersMETER深度直接输出米,省换算
camera_resolutionInitParametersHD7201280×720,均衡
confmodel.predict0.5测距宁漏勿误
min_valid/max_validbox_distance0.1/10.0按场景调,超范围深度不可信
取深度方式box_distance中位数抗背景干扰

这张表是我调这套东西时最常改的几个值,先按推荐值跑通,再根据实际表现微调。

4. 避坑与排查:双目 + YOLO 测距最容易翻车的 5 个点

4.1 现象:距离数值乱跳,同一物体一会儿 2 米一会儿 8 米

原因:框内混进了背景或远处的无效深度,取均值时被拉偏;或者深度图本身在弱纹理区域大面积无效,中位数也不稳。

解决:先确认取的是中位数不是均值;再把max_valid收紧到场景合理范围;如果目标表面是玻璃、白墙这类弱纹理,ZED 深度本身就不可靠,这种情况要么换测距方式,要么在目标上贴纹理标记。我一般会先把深度图可视化出来看一眼,确认目标区域有没有有效值,再谈算法。

4.2 现象:YOLO 框的位置和深度图对不上,测出来的距离明显偏

原因:图像和深度图分辨率不一致,或者用了右目图像去检测却拿左目深度去索引。ZED 的MEASURE.DEPTH默认是左目坐标系,如果你retrieve_image取的是VIEW.RIGHT,坐标就错位了。

解决:检测和深度都用左目。如果因为某些原因必须用右目图像,那深度也要取右目视角,或者做像素坐标映射。最稳的做法是全程左目,分辨率统一用camera_resolution设定的值,不要中途 resize 图像却不 resize 深度。

4.3 现象:一开相机就崩,或者 grab 一直返回失败

原因:ZED SDK 版本和 CUDA 驱动不匹配,或者相机被其他进程占用(比如 ZED Explorer 还开着)。

解决:先关掉所有可能占用相机的程序,再确认 SDK 版本。zed.open()的返回码要打印出来看,ERROR_CODE会告诉你具体原因。如果是驱动问题,重装匹配版本的 SDK,这一步没有捷径。

4.4 现象:帧率很低,测距延迟大

原因:DEPTH_MODE.ULTRA本身吃算力,YOLO 又占一份,两个一起跑在 CPU 上必然慢。

解决:深度模式降到PERFORMANCE或NEURAL;YOLO 换更小的权重(n 换到更小没有就保持 n);确认 YOLO 跑在 GPU 上而不是 CPU。实测在带独显的机器上,HD720 + ULTRA + yolov8n 能到 20 帧以上,纯 CPU 就个位数了。

4.5 现象:近距离(1 米内)测距反而不准

原因:ZED 的最小工作距离有限制,太近时左右目视场重叠不够,立体匹配失效。

解决:确认目标在相机的最小工作距离之外(ZED 2 大概 0.3 米,具体看型号)。如果场景就是超近距离,这套方案不合适,得换结构光或 ToF。这是选型边界问题,不是调参能解决的。

5. 进阶:让测距更稳的几个具体技巧

跑通之后,真正决定这套东西能不能上项目的,是稳定性。我踩过一圈之后,留下几个习惯。

第一,深度取值不要只取一帧。同一目标连续多帧取中位数,再对时间轴做一次滑动平均,能压掉大部分抖动。代价是响应变慢,但测距场景通常不需要毫秒级响应。实现上维护一个长度 5 的队列,每次 push 新值取中位数即可。

第二,按目标类别设不同的深度策略。比如测人,框的下半部分(腿部)深度通常比上半部分稳,因为上半部分容易和背景混;测车,取框中心区域更靠谱。这个没有通用公式,得按你的场景试。我一般会在box_distance里加一个roi_ratio参数,控制取框内哪个子区域。

第三,把无效深度的比例作为置信度指标。如果框内有效深度像素占比低于某个阈值(比如 30%),这个距离直接丢弃,不要显示。这比显示一个错误数字强得多。代码上就是valid.size / roi.size判断一下。

第四,YOLO 的conf和iou要一起调。iou控制重叠框的合并,测距场景里同一个物体出两个框会导致两个距离,取哪个都别扭。把iou适当调低(比如 0.5),让重叠框合并掉。

第五,长期跑的话,给深度值加一个物理合理性校验。比如连续两帧距离突变超过 50%,大概率是异常,直接沿用上一帧的值。这是工程上的「后悔药」,能避免画面上一秒 2 米下一秒 8 米的玄学跳动。

这套 zed + yolo 测距链路,我自己的经验是:跑通只要半天,调稳要一周,能不能上项目取决于你对场景边界的判断——ZED 的深度在它擅长的距离和纹理条件下很可靠,超出边界就别硬撑。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询