简介面向计算机视觉初学者与自动驾驶、机器人导航方向开发者这份资源围绕YOLOv5目标检测与单目深度估计的结合展开解决无深度相机条件下如何从单张彩色图像推断物体距离的问题。包内共132个文件以34个py脚本、40个yaml配置、16个pyc编译文件为主辅以yml环境配置、sh启动脚本、md说明文档及预训练pt权重压缩包约13.87MB覆盖模型代码、训练与测试脚本、数据集配置等模块便于直接复现检测加测距流程。已有3868人学习下载说明其在同类实践中具备一定参考价值。读者可据此理解YOLOv5的特征提取与锚框预测机制掌握深度映射函数的学习思路并将检测框与距离估计结果结合用于辅助决策系统感知环境在控制硬件成本的同时提升系统便携性。1. 从一张二维框到真实距离YOLOv5 单目测距能解决什么摄像头拍到一个目标YOLOv5 给你一个二维框框里写着person 0.87。但业务方要的往往不是这个框而是「这个人离车头还有多少米」。三维测距听起来像要上双目、上激光雷达其实在不少场景里单目加一个已知尺寸的假设就能给出够用的距离估计成本几乎为零。这份 YOLOv5 单目测距的 Python 工程干的就是把检测框的像素高度换算成物理距离这件事检测负责「找到谁」几何负责「算多远」。适合做辅助驾驶预警、园区人流距离估计、机器人避障前置判断的开发者也适合刚学完 YOLOv5 想找个真实落地小项目练手的人。它不追求毫米级精度追求的是在普通 USB 摄像头上跑通一条从像素到米的完整链路。2. 工程结构与技术选型检测、标定、测距三段式2.1 目录怎么摆各文件管什么这类项目我一般会拆成四层模型推理、相机参数、测距几何、可视化输出。目录大致长这样具体文件名可以按自己习惯改但职责边界别混yolov5_mono_distance/ ├── weights/ │ └── yolov5s.pt # 检测权重 ├── configs/ │ └── camera.yaml # 焦距、目标真实高度、图像尺寸 ├── core/ │ ├── detector.py # YOLOv5 加载与前向 │ ├── calibrate.py # 焦距标定 │ └── distance.py # 像素高度 - 物理距离 ├── utils/ │ └── draw.py # 画框、写距离文字 ├── main.py # 主循环读帧 - 检测 - 测距 - 显示 └── requirements.txtdetector.py只关心把 BGR 帧喂进去、拿回xyxy框和类别distance.py只吃框高度和相机参数吐一个浮点米数main.py负责把两者串起来。这样拆的好处是换检测模型比如换 YOLOv8时只动detector.py测距公式一行不用改。常见做法是把相机内参放 YAML 而不是硬编码因为同一份代码经常要在不同摄像头上跑硬编码焦距是后期返工的重灾区。2.2 为什么选 YOLOv5s 而不是更大模型单目测距的误差主要来自几何假设和标定检测精度反而是次要矛盾。YOLOv5s 在 640 输入下普通显卡甚至 CPU 都能跑到可用帧率框的抖动对距离的影响远小于焦距估错。用 YOLOv5m 或 l 当然框更稳但帧率掉下来实时预警就变成幻灯片。我的取舍是先用 s 把链路跑通确认测距误差在可接受范围再考虑换模型。权重直接从 Ultralytics 的 YOLOv5 仓库拿yolov5s.pt即可工程里通过torch.hub或本地models.experimental加载都行下面用前者依赖少。2.3 依赖与运行环境requirements.txt里核心就几项版本不必锁死到小数点后但 torch 和 opencv 要给个下限torch1.8 torchvision0.9 opencv-python4.5 numpy1.21 pyyaml5.4装完直接python main.py --source 0就能开摄像头。第一次跑会下载权重网络不通就手动把yolov5s.pt放进weights/代码里判断本地存在就优先用本地。这一步是新手最容易卡住的地方别等到报错才回头找权重。3. 核心链路拆解从检测框到米数的完整计算3.1 检测器封装把 YOLOv5 包成一个干净函数detector.py的目标是让上层完全看不到 YOLOv5 的预处理细节。用torch.hub加载最省事import torch class Detector: def __init__(self, weightsweights/yolov5s.pt, conf0.4, iou0.45, deviceNone): # 优先本地权重避免每次联网 self.model torch.hub.load(ultralytics/yolov5, custom, pathweights, sourcelocal) self.model.conf conf # 置信度阈值 self.model.iou iou # NMS 的 IoU 阈值 self.device device or (cuda if torch.cuda.is_available() else cpu) self.model.to(self.device).eval() def infer(self, frame_bgr): # YOLOv5 hub 接口吃 RGB 或 BGR 都行内部会处理 results self.model(frame_bgr, size640) # xyxy: [x1,y1,x2,y2,conf,cls] return results.xyxy[0].cpu().numpy()conf设 0.4 是经验值太低会把远处小目标也框进来框高只有十几个像素算出来的距离噪声极大太高又漏检。iou保持 0.45 默认即可。size640是推理分辨率和训练一致改大能提升小目标召回但拖慢帧率。返回的xyxy每行前四个是框坐标第五个是置信度第六个是类别索引后面测距只用到框高度y2 - y1。3.2 焦距标定单目测距的命门单目测距的公式本身很简单距离 焦距 × 目标真实高度 / 像素高度。难的是焦距怎么来。严格做法是用棋盘格做相机标定拿内参但很多 USB 摄像头拿不到可靠内参工程里常用一个简化标定把已知高度的物体放在已知距离反解焦距。def calibrate_focal(known_distance_m, known_height_m, pixel_height): # f d * h_pixel / H_real return known_distance_m * pixel_height / known_height_m比如让一个身高 1.7 米的人站在 5 米处框高量出来 200 像素焦距就是5 * 200 / 1.7 ≈ 588。这个值写进camera.yaml。注意标定时的图像分辨率必须和运行时一致640×480 标出来的焦距拿到 1280×720 上用距离会整体偏一倍这是血泪经验。标定距离尽量选在实际工作距离的中段太近太远都会放大误差。3.3 测距模块一个除法背后的假设distance.py把公式落地并对异常框做保护def pixel_to_distance(box_h_px, focal_px, real_height_m): if box_h_px 1: return None # 框太小拒绝输出避免除出天文数字 return focal_px * real_height_m / box_h_px参数含义box_h_px是检测框像素高度focal_px是上一步标定的焦距real_height_m是目标类别的真实高度人取 1.7小汽车取 1.5 左右按类别查表。这里有个绕不开的假设目标高度固定。同一个人蹲下、侧身、被遮挡框高都会变距离就跟着跳。所以工程上一般对连续多帧的距离做滑动平均或者只在框高稳定时才输出。我一般会加一个 5 帧的中值滤波抖动肉眼可见地小一半。3.4 主循环把三段串起来main.py的骨架就是读帧、检测、逐框测距、画图import cv2, yaml from core.detector import Detector from core.distance import pixel_to_distance cfg yaml.safe_load(open(configs/camera.yaml)) det Detector(cfg[weights], confcfg[conf]) cap cv2.VideoCapture(0) while True: ok, frame cap.read() if not ok: break dets det.infer(frame) for x1, y1, x2, y2, conf, cls in dets: h_px y2 - y1 real_h cfg[class_height].get(int(cls), 1.7) dist pixel_to_distance(h_px, cfg[focal_px], real_h) if dist is None: continue cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) cv2.putText(frame, f{dist:.1f}m, (int(x1), int(y1) - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow(mono distance, frame) if cv2.waitKey(1) 0xFF 27: breakclass_height是个字典把类别索引映射到真实高度COCO 里人对应 0车对应 2按需补。focal_px来自标定。这段代码没有任何玄学跑不通通常是权重路径或摄像头索引的问题先确认cap.isOpened()再往下查。4. 避坑与排查单目测距最容易翻车的五件事4.1 距离整体偏大或偏小一倍现象所有目标测出来都稳定地差一个比例。原因标定分辨率和运行分辨率不一致或者焦距用了另一台相机的值。解决在camera.yaml里同时记录calib_width/calib_height启动时比对当前帧尺寸不一致就按比例缩放焦距别靠记忆。4.2 框高抖动导致距离跳变现象同一个人站着不动读数在 4.2 到 5.8 米之间乱跳。原因检测框本身逐帧抖动除以小像素高度会放大误差。解决对框高做滑动中值滤波或对距离做指数平滑框高小于 30 像素时直接不输出距离。4.3 远处目标测出几百米现象画面边缘的小框算出离谱距离。原因框高只有几像素除法分母趋零。解决设最小框高阈值并在pixel_to_distance里返回None上层跳过绘制别让异常值污染显示。4.4 换摄像头后全部失准现象同一份代码换台机器距离全错。原因不同摄像头视场角不同焦距不可通用。解决每台相机单独标定一次把焦距写进各自的配置文件用命令行参数选择配置别共用一份。4.5 CPU 上帧率低到无法预警现象没有独显时画面卡成 PPT。原因YOLOv5 默认输入 640CPU 前向很慢。解决降到 416 或 320 输入或改用 ONNX Runtime / OpenVINO 推理测距逻辑本身开销可忽略瓶颈永远在检测。5. 二次开发把单目测距改成能上业务的模块拿到这份工程后真正要改的通常不是公式而是「怎么让距离可信、怎么接进现有系统」。我一般按三步走。第一步把Detector换成支持批量或半精度推理的版本model.half()在支持 GPU 的机器上能明显提帧率测距部分完全不用动。第二步给距离加时间维度维护一个按目标 ID 索引的队列用简单 IoU 匹配前后帧的框对同一目标的距离做中值滤波输出dist_smooth而不是瞬时值。第三步把阈值判断抽成独立函数比如if dist 3.0: trigger_warning()这样业务规则和几何计算解耦调预警距离不用碰核心代码。验证方法上别只看画面上的数字顺眼。我的习惯是拿卷尺量三个固定距离比如 2 米、5 米、8 米让目标站过去记录读数算相对误差。单目测距在 2 到 8 米区间做到 10% 以内就算可用超过 10 米误差会快速变大这是几何决定的不是代码 bug。如果误差随距离系统性增大优先怀疑焦距标定距离选得太近。还有一个容易被忽略的改造点类别高度表。COCO 里「人」是一个类但大人小孩高度差很多统一用 1.7 米会让儿童的距离系统性偏大。实际项目里要么按场景限定只测成人要么引入宽高比辅助判断别指望一个常数打天下。表格里把每个类别的real_height和允许误差范围列清楚比散落在代码里的魔法数字好维护得多。类别真实高度(m)建议有效距离(m)备注person1.702–10儿童需单独处理car1.505–30按车型微调bicycle1.002–8骑行姿态影响大从那以后我每次接单目测距的活都强制先做一遍标定并当场用卷尺复核三个点确认误差曲线再写业务逻辑省得后面被「距离不准」反复拉扯。希望帮到你。本文还有配套的精品资源点击获取