Realsense D455 + YOLO V5实战:深度相机与目标检测融合测距 做视觉的朋友应该都有这种感觉模型训练跑通只是开始真正有价值的是把它放进真实场景里跑起来。realsense D455深度相机配YOLO V5目标检测就是一个非常适合落地的组合深度相机给出空间距离YOLO V5给出目标类别和位置两者一结合就能做出不少“看得见、测得准”的应用。这个项目我从零开始搭了一套前前后后踩了不少坑这里把完整思路和步骤记录下来一是方便自己后面回顾二是给准备入门深度相机目标检测的朋友一个能直接参考的实操样本。整套方案解决的需求很直接普通摄像头告诉你视野里“有什么、在哪里”但不知道“离我多远”。室内机器人避障、人员接近预警、仓储盘点、智能看护这类场景光靠二维检测是不够的得拿到目标的真实距离这时候深度相机就派上用场。D455不仅能出RGB图像还能同步输出一帧一像素的深度图把YOLO输出的检测框中心坐标映射到深度图上就能直接读出目标距离。适合正在做相关课题的学生、刚接触深度相机的工程师以及想把目标检测从纯算法demo推向真实场景的人。下面就把这套方案的完整拆解、环境搭建、核心代码和踩坑记录都倒出来从硬件选型到最终跑通尽量讲透。1. 项目整体设计与思路拆解1.1 为什么选realsense D455而不是其他型号Realsense系列里D435/D435i用的也非常多我当时选D455主要看重三点第一D455的深度有效距离更远标称能达到6米左右室内走廊、工位这类场景完全够用第二D455左右目基线更长深度精度比D435高一些在3-5米这个范围内误差控制得更好第三D455内置IMU后面如果做相机自标定或者机器人底盘融合不用额外加硬件。当然如果只做桌面近景识别D435性价比更高但考虑到后续扩展我直接上了D455。D455用的是主动红外双目立体方案左右两个红外相机加上一个红外点阵投射器投射器在弱纹理环境比如白墙、桌面打上随机散斑帮助双目匹配算出深度。这就解释了一件事红外点阵投射器在工作时会有轻微噪声但不影响RGB图像和YOLO检测结果。1.2 YOLO V5在当前阶段依然能打YOLO系列每年都有新版本V8、V9都出来了但我这次选YOLO V5并不是因为它最新而是因为它在工程落地上最省心。YOLO V5的代码结构清晰模型导出成TorchScript和ONNX非常简单部署到嵌入式平台或者用OpenCV DNN推理都很方便。Ultralytics团队维护的仓库文档齐全遇到问题基本都能搜到答案。相比之下新版本虽然精度高一些但部分配套工具链还不够成熟踩坑成本高。实际测试下来YOLO V5s模型在D455的1280x720彩色流上做推理单帧耗时大概在15-25毫秒和GPU有关加上取流和对齐操作整体帧率能稳定在25-30 FPS左右基本能做到实时检测。如果追求更快还可以把输入分辨率降到640x640检测精度略微下降但帧率能上到40 FPS以上。这个平衡在机器人项目里还是很重要的。1.3 整体流程从像素坐标到空间距离整套系统的数据流大概是这样的D455同时输出彩色流和深度流深度流经过对齐模块rs.align转换到彩色相机的坐标系这样彩色图和深度图的像素坐标一一对应。接着YOLO V5从彩色图中检测出目标框拿到框中心点的像素坐标比如x634, y287再把这个坐标塞进对齐后的深度图调用depth_frame.get_distance(634, 287)就能读出这个点对应目标的实际距离单位是米。这个“对齐”是整个方案的核心如果深度图和彩色图没有对齐直接用彩色图上的检测框去深度图里取值拿到的一定是错位的距离。后面我会详细讲对齐的代码实现这里先记住一个结论对齐必须在深度图上进行而不是把深度图转换到彩色坐标系rs.align(rs.stream.color)就是这么干的。2. 环境准备与工具链搭建2.1 硬件清单除了realsense D455相机还需要一台带NVIDIA独立显卡的电脑我用的配置是GTX 1660 Super显存6GB跑YOLO V5s绰绰有余。如果没有显卡CPU也能跑但帧率会掉到个位数实时性就别想了。还需要一个USB 3.0接口D455输出1280x720x30的彩色流和深度流USB 2.0的带宽根本撑不住。我一开始插在USB 2.0口上取流直接报错换成3.0蓝色接口才正常。系统我用的是Ubuntu 20.04Windows也能跑但Linux对Realsense的支持更完善尤其是后面要装realsense-viewer调试工具、配置udev规则Linux下更顺手。当然Windows下用WSL也不算太折腾但这篇文章还是以Ubuntu为主。2.2 安装librealsense和pyrealsense2先装SDK。最简单的方式是添加Intel的官方仓库通过apt安装。注意不要自己从源码编译librealsense除非你需要修改底层代码否则官方仓库的release版本已经完全够用。安装命令如下sudo mkdir -p /etc/apt/keyrings curl -sSf https://librealsense.intel.com/Debian/librealsense.pgp | sudo tee /etc/apt/keyrings/librealsense.pgp /dev/null echo deb [signed-by/etc/apt/keyrings/librealsense.pgp] https://librealsense.intel.com/Debian/ubuntu $(lsb_release -cs) main | sudo tee /etc/apt/sources.list.d/librealsense.list sudo apt update sudo apt install librealsense2-dev librealsense2-utils装完之后插上相机终端运行realsense-viewer如果能看到画面说明驱动没问题。接着装Python绑定pip install pyrealsense2这里有个容易踩的坑有些教程会建议从源码编译pyrealsense2其实除非你需要最新的开发特性否则pip安装的版本和apt安装的SDK是配套的直接装就行。如果后面出现版本冲突再考虑统一升级到同一版本。2.3 配置YOLO V5运行环境YOLO V5官方推荐用PyTorch我装的是PyTorch 1.12配合CUDA 11.3。装PyTorch时记得选对CUDA版本否则即使有显卡也用不上GPU推理检测速度会慢得离谱。我用的安装命令是pip install torch1.12.0 torchvision0.13.0 --extra-index-url https://download.pytorch.org/whl/cu113接着克隆YOLO V5仓库并安装依赖git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt初次运行时会自动下载yolov5s.pt权重如果网络状况一般也可以手动下载权重文件放到yolov5目录下。到这里环境就基本齐了接下来先用官方自带的图片测试一下YOLO V5能不能正常推理。2.4 验证工具链先跑通再往下走建议先做一个快速验证不直接上相机而是随便找一张包含人、车或者猫狗的图片放到yolov5目录下运行检测脚本确认模型和GPU都正常python detect.py --source test.jpg --weights yolov5s.pt看到输出目录里生成了带检测框的图片说明环境没问题。这个步骤非常关键把问题前置否则后面一旦把相机和模型串在一起报错的时候很难分清是相机的问题还是模型的问题。3. 核心实操相机取流与深度对齐3.1 初始化相机并配置彩色流相机取流用pyrealsense2的pipeline配置相对简单。这里我踩过第一个坑D455的彩色传感器最高支持1920x1080但如果深度流和彩色流同时打开1080pUSB带宽容易紧张导致画面卡顿甚至掉帧。我的做法是统一用1280x720帧率30既保证分辨率又留出带宽余量。import pyrealsense2 as rs import cv2 import numpy as np pipeline rs.pipeline() config rs.config() # 启用彩色流和深度流统一分辨率和帧率 config.enable_stream(rs.stream.depth, 1280, 720, rs.format.z16, 30) config.enable_stream(rs.stream.color, 1280, 720, rs.format.bgr8, 30) # 启动pipeline并获取相机参数 profile pipeline.start(config) depth_sensor profile.get_device().first_depth_sensor() depth_scale depth_sensor.get_depth_scale() print(深度单位, depth_scale, 米/刻度)这里有个细节值得说明深度缩放比例depth_scale和单位有关默认情况下D455的depth_scale通常是0.001意味着深度图里每个数值刻度代表1毫米。后面计算距离时从get_distance()拿到的是浮点数米但如果直接读深度图的raw值记得要乘以depth_scale才是真实距离。3.2 对齐模块把深度图映射到彩色坐标系这是整个项目最重要的一步。D455的深度传感器和彩色传感器在硬件位置上是分开的视角也略有差异直接用深度图去读检测框坐标必然错位。解决办法是使用rs.align将深度帧对齐到彩色帧坐标系对齐后深度图的尺寸、视野和彩色图完全一致每个像素位置都一一对应。align rs.align(rs.stream.color) def get_aligned_frames(): frames pipeline.wait_for_frames() aligned_frames align.process(frames) aligned_depth_frame aligned_frames.get_depth_frame() color_frame aligned_frames.get_color_frame() return aligned_depth_frame, color_frame注意不用手动改变深度图的尺寸rs.align会自动处理好内参重投影。我一开始没搞懂这个模块的原理误以为对齐就是把深度图用cv2.resize放大到彩色图尺寸结果距离数据全是乱的。rs.align背后做的是基于相机内参的坐标变换不是简单缩放所以别自己动手resize。提示对齐处理会带来一点点额外的计算开销但相比整个深度计算流程来说几乎可以忽略实时性完全没有问题。3.3 把深度图可视化确认对齐效果对齐之后建议立刻把深度图可视化出来这一步能帮你快速判断对齐是否正确。深度图的原始数据是16位的直接显示会是一团漆黑需要对数值做归一化处理常用的是把距离在0.3米到3米之间的区域映射到0-255。代码如下def get_depth_visual(depth_frame, depth_scale): depth_image np.asanyarray(depth_frame.get_data()) # 将深度值转换为米 depth_in_meters depth_image * depth_scale # 截取0.3m ~ 3m范围避免远处噪声干扰 depth_vis np.clip(depth_in_meters, 0.3, 3.0) depth_vis (depth_vis - 0.3) / (3.0 - 0.3) * 255.0 depth_vis depth_vis.astype(np.uint8) return cv2.applyColorMap(depth_vis, cv2.COLORMAP_JET)把彩色图和深度图并排显示后观察画面边缘的物体是否都出现在同一位置桌椅、人的轮廓是否完全重合。如果边缘出现明显的偏移多半是对齐没生效回去检查rs.align的使用方式。3.4 采集数据并保存在做正式检测之前建议先写一个小工具从相机里采集若干RGB图像和对应的深度图像用于后续调试。我在这个阶段保存了大概50帧室内场景方便在不开相机的情况下反复调试YOLO推理和深度映射逻辑。保存时彩色图用cv2.imwrite即可深度图要保存成16位PNG不能直接存成jpg否则深度信息会丢失。color_image np.asanyarray(color_frame.get_data()) depth_image np.asanyarray(aligned_depth_frame.get_data()) cv2.imwrite(sample_color.png, color_image) cv2.imwrite(sample_depth.png, depth_image) # 16位PNG采集时注意避免相机快速移动否则画面运动模糊会造成深度值出现大片黑洞这类数据不适合作为调试样本。4. YOLO V5模型加载与推理实现4.1 加载模型的两种方式YOLO V5模型加载有两条路一是直接用torch.hub加载官方仓库的模型二是用YOLO V5自带的detect.py脚本。后者做的是离线检测不太方便实时接入相机数据流所以我的方案是前者把模型作为Python对象直接集成到自己的代码里。import torch # 自动从官方仓库加载YOLOv5s模型 model torch.hub.load(ultralytics/yolov5, yolov5s, pretrainedTrue) model model.to(cuda).eval()如果本地已经下载过权重可以指定权重路径避免每次运行都去检查网络model torch.hub.load(ultralytics/yolov5, custom, pathyolov5/yolov5s.pt)torch.hub加载过程会自动处理依赖但有个小坑如果当前目录下已经有一个yolov5文件夹或者之前装过其他版本的YOLO可能会出现模块冲突。遇到这种情况把当前目录下的yolov5临时改名或者用torch.hub.dir参数指定缓存目录即可。4.2 推理参数设置与检测框解析模型输入有三种常用尺寸640、736、1280。分辨率越高检测小目标能力越强但速度越慢。我实测在1280x720输入下使用model(img)默认的640推理尺寸已经够用因此下面代码里直接使用默认参数。def detect_objects(color_image): results model(color_image) detections results.pandas().xyxy[0] # DataFrame boxes [] for _, row in detections.iterrows(): boxes.append({ x1: int(row[xmin]), y1: int(row[ymin]), x2: int(row[xmax]), y2: int(row[ymax]), class_id: int(row[class]), class_name: row[name], confidence: float(row[confidence]) }) return boxes解析检测结果时最需要注意的是坐标类型Pandas的xyxy[0]返回的是相对原始输入图像尺寸的像素坐标单位是像素。当我们输入的是1280x720彩色图像时坐标就是1280x720坐标系下的值后续直接传给depth_frame.get_distance即可不需要再做缩放。4.3 类别过滤与置信度阈值室内场景下YOLO V5默认的80类检测往往会出现很多无关目标比如书架、杯子、椅子。如果项目只关心人和动物可以在解析时加一个类别白名单减少无效检测框干扰。置信度阈值默认是0.25但在深度映射场景里建议把阈值提高到0.4左右减少低置信度误检导致的错误距离读数。这个调优细节在演示demo里没什么感觉但在实际部署中低置信度的误检框配上错误的深度值会让整个系统看起来非常不稳定。allowed_classes [0, 16] # 0: person, 16: dog filtered_boxes [b for b in boxes if b[class_id] in allowed_classes and b[confidence] 0.4]5. 深度融合把距离测量接到检测框上5.1 从检测框中心点获取深度距离有了检测框坐标理论上只要把框的中心点传给get_distance即可。但在实际场景中直接取中心点并不稳妥如果目标中心刚好在纹理缺失的区域比如纯色衣服的胸口、反光桌面深度相机在这一点的深度值可能为0也就是无效数据。更稳的做法是在检测框内取一个区域做深度值的统计用中位数表示目标距离。def get_distance_roi(depth_frame, x1, y1, x2, y2): # 取检测框中心周围30x30像素区域 cx int((x1 x2) / 2) cy int((y1 y2) / 2) roi depth_frame.get_data()[cy-15:cy15, cx-15:cx15] roi_meters roi * depth_scale valid roi_meters[roi_meters 0] if len(valid) 0: return None return float(np.median(valid))用中位数而不是均值是为了避免个别异常深度点把距离值带偏。实测中一帧里偶尔会出现几个明显偏大或偏小的噪声点如果用均值噪声影响很明显中位数则稳得多。如果ROI范围内有效深度点太少说明相机可能在这个区域没有捕获到有效的红外反射这时候返回None提示用户而不是给一个错误距离。提示如果检测目标是细长物体比如水瓶中心点的深度值可能落在背景上导致距离错误。这种情况可以把ROI纵向拉长只取检测框中间偏下区域因为大部分物体底部更靠近支撑面深度数据更可靠。5.2 实时深度映射与可视化把上一步整合成一个循环取帧、对齐、检测、距离、画框。为了让效果直观可以把距离信息直接写在检测框标题上显示“person 1.2m”这样的标注。这里直接用OpenCV的putText即可。while True: # 读取对齐后的彩色和深度帧 aligned_depth_frame, color_frame get_aligned_frames() if not aligned_depth_frame or not color_frame: continue color_image np.asanyarray(color_frame.get_data()) # 运行YOLO检测 boxes detect_objects(color_image) for b in boxes: dist get_distance_roi(aligned_depth_frame, b[x1], b[y1], b[x2], b[y2]) label f{b[class_name]} if dist is not None: label f{dist:.2f}m else: label N/A cv2.rectangle(color_image, (b[x1], b[y1]), (b[x2], b[y2]), (0, 255, 0), 2) cv2.putText(color_image, label, (b[x1], b[y1] - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow(Depth YOLO, color_image) if cv2.waitKey(1) 0xFF ord(q): break pipeline.stop() cv2.destroyAllWindows()5.3 距离精度的实测误差我在3米范围内测试过D455的距离精度。把相机固定在三脚架上人站在1米、1.5米、2米、2.5米位置分别测试每个距离取50次测量的平均值。1米以内误差基本在1%以内2米左右误差约2厘米到了2.5-3米误差大概在5厘米左右。这个精度对大部分室内应用足够了但如果做3米以上的测量建议配一个标定板做误差补偿否则累计误差会慢慢变大。6. 常见问题与排查技巧实录6.1 相机枚举失败或图像花屏最常见的原因是USB接口带宽不足。D455对USB 3.0带宽要求很高如果接入的是扩展坞或者前置USB口经常会出现枚举成功但实时流中断的情况。排查方法很简单换一个主板背面的USB 3.0口直插同时保证线缆是USB 3.0规格。另外记得在Linux下检查一下权限sudo apt install librs-udev-rules装完规则后重新插拔相机一般权限问题就解决了。6.2 深度图大量黑色区域深度图黑色表示该处深度值为0也就是无效点。常见原因有目标距离太近小于0.2米超出D455最小工作范围、目标表面反光太强、或者红外投射器被遮挡。解决办法是调整相机距离或者用rs-enumerate-devices检查红外发射器是否正常。室内阳光直射环境下红外光会被强烈干扰导致大面积无效点拉上窗帘测试一下就能验证。6.3 YOLO检测正常但距离值跳动严重这个问题排查思路主要集中在对齐上。如果彩色图和深度图没有完全对齐检测框中心点取到的深度值来自背景物体距离自然乱跳。建议先把可视化调出来同时叠加显示彩色图、深度图以及检测框的可视化效果确认检测框是否真的框在目标的对应位置。如果框在正常位置但深度还是乱跳再检查ROI内有效深度点比例比例低于30%时换一个更靠近目标中心的区域或者适当扩大ROI尺寸。6.4 推理速度太慢先用nvidia-smi确认GPU在工作。如果GPU利用率接近100%但还是慢说明模型推理是瓶颈建议降低输入分辨率到640x640或者换更轻量的YOLO V5n模型。如果GPU利用率只有40-60%说明数据预处理或取流环节存在阻塞检查是否有同步等待时间过长的问题比如wait_for_frames设置不当导致帧率不匹配。我实际遇到过一次彩色流是30FPS深度流也是30FPS但两路硬件时钟略有差异偶尔会因为等待超时导致整帧丢弃换成硬件同步优先级配置后问题消失。6.5 常见问题速查表现象可能原因解决方案相机枚举失败USB带宽不足、权限问题换USB 3.0直连口安装udev规则深度图全黑距离过近、红外干扰拉远距离避免阳光直射对齐后图像错位rs.align用法错误确认使用align.process处理frames距离读数忽大忽小中心点无效深度、对应点错位用ROI中位数检查对齐效果检测速度低于10FPSCPU推理或USB掉帧启用GPU推理降低分辨率depth frame为None等待超时或配置出错打印帧元数据检查两路流参数7. 实操收尾与后续扩展思路整套流程跑通之后我在几个场景里分别做了测试一个是室内静态物体测距另一个是模拟人员靠近预警。静态物体测距误差很稳定人员靠近预警这个场景就比较有意思了因为人的走动会让检测框大小和位置快速变化如果get_distance_roi的ROI区域选得不好距离会偶尔跳一下。后来我把ROI改成动态的根据检测框大小自适应缩放距离输出就平滑多了这个小改动很值得记下来。另外有两点实际经验一是每次启动相机前最好调用reset_device做一次复位否则长时间拔插后偶尔会出莫名奇妙的取流错误二是深度图直接取中心点距离很多时候不靠谱尤其是目标刚进入画幅边缘时检测框还没有完全稳定此时输出距离的意义不大可以加一个简单的逻辑检测框连续存在超过5帧后再计算距离这样输出的数据基本是稳定可信的。这个项目做到这里只是第一步。后面我打算扩展的方向包括用YOLO V5训练自己标注的特定目标来做更精准的检测把深度信息喂给3D点云生成模块做更完整的三维目标估计以及把整个流程迁移到NVIDIA Jetson设备上做边缘部署。D455 YOLO V5这套组合潜力很大值得继续往下挖。如果你也正在搞类似的项目欢迎一起交流踩坑经验。