
简介利用RealSense深度相机与YOLOv5结合的目标检测与测距项目面向需要同时获取物体类别、位置和距离信息的视觉开发者解决普通RGB图像无法直接测距的问题。压缩包共四十六个文件以Python脚本为主体涵盖检测推理、训练以及深度相机调用逻辑并配有YAML模型参数、Docker容器配置、依赖清单与Markdown说明文档便于快速搭建和运行环境。整个资源包体积仅三点五三MB结构紧凑轻量易部署。目前已有四百二十九人学习下载。下载后可获得可直接运行的检测与测距代码、清晰的目录框架和模型配置能够在此基础上理解深度相机与YOLOv5的融合方式并根据实际场景替换模型或调整深度处理参数适合作为计算机视觉、机器人相关项目的参考起点也可直接用于教学演示或原型验证。1. 从“框住目标”到“量出距离”这个项目把 YOLOv5 和 RealSense 深度相机绑在了一起做视觉检测的都知道YOLOv5 输出的是一堆像素坐标——中心点在哪、框有多宽但目标距离相机多远模型不知道。做巡检机器人、机械臂抓取、安防监控的人真正想要的是检测到人的同时直接告诉我他站在几米外。这份资源做的就是这件事用 Intel RealSense D435i 深度相机实时出深度图YOLOv5 跑目标检测检测框直接映射到深度图上取出中心点的深度值再把距离打在框的标签上。你不用自己拼装识别和测距两条链路项目包里已经写好了realsensedetect.py这个核心脚本模型、工具函数、训练代码都齐适合两类人一是刚接触 YOLOv5、想跑通完整检测流程的开发者二是已经在做检测但需要把“像素坐标”升级成“真实距离”的一线工程师。2. 为什么是 YOLOv5 RealSense D435i深度信息与检测框的映射原理2.1 RealSense D435i 的深度是怎么来的D435i 用的是主动红外立体视觉方案左右各有一个红外相机中间还有一个红外点阵投射器。投射器会往场景里打一片肉眼不可见的红外纹理两个红外相机同时捕捉然后靠立体匹配算法计算每个像素的视差最后转成深度值。这就是为什么 D435i 在黑暗环境里也能出深度因为它不依赖环境光。这个方案有几个硬指标你跑项目之前先记住近距离下限大约 0.28 米超过这个距离深度值全部无效最远标称能到 10 米但实际超过 3 米误差就会明显变大。精度方面官方给的是 2 米内误差小于 2%这个取决于场景纹理和表面材质。黑色吸光物体、透明玻璃、高反光表面深度数据基本是黑洞或者跳变值这个坑后面专门讲。2.2 YOLOv5 的检测框如何对应到深度图YOLOv5 是 One-stage 检测器输入一张 BGR 图像输出检测框坐标坐标原点在图像左上角。RealSense 的彩色流和深度流是两个独立传感器它们的视野范围、分辨率、安装位置都不同。所以要直接用(cx, cy)去深度图上取距离必须先做对齐Align操作。对齐的本质是把深度图按已知的双目外参和深度内参重投影到彩色相机坐标系让深度图的每个像素和彩色图像的像素一一对应。对齐完成后深度帧的分辨率会变成彩色帧的分辨率此时在检测框中心取depth_frame.get_distance(cx, cy)拿到的就是那个空间点沿光轴方向到相机的距离。注意get_distance()返回的是米不是毫米。很多人把深度图的原始像素值Z16 格式单位毫米当成距离直接除以 1000 来用两者在大多数情况下数值一致但一旦涉及内参重投影原始像素值就不再等于实际深度所以项目里统一用对齐后深度帧的get_distance()是更稳的做法。2.3 这个组合适合什么场景不适合什么场景适合的场景有这么几类室内巡检小车避障预警检测到前方人的同时给出距离触发减速或停止桌面机械臂的抓取目标定位先知道目标在哪个区域、多远再去调整机械臂路径安防摄像头的人形测距判断目标是否进入警戒线。这类场景的共同点是环境光相对可控目标距离集中在 0.3 到 3 米实时性要求不低于 15 帧。不适合的场景也明确一下大太阳底下的室外红外纹理会被阳光里的红外成分冲掉深度图会出现大面积空洞玻璃幕墙和镜面场景深度值极其不稳定需要精确到毫米级的工业测量场景这个方案精度不够得上激光位移传感器。项目里默认用的 YOLOv5s 模型在 COCO 上训练认得人、车、猫狗这些常见类别如果你要检测工业零件、特定缺陷需要自己训练模型这个资源里也带了完整的train.py后面讲怎么接。3. 环境搭建与首次运行把相机点亮、把权重下载好、把脚本跑起来3.1 硬件接线和驱动安装先确认相机被系统识别RealSense D435i 用 USB 3.0 接口连接普通 USB 2.0 也能出图但帧率掉得厉害建议直接插主板后置 USB 3.0 口。Windows 上装好 Intel RealSense SDK 之后设备管理器里能看到Intel(R) RealSense(TM) Depth Camera 435i。Linux 上需要先安装librealsense2并配置 udev 规则否则没有权限打开 USB 设备。装完驱动后用下面这个 Python 片段确认相机能被 pyrealsense2 找到import pyrealsense2 as rs ctx rs.context() devices ctx.query_devices() print(f检测到 {len(devices)} 台设备) for dev in devices: name dev.get_info(rs.camera_info.name) serial dev.get_info(rs.camera_info.serial_number) print(f设备名称: {name}, 序列号: {serial})逻辑很简单创建context后query_devices()会列出所有已连接的 RealSense 设备。如果在输出里看不到设备大概率是驱动问题或 USB 线接触不良这时候重插一次并检查接口协议是否为 USB 3.0。3.2 依赖安装与权重下载两个文件解决运行前置条件项目根目录有requirements.txt和weights/download_weights.sh前者是 Python 依赖清单后者是 YOLOv5 预训练权重下载脚本。pip install -r requirements.txt cd weights bash download_weights.shrequirements.txt里锁定的是 YOLOv5 6.0 那一代的依赖版本主要包含torch1.7.0、opencv-python4.1.2、pyrealsense2、numpy、scipy。如果你用的是 PyTorch 2.x 环境大概率也能直接跑但个别 API 兼容性问题可能在utils/general.py的 NMS 调用中出现后面避坑章会提。download_weights.sh脚本会从 GitHub 拉取yolov5s.pt、yolov5m.pt等权重文件。如果网络不稳定导致脚本中断别反复重试直接手动下载yolov5s.pt放进weights/目录或者用你本地已有的其他 YOLOv5 权重替换脚本的本质只是把官方权重搬运到本地。3.3 第一次运行 realsensedetect.py从摄像头到检测框再到距离驱动和依赖都就绪后先不带任何参数跑一次python realsensedetect.py脚本默认打开 RealSense 的深度流和彩色流分辨率 640x480帧率 30然后加载weights/yolov5s.pt启动检测循环。如果一切正常你会看到一个 OpenCV 窗口画面里的每个检测框左上角标签长这样person 0.87 1.25m。这个脚本里通常有一段预处理逻辑把深度帧和彩色帧对齐到同一坐标系再喂给 YOLOv5。如果运行时提示找不到相机检查 3.1 节的设备探测代码是否能输出序列号如果提示FileNotFoundError: weights/yolov5s.pt说明权重文件确实没放对位置。这里有个常见参数脚本如果支持--conf-thres你可以在命令行指定置信度阈值python realsensedetect.py --conf-thres 0.4置信度阈值越高误检越少但漏检会变多。默认 0.25 是 YOLOv5 官方的平衡值如果你只关心很明显的大目标调到 0.5 以上效果更清爽。4. 核心代码逐段拆解realsensedetect.py 是怎么让 YOLOv5 输出真实距离的4.1 对齐Align是深度测距的第一个关键操作realsensedetect.py主流程的第一件事不是加载模型而是初始化相机和对齐对象。下面这段是这个脚本最常见的写法import pyrealsense2 as rs pipe rs.pipeline() cfg rs.config() cfg.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30) cfg.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30) profile pipe.start(cfg) # 对齐到彩色流把深度图重投影到彩色相机的坐标系 align rs.align(rs.stream.color)注意rs.align(rs.stream.color)表示以彩色流为参考坐标系对齐后深度图的分辨率会被强制改成彩色图的分辨率这样后续直接用像素坐标去深度图上取值才不会错位。如果不做这一步深度图是 640x480、彩色图也是 640x480 时看起来没区别但实际传感器的视场角不同左上角像素对应的空间点根本不在同一条射线上测出来的距离自然不可信。4.2 主循环里的“检测 测距”具体逻辑对齐只是前提真正核心的是下面主循环的写法。我拆过这个脚本结构基本是四个步骤取帧、对齐、推理、在检测框中心取深度。import cv2 import torch from models.experimental import attempt_load from utils.general import non_max_suppression # 加载模型 weights weights/yolov5s.pt device torch.device(cuda if torch.cuda.is_available() else cpu) model attempt_load(weights, map_locationdevice) model.eval() while True: frames pipe.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 img cv2.cvtColor( np.asanyarray(color_frame.get_data()), cv2.COLOR_BGR2RGB ) # YOLOv5 推理输入 640x640 pred model(img, augmentFalse)[0] det non_max_suppression(pred, 0.25, 0.45)[0] if det is not None: for *xyxy, conf, cls in det: x1, y1, x2, y2 [int(v) for v in xyxy] cx, cy (x1 x2) // 2, (y1 y2) // 2 dist_m depth_frame.get_distance(cx, cy) label f{model.names[int(cls)]} {conf:.2f} {dist_m:.2f}m cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)核心就两行depth_frame.get_distance(cx, cy)拿到中心像素的深度cv2.putText把距离拼进标签。non_max_suppression的两个参数分别是置信度阈值和 NMS 的 IoU 阈值保持 0.25 和 0.45 可以复现 YOLOv5 官方 detect.py 的效果。这里有一个细节值得展开get_distance()内部做的是沿相机光轴方向的距离测量不是目标到相机的欧氏直线距离。对框中心点来说如果目标偏离画面中心很远光轴距离会比直线距离小几个百分点。要算出真正“点到点”的直线距离需要在第 6 章讲的内参反投影里处理。4.3 取中心点的深度值并不总是可靠邻域中值才是常用做法大部分第一次跑这个项目的人会直接取(cx, cy)一个点的深度。这个做法在目标纹理丰富、光照均匀的时候没问题但碰到白墙、深色衣服、反光表面单点深度会瞬间变成 0 或直接跳变。我在实测里遇到过 label 上距离一会 1.2 米一会 0.8 米的翻车现场后来改成在检测框中心取一个 5x5 邻域的中位数瞬间稳定了。def median_distance(depth, cx, cy, radius3): values [] h, w depth.get_height(), depth.get_width() for dy in range(-radius, radius 1): for dx in range(-radius, radius 1): u, v cx dx, cy dy if 0 u w and 0 v h: d depth.get_distance(u, v) if 0 d 10: # 过滤无效值和超出量程的噪点 values.append(d) if not values: return 0.0 values.sort() return values[len(values) // 2]传入的depth是对齐后的深度帧radius3表示取中心周围 3 个像素半径的区域也就是 7x7 的窗口。排序后取中位数比取均值更稳因为均值会被个别异常大值拉偏而中位数天然抗离群点。5. 避坑清单跑 RealSense YOLOv5 常见的五个坑5.1 检测框正常但距离全是 0.00m现象画面里框、类别、置信度都显示正常唯独距离显示 0.00m或者显示nan。原因最常见的是没做align.process(frames)直接用原始深度帧去取检测框坐标——两个传感器的内参和视角不同坐标对不上取到的是深度图上的空洞其次是目标离相机太近D435i 的最小深度是 0.28 米小于这个距离深度值直接无效第三种是物体表面是纯黑或强反光材质深度值本身为 0。解决先确认代码里有aligned_frames align.process(frames)并用对齐后的depth_frame去取值再把测距目标放在 0.3 到 3 米范围内最后对取出的距离做一次有效性判断——如果返回 0 或nan用 4.3 节的median_distance函数在邻域里捞有效值。5.2 帧率只有 10 帧出头画面明显卡顿现象程序跑起来 CPU 占用接近 100%OpenCV 窗口预览像幻灯片实测帧率不到 15。原因YOLOv5s 在 CPU 上推理一张 640x640 的图像大约需要 80 到 150 毫秒再加上深度图对齐和图像转换单线程跑满也就 8 到 12 帧。解决有 NVIDIA GPU 就直接用 CUDA跑python realsensedetect.py --device cuda帧率能到 30 以上只有 CPU 的话把输入分辨率降到 320、模型换成 YOLOv5n或者每 2 帧检测一次、中间帧沿用上一帧的检测框这个“隔帧检测”的写法很多部署项目都在用能在不明显影响体验的情况下把帧率翻倍。5.3 距离数值在几十厘米范围内来回跳现象目标静止不动但标签上的距离值 1.2、1.4、0.9、1.3 这样跳完全没法用。原因单像素深度本身就有噪声D435i 在 2 米处的标准差通常在 1% 左右也就是 ±2 厘米。但遇到反光或半透明材质某些帧会突然出现离群噪点直接把单点距离拉偏。解决用 4.3 节的邻域中值替换单点取值再叠加一个时间维度的滑动窗口保存最近 10 帧的距离值取中位数或者做一次一阶低通滤波smoothed 0.7 * smoothed 0.3 * current。这两层处理叠加后距离显示基本就稳定了。5.4 换相机型号或改分辨率后距离全部错位现象同一个程序把相机从 D435i 换成 D435或者把彩色流分辨率从 640x480 改成 1280x720检测框位置看起来正常但距离值明显不对。原因D435 和 D435i 的 RGB 传感器型号相同但出厂标定的内参未必一致更关键的是换分辨率后对齐对象rs.align会按新内参重新投影但代码里如果硬编码了旧的像素偏移量就会错位。解决每次启动时动态读取当前流的get_intrinsics()不要写死任何内参常量。改分辨率后至少重新跑一次 6.1 节的标尺验证流程确认距离输出和实际值吻合。5.5 黑色物体和玻璃表面出距离黑洞现象检测框稳稳锁住目标但框内深度值是 0邻域中值也救不回来距离直接空白。原因D435i 靠红外纹理做立体匹配纯黑物体大量吸收红外光玻璃和镜面让红外光直接反射走左右相机拍到的纹理不匹配深度算法匹配失败输出 0。解决检查相机前方的红外点阵投射器是否被遮挡保证投射器不被堵住调整相机角度尽量避免直射黑色或镜面材质如果目标是深色物体把它放在有纹理的背景前立体匹配会更容易成功。真要测黑色物体的距离D435i 不是好选择这是物理限制换 ToF 方案或三角激光方案才治本。6. 验证与进阶用一把卷尺和一个 BAG 文件就能把距离精度测明白6.1 标尺验证误差评估的标准流程跑通之后别急着接业务先验证深度值的可信度。拿一把卷尺把相机固定好选一个纹理丰富的目标比如纸箱分别放到 0.5 米、1.0 米、1.5 米、2.0 米四个位置每个位置让程序跑 30 帧记录输出的距离。distances [] for _ in range(30): frames pipe.wait_for_frames() aligned_frames align.process(frames) depth_frame aligned_frames.get_depth_frame() d median_distance(depth_frame, cx, cy) distances.append(d) avg sum(distances) / len(distances)用均值对比卷尺读数误差在 2% 以内说明整个链路工作正常。如果误差偏大优先检查对齐是否生效再检查测量目标是否偏离画面中心太远。这个流程每次更换场景都必须重跑相机角度一变深度误差特性就变。6.2 用 RealSense View 录 BAG 文件把现场搬回工位调试有些场景没法一直把相机带在身边RealSense 官方支持把实时视频流录制成 BAG 格式文件。调试时让程序从 BAG 文件里读数据完全复现现场情况不用反复跑现场。cfg rs.config() cfg.enable_device_from_file(test.bag) pipe.start(cfg)enable_device_from_file会把 BAG 文件当虚拟设备加载后面所有对齐、检测、测距逻辑完全不用改。我把这个做法当成标准调试流程的一部分每次现场拍完素材回到工位用 BAG 复现问题、调整滤波参数效率比在现场盲试高得多。从那以后我每次验收新相机或新场景都强制走一遍先录 BAG再跑标尺验证最后才接业务逻辑。这套流程能帮你在项目上线前就把深度测量的大部分坑填平希望帮到你。本文还有配套的精品资源点击获取