ARTICLE DETAIL

资讯详情

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

RealSense深度相机与YOLOv5结合:目标检测同时测距的实践指南

RealSense深度相机与YOLOv5结合:目标检测同时测距的实践指南 简介利用RealSense深度相机与YOLOv5结合实现目标检测的同时输出距离信息是一份面向计算机视觉学习者、机器人及安防领域开发者的完整工程包。资源共46个文件压缩包仅3.53MB包含18个Python脚本含realsensedetect.py、newdetect.py等核心程序、8个YAML模型配置文件、训练与推理脚本、Dockerfile及依赖清单同时提供README和Jupyter教程方便快速上手。已有429人学习下载。资源将YOLOv5成熟的检测框架与RealSense深度数据相融合既保留了YOLO系列One-stage算法的高效推理能力又利用深度相机获取物体距离可直接用于安全监控、智能机器人避障、工业测量等场景。针对希望从纯图像检测进阶到空间定位的开发者这份代码组织清晰、模块划分明确涵盖模型配置、训练测试、深度图像获取与距离换算等环节是值得参考与扩展的实用范例。1. 利用深度相机补上目标检测缺的那一维距离在安全围栏、机械臂工作区或者倒车辅助这类落地场景里单靠一张检测图并不能解决核心问题。我在现场调试服务机器人时摄像头已经能稳定识别“人”这个类别画框也画得准可是上位机并不知道人离机器人是不是只有半米。利用RealSense深度相机实现YOLOv5目标检测的同时测出距离直接决定这套视觉系统能不能从“实验室演示”变成“现场可用功能”。方案的核心思路很直白YOLOv5在RGB图像上输出目标的xyxy框RealSense在同一坐标系下提供每个像素的深度值两者一合并画框的同时就能读出目标距离。适合做安防告警、机械臂防碰撞、服务机器人避障的工程师也适合做课程设计想快速出效果的学生。2. 先把环境与相机调明白pyrealsense2、D435i参数与深度对齐2.1 用conda把YOLOv5和pyrealsense2一次装干净Intel RealSense SDK的Python绑定只有一个包名pyrealsense2。这个包名容易让人误会以为只支持第二代相机实际上D400系列全部走这个接口。我习惯在conda环境里同时安装YOLOv5的依赖因为YOLOv5的requirements.txt与pyrealsense2对OpenCV、numpy的版本要求没有冲突放同一个环境不会互相打架这正是conda yolov5方案最省事的地方。# 1. 创建虚拟环境Python 3.8~3.10均可 conda create -n rs_yolo python3.9 -y conda activate rs_yolo # 2. 安装PyTorch有NVIDIA显卡时用官方GPU源 pip install torch torchvision # 3. 按YOLOv5官方说明安装依赖 # 在yolov5代码目录下执行 pip install -r requirements.txt # 4. 安装Intel RealSense的Python绑定 pip install pyrealsense2 # 5. 验证D435i是否被系统识别 rs-enumerate-devices | grep -i serial第一行conda create创建独立环境避免把系统Python装乱。pyrealsense2这个包名和Intel RealSense SDK同名装完就能import不需要额外配置环境变量。rs-enumerate-devices是RealSense工具集自带的诊断命令如果这条命令没有输出先不要急着跑程序大概率是USB线或者驱动问题。开发中常见的情况是YOLOv5依赖装完再装pyrealsense2顺序反过来容易因为numpy版本降级导致RealSense的数组转换报错。拿到相机后建议先对照下面这张参数表做一次硬件体检很多后面的测距异常其实在第一步就能看出来。参数D435i典型值对项目的影响深度分辨率最高1280x720 30fps分辨率越高远处目标像素越多测距越稳RGB分辨率最高1920x1080 30fpsYOLOv5输入一般降到640不需要开到最高深度基线50mm左右基线决定近距离精度上限深度范围0.2m到10m10米是极限实际好用范围在5米以内内置IMU有需要位姿融合时才用得上本方案不用接口USB 3.0带宽不足会丢帧USB2.0基本跑不动2.2 深度单位与对齐为什么深度值不是米pyrealsense2从深度流拿到的原始帧是一个uint16数组每个像素的数值不是直接可用的距离而是“原始深度单元”。要把原始值换到米需要乘上depth_units这个缩放因子。D435i出厂默认depth_units等于0.001也就是说原始值1000对应1米用get_data()拿到数组后里面的数值可以近似看成毫米。很多初学者在这一步直接把数组传给人脸检测的距离函数算出来的结果差了1000倍。我一般会先打印一帧深度图的中心值对照相机到墙面的实际距离确认单位再往下做。对齐这个问题更隐蔽。RealSense的深度镜头和RGB镜头是两个物理位置两幅图像的像素坐标并不天然重合。同一个目标在深度图里的x坐标和RGB图里差十几个像素是常有的事。如果直接用YOLO在RGB图里画的框去挖原始深度图测出来的距离基本都是错的人头的位子甚至会落到背景上。解决办法是创建一个rs.align对象把深度图重投影到RGB坐标系下。import pyrealsense2 as rs # 深度图对齐到彩色图坐标系 align rs.align(rs.stream.color) aligned_frames align.process(frames)align.process这一步同时做了两件事一是时间戳同步把同一时刻的深度帧和彩色帧配对二是空间对齐让深度图与彩色图像素一一对应。对齐之后再取深度YOLO画出的检测框中心坐标就能直接用来索引深度图。2.3 realsense-viewer快速体检先看深度图再写代码在写任何代码之前最好先用官方工具看一次深度图。Realsense Viewer在Linux和Windows上叫realsense-viewer树莓派5这类arm64设备上也有对应版本。打开后选深度流用伪彩色显示第一件事是看远处墙面是否平整近处伸手是否能看清轮廓。如果深度图大面积黑色或者边缘有锯齿先解决相机问题不要急着调算法。还有一个检查点容易被忽略把深度流和彩色流同时打开观察RGB画面里的物体边缘是否与深度画面中的轮廓基本重合。如果发现错位明显说明这台设备的外参偏移已经比较严重直接做标定。把这一步放在正式开发前能避免后面花费大量时间排查一个其实不存在的算法bug。3. 把YOLOv5检测框映射到深度图三种取值策略与最小可跑代码3.1 最小可跑的联调循环一帧同时拿深度与检测结果把YOLOv5的detect.py完整搬过来太重我通常只保留推理循环。下面的脚本用torch.hub加载模型对连续视频帧做检测并把xyxy框和对应距离画回画面。这个流程已经包含了相机初始化、深度对齐、YOLO推理三个核心步骤。import cv2 import numpy as np import pyrealsense2 as rs import torch # 初始化RealSense管道 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) pipeline.start(config) # 深度对齐到彩色坐标系 align rs.align(rs.stream.color) # 加载YOLOv5AutoShape会处理OpenCV的BGR输入 model torch.hub.load(ultralytics/yolov5, yolov5s, pretrainedTrue) model.conf 0.4 model.max_det 20 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 # 深度图像素值近似毫米color_img是BGR depth_img np.asanyarray(depth_frame.get_data()) color_img np.asanyarray(color_frame.get_data()) results model(color_img) for *xyxy, conf, cls in results.xyxy[0].tolist(): x1, y1, x2, y2 map(int, xyxy) # 区域中位数策略函数见下一节 dist get_distance(depth_img, (x1, y1, x2, y2), modemedian) cv2.rectangle(color_img, (x1, y1), (x2, y2), (0, 255, 0), 2) label f{dist:.2f}m if dist else invalid cv2.putText(color_img, label, (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) cv2.imshow(rs_yolo, color_img) if cv2.waitKey(1) 0xFF ord(q): break pipeline.stop()这里的wait_for_frames会阻塞等待下一组同步帧返回的frameset同时包含深度和彩色。align.process把深度图投影到彩色视角因此depth_img和color_img的像素坐标一一对应。YOLOv5通过torch.hub加载时自带AutoShape可以直接接受OpenCV的BGR图像不需要手动转RGB。3.2 测距策略中心点、区域中位数、区域均值怎么选取距离最直接的想法是拿检测框中心点去读深度图但这个做法在真实场景最容易翻车。中心点可能落在目标的空洞上、两个物体之间的缝隙上或者目标边缘的反光处读出来一个0值或者一个明显偏远的点。我一般使用检测框中心区域做统计而不是单点读取让一小块区域的深度互相印证。def get_distance(depth_img, box, modemedian, margin0.25): x1, y1, x2, y2 box w x2 - x1 h y2 - y1 # 只取检测框中心一块避开目标边缘的背景混入 cx1 int(x1 w * (1 - margin) / 2) cx2 int(x2 - w * (1 - margin) / 2) cy1 int(y1 h * (1 - margin) / 2) cy2 int(y2 - h * (1 - margin) / 2) roi depth_img[cy1:cy2, cx1:cx2] # 0代表无效深度8000毫米之外的数据噪声过大直接丢弃 mask (roi 0) (roi 8000) if mask.sum() 5: return None vals roi[mask] if mode center: d depth_img[(y1 y2) // 2, (x1 x2) // 2] return float(d) / 1000.0 if d 0 else None if mode median: return float(np.median(vals)) / 1000.0 return float(np.mean(vals)) / 1000.0margin参数默认0.25表示只取检测框中心25%面积参与统计这是一个比较保守的设置。如果目标本身很小比如远处行人只有30像素高center区域可能只剩5个有效像素统计意义不足这时把margin调到0.4多纳入一些目标像素。中位数和均值的差别在行人身上很明显行人身体包含头、肩膀、手臂多个深度层次均值容易被手臂或背包拉偏中位数更能代表目标主体离相机的距离。对于车辆、货架这类表面平整的目标均值和中位数差异不大用均值运算稍快。3.3 换自己的模型训练完的best.pt怎么接入前面用pretrained模型验证流程真正部署时要换成自己训练的数据集权重。YOLOv5训练完把模型加载方式改成custom即可唯一要改的超参数是置信度阈值和输入分辨率。# 调用自己训练好的权重 model torch.hub.load(ultralytics/yolov5, custom, pathbest.pt) # 小目标漏检时把输入分辨率从640提到960 model.fp16 False训练YOLOv5自己的数据集时默认超参数基本够用。如果发现小目标漏检优先把输入尺寸从640提到960而不是去调整anchor或者损失权重。模型文件放在项目目录下用相对路径引用部署时避免因为路径问题启动失败。检测框的xyxy坐标已经映射回原图分辨率因此无论输入尺寸改成多少deep_img索引方式都不需要变。4. 避坑清单中心点0值、外参漂移、远距抖动等5个现场问题这一章写的都是我实际踩过的坑按现象、原因、解决的顺序展开读者直接对号入座就行。4.1 检测到目标但距离永远输出0.0现象检测框正常画出来但距离位置一直显示invalid或者0.0。原因检测框中心点恰好落在深度无效区域。RealSense的深度依赖红外纹理纯黑色吸光物体、强反光表面、阳光直射下的地面都会让该区域深度变成0。解决不要用单点读取改用区域中位数统计并在边缘情况下保持上一次有效输出。深度无效是设备物理限制不是代码bug区域统计可以把偶发的无效点过滤掉但如果整个目标都是黑色材质任何算法都救不回来。4.2 深度图和RGB边缘错位测出的距离整体偏移现象画框位置看着正常但目标边缘和深度轮廓明显错开测距数值在同一物体上左右移动几十厘米。原因深度相机和RGB相机的外参出厂值是标定好的但相机经过运输磕碰或长时间高温工作后外参偏移会超出容忍范围。解决做一次realsense d435i标定。在realsense-viewer中打开自标定功能打印官方标定图并贴在平整表面按照流程拍摄十几帧校准结果会写入设备Flash。需要注意标定图打印质量直接影响结果用普通A4纸可以但不能有折痕拍摄时不要让手出现在画面里。4.3 5米外的目标距离读数来回跳现象目标在3米内输出平滑一旦超过5米数值在8.2米到11米之间大幅度摆动。原因深度测量误差随着距离增长而放大同时远处目标在深度图中占的像素少中位数统计的样本不够抗噪能力变差。这本质上是D435i采样的距离分辨率的限制不是滤波参数没调好。解决先把测量范围限制在5米内其次对输出做时间维度上的平滑最简单的是指数滑动平均如果一定要测10米换更高分辨率的深度相机或改用激光雷达不要指望算法层面解决。4.4 USB3.0连线还是频繁掉设备现象程序运行几分钟后pipeline.start()抛出异常提示找不到设备重启后又正常。原因USB供电不足或系统自动挂起接口。笔记本扩展坞是重灾区尤其同时给相机和显示器供电时。解决把相机插到主板原生USB3.0口不要走扩展坞在Linux下执行systemctl mask sleep.target suspend.target关闭自动挂起把深度流分辨率降到640x480。我遇到多次“跑一会就断”的问题最后发现是扩展坞供电不稳换接口后整晚没再掉过。4.5 树莓派5上部署帧率只有3到5帧现象在树莓派5上跑完整脚本YOLO推理加深度对齐全部占满CPU帧率低到不可用。原因深度对齐和YOLO推理都在抢计算资源特别是彩色流开到1280x720时CPU忙于缩放和图像转换。解决把深度图降到640x48015RGB降到640x480模型换成yolov5n单帧处理一次深度对齐。树莓派上先用arm64版本的realsense-viewer确认相机在低分辨率下深度正常再跑推断程序。这样调整后能到10帧以上对安全距离监控这类场景完全够用。5. 测距精度的底线误差增长、斜距修正与毫米波雷达的边界5.1 距离误差不是恒定百分比而是越远越差很多刚接触深度相机的朋友会问D435i的测距精度是不是固定2%实际上误差不是一条平坦的线更像一条随距离放大的曲线这也是为什么现场项目里通常说“距离衰减函数”而不说“测距精度”。下面是D435i在不同距离区间的实测经验值使用640x480分辨率和默认环境光照。距离区间单帧典型误差范围现场感受0.3m到1m正负1%到2%数值很稳适合桌面级抓取1m到3m正负1%到2%最佳工作区误差不明显3m到5m正负2%到3%读数开始有轻微呼吸感5m到8m正负3%到5%单帧噪声大必须做时间滤波8m以上正负5%以上仅适合粗判断不建议做精确测距误差来源有两部分。一是量化误差深度图的每个像素值只能表达毫米级整数靠近时相对误差小远离时同样的量化步长占据的比例更大。二是三角测量误差D435i基线只有50mm目标越远左右图视角差异越小深度计算对像素匹配误差越敏感。理解了这两点就能明白为什么红外补光、更高分辨率都不能根治远距离误差这是物理特性决定的天花板。5.2 斜距修正深度值不是目标的直线距离RealSense返回的深度值代表目标到相机平面的垂直距离也就是Z轴方向的距离不是目标到相机光学中心的直线距离。当目标位于画面边缘时两者差异会达到百分之十几。很多安全项目只在画面中心区域画警戒线这个误差不致命但机械臂避障和无人机定距时必须修正。修正公式来自针孔模型需要拿到深度相机的内参。# 从深度帧中获取内参 intr depth_frame.profile.as_video_stream_profile().intrinsics # u, v 是检测框中心的像素坐标 z dist_m x (u - intr.ppx) / intr.fx * z y (v - intr.ppy) / intr.fy * z # 目标是相对相机坐标系的真实直线距离 true_dist (z * z x * x y * y) ** 0.5当目标正好在画面中心时x和y接近0true_dist约等于z。目标移到画面边缘x或y变大true_dist明显大于z。D435i水平视场角接近86度取极限位置计算z还是10米时斜距能到13米上下。先输出修正后的三维坐标再乘上相机外参就能换算到机器人基座坐标系这也算给之后做三维目标检测留了一个干净的接口。5.3 RealSense加YOLOv5和毫米波雷达、激光雷达怎么分工RealSense加YOLOv5的独特价值在于它同时提供语义信息和距离信息这是毫米波雷达和激光雷达做不到的。但它的短板也很明确超过8米精度断崖式下降强光、黑布、玻璃都会导致深度失效。做方案选型时要清楚边界不要指望一套设备包打天下。方案输出内容有效范围主要限制RealSense YOLOv5类别名称加距离0.3m到8m强光、吸光材质、玻璃毫米波雷达目标点速度与距离0.2m到40m无类别信息金属多径反射单线激光雷达扫描平面点云0.05m到10m只能测单平面成本较高室内服务机器人、安全围栏、机械臂工作区RealSense是性价比最高的方案。室外停车场或者车辆预警需要10米以上直接考虑毫米波雷达配合视觉分类深度相机做不了这个活。方案没有好坏只有适不适合现场的距离区间这一点最好在项目开始时对客户说清楚。6. 把测距输出打磨到可信EMA滤波、丢帧保持与交付检查6.1 一阶指数滑动平均加消失保持单帧中位数测量结果在3米内已经比较平滑但5米边界处还需要时间滤波。一阶指数滑动平均EMA计算量小适合嵌入式设备配合一个简单的丢帧保持逻辑就能应付大部分现场。ema_dist None alpha 0.25 lost_frames 0 max_lost 3 # 在每帧推理后更新 if dist is not None: lost_frames 0 if ema_dist is None: ema_dist dist else: ema_dist alpha * dist (1 - alpha) * ema_dist output ema_dist else: lost_frames 1 if lost_frames max_lost: output ema_dist else: output Nonealpha取0.25时新数据权重较大响应快但平滑效果一般安全围栏这类场景更看重平滑alpha可以降到0.1。丢帧保持的逻辑也很实用目标短暂被遮挡时输出还能维持上一次的有效距离不至于让下游逻辑误判目标消失。连续丢失超过3帧才把输出置为None这个阈值可以根据现场遮挡频率调整。6.2 交付前的三个验证点第一用激光测距仪在0.5米、1米、2米、3米、5米五个距离各测一组数据看输出是否落在误差范围内。第二找一块纯色黑布挡住镜头几秒观察距离输出是否保持上一有效值不能疯狂跳0。第三打开系统的性能日志记录每帧深度对齐耗时和YOLO推理耗时确认在低端设备上帧率还能接受。我给自己定的规矩是测距不是加到程序里就结束必须导出带时间戳的log做离线校验。前几次做这类项目我把大量时间花在调滤波阈值最后才发现是USB掉帧和标定偏移这类低级问题早该先看日志再动手。这也是我最想提醒读这篇笔记的同行的地方。希望帮到你。本文还有配套的精品资源点击获取
返回列表