尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

RealSense深度相机融合YOLOv5:从2D检测框到精确测距

发布时间:2026/9/28 20:37:23

资讯中心
01
ARTICLE

RealSense深度相机融合YOLOv5:从2D检测框到精确测距

RealSense深度相机融合YOLOv5:从2D检测框到精确测距
简介利用Intel RealSense深度相机与YOLOv5目标检测框架提供一套可同时完成物体识别与距离测量的工程代码面向计算机视觉开发者、机器人或安防测距场景的实践者。压缩包共46个文件、约3.53MB包含18个Python脚本如realsensedetect.py、detect.py、train.py、8个YAML模型配置覆盖YOLOv5s/m/l/x、Dockerfile、Shell权重下载脚本以及README说明文档便于快速搭建环境并理解项目结构。已有429人学习下载资源以简洁的模块化设计呈现可直接修改使用。项目内置模型导出与检测流程并附带示例图片用户可参考如何将深度图与目标框结合输出类别、位置和距离信息实现“识别即测距”的完整流程。适合熟悉YOLO系列、希望扩展深度相机应用的开发者作为实战参考。1. 用realsense深度相机给yolov5目标检测补上第三维先看懂这幅图在解决什么很多做目标检测的人卡在同一个地方模型能告诉你框里是什么屏幕上的x、y坐标是有的但它离我多远完全不知道。做避障、抓取、巡检时这个第三维才是真正要的答案。标题里这个realsense深度相机加yolov5目标检测的组合解决的就是把检测框从“像素坐标”升级成“像素坐标米”。比起纯视觉方案深度相机直接把三维信息给到程序里不用靠标定板估单目距离拿到检测框中心像素后查一下深度图就能输出距离。这套方案适合已经被yolov5环境配置折磨过、想在本地把realsense d435或d435i接进现有检测流程的开发者也适合刚入门的人沿着一条主线跑通测距。2. 跑通前的三件事选对相机、搭好conda环境、搞懂深度对齐2.1 D435还是D435i测距场景下选型的核心区别realsense系列里最常被拿来接yolov5的是D435和D435i。这两个型号在深度成像上几乎没有差别都靠左右两个红外相机做立体匹配配合一个主动红外投影仪在弱纹理区域打上纹理点从而算出视差。D435i只是在主板上多集成了一颗IMU能输出加速度和角速度主要服务于SLAM、轨迹估计这类需要位姿融合的应用。对于标题里“检测到目标同时测出距离”这种需求IMU帮不上什么忙选D435就够了价格更低发热也更可控。如果你手里已经有一块D435i那就直接用它不用纠结。真正要纠结的是“realsense d435i标定”这句话标定分两个层面一是双红外相机的出厂内参标定二是IMU与相机之间的外参标定。出厂时内参已经写在设备里pyrealsense2会直接读取你不用跑标定程序但如果你要做的是把检测结果和机器人里程计融合、长时间在高温或剧烈震动环境下工作那IMU外参漂移会明显影响位姿估计。单纯做静态测距的话先把标定放一边信任出厂参数后面遇到精度问题再回头查硬件。选型还要注意使用距离范围。D435/D435i的有效深度测量范围大约在0.2米到3米之间超出这个区间深度值的噪声会快速放大。我见过有人把它架在楼道里测10米外的目标测出来数据漂得厉害这不是相机坏了而是超出了立体视觉的可靠工作带。做测距实验前先看一眼目标离相机的物理距离别在3米外追求毫米级精度。2.2 conda yolov5环境配置pyrealsense2、torch和yolov5源码一次到位这套方案的最小环境依赖就四样pyrealsense2、opencv-python、torch、yolov5源码。我习惯先建一个独立conda环境避免和已有的tensorflow或者老版opencv互相打架。conda create -n rs_yolo python3.9 -y conda activate rs_yolo pip install pyrealsense2 opencv-python numpy pip install torch torchvision git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txtpyrealsense2是Intel RealSense官方控制仓库对应的Python绑定装好后把相机插到USB 3.0口在命令行里执行python -c import pyrealsense2 as rs; print(rs.pipeline().doc)能正常输出就说明驱动和SDK已经通了。opencv-python负责图像读写和画框numpy用于把RealSense返回的帧数据转成数组。torch和torchvision按你机器的CUDA情况装先装torch再执行yolov5的requirements.txt是个小技巧因为requirements里也会声明torch依赖提前装好可以避免它把GPU版torch覆盖成CPU版。yolov5环境配置最常见的翻车点不是模块缺漏而是版本错位。比如pyrealsense2依赖的numpy版本和yolov5里要求的numpy版本冲突pip在装requirements时会提示“numpy 2.x is incompatible”。遇到这种情况不要硬改版本号直接在环境里重新pip install numpy1.26.4这类完全兼容的版本然后再跑一遍验证脚本比逐个试错快得多。2.3 深度图与彩色图对齐所谓“同时测距”的前提是把两个坐标系对齐为什么需要对齐因为realsense的彩色相机和深度相机是两个物理传感器它们的光心位置、朝向都不一样。彩色图里检测框坐标是(u, v)深度图里同一物理点对应的像素位置并不在同样的(u, v)上。如果不做任何处理拿着yolov5给出的检测框坐标直接去深度图里取深度值误差可能达到几十个像素近处目标到测出来的距离可能偏到完全不可用。pyrealsense2里处理这件事的组件叫align。它利用设备固件里保存的相机内参和两个传感器之间的外参把深度图重投影到彩色相机的视角下。对齐之后彩色图里的像素坐标就能直接作为索引去访问深度图数组两个图的分辨率相同每个像素一一对应。这个“查表”动作就是标题里“同时测出距离”的本质目标检测产生2D框深度对齐让2D坐标能对应到3D距离。深度图的数据格式也要搞清楚。d435输出的depth stream默认是z16格式也就是每个像素用16位无符号整数表示深度值这个值不是直接的物理距离。要转换成米必须乘一个系数叫depth_scaleD435出厂默认大约是0.001也就是深度值3000表示3米。这个系数通过get_device().first_depth_sensor().get_depth_scale()读取不要自己硬编码因为不同固件可能给出不同值。另外深度值为0的像素代表无效测量不是距离0米这一点后面取深度时需要过滤。3. 把yolov5检测框和realsense深度图接在一起最小可用的测距主循环3.1 主循环结构读帧、对齐、推理、取深度、画框标注环境配好后先跑通一个最小循环。下面这段代码把realsense的彩色流和深度流同时打开对齐后喂给yolov5拿到每个检测框后取中心像素对应的深度值并换算成米最后用OpenCV画框显示。import cv2 import numpy as np import torch import pyrealsense2 as rs # 初始化RealSense管线同时开color和depth流 pipeline rs.pipeline() config rs.config() config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30) config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30) profile pipeline.start(config) # 把深度图重投影到彩色图坐标系这是坐标一致性的关键 align rs.align(rs.stream.color) depth_scale profile.get_device().first_depth_sensor().get_depth_scale() # 加载yolov5官方模型也可以换成自己训练的权重 model torch.hub.load(ultralytics/yolov5, yolov5s, pretrainedTrue) model.conf 0.35 model.iou 0.45 while True: frames pipeline.wait_for_frames() aligned_frames align.process(frames) color_frame aligned_frames.get_color_frame() depth_frame aligned_frames.get_depth_frame() if not color_frame or not depth_frame: continue color_image np.asanyarray(color_frame.get_data()) # BGR rgb_image cv2.cvtColor(color_image, cv2.COLOR_BGR2RGB) depth_image np.asanyarray(depth_frame.get_data()) # uint16 results model(rgb_image) for det in results.xyxy[0]: x1, y1, x2, y2 [int(v) for v in det[:4]] conf float(det[4]) cls int(det[5]) # 检测框中心查深度图乘depth_scale换算成米 cx, cy (x1 x2) // 2, (y1 y2) // 2 dist_meters depth_image[cy, cx] * depth_scale label f{results.names[cls]} {conf:.2f} {dist_meters:.2f}m cv2.rectangle(color_image, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(color_image, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow(realsense_yolov5_distance, color_image) if cv2.waitKey(1) 0xFF ord(q): break pipeline.stop() cv2.destroyAllWindows()代码的执行顺序是固定的先pipeline.start启动设备再align.process对齐帧然后跑yolov5推理最后用检测框坐标去depth_image里取值。这里有一个容易忽略的点我把color_image转了RGB再喂给模型但画框用的是原始的BGR图。pyrealsense2的color stream我配置成bgr8格式如果你设成rgb8那就不需要cvtColor这一步。yolov5官方torch.hub接口本身能自动处理常见颜色顺序但显式转换能保证你从RealSense拿到的数据和自己用cv2.imread读图时的行为一致排错时少一个变量。results.xyxy[0]里每一行是[x1, y1, x2, y2, confidence, class_id]坐标已经映射回原图尺寸因为torch.hub内部做了letterbox并会把坐标还原。如果你脱离torch.hub、自己写预处理就必须自己做letterbox的逆变换不然检测框坐标和原图对不上取到的深度自然全错。这是这套流程里最容易出现“检测很正常但距离完全错误”的环节。3.2 检测框中心点的深度值怎么取二维框坐标查深度图取距离的思路很简单检测框中心像素在彩色图里是(cx, cy)经过align后深度图与彩色图对齐所以depth_image[cy, cx]就是这个像素的深度原始值乘depth_scale就是米。代码里demo只用单点这是最朴素的实现足够验证链路通不通。但单点取值有几个隐含前提目标表面在这个像素处必须有有效深度且中心点不能落在目标边缘。如果你在调试时发现输出0.00m先不要怀疑公式去看depth_image[cy, cx]是不是0。深度值为0有几种情况目标太近或太远超出测量范围表面是镜面或纯黑色吸光材质目标很薄导致红外投影仪打不出纹理。D435的红外投影仪在近距离上对白色墙面、纸箱这类低纹理物体效果很好但对透明塑料瓶、黑色轮胎、阳光直射下的物体经常给不出有效值。另外一个细节是depth_image是二维uint16数组访问顺序是行在前列在后也就是depth_image[cy, cx]而不是depth_image[cx, cy]。OpenCV里图像数组也是这个顺序但很多人刚从三维坐标习惯切过来时会在这一行线上犯迷糊。写成行的形式就是在检测框中心画一个十字线把中心点可视化深度值是否对应当前目标一眼就能看出来。3.3 yolov5超参数与推理设置conf、ioup和imgsz的取舍yolov5的推理参数对测距结果的影响不在深度侧而在“有没有测对目标”这一侧。model.conf是置信度阈值低于这个值的检测框会被丢掉model.iou是NMS的IoU阈值控制重叠框的去留。测距场景里我一般把conf调到0.25到0.35之间比纯入稿调低一点。理由很直白测距的最终消费者往往是避障逻辑或机械臂抓取漏检的代价远高于误检宁可让一个可疑目标进入距离计算也不能让真目标完全消失。model.imgsz控制输入分辨率默认640。如果你用的是yolov5s、跑在普通独立显卡上640分辨率能到30帧每秒左右如果是树莓派或者CPU推理建议降到416甚至320帧率提升比重新换小模型还明显。降分辨率会影响小目标检测能力但对常见的中大型目标影响有限。测距任务里还有一个被忽略的参数是max_det默认1000实际使用中限制成10到20个就够能略微减少后处理耗时。如果你训练了自己的数据集注意类别名称对不上的问题。torch.hub加载本地权重时要用model torch.hub.load(ultralytics/yolov5, custom, pathbest.pt)然后results.names就会带着训练时的类别名代码里取names[cls]才不会报越界。yolov5后处理部分wanst该改的只有conf和iou其他参数保持默认往往比乱调更稳。4. 从“能测”到“测得稳”多点采样、中值滤波和失效回退4.1 单点测距为什么翻车中心像素落在背景或空洞第3节的demo能跑通但实际部署时会发现一个很烦人的问题检测框明明稳稳地框住目标输出的距离却在目标与背景之间来回跳。原因多数出在中心点上。目标检测框是矩形中心像素不一定落在目标实体上可能是目标的镂空处、两个物体之间的缝隙、目标的弧形表面边缘甚至是目标前面伸出来的一根细枝。中心点到目标中心的距离没问题一旦落到缝隙里深度值就变成背景的距离。另一个重要来源是深度空洞。立体匹配在遮挡边界和低纹理区域会产生无效像素D435给出的深度图上这类空洞很常见。中心像素一旦遇到空洞深度值就是0单点逻辑直接失效。我自己的经验是单点测距适合实验室验证不适合任何真实场景。真实场景至少要用一个区域来代替一个点这个区域就是检测框本身。4.2 多点采样加中值把检测框内有效深度值做统计把取点改成取区域核心是先用检测框裁剪深度图过滤掉无效值再做统计。下面的函数是对中心单点的直接替换def robust_depth_at_box(depth_image, depth_scale, x1, y1, x2, y2): roi depth_image[y1:y2, x1:x2] # 过滤无效深度0值代表测量失败 valid roi[roi 0].astype(np.float32) if valid.size 0: return None # 中值对离群点更鲁棒均值容易被边缘噪声拉偏 dist_meters float(np.median(valid)) * depth_scale return dist_metersmedian和mean的选择值得说一句。深度图的噪声不是均匀的高斯噪声而是受反光、遮挡、边缘影响产生的大幅离群值。一组深度值里如果混进几个背景值均值会明显被拉偏而中值几乎不受影响。对于前方障碍物测距如果目标是避障逻辑的输入还可以把median换成percentile取有效值的10%分位数代表“离我最近的可靠距离”这样对凸出的遮挡物更敏感。框内采样的边界也需要控制。检测框很大时比如一个人体框目标本身只占中间一部分全框采样会把左右两侧的背景墙面也算进去中值虽然能压住噪声但测出来的距离仍会偏大。我一般会先按比例收缩检测框保留中心区域再做统计。具体比例没有定式常规做法是取框宽高的40%到60%作为中心矩形避开边缘干扰。4.3 深度失效回退当前帧没有有效深度时不能直接输出0即使做了区域统计依然会遇到整块区域都没有有效深度的情况。比如目标表面是黑色吸光材质或者目标进入强红外干扰环境。这时函数返回None调用方必须有回退逻辑不能把None当成0.00m输出。一个简单的做法是保留上一帧有效值并记录连续失效次数import collections last_valid {} fail_count collections.Counter() for det in results.xyxy[0]: x1, y1, x2, y2 [int(v) for v in det[:4]] cls int(det[5]) obj_id cls dist robust_depth_at_box(depth_image, depth_scale, x1, y1, x2, y2) if dist is not None: last_valid[obj_id] dist fail_count[obj_id] 0 else: fail_count[obj_id] 1 # 连续5帧无有效深度才放弃否则沿用旧值 dist last_valid.get(obj_id, None) if fail_count[obj_id] 5: dist None这里按类别做了区分不同类别各自维护上一帧距离避免一个目标失效导致所有类别的距离被同一个旧值污染。连续帧数这个参数可以根据你的帧率调整30帧每秒的情况下连续5帧失效约等于目标丢失0.17秒视觉上不会察觉距离跳动。如果目标经常被遮挡比如巡检机器人抓拍动态目标可以把阈值放宽到10帧换取更平滑的输出。时间维度上的滤波同样重要。深度相机单帧噪声在0.5米到2米范围内通常是几个毫米到一两厘米但叠加温漂和动态场景后同一目标的读数值会有肉眼可见的抖动。常见的做法是用一个定长deque缓存最近几帧的距离值取中值作为最终输出d collections.deque(maxlen5) d.append(dist_meters) smoothed_dist float(np.median(d))这种滑动中值滤波比简单指数平滑更稳因为它不会让一次严重的跳变缓慢“拖尾”而是直接把它当作离群点剔除。5. 常见问题排查与避坑记录深度黑屏、测量跳动的五个真实原因5.1 深度图一片黑检测框中心深度值为0现象彩色图正常运行窗口里target距离一直显示0.00m打开深度可视化看到整块区域是黑的。原因深度值全部是0硬件层面没拿到有效深度。常见诱因包括目标距离小于0.2米超出最近测量范围、目标表面是透明或强反光材质、红外投影仪被遮挡、USB带宽不足导致深度流降帧。解决先把目标放在0.3米到1.5米的正常范围用官方查看器开一下active infrared选项看是否有红外纹理确认USB线插在主板原生USB 3.0口而不是扩展坞上代码层面在区域统计中过滤值为0的像素并用上一帧有效值做回退而不是让0参与运算。5.2 RGB图和深度图错位导致测距整体偏差现象检测框稳稳框住目标距离却是目标旁边墙面或背景物体的读数而且偏差方向和大小固定。原因直接用未对齐的原始深度图索引彩色图坐标或者aligned_frames没处理好就进入下一帧。很多时候是代码里忘了调用align.process直接把frames.get_depth_frame()拿来做坐标映射。解决统一走aligned_frames并且在每次wait_for_frames后都重新process对每一帧都重新对齐。另外注意depth_frame和color_frame都来自同一个aligned_frames对象不要混用原始frames里的帧。5.3 静止目标的测距结果反复跳动现象目标放在桌上不动输出距离在0.8m到1.2m之间来回晃肉眼可见地不稳定。原因单帧深度噪声、立体匹配在低纹理区域的不确定性、边缘像素把背景值带进统计区域。深度相机的原始输出本身就不是稳定的逐像素“真值”而是带空间和时间噪声的观测值。解决先加后处理滤镜pyrealsense2自带的spatial filter和temporal filter对静态场景改善明显代价是动态目标会有轻微拖影再加滑动中值滤波把最近5帧到10帧的距离做排序取中值最后检查采样区域是否覆盖了检测框边缘收缩到中心区域能显著降低背景干扰。5.4 USB带宽与供电不足引发的间接故障现象pipeline.start()偶发性失败wait_for_frames报“Frame didnt arrive within N ms”或者彩色图正常但深度图帧率只有个位数。原因D435是USB 3.0设备在USB 2.0口或劣质扩展坞上带宽不够深度和彩色两个流抢通道导致深度帧丢失。解决插主板原生USB 3.0口不要插机箱前面板转接口必须用扩展坞时选支持USB 3.0 Gen1以上的代码层面把深度流和彩色流统一降到640x48015fps或320x24030fps带宽占用减少一半以上。这条坑最容易出现在笔记本上电源策略还会限制USB口供电必要时把电源模式调到高性能。5.5 出厂参数与d435i标定什么时候才需要认真对待现象近距离测距误差在几厘米内距离拉到2米后误差明显膨胀甚至固定偏移几十厘米。原因立体视觉的误差本身随距离平方增长这属于物理限制另一个原因是相机内部结构受温漂影响出厂标定的内外参数在温度变化大的环境中会产生偏移。解决先排除物理因素在1米和2米各测几组数据计算平均误差而不是单次误差如果误差稳定且偏大考虑让相机开机预热10分钟再工作如果对精度有强需求比如机械臂抓取或毫米级测量再去做d435i标定流程普通目标检测测距场景下优先保证相对距离稳定不要追求绝对值做到仪器级精度。6. 用卷尺给测距结果验验光精度验证、深度可视化和部署延伸6.1 用测量仪器做对照实验误差算出来心里才有底深度相机测出来的距离不是一个“绝对正确”的数字工程上需要知道它在什么范围内可信。我习惯的做法是准备一台激光测距仪或一把卷尺把目标物依次摆到0.5米、1.0米、1.5米、2.0米、2.5米的位置每个位置连续记录50帧距离值统计平均值和标准差。下面这个表是一个典型的实验记录模板设定距离(m)平均输出(m)标准差(m)误差(m)0.500.520.020.021.001.030.030.031.501.550.050.052.002.100.080.10表格里的数值是示意但趋势是真实的距离越近绝对误差越小超过2米后标准差和误差都明显增大。如果测出来的误差稳定在同一个方向说明存在系统偏差可以在输出侧做一次线性校正如果误差正负随机那就要靠滤波和后处理来压噪声。6.2 深度可视化把深度图变成能肉眼检查的图像调试时只看打印出来的数字不够直观把深度图转成伪彩色图叠加显示能立刻发现空洞和错位问题。常用的做法是把uint16的深度图缩放后映射到COLORMAP_JETdepth_visual np.clip(depth_image * depth_scale, 0, 3) # 只显示0~3米 depth_visual (depth_visual / 3.0 * 255).astype(np.uint8) depth_color cv2.applyColorMap(depth_visual, cv2.COLORMAP_JET)注意这里是“可视化用的深度图”不是真正用来计算距离的depth_image。JET色图中蓝色代表近、红色代表远你很容易看出检测框内有没有黑色空洞。调试完再决定要不要把这一路叠加显示关掉毕竟彩色图加伪彩图双路显示对帧率有影响。6.3 向嵌入式部署延伸树莓派和低算力设备上的取舍这套方案不是只能跑在台式机上。树莓派5上部署自己训练的yolov5模型时最大的瓶颈在推理耗时和USB带宽两块。模型优先换yolov5n或量化版输入尺寸降到320x320实测能在单片机上跑到接近实时深度流和彩色流分辨率降到640x48015fps避免USB带宽不够导致深度帧丢失。测距逻辑不变只是要把“连续5帧无有效深度才回退”这个参数调大因为帧率降了一半5帧对应的真实时间翻倍了。我在做这类项目时有一个习惯把每个检测框的距离字段整理成结构化字典输出而不是直接画在图上这样无论接机械臂控制还是存日志都好用。后来证明这个习惯省了大量调试时间因为测距逻辑本身和可视化逻辑经常要分开验证。这套组合里最花时间的往往不是模型本身而是深度图和检测框之间那层数据形态的对齐与过滤希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。