
简介基于YOLOv5与Intel RealSense D455深度相机实现的单目测距系统源码包面向计算机视觉开发者、无人机/机器人避障及智能交通测距场景适合需要快速验证单目测距方案的中高级学习者。项目运行入口为realsensedetect.py基于YOLOv5-3.1源码构建需特别留意训练自定义模型时必须使用3.1版本源码5.0及以上版本源码训练的权重无法直接调用这一兼容性约束对后续模型替换至关重要。包内共47个文件压缩包约17.76MB包含18个Python脚本覆盖检测、训练、测试、模型导出与工具函数、8个YAML模型配置yolov5s/m/l/x系列、9个pyc编译文件、预训练权重yolov5s.pt、Dockerfile部署文件、requirements.txt依赖清单、tutorial.ipynb演示笔记及mp4运行效果视频等目录按code/inference/weights/models/utils划分便于按模块检索与二次开发。已有246人浏览学习适合希望快速搭建单目测距原型、研究YOLOv5与RealSense融合方案的开发者也可作为物体检测与距离估计结合的课程项目参考。1. 单目测距和D455这对组合看起来矛盾实际是两条路“单目测距”和D455摆在一起第一眼确实矛盾。D455的深度由主动红外立体视觉算出本身就能给毫米级深度图压根不是单目可在基于yolov5realsense-D455的单目测距系统里设计思路恰恰是把D455当“一只眼睛”用只在RGB图上跑yolov5检测、按几何关系估算距离深度流只用来做校验和兜底。这样做的好处是算法不依赖昂贵深度硬件换任何USB工业相机也能复现。适合手里握有D455、想跑通“检测测距”完整链路的工程师也适合做机器人避障、安防抓拍、料箱定位这类近中程感知的人。看明白标题里这条“歪路”后面工程的选型和坑位就好理解了。2. 系统拆解YOLOv5负责“看见”D455负责“量距离”2.1 单目测距的前提先知道目标是谁、在图上占多宽单目测距的数学基础非常简单就是小孔成像模型。空间里一个宽度为W的物体投影到感光元件上像素宽度w和物距Z满足关系式Z fx * W / w。其中fx是相机内参中的焦距单位是像素。这个公式里没有任何深度传感器参与你只需要知道物距物体的真实宽度以及它在图像里占了多宽。但这里有个绕不开的病态问题同一张图小物体放近处和大物体放远处成像结果可能完全一样。也就是说单目测距必须“先知道目标是谁才能知道目标多大”。这正是yolov5在这个系统里的第一重作用——它不只是画个框而是给这个框贴了个类别标签。类别决定了你去查哪个实测宽度行人按肩宽算、轿车按车宽算、两轮车按车把宽算。分类错了距离就差出一两米。我一般倾向用“宽度”而不是“高度”做分母。原因很实际检测框在竖直方向经常截断行人没框到头顶、车轮没框到底边都常见高度差5个像素就会让距离读数明显波动而横向框通常是贴着目标两侧的px宽度更稳定。实在拿不准时宽度和高度都算一遍谁的分母像素更多、更稳就用谁。2.2 D455在系统里的真实角色RGB来源 深度兜底D455不是单目相机它是双目IR投射器的组合基线95mm比老款D435长了一倍中近程深度精度更好推荐工作范围大概在0.4m到6m。它自带一路RGB传感器物理分辨率1280x800和深度传感器是两套独立模组。关键问题在于这两个模组的光心不重合、视场角也不一样RGB约90度x65度深度约87度x58度。所以同一个像素坐标下RGB看到的物体和深度看到的物体并不天然对齐后面章节里rs.align不是可选项是必须项。这套系统里D455是双重身份。第一重是当普通RGB相机用输出彩色帧给yolov5做检测走纯单目测距链路这样做的好处是迁移性好以后把D455换成普通工业相机测距算法不用动。第二重才是深度本色拿深度图校验单目估算。单目在目标倾斜、宽度猜错时会产生系统性偏差深度流能把这个偏差拉回来但不能简单平均得按有效深度做信任切换。两条测距路径的特性差别很大选哪条要看场景维度单目估算D455深度适用范围0.2m到十米以上都能算0.4m-6m精度较好依赖条件相机内参 目标真实宽度深度图质量与表面材质误差特征目标侧斜时系统性偏大黑色/反光目标出现空洞硬件要求任意USB相机D455或同类深度相机实际项目里两条路都要跑单目保证连续性深度保证绝对精度合流策略放到第4章细说。2.3 YOLOv5选型理由推理快、部署生态熟、后处理源码可改选yolov5不是因为“最新”而是因为这套方案要解决的是工程问题不是模型精度竞赛。首先源码可读性强检测头和NMS都在工程目录里想改输入输出接缝直接改repo不用把一个黑匣子模型当魔法供着。其次部署生态成熟yolov5对ONNX、TensorRT的导出流程已经很顺D455拿到Jetson或x86工控机上跑导出转模型是成熟套路。再有就是训练链路完整如果检测目标不是COCO那80类要自己标注数据重新训练时数据集格式、入口脚本、超参数配置imgsz、batch、hyp都是现成的改造量小。这类源码包的常见做法是把yolov5官方repo完整拉下来在检测脚本里加一个realsense输入口再外挂一个测距函数。不要重写模型把D455摄像头的输出对齐进yolov5的输入这才是务实的集成路线。模型权重和检测头都可以直接用官方预训练项目真正要写的是相机初始化、像素对齐和距离计算这三段代码。3. 从零搭环境驱动安装、依赖清单与第一帧画面3.1 依赖三件套pyrealsense2、OpenCV、YOLOv5工程Python版本建议固定在3.8。yolov5官方依赖在3.8上验证最充分pyrealsense2在PyPI上也有这个版本的wheel不用本地编译。先建一个干净环境避免和机器上其他项目互相污染conda create -n yolod455 python3.8 -y conda activate yolod455 pip install pyrealsense2 opencv-python numpy pillow cd ~/work git clone https://github.com/ultralytics/yolov5 pip install -r yolov5/requirements.txt几个依赖的职责要分清。pyrealsense2是Intel官方的Python绑定装完它就不需要单独装librealsenseLinux下如果插上D455读不到设备先把SDK里的99-realsense-libusb.rules复制到/etc/udev/rules.d/再执行sudo udevadm control --reload-rules然后重新插拔一次。opencv-python用非contrib版就够标定、画框、读视频全是它的活。yolov5的requirements.txt会自动装torch、torchvision等所以前面手动安装只装realsense和opencv这类基础库避免版本要求和yolov5的依赖打架。有NVIDIA显卡就在装torch前先装好对应CUDA版本的wheel没有显卡就等pip装CPU版检测速度会慢一些但流程不受影响。3.2 相机初始化与深度对齐D455的像素不是默认对齐的pyrealsense2里所有操作都围绕pipeline对象展开。D455要同时开深度和彩色两路流然后立刻创建对齐器。这里有个新手极易忽略的点wait_for_frames只能保证深度帧和彩色帧时间上相近不能保证空间是同一个像素坐标系必须用rs.align处理import cv2 import numpy as np import pyrealsense2 as rs 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, 800, rs.format.bgr8, 30) profile pipeline.start(config) align rs.align(rs.stream.color) # 深度对齐到彩色坐标系 frames pipeline.wait_for_frames() aligned_frames align.process(frames) depth_frame aligned_frames.get_depth_frame() color_frame aligned_frames.get_color_frame() color np.asanyarray(color_frame.get_data()) depth np.asanyarray(depth_frame.get_data()) print(color.shape, depth.shape, depth.dtype)参数不能随手填说三个关键的。z16表示深度帧是16位无符号整数单位是毫米值0代表测不到深度流1280x72030是D455的满分辨率CPU和USB带宽压力都不小如果和RGB同时满跑USB3.0下勉强能撑住USB2.0基本刷不出连续帧彩色流1280x800是D455 RGB传感器的原生物理分辨率检测框坐标直接在这个分辨率下使用不需要缩放。align rs.align(rs.stream.color)这行做的事是利用出厂标定好的内外参把每个深度像素重投影到彩色相机坐标系下再重采样成与彩色图同尺寸的深度图。只有经过这一步后面depth_frame.get_distance(x, y)里的(x, y)才和检测框中心是同一个像素含义。不做这步深度图和彩色图FOV不同、光心不同取出来的深度值整体错位后面所有校验都是白做。3.3 用YOLOv5跑出第一帧检测两种加载方式有了彩色帧先确认检测链路能跑通。yolov5官方repo自带的detect.py可以直接用它会调用OpenCV读相机但问题在于D455不是标准UVC设备OpenCV的cap.read读不到深度通道所以这套系统里一般不直接用detect.py的--source 0而是写脚本把realsense的color帧喂给yolov5模型对象。脚本里加载模型最省事的方式是torch.hub它会自动拉取并缓存yolov5仓库代码import torch model torch.hub.load(ultralytics/yolov5, custom, pathyolov5s.pt, force_reloadFalse) model.conf 0.5 model.iou 0.45 model.max_det 20 results model(color) # color 是前面 realsense 输出的 BGR ndarray boxes results.xyxy[0].cpu().numpy() # 已是 NMS 后的结果conf和iou是两个最值得调的参数。conf太低背景会被误检成长条目标直接污染后面接的距离conf太高远处小目标又被丢掉导致测距在远段没有输出。室内近中程任务我一般放conf 0.5、iou 0.45室外远段降到0.35试试。results.xyxy[0]已经是yolov5工程封装好的后处理输出每一行的列含义是[x1, y1, x2, y2, conf, cls]到这步NMS已经做完了比自己手写后处理省事得多。这也是“源码包”这个字的含金量所在官方repo把这些都做好了我们只改输入输出的接缝。注意model(color)传的是BGR的numpy数组yolov5的接口内部会再做一次BGR到RGB的转换不用自己操心。到这里第一帧带检测框的画面就能画出来了但还只是“看见”距离还没有接上。4. 落到代码检测框到距离的两种算法与必调参数4.1 先标定不然公式里的焦距全是玄学单目测距公式里有个fx焦距像素单位最怕有人拿FOV标称值硬算。D455的RGB FOV确实标称90度但每台设备出厂装配误差、镜头畸变都不完全一样直接按1280/2 / tan(45°)640代进去1米处就能差出几厘米到十几厘米对测距项目来说不可接受。用棋盘格做一次张氏标定20张图是底线CHECKERBOARD (7, 10) # 棋盘格内角点数按你打印的棋盘改 objp np.zeros((CHECKERBOARD[0] * CHECKERBOARD[1], 3), np.float32) objp[:, :2] np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2) obj_points, img_points [], [] for i in range(20): frames pipeline.wait_for_frames() color np.asanyarray(frames.get_color_frame().get_data()) gray cv2.cvtColor(color, cv2.COLOR_BGR2GRAY) ret, corners cv2.findChessboardCorners(gray, CHECKERBOARD, None) if ret: obj_points.append(objp) img_points.append(corners) ret, K, dist, rvecs, tvecs cv2.calibrateCamera( obj_points, img_points, gray.shape[::-1], None, None) fx K[0, 0] print(fx , fx, 畸变 , dist.ravel())标定时有个细节彩色流必须和后面做检测时用的是同一路流、同一个分辨率。如果标定用1280x800后面检测又临时降到640x480fx必须重新标或者按分辨率比例缩放后使用不能直接沿用。这也是很多人“明明标定过了距离还是偏”的根本原因。标定完把K和dist存成numpy文件检测循环里启动时一次性读入。提示标定结果里的fx大约在640上下浮动如果偏差超过5%先查棋盘格角点数填没填对再查是不是用了缩放后的图像。4.2 算法一纯单目测距公式与目标宽度表拿到yolov5的检测框后单目测距的核心就一行代码def mono_distance(box, real_width_mm, fx): x1, y1, x2, y2 [int(v) for v in box] pixel_w max(x2 - x1, 1) return (fx * real_width_mm) / pixel_w逻辑是一个相似三角形目标在传感器上占的像素宽与距离成反比已知真实宽度和焦距反推距离。这是“单目”两个字最真实的含义——不靠视差靠假设目标物理尺寸。那real_width_mm从哪来检测的类别已经给了提示。常用的参考值如下目标类别典型宽度mm备注行人500肩宽目标正对镜头时轿车1800车宽正面或背面视角卡车2500车宽两轮车700车把宽度交通锥300底面直径这张表的数值决定整个测距的“假设”换场景必须自己量。见过一个翻车案例把行人宽度设成400mm3米外误差直接偏了20%。所以宽度这栏宁可量五次取中位数也别从网上随手搜一个就填。另外要注意的是这个公式只在目标平面垂直于光轴时成立目标一旦侧转像素宽收缩距离就会系统性偏大这个坑在第5章专门讲。4.3 算法二对齐深度图直接取距离D455的深度路径不需要猜目标宽度深度图本身就是距离。但“从深度图里取一个点”是最容易踩坑的写法——中心点恰好落在目标边缘、或者落在被遮挡的背景上一个点就能偏出几十厘米。实践上我会在检测框的中部取一个感兴趣区收集有效深度后取中位数def depth_median(depth_frame, box, valid_min0.2, valid_max8.0): x1, y1, x2, y2 [int(v) for v in box] w x2 - x1 h y2 - y1 x1 x1 w // 4 x2 x2 - w // 4 y1 y1 h // 4 y2 y2 - h // 4 vals [] for v in range(x1, x2, 3): for u in range(y1, y2, 3): d depth_frame.get_distance(v, u) if valid_min d valid_max: vals.append(d) if not vals: return None return float(np.median(vals))get_distance返回值单位是米注意和z16原始数据毫米区分别换算错。取中位数而不是均值是因为检测框内往往混入几颗前景树叶、栏杆或其他背景噪声点均值会被几个离群大数拉飞中位数扛得住这种污染。收缩到中间50%区域是为了避开检测框边缘那圈最容易包含背景的像素。valid_max给到8米是因为D455在8米外深度值已经不可靠与其给一个没把握的数不如直接判无效交给单目路径兜底。4.4 两条路径合流深度优先但要留退路两条路径各有各的失效场景单目在目标倾斜时偏大深度在黑色或反光表面拿不到值。合流策略我采用的是“有有效深度时以深度为主没有时回退单目”再加一阶低通滤波去抖ema_dist None alpha 0.3 # 新值权重越小越平滑 for frame in frames: # 循环里先做align取出depth_frame和color略 results model(color) for box in results.xyxy[0].cpu().numpy(): d_mono mono_distance(box, real_width_mm, fx) d_depth depth_median(depth_frame, box) if d_depth is not None: d 0.7 * d_depth 0.3 * d_mono else: d d_mono if ema_dist is None: ema_dist d else: ema_dist alpha * d (1 - alpha) * ema_dist print(mono%.2f depth%.2f final%.2f % (d_mono, d_depth, ema_dist))这个合流权重不是死的。目标近小于1米且表面是漫反射材质时深度几乎总是准的0.7/0.3可以偏到0.9/0.1目标远大于4米时深度开始变毛糙单目反而是连续稳定的估计权重就该反过来。alpha设0.3时响应快且能压住大部分跳变如果输出要直接给控制回路可以再降到0.1代价是目标快速接近时读数会滞后半拍左右。5. 避坑D455YOLOv5落地最常踩的五个坑5.1 深度和彩色不对齐坐标偏了半个人现象RGB图里目标明明在图像左侧按检测框中心去取深度结果深度落到了右侧背景上距离一下变成十米开外。原因D455的深度模组双目IR投射器和RGB模组是两套独立硬件光心不同、FOV不同。wait_for_frames只保证时间同步不保证空间对齐。深度图和彩色图在没对齐前是两个像素坐标系直接取坐标必然错位。解决每次取帧后必须用rs.align(rs.stream.color).process(frames)处理。对齐后的深度帧尺寸等于彩色帧像素坐标可以直接一一对应。很多人发现depth.shape和color.shape对不上就怀疑驱动坏了其实是对齐没做。这个坑是最常见的首个报错点先查它。5.2 USB带宽不够报“Frame didn’t arrive within 5000”现象程序跑几秒到十几秒后突然报错Frame didn’t arrive within 5000或者帧率掉到个位数检测画面像幻灯片。原因D455同时出深度彩色两路流带宽占用大。插在USB2.0口上或者经过扩展坞、USB Hub带宽喂不满pipeline取不到新帧就抛超时异常。解决插机箱背板原生USB3.x口别经hub和扩展坞同时把帧率降到15fps或者分辨率降到640x480。血泪经验深度流和彩色流都开满分辨率跑哪怕在原生USB3.0口上也偶尔掉帧降一档分辨率立刻稳定。修改方法就是config.enable_stream里改成640x480和30别的代码不用动。5.3 检测框抖动距离读数跟着跳舞现象目标静止不动打印出来的距离在±0.3米范围跳让人一度以为是相机坏了。原因yolov5的检测框宽受置信度波动影响同一目标相邻几帧可能差2到5个像素。单目公式里距离和像素宽是反比关系分母差5像素近距离时能造成几十厘米的偏差。解决不要在原始box上直接算距离先把box宽或距离本身做一阶低通滤波EMA再输出更稳的办法是像4.3那样在深度图上取检测框中心邻域的中位数深度值的帧间一致性比检测框宽好得多。顺带可以把NMS的iou阈值调高一点框会更贴目标抖动更小。5.4 目标斜停或侧身单目距离系统性偏大现象目标正对镜头时测距很准稍微偏一点角度比如轿车斜停、行人侧身距离读数突然变远而且这个偏差是固定的不是随机抖动。原因单目测距假设被测边和目标朝向垂直。目标旋转后投影到图像上的那一边收缩了像素宽变小距离公式算出来就偏大。这不是噪声是系统性的几何偏差。解决量宽度时优先用不随姿态变化的尺寸行人用肩宽而不是身高车用正面视角的车宽或者在代码里加一个偏航角估计按第6章的公式校正。预算不足时有个土办法只认正面目标测到侧面就信深度、不信单目。5.5 黑色吸光和玻璃反光深度图空洞现象黑色轮胎、深色外套、玻璃幕墙区域的深度值是0或者数值剧烈跳变中位数过滤后返回None合流逻辑被迫全程走单目。原因D455用主动IR投影辅助立体匹配。黑色物体把IR吸收掉镜面把IR反射走左右相机都找不到匹配点深度自然算不出来。解决开启深度后处理滤镜常见做法是先时间滤波再空间滤波temporal rs.temporal_filter() spatial rs.spatial_filter() depth_frame spatial.process(temporal.process(depth_frame))两个滤镜对CPU有额外开销实时性吃紧时只开temporal它对时间维度的空洞填补最明显。再配合4.3的中位数区域取法能救回大部分空洞区域。如果这块区域还是全0就按设计回退到单目估算不要强行插值。5.6 快速自查表症状优先排查一句话修法检测框坐标和深度对不上align是否调用用rs.align(rs.stream.color)处理频繁USB掉帧报错端口和分辨率原生USB3.x 降到640x480距离读数抖动box宽和滤波EMA平滑或取深度中位数远段距离系统性偏大目标朝向换不随姿态的尺寸或角度校正深度值大面积为0表面材质深度滤镜 单目回退6. 进阶验证误差曲线、角度修正与深度兜底6.1 用卷尺把误差曲线拉出来上手后第一件事不是调参是验证。把相机固定好在地上用卷尺标记0.5米到3.0米每隔0.25米放一次目标分别记录单目估算和深度输出for d_real in [0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 2.5, 3.0]: # 每个距离放一次目标按键记录 d_mono mono_distance(box, real_width_mm, fx) d_depth depth_median(depth_frame, box) print(d_real, round(d_mono, 2), round(d_depth, 2))把这组数据画成曲线你会看到两条规律近处深度准、远处单目更连续误差最大的点往往对应目标朝向不对或检测框截断。这张曲线图决定了后面的回退点、EMA系数甚至合流权重比任何理论推导都有说服力。6.2 角度修正与深度兜底如果目标朝向确实约束不住可以做一阶修正。设目标朝向与光轴夹角为θ真实距离近似为d fx * real_width / (pixel_width * cos θ)。θ可以从目标历史轨迹的方向粗估或者干脆按类别给一个固定朝向假设。这个修正公式在±30度内能把偏差压回来一半左右超过45度就别指望了老老实实信深度。深度兜底逻辑我现在固定成这样单目和深度都在时1米内信深度3米外信单目中间按有效深度加权深度空洞出现时整段回退单目不做空洞插值。这套系统做完最值钱的不是模型权重而是那条“假设—测量—验证”的标定链。我现在拿到类似的源码包第一件事是重建环境、标定、拉误差曲线三步走完再谈调参。硬件误差往往比模型误差更早暴露把D455当普通RGB相机用、把深度当验证器这套组合的下限很高。希望帮到你。本文还有配套的精品资源点击获取