ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RealSense+YOLOv5深度相机目标测距实践:从像素框到实际距离

RealSense+YOLOv5深度相机目标测距实践:从像素框到实际距离 简介利用Intel RealSense深度相机与YOLOv5结合实现目标检测与实时测距的资源包面向计算机视觉入门者以及机器人、安防监控等领域的开发者。压缩包共46个文件大小3.53MB包含18个Python脚本、8个YAML模型配置文件、4个Markdown说明文档、Dockerfile与权重下载脚本等覆盖从模型配置、训练推理到深度相机调用与距离解算的完整链路。YOLOv5作为典型单阶段算法直接在网络中提取特征完成分类与定位配合RealSense生成的深度图可同步输出目标距离无需复杂区域提议过程特别适合实时交互场景。目前已有429人学习下载。资源整合了YOLOv5官方项目结构与RealSense驱动调用示例提供可直接运行的检测脚本、模型配置及配套教程便于读者在统一框架下理解检测与测距的协同方式是一份兼顾原理与实践的入门进阶材料。1. 把深度值贴着检测框一起输出这份 RealSenseYOLOv5 资源能做什么做目标检测的人迟早会碰到同一个问题模型告诉你在像素坐标 (320, 240) 有一个框可这个框离你物理世界到底几米这份基于 Intel RealSense 深度相机 YOLOv5 的资源把纯 2D 的目标检测扩展到了「检测结果带上距离」用 realsensedetect.py 同时拉彩色流和深度流YOLOv5 出框再从深度帧里读框内目标的深度值换算成毫米距离。适合做巡检机器人、安防联动、视觉抓取预定位的开发者也适合刚把 YOLOv5 环境配好、想给 demo 加一个「测距」卖点的人。下面把它拆到能直接复现的粒度原理、环境、主流程、参数以及我实际跑下来的五个坑。2. 为什么是 RealSense YOLOv5从「像素里有框」到「框里真的有东西」2.1 深度相机和普通 RGB 相机差在哪一个普通 RGB 相机拿到的是一张 8 位三通道图每个像素记录颜色。RealSense D435i 这类主动立体视觉深度相机同时输出 color、depth、红外三路数据其中 depth 流每个像素直接记录「该点到相机平面的距离」。D435i 用的是左右两个红外传感器加一个红外点阵投影仪靠匹配左右 IR 图的视差算深度所以在白墙、纯色这种传统双目会失效的纹理缺失区域它也能给出完整深度——这是它比普通双目方便的地方。深度流的原始数据是 z16 格式每像素 16 位单位由 depth scale 决定。D435i 默认 scale 是 0.001也就是 1 个 LSB 等于 1mm。pyrealsense2 里get_depth_scale()返回的就是这个系数很多新人直接拿原始值当毫米用其实 raw value 乘 scale 才是以米为单位的距离get_distance()返回的则是已经换算好的米值。这一段是你用 RealSense 深度相机时遇到的第一道坎后面所有测距换算都建立在这个认知上。如果你手里是 D435 而不是 D435i或者换了 D455原理都一样只是硬件参数有差异i 后缀带 IMU可以输出加速度和角速度适合做 VIO 和 SLAM 融合D455 的左右相机基线更长近距离精度更好。纯桌面测距D435 完全够用。这份包里的代码对这几个型号都通用因为 pyrealsense2 统一了 API只会在设备枚举阶段看到型号名差异。大多数新手容易忽略的是对齐问题。color、depth、IR 三个流各有自己的内参和镜头位置物理上不共心所以同一点的像素坐标在 color 和 depth 里并不一样。必须用rs.align(rs.stream.color)把深度图重投影到彩色坐标系下才能拿 YOLOv5 的框坐标去取深度。对齐做得对不对直接决定你测出来的是目标距离还是背景距离。这个理解是整份工程最核心的部分。2.2 为什么用 YOLOv5 而不是传统方法目标检测算法分 two-stage 和 one-stage 两大阵营。two-stage 先出候选区域再分类回归精度高但慢one-stage 直接回归 bbox 和类别。YOLO 属于 one-stage把整张图一次算完。在工程里「实时」往往比「多一个点 mAP」更值钱尤其深度相机已经在占用一部分 CPU/带宽资源的情况下检测端能省就省。YOLOv5 在这个包里有 s/m/l/x 四种结构定义models 目录下 yolov5s.yaml 到 yolov5x.yaml 排开。对应关系是网络的 depth multiple 和 width multiple 不同s 最轻适合 CPU 和嵌入式x 最重精度最高但推理耗时翻倍。测距场景一般不需要极限精度我建议从 yolov5s 起步帧率稳定后再换更大的模型。你如果跑在 Jetson 或树莓派这类设备上s 几乎是默认选择。再说一个常被问到的点为什么不用 RealSense 自带的能力测距因为 D435i 本身不做语义识别它给的是深度点图和彩色图没有「这是人还是桌子」的判断。YOLOv5 补的正是语义层。两者配合是「深度给尺度检测给语义」拆开都不完整。这个分工想清楚后面调试时排查范围就明确框不准怪检测距离不准怪深度链路。YOLOv5 的原始输出是一堆重叠框需要 NMS 和置信度阈值两级过滤这部分在包的 utils 和入口脚本里都有现成实现。这份代码的 utils 目录用的是 general.py、torch_utils.py 这样的早期目录结构最新版 YOLOv5 已经把这些工具拆到了 metrics.py、plots.py你如果拿这份老包去对照新教程文件名对不上是正常的功能等价不用慌。2.3 代码资产盘点先说这份包的核心文件结构我按用途拆成五块文件/目录作用realsensedetect.py主入口同时开深度流和彩色流YOLOv5 推理后测距newdetect.py无相机检测入口喂图片或视频不碰深度设备models/YOLOv5 网络结构定义yolov5s/m/l/x 四种utils/检测、训练通用的工具函数general.py、datasets.py 等weights/download_weights.sh下载官方预训练权重的脚本requirements.txtPython 依赖清单tutorial.ipynb交互式教程适合先跟着走一遍0210901110009.jpg包里自带的一张测试图片需要特别强调 realsensedetect.py 和 newdetect.py 的定位差别前者是带深度相机的完整测距程序后者是纯 YOLOv5 检测入口。很多下载资源的人一上来就跑 realsensedetect.py相机没接好报错之后也不知道到底是 YOLOv5 的问题还是相机的问题。正确顺序是先用 newdetect.py 跑通检测链路再接相机这样问题范围能压缩一半。weights 目录里的 download_weights.sh 负责下载官方权重默认可以拿到 yolov5s.pt 这类预训练模型。models 和 utils 属于 YOLOv5 本体正常情况下你不用改最多动 conf 阈值和测距段。README.md 和 tutorial.ipynb 给的是官方说明和交互示例首次接触这套工程的人建议把 tutorial.ipynb 完整执行一遍它会在 Jupyter 里逐步展示检测结果能帮你确认环境没有问题。3. 把环境一次配稳依赖版本、权重下载与两个入口的分工3.1 环境清单pyrealsense2、PyTorch、CUDA 的版本配套先看 requirements.txt 里声明的依赖通常是 torch、torchvision、numpy、opencv-python、pyrealsense2 这几个。这里最需要留意的是 pyrealsense2 必须和系统里的 librealsense 版本配套否则相机枚举会直接失败。推荐用 conda 隔离环境避免把系统 Python 搞乱conda create -n rs_yolo python3.8 -y conda activate rs_yolo pip install torch1.10.0 torchvision0.11.0 --index-url https://download.pytorch.org/whl/cu113命令逻辑说明先建一个干净的 Python 3.8 环境再装与 CUDA 11.3 配套的 PyTorch。为什么给这个版本作为示例这份包的 utils 是 YOLOv5 较早的结构用保守的 torch 版本成功率最高。如果你的机器是 CUDA 11.8把 index-url 换成 cu118没有独立显卡就跳过 index-url 直接装 CPU 版推理会慢一些但整条链路能跑通。参数说明python 3.8 是当初验证最充分的版本3.9 和 3.10 一般也能跑如果后面出现 libstdc 相关报错常见原因是 conda 环境缺编译库装一下gxx_linux-64就能解决。Jetson 平台则要用 JetPack 自带的 PyTorch 轮子不要从 PyPI 硬装 x86 版本。然后补装深度相机和图像处理依赖pip install pyrealsense2 opencv-python numpy这里有两个建议先装 pyrealsense2 再装 torch这样如果 pyrealsense2 安装失败可以早点发现避免后面排查时混在一起。opencv 的版本一般随 torch 走主要提供图像读取和窗口显示能力numpy 版本别太新某些旧代码对 numpy 2.x 的 API 变动敏感建议装 1.23 或 1.24。3.2 下载权重与目录结构确认环境装好后下一步是获取 YOLOv5 预训练权重cd 项目根目录 bash weights/download_weights.sh ls -lh weights/逻辑说明download_weights.sh 会按脚本里写好的 URL 把 yolov5s.pt 等权重拉到 weights 目录。如果因为网络原因拉取失败先打开脚本看目标 URL 是否能访问可以手动用 wget 或 curl 下载到 weights 目录。校验下载完整性看文件大小就行yolov5s.pt 大约 14MByolov5m.pt 大约 42MB差太多就是下载中断。我特别提醒一点这个工程必须在项目根目录运行因为 realsensedetect.py 和 newdetect.py 用相对路径导入 models 和 utils你如果不小心 cd 到别的目录再执行会直接报 ModuleNotFoundError。这是新手最容易翻车的点排查成本很低但熟练工也偶尔会踩。目录确认用一条命令就能完成ls -l正常能看到 models、utils、weights、两个入口脚本和测试图片。建议把 weights 里的 .pt 文件先确认一遍再去跑推理。3.3 先跑通 newdetect.py无相机入口当自检现在进入第一个可执行步骤用 newdetect.py 做环境自检。这个入口不碰相机只吃图片和视频最适合验证 YOLOv5 链路是否完整python newdetect.py --source 0210901110009.jpg --weights weights/yolov5s.pt --img-size 640 --conf-thres 0.25参数说明--source是输入源支持单张图片、视频路径和摄像头编号--weights指向刚才下载的权重--img-size是推理输入尺寸YOLOv5 内部会做 letterbox 缩放不是 640 也不会崩--conf-thres是置信度阈值低于它的框会被丢掉0.25 是官方默认。--iou-thres控制 NMS 的重叠框抑制一般保持 0.45 不动。运行成功后结果会输出到 runs/detect/exp 目录下里面是画好框的测试图。这一步通过说明 pytorch 加载模型、图像预处理、前后处理链路全通。之后 realsensedetect.py 如果出问题就可以把排查范围锁定在相机、对齐和深度读取这三段而不是 YOLOv5 本体。跑完 newdetect.py 还可以顺手验证 opencv 后端把--source改成 0接一个普通 USB 摄像头试试实时检测。这样能把「相机枚举」和「YOLOv5 推理」进一步解耦。两步都通过后再接 RealSense 就是水到渠成的事。4. 让 YOLOv5 开口报距离realsensedetect.py 主流程与测距窗口4.1 核心流程开相机、对齐、推理、取深度realsensedetect.py 的骨架是四段式初始化 pipeline → 配置 colordepth 流 → 每帧 align → YOLOv5 推理后取深度。先把相机这半段打开import pyrealsense2 as rs pipeline rs.pipeline() config rs.config() config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30) config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30) profile pipeline.start(config) align rs.align(rs.stream.color) depth_sensor profile.get_device().first_depth_sensor() depth_scale depth_sensor.get_depth_scale() print(depth_scale:, depth_scale)逻辑说明enable_stream里的 640x48030 是深度和彩色同时传输时的最佳平衡点分辨率太高容易撞上 USB 带宽上限太低则测距精度不够。z16 是 16 位深度格式bgr8 是 OpenCV 需要的三通道格式。pipeline.start(config)返回 profile里面能拿到设备信息和深度 scale。rs.align(rs.stream.color)创建对齐对象之后每帧都要调用它把深度图转到彩色坐标系。主循环部分是最有辨识度的代码段while True: frames pipeline.wait_for_frames() aligned_frames align.process(frames) depth_frame aligned_frames.get_depth_frame() color_frame aligned_frames.get_color_frame() if not depth_frame or not color_frame: continue results model(color_frame) boxes results.xyxy[0].cpu().numpy() for box in boxes: x1, y1, x2, y2, conf, cls box u int((x1 x2) / 2) v int((y1 y2) / 2) dist_m depth_frame.get_distance(u, v) print(fclass {int(cls)} conf {conf:.2f} dist {dist_m:.3f} m)逻辑说明wait_for_frames阻塞等待新一帧数据align.process后拿到的 depth 和 color 视野一致坐标可以直接对应。results.xyxy[0]是 YOLOv5 输出的六列数组分别是 x1、y1、x2、y2、置信度、类别索引。中心点坐标用 bbox 对角坐标取均值就行。get_distance(u, v)返回的是已换算好的米值注意不是毫米。这个最小例子里用的是中心单点取深度能演示已经很直观了。但真实部署时单点很容易被边缘和反光带偏所以下一节把它升级成区域中值同时加入时间滑动窗口。4.2 测距策略把单点改成区域中值加时间滑动窗口中心单点取距离的风险检测框中心可能正好落在盒子边缘、反光拉丝或两体夹缝深度值在相邻帧能差出半米。常见做法是取中心邻域内的有效深度值再取中值import statistics def measure_region(depth_frame, u, v, radius10): h depth_frame.get_height() w depth_frame.get_width() values [] for du in range(-radius, radius 1): for dv in range(-radius, radius 1): cu u du cv v dv if 0 cu w and 0 cv h: d depth_frame.get_distance(cu, cv) if d 0: values.append(d) if len(values) 3: return statistics.median(values) return 0.0逻辑说明以检测框中心为中心遍历边长为 21 的正方形邻域把所有大于 0 的距离值收集起来。判断len(values) 3是为了防止大部分像素无效时仍然返回一个不可靠的数字。中值比均值抗离群比如中心有一小块前景物体遮挡时中值能把这一跳压住。时间滤波是第二个层次的稳定手段对同一个跟踪对象的连续多帧距离再做中值history {} def smooth_dist(track_id, dist, max_len7): history.setdefault(track_id, []) history[track_id].append(dist) if len(history[track_id]) max_len: history[track_id].pop(0) return statistics.median(history[track_id])参数说明track_id 可以暂时用类别编号代替简单场景够用max_len 是滑动窗口长度。窗口越长输出越顺滑但响应越迟钝动态目标建议 5 帧静态目标的测距可以放宽到 15 帧。这个属于深度后处理和 YOLOv5 的 NMS 后处理是两个层面调参时要分开看不要混在一起乱调。4.3 推理超参数和相机参数怎么搭配到这里可以把「YOLOv5 超参数」和「测距窗口参数」分开看前者管框准不准后者管距离稳不稳。参数推荐值备注--img-size640yolov5s 默认输入调大会增加推理耗时--conf-thres0.25~0.4场景光线差时降到 0.15误检多时调高--iou-thres0.45NMS 的 IoU 阈值一般不动color/depth 分辨率640x480720p 精度更高但 USB 带宽紧张帧率30低于 15 建议降分辨率测距窗口 radius10~15目标小就缩小目标大就放大conf-thres 调到 0.5 时误检少但框会变少测距输出跟着少调到 0.15 时框多但可能混入错误检测距离输出里会出现背景值。我的习惯是先用 newdetect.py 离线调好 conf 和 iou再接深度相机折腾测距参数这样变量少出问题好定位。相机分辨率与推理 img-size 之间要保持固定映射。如果 realsensedetect.py 内部先对彩色帧做了 letterbox 再送进模型你取深度时用的中心坐标必须是原图坐标否则采样点会整体偏移。调试时把彩色帧用 OpenCV 窗口显示出来同时画一个十字标记采样点深度值实时打印到终端这是最直接的破局手段。5. 避坑记录深度全黑、距离乱跳、USB 带宽不够的五个现场这节按我实际跑这套工程的踩坑顺序来写从「完全没图像」到「数值差一点」越往后越接近真实部署问题。5.1 深度图全黑取到的深度全是 0现象运行 realsensedetect.py 后彩色画面正常但深度图区域是黑的打印的距离全是 0。原因最常见的是目标离相机太近超出 D435i 的最小工作距离。D435i 的近距下限大约在 0.1m 左右贴着镜头 5cm 是拿不到有效深度的。其次是 pipeline 里根本没启用深度流只有彩色流在跑。另外室内强红外干扰会让 emitter 被自动关闭深度会大面积失效。解决先确认目标在 0.3m 以外再打印 profile 里的流配置确认 depth 流存在最后检查 emitter 状态depth_sensor profile.get_device().first_depth_sensor() print(depth_sensor.get_option(rs.option.emitter_enabled))正常默认是开启的。这套排查顺序能覆盖 90% 的「全黑」问题。5.2 物体不动距离在 0.5m 和 0.8m 之间跳现象人站在镜头前不动输出距离在中远两个值之间来回切换看起来毫无规律。原因中心点落在目标边缘或反光面上或者中心点后面是背景墙检测框中心并没有真正打在目标实体表面。单点算法必然这样不是 YOLOv5 的框错了是深度采样策略太脆弱。解决改用 4.2 节的区域中值并把采样点在 RGB 画面里可视化确认十字确实落在目标主体上。透明水瓶和镜面是深度相机的物理天敌这类材质无论怎么调都容易跳只能靠多传感器融合或者约束使用场景。5.3 彩色图里框的位置和取深度的位置明显错位现象检测框正好框住杯子但取到的深度却是后面墙壁的距离。原因没有调用 align.process直接用彩色图的 bbox 坐标去索引原生深度帧。两个传感器的视场和原点不同坐标不经过重投影必然错位。这是深度相机内参转化最常见的误区以为像素坐标在所有流里通用。解决每帧都执行 align 后再取 depth_frame并验证对齐后深度图的宽高和彩色图一致assert depth_frame.get_width() color_frame.get_width() assert depth_frame.get_height() color_frame.get_height()如果宽高一致但仍有固定偏移检查两个流的分辨率设置是否完全一致比如彩色 640x480、深度 1280x720这种不对称配置最容易出现隐性偏移。5.4 帧率个位数画面反复卡顿现象实时预览像幻灯片帧率只有个位数甚至相机偶尔断连。原因最常见是插了 USB2.0 接口带宽只有 480Mbps同时传 720p 深度加 720p 彩色直接爆掉另一个是 YOLOv5 推理耗时太长GPU 或 CPU 占用接近饱和。解决先把线插到 USB3.0 蓝色口深度和彩色都降到 640x480帧率设 30 以下YOLO 侧开半精度img-size 降到 416 或 320。实测里 img-size 从 640 降到 416推理耗时能减少接近一半测距精度损失很小对验证性质的工程完全够用。5.5 距离整体偏移比如实测 1.5m 测出来 1.1m现象数值稳定但整体偏近或偏远呈现固定比例偏差不是随机跳变。原因采样点可能打在目标前伸部位上比如人的手比躯干更靠近相机也可能是相机出厂内参和实际安装有偏差尤其是拆装过或经历温度变化后深度标定会漂移。解决先把采样点可视化排除采样位置问题再做线性校准。找三个已知距离测三组数拟合y a*x b把系数写进工程a 1.12 # 示例拟合系数 b -0.08 # 示例拟合系数 dist_cal a * dist_raw b三组点拟合出来的误差通常能压到几个百分点以内。RealSense 官方工具链也支持深度自标定但工程现场线性回归往往够用这是我最常用的后悔药先确认原理没错再怀疑设备漂移。6. 进阶把距离换算成三维坐标、落盘 CSV 与一次精度实测6.1 从像素坐标到空间点拿到了距离还不够很多下游任务要的是三维坐标。深度相机内参转化公式是x (u - cx) * z / fx y (v - cy) * z / fy z dist其中 fx、fy、cx、cy 是深度流的内参。从 pyrealsense2 里可以直接取intr depth_frame.profile.as_video_stream_profile().get_intrinsics() z dist_m x (u - intr.cx) * z / intr.fx y (v - intr.cy) * z / intr.fy注意 RealSense 坐标系是 x 向右、y 向下、z 向前而 ROS 相机坐标系通常也是 z 向前接入 ROS 时先确认坐标朝向避免结果整体翻转。6.2 按帧落盘 CSV调试和部署都建议把每帧的检测和测距结果存下来import csv with open(dist_log.csv, w, newline) as f: writer csv.writer(f) writer.writerow([frame, class, conf, u, v, dist_m]) # 每帧推理后追加一行这样后续分析误差、复现问题都方便不用靠肉眼盯屏幕记数据。6.3 一次精度实测选 0.5m、1.0m、1.5m 三个实测点每个点静止测 20 帧取中值和卷尺读数对比再线性拟合。这套流程走完测距结果的误差范围就能写进交付文档下游用起来心里有底。那次之后我每拿到一台新的 RealSense 相机都强制按同一顺序走一遍先 newdetect.py 确认模型链路再开 align 确认深度对齐最后做三个距离点的线性标定才把测距结果交给下游。这套顺序救过我很多次。希望帮到你。本文还有配套的精品资源点击获取
返回列表