ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

ZED双目相机+YOLO目标检测测距实战:从深度图到三维坐标

ZED双目相机+YOLO目标检测测距实战:从深度图到三维坐标

简介:面向计算机视觉学习者与开发者的ZED相机结合YOLO双目测距实践包,围绕立体视觉相机采集、实时目标检测与深度距离计算展开,可用于机器人导航、自动驾驶感知等场景的环境搭建与代码复现。压缩包共42个文件,约78.97MB,以Python脚本为主体,辅以YOLOv4及轻量模型的cfg配置与weights权重、C++/CUDA插件源码、XML工程配置和README说明,覆盖检测推理、相机标定、深度显示与结果输出等模块,便于按目录定位关键实现。已有3016人学习下载,说明其在同类视觉项目中具备较高参考度。读者可从中获得从ZED SDK配置、numpy版本适配到YOLO模型接入的完整工程线索,也能参考双目测距与目标检测联调的排错思路,适合希望将检测结果转化为距离信息并落地到实际项目的中级学习者。

1. zedceju.py 在算什么:YOLO 给框、ZED 给深度,一帧出一个米数

通道口架一台双目相机,画面里走进一个人,程序要在两三百毫秒内报出「他离相机 2.7 米」,误差还得控制在厘米级。单目相机配一个已知身高去反推距离,人一弯腰就全错;自己拿两个 USB 摄像头做双目,标定、极线校正、立体匹配三座大山,调一周也未必稳。ZED 这类成品双目相机把标定和深度引擎做进了固件和 SDK,开发者拿到的直接就是逐像素的深度图,YOLO 负责说「这里有个目标」,ZED 负责说「这个像素离我多远」,两者一乘就是带语义的测距结果。这份资源里的zedceju.py就是这条链路的最小可运行实现,适合做机器人避障、人流距离统计、工位安全预警的团队拿来当起点。

2. 环境与骨架:pyzed、CUDA、YOLO 三者的版本咬合与工程目录

先把最容易翻车的地方说在前面:这类项目跑不起来,八成不是代码写错了,而是 SDK、CUDA、PyTorch 三者的版本没咬合上。pyzed是编译好的二进制绑定,它链接哪个 CUDA runtime 是固定的;YOLO 那边的 torch 又自带一份 CUDA runtime。两边版本差太远,轻则深度引擎悄悄从 NEURAL 掉回 PERFORMANCE(画面看着正常,深度精度掉了你却不知道),重则import pyzed.sl直接抛ImportError: libsl_zed.so: cannot open shared object file。

2.1 三条依赖链的对齐清单

相机插上之后,第一件事不是写代码,是确认三件事:系统认到了 USB3 设备、SDK 认到了相机、Python 能 import 到匹配的绑定。这三步任何一步跳过去,后面都是浪费时间。

组件检查方式不匹配时的典型现象
USB 链路lsusb -t,看相机那一路是不是 5000M帧率锁在 15,grab()频繁超时
ZED SDKZED_Diagnostic -c(SDK 安装目录的 tools 下)open()返回CAMERA_NOT_DETECTED
CUDAnvidia-smi与 SDK 安装说明里的支持列表对照NEURAL 深度模式初始化失败,自动降级
pyzedpython -c "import pyzed.sl as sl; print(sl.Camera.get_sdk_version())"ImportError,或版本号与 SDK 主版本对不上
torchpython -c "import torch; print(torch.__version__, torch.cuda.is_available())"打印 False,YOLO 跑 CPU,一帧 300 ms

pyzed不建议pip install pyzed去装一个网上的版本,SDK 安装包里自带生成脚本,跑出来的 wheel 才是和本机 SDK 完全对齐的。

# 1) 确认相机跑在 USB3 上:输出里要有 5000M,出现 480M 就是插错口或线不对 lsusb -t # 2) 用 SDK 自带脚本生成并安装 Python 绑定(Linux 路径,Windows 在 SDK 安装目录下同名脚本) python3 /usr/local/zed/get_python_api.py # 3) 验证版本号,应该和 SDK 主版本一致 python3 -c "import pyzed.sl as sl; print('SDK', sl.Camera.get_sdk_version())" # 4) YOLO 侧:ultralytics 会自动带 torch,但要先确认它拿到的是 GPU 版 pip install ultralytics opencv-python numpy python3 -c "import torch; print(torch.cuda.is_available())"

参数说明上,get_python_api.py会读当前 Python 解释器的 ABI 去挑对应的 wheel,所以务必用你实际运行脚本的那个解释器去执行它,不要在系统 Python 装完再用 conda 环境跑。第 4 步如果打印 False,别急着往下走,先去装 CUDA 版 torch,否则后面所有帧率相关的调试都是白费。

2.2 工程骨架与启动顺序

原始资源是单文件zedceju.py,脚本形态跑 demo 完全够用,但一旦要接下游(串口、ROS 话题、HTTP 接口),单文件很快就会变成两千行的黑匣子。我在复现时按职责拆了一层,主入口保持脚本形态,方便直接python zedceju.py跑通。

zed_ceju/ ├── zedceju.py # 主入口:开相机、主循环、检测、测距、显示 ├── zed_camera.py # 相机生命周期封装(open / grab / close) ├── depth_utils.py # 区域深度取值、像素转三维、滤波 ├── models/ │ └── yolov8n.pt # 检测权重,自己训练的就放这里 ├── configs/ │ └── default.yaml # 分辨率、置信度、类别白名单、距离阈值 └── requirements.txt

启动顺序也有讲究:先把相机插到主板直连的 USB3 口,等系统枚举完,再开终端跑脚本。热插拔之后如果 SDK 报过CAMERA_NOT_DETECTED,最省事的做法是拔掉重插而不是反复重跑脚本,因为 SDK 内部对设备句柄的回收不是每次都能干净。第一次运行 SDK 会做一次固件版本检查,这期间open()可能比平时慢两三秒,属于正常现象,别以为是卡死。

2.3 一帧的六步数据流

把主循环剥开,一帧就六步。顺序不能乱,尤其 grab 和 retrieve 之间的先后关系,是这个脚本最容易写错的地方。

第一步zed.grab(runtime),从相机取一组新的左右目图像并触发深度计算,返回值是ERROR_CODE枚举。第二步retrieve_image(VIEW.LEFT)拿左目彩色图,注意它返回的是 BGRA 四通道,不是 OpenCV 默认的 BGR。第三步retrieve_measure(MEASURE.DEPTH)拿同一帧的深度图,浮点数组,每个像素一个距离值。第四步把彩色图喂给 YOLO,拿到检测框。第五步按框在深度图上取一个统计量,得到目标距离。第六步可选,用内参把「像素 + 距离」反算成以米为单位的三维坐标,给下游做定位用。

这里有个隐式约束:一次grab()只能对应一轮retrieve,如果两次 grab 之间忘了 retrieve,或者 retrieve 了两次,第二次拿到的可能是复用缓冲区里的旧数据,表现出来就是画面和深度对不上、人走过去距离还停在原地。我一般把 retrieve 紧跟在 grab 后面,中间不插任何耗时逻辑。

3. 核心链路拆解:从 grab 到三维坐标,一帧里的 6 次关键调用

整段代码不长,但每一个参数都影响最后的米数。下面按调用顺序拆,代码可以直接抄进zedceju.py跑。

3.1 打开相机:InitParameters 里真正影响测距的四个字段

初始化参数有十几个,真正决定测距质量的是分辨率、深度模式、单位、深度范围这四项,其余保持默认即可。

import pyzed.sl as sl def open_zed(): zed = sl.Camera() init = sl.InitParameters() init.camera_resolution = sl.RESOLUTION.HD720 # 1280x720,和常用检测输入比例接近 init.camera_fps = 30 init.depth_mode = sl.DEPTH_MODE.NEURAL # 深度学习深度,弱纹理场景更稳 init.coordinate_units = sl.UNIT.METER # 之后所有深度值、坐标单位都是米 init.coordinate_system = sl.COORDINATE_SYSTEM.RIGHT_HANDED_Y_UP init.depth_minimum_distance = 0.3 # 低于这个距离直接判无效 init.depth_maximum_distance = 15.0 # 超过这个距离精度已经很差,直接截断 err = zed.open(init) if err != sl.ERROR_CODE.SUCCESS: raise SystemExit(f"ZED 打开失败: {err}") return zed

camera_resolution选 HD720 而不是 HD1080,是因为 720p 下帧率能稳住 30,且深度引擎的算力开销小一截;如果你的场景要看清 8 米外的人脸,那就得升到 1080,代价是帧率掉到 15 左右,得自己权衡。depth_minimum_distance这个下限不是随便填的,双目在太近的地方视差超出搜索范围,深度会成片失效,0.3 米是多数型号的安全边界。coordinate_units只在这里设一次,后面所有代码都不要再去乘除 1000,单位换算写两处必然出一次错。

注意:depth_minimum_distance/depth_maximum_distance是老版本没有的字段,如果你的 SDK 版本较旧,删掉这两行,改用后处理过滤nan和超范围值,效果一样。

3.2 取图与取深度:VIEW.LEFT 与 MEASURE.DEPTH 的对齐关系

retrieve_measure(MEASURE.DEPTH)拿到的深度图,坐标系和左目图像是一一对应的,这是整个方案成立的前提。也就是说左目图像上第 (u, v) 个像素的深度,就是深度图第 (u, v) 个元素,不需要做任何偏移。

import cv2 import numpy as np import pyzed.sl as sl zed = open_zed() # 内参只需要左目这一套,取一次就够,别放进循环 cam_info = zed.get_camera_information() calib = cam_info.camera_configuration.calibration_parameters.left_cam K = {"fx": calib.fx, "fy": calib.fy, "cx": calib.cx, "cy": calib.cy} rt = sl.RuntimeParameters() rt.confidence_threshold = 60 # 0-100,越高越保守,空洞变多但错值变少 rt.texture_confidence_threshold = 100 left = sl.Mat() # 在循环外创建并复用,避免每帧分配 depth = sl.Mat()

confidence_threshold是运行时最容易调的旋钮:调到 30,深度图看起来很饱满,但桌沿、地砖缝这些地方会冒出明显不对的值;调到 80,人身上可能缺一块。60 是个折中起点,具体场景再微调。left和depth这两个sl.Mat必须在循环外创建,retrieve_*会复用它们内部的缓冲区,每帧新建等于每帧申请一次显存,跑十分钟就能看到内存曲线往上爬。

3.3 YOLO 推理与坐标对齐

检测这一步没有太多花样,重点是类别白名单和置信度。做测距的场景通常只关心有限的几类目标,把classes传进去,能省掉大量无用的框。

from ultralytics import YOLO model = YOLO("models/yolov8n.pt") frame = cv2.cvtColor(left.get_data(), cv2.COLOR_BGRA2BGR) # classes=[0] 表示只保留 person 一类,按自己的模型类别表改 result = model.predict(frame, conf=0.45, classes=[0], verbose=False)[0] for box in result.boxes: # xyxy 已经是 frame 原始尺寸下的像素坐标,不需要再按比例还原 x1, y1, x2, y2 = box.xyxy[0].cpu().numpy().astype(int) cls_name = result.names[int(box.cls)] conf = float(box.conf)

这里的关键点是:predict内部会自己做 letterbox 缩放和坐标还原,返回的xyxy是相对于你传进去那张frame的原始像素坐标。所以只要你传进去的frame和depth_map尺寸完全一致,框就能直接拿去索引深度图。反过来说,如果你为了提速先把frameresize 到 640 宽再送模型,那框的坐标系就和深度图错位了,必须手动按比例乘回去——这是「框明明套在人身上,读出来的却是背景距离」最常见的原因。

3.4 像素深度到三维坐标:内参、单位、符号

拿到距离之后,很多时候还要知道目标在相机坐标系下的位置,比如给机械臂做抓取。这一步用左目内参反投影就行,不需要点云。

def pixel_to_xyz(u, v, z, K): """像素坐标 + 深度 -> 相机坐标系下的三维点,单位米""" x = (u - K["cx"]) * z / K["fx"] # 坐标系是 RIGHT_HANDED_Y_UP:图像 v 轴向下,世界 Y 轴向上,所以这里取负 y = -(v - K["cy"]) * z / K["fy"] return x, y, z

fx、fy是焦距(像素单位),cx、cy是主点,全部从calibration_parameters.left_cam里取,不能用你自己填的经验值。如果你把coordinate_system改回了默认的IMAGE,那y就不用取负,此时 X 向右、Y 向下、Z 向前,和图像坐标系一致——两种写法都行,但一定要和初始化里的设置对上,混着用会出现「上下颠倒」的诡异现象。

如果只是调试单个点,SDK 也提供了直接查点云的接口,省去自己算内参:

import pyzed.sl as sl point_cloud = sl.Mat() zed.retrieve_measure(point_cloud, sl.MEASURE.XYZ) # 一次 retrieve 拿到全图点云 err, pt = point_cloud.get_value(cx, cy) # pt = [x, y, z, ...],单位米

get_value是逐个像素的 Python 调用,一次两次没问题,几万个点这么取会慢到没法用,批量场景还是走内参向量化计算更快。

3.5 主循环完整形态与结果绘制

把上面几块拼起来就是zedceju.py的主干。注意整个循环里没有任何单位换算和重复分配。

while True: if zed.grab(rt) != sl.ERROR_CODE.SUCCESS: continue # 偶发丢帧直接跳过,不要 break zed.retrieve_image(left, sl.VIEW.LEFT) zed.retrieve_measure(depth, sl.MEASURE.DEPTH) frame = cv2.cvtColor(left.get_data(), cv2.COLOR_BGRA2BGR) depth_map = depth.get_data() # float32,单位米,无效值为 nan 或 0 result = model.predict(frame, conf=0.45, classes=[0], verbose=False)[0] for box in result.boxes: x1, y1, x2, y2 = box.xyxy[0].cpu().numpy().astype(int) d = roi_depth(depth_map, x1, y1, x2, y2) if d is None: cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(frame, "no depth", (x1, y1 - 6), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) continue cx, cy = (x1 + x2) // 2, (y1 + y2) // 2 X, Y, Z = pixel_to_xyz(cx, cy, d, K) label = f"{result.names[int(box.cls)]} {d:.2f}m" cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, label, (x1, y1 - 6), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) print(f"bbox=({x1},{y1},{x2},{y2}) dist={d:.2f}m xyz=({X:+.2f},{Y:+.2f},{Z:+.2f})") cv2.imshow("zed+yolo distance", frame) if cv2.waitKey(1) & 0xFF == 27: break zed.close() cv2.destroyAllWindows()

grab失败时用continue而不是break很重要。相机偶尔会因为 USB 抖动丢一帧,break出去就意味着整个程序退出,而现场往往是无人值守跑的。cv2.waitKey(1)的返回值只取低 8 位再和 27 比较,这是 OpenCV 在部分平台上的老问题,不这么写 ESC 键可能没反应。

4. 精度调优:同一个 1 米为什么读出 0.93 和 1.07

脚本能跑通只是第一步。真拿到现场,你会发现同一个静止目标,距离读数在 0.93 到 1.07 之间来回跳,这时要判断这是正常的深度噪声,还是取值方式有问题。双目的深度误差和距离是平方关系:δz ≈ z² / (f · B) · δd,其中 f 是焦距(像素)、B 是基线(米)、δd 是视差精度(亚像素级)。距离翻一倍,误差变四倍,这是物理决定的,任何滤波都只能压抖动,压不掉系统性偏差。

4.1 单点取值 vs 区域统计

最直觉的写法是取框中心那一个像素的深度。这个写法在实验室里看着挺准,到现场就废了:框中心可能落在两个人之间的空隙、椅背的镂空处、或者目标边缘的深度突变带上,读到的是背景距离。

import numpy as np def roi_depth(depth_map, x1, y1, x2, y2, mode="median"): """在检测框中央区域取一个稳健的深度值,单位米;无有效值时返回 None""" h, w = depth_map.shape x1, y1 = max(0, x1), max(0, y1) x2, y2 = min(w, x2), min(h, y2) if x2 - x1 < 8 or y2 - y1 < 8: return None # 只取框中央 40% 的区域:避开边缘的深度空洞和混进来的背景 bw, bh = x2 - x1, y2 - y1 rx1, rx2 = x1 + int(bw * 0.3), x2 - int(bw * 0.3) ry1, ry2 = y1 + int(bh * 0.3), y2 - int(bh * 0.3) roi = depth_map[ry1:ry2, rx1:rx2] valid = roi[np.isfinite(roi) & (roi > 0.1)] # 过滤 nan 和无效 0 值 if valid.size < 30: # 有效点太少,宁可不报 return None if mode == "near": # 避障场景要最近面,不要中位数 return float(np.percentile(valid, 10)) return float(np.median(valid))

用中位数而不是均值,是因为深度图里零星几个错值(比如打到玻璃上的反射)会把均值拉偏,中位数对这种离群点天然免疫。valid.size < 30这个门槛是血泪经验——目标只露出小半个身子时,框里有效点可能就十来个,这时候报出来的数字还不如不报。mode="near"走 10 分位数,适合避障:你关心的是「最近的那个面离我多远」,而不是「目标质心多远」。

4.2 深度模式与置信度阈值的取舍

SDK 提供多种深度模式,差别在算力和弱纹理表现上。NEURAL 在低纹理、远距离、弱光下明显更稳,代价是吃 GPU;PERFORMANCE 快但在白墙、玻璃、纯色衣服上容易出大片空洞。

场景建议深度模式confidence_threshold说明
室内人形测距,纹理正常NEURAL50~60默认起点,先跑通再微调
强光/逆光通道口NEURAL40~50阈值调低补空洞,靠区域中位数压噪
工业件、纯色表面ULTRA 或 NEURAL60~70阈值调高,宁可缺一块也别报错值
算力紧张的嵌入式PERFORMANCE50帧率优先,接受更多空洞

调整顺序建议是:先把confidence_threshold定下来,再去看深度模式。反过来的话,你永远不知道精度变化是模式带来的还是阈值带来的。

4.3 距离范围与被测物材质的硬约束

双目测距有几类物理上无解的情况,代码写得再好也没用。第一类是超出范围:近距离低于 0.3 米时视差超出搜索窗口,远距离超过十几米时视差只剩一两个像素,误差直接飙到分米级。第二类是弱纹理表面——白墙、玻璃、镜面、深色绒布,左右目找不到匹配点,深度图上是成片的空洞。第三类是重复纹理,格子衬衫、瓷砖墙、栏杆,匹配算法会错位到隔壁格子上,读出完全错误的距离。

工程上的应对是「承认边界」:给每个检测结果同时输出一个置信标记,有效点占比低于阈值就标成不可信,让下游逻辑自己决定是报警还是忽略。这比强行输出一个看起来很精确的错误数字要安全得多。

4.4 时间维度滤波与输出节流

就算单帧取值做得很稳,逐帧读数还是会跳。加一层指数滑动平均,再配一个突变抑制,效果立竿见影。

class DistanceFilter: """一维指数滑动平均 + 突变抑制,按跟踪 ID 分别维护状态""" def __init__(self, alpha=0.35, jump=1.0, hold=5): self.alpha = alpha # 越小越平滑,代价是响应变慢 self.jump = jump # 超过这个变化量(米)先认为是噪声 self.hold = hold # 连续 N 帧都跳,才承认目标真的换了 self.state = {} def update(self, tid, d): prev = self.state.get(tid) if prev is None: self.state[tid] = {"v": d, "bad": 0} return d if abs(d - prev["v"]) > self.jump: prev["bad"] += 1 if prev["bad"] < self.hold: return prev["v"] # 先按旧值输出,不跟着抖 prev["bad"] = 0 prev["v"] = self.alpha * d + (1 - self.alpha) * prev["v"] return prev["v"]

tid来自 YOLO 的跟踪结果,把model.predict换成model.track(frame, persist=True, ...),然后读box.id就有稳定 ID 了。没有 ID 的话,目标一多,第 0 个框这一帧是 A、下一帧是 B,滤波状态会串得一塌糊涂。alpha=0.35意味着新值占三成权重,人对走过来的响应延迟大概两三帧,够用;做快速移动目标就调到 0.5 以上。输出到屏幕或串口时再节流到 2~5 Hz,既省 IO 又不影响观感。

5. 避坑排查:深度全 0、框错位、掉帧这五类问题

5.1 深度图上全是无效值,框上一直显示 no depth

现象:彩色图正常,检测框也套得挺准,但距离那一栏永远是no depth,或者打印出来深度图的最小最大值都是 0。

原因:三种可能叠在一起。一是目标在depth_minimum_distance之内或depth_maximum_distance之外;二是confidence_threshold设得太高,弱纹理表面被整体判为不可信;三是相机确实在工作但深度模式初始化失败,SDK 静默降级了。

解决:先在画面里放一个纸箱到 1~2 米的距离做对照,排除范围问题;再打印np.nanmin/np.nanmax(depth_map)看整幅图有没有有效值,全图都无效说明是模式问题,把depth_mode改成PERFORMANCE试一次,能出值就是 NEURAL 初始化没成功,回去查 CUDA 版本。最后才动阈值,从 60 往下调到 40 观察空洞变化。

5.2 距离读到了背景上,人往前走数字不降

现象:人站在 1.5 米处,读数稳定显示 4.2 米,走过去也几乎不变。

原因:框的坐标系和深度图坐标系错位。最常见的两个来源是——送 YOLO 之前对frame做过 resize 却没有把框坐标还原;或者camera_resolution设的是 HD720,但显示和索引用的是另一个尺寸的缓冲区。还有一种隐蔽情况:retrieve_image取的是VIEW.LEFT,而深度是从VIEW.RIGHT或VIEW.DEPTH取的,两者视点差 12 厘米,近距下偏移明显。

解决:在循环里加一行断言assert depth_map.shape[:2] == frame.shape[:2],跑一次就能定位。确认两者同源之后,再检查有没有在送模型前改过尺寸。这类问题我一般用「画一个实心圆点标在框中心」的方式快速验证——圆点落在人身上,说明坐标对;落在背景或偏移位置,就是坐标系问题。

5.3 帧率越跑越低,跑十分钟内存涨到几个 G

现象:启动时 25 帧,十分钟后掉到 8 帧,nvidia-smi里显存占用一路往上。

原因:sl.Mat或者 numpy 数组在循环里被反复创建,SDK 的 Python 绑定依赖引用计数回收显存,但和 OpenCV 的转换中间对象一多,回收就滞后。另一个常见来源是print每帧输出几十行,终端 IO 阻塞把整条流水线拖慢。

解决:把sl.Mat()、model这些全部提到循环外创建并复用;print改成按帧计数每 15 帧输出一次;cv2.imshow之后必须跟cv2.waitKey(1),漏掉这一句窗口不刷新,OpenCV 内部缓冲会堆积。

5.4 grab 频繁失败,日志里刷 CAMERA_NOT_DETECTED

现象:跑几分钟之后开始间歇性丢帧,严重时直接返回CAMERA_NOT_DETECTED,拔掉重插又能跑一阵。

原因:九成是 USB 链路问题。相机插在了 USB3 Hub 上、用了 USB2 的延长线、或者和一堆高带宽设备共享同一个控制器。双目相机原始数据量大,链路带宽不够时最先表现就是丢帧。

解决:lsusb -t看相机挂在哪个控制器下,尽量直连主板后置口;换一根标称支持 USB3 的短线;把同一控制器下的其他高带宽设备(外置硬盘、采集卡)拔掉。这一条属于硬件层面,代码层面怎么调都没用。

5.5 多目标时距离来回串,A 的距离显示在 B 的框上

现象:画面里两个人,读数偶尔互换,或者一个人走着走着数值跳到另一个人身上。

原因:按result.boxes的索引顺序去对应滤波状态。检测框的顺序每帧可能变化,索引 0 这一帧是左边的人、下一帧是右边的人,状态就串了。

解决:改用model.track(..., persist=True)拿到稳定的box.id,滤波器和输出都以 ID 为键。要注意persist=True意味着状态跨帧保留,切换视频源或重开相机时要手动model.predictor.trackers[0].reset(),否则旧的轨迹会污染新数据。

6. 二次开发:把 zedceju.py 拆成可复用的测距模块

单脚本跑通之后,真正要接到业务里,第一件事是把「取帧—检测—测距」这条链改成生成器形态,让调用方只关心结果,不关心相机句柄。这样同一套代码既能配 OpenCV 窗口做调试,也能配串口或网络接口做部署。

def distance_stream(zed, rt, model, K, conf=0.45, classes=(0,)): """产出 (frame, bbox, distance),distance 为 None 表示该目标当前不可信""" left, depth = sl.Mat(), sl.Mat() while zed.grab(rt) == sl.ERROR_CODE.SUCCESS: zed.retrieve_image(left, sl.VIEW.LEFT) zed.retrieve_measure(depth, sl.MEASURE.DEPTH) frame = cv2.cvtColor(left.get_data(), cv2.COLOR_BGRA2BGR) dm = depth.get_data() res = model.predict(frame, conf=conf, classes=list(classes), verbose=False)[0] out = [] for box in res.boxes: x1, y1, x2, y2 = box.xyxy[0].cpu().numpy().astype(int) out.append(((x1, y1, x2, y2), roi_depth(dm, x1, y1, x2, y2))) yield frame, out

换成自己的模型时,只需要改两处:classes白名单换成自己类别表里的索引,roi_depth的取样区域按目标形态调整——细长物体(比如管材)把中央区域比例从 0.3 放大到 0.2,避免取样区落空。如果换成实例分割模型,就别用矩形框了,直接拿 mask 做布尔

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

返回列表