资讯详情

资讯详情

YOLOv5单目测距:原理、实现与误差分析

简介面向计算机视觉初学者与自动驾驶、机器人导航方向的开发者这份资源围绕YOLOv5目标检测与单目深度估计的结合展开解决在无额外深度硬件条件下估算场景物体距离的问题。压缩包共132个文件约13.87MB以34个Python脚本、40个YAML配置、16个pyc编译文件为主辅以Dockerfile、shell脚本、Markdown说明与少量图片和权重文件覆盖模型代码、预训练权重、训练与测试脚本、数据集配置等模块目录结构清晰便于按功能检索。已有3868人学习下载说明其在目标检测与三维测距交叉场景中具备一定参考价值。读者可据此理解YOLOv5的锚框预测与特征提取流程掌握将检测框与深度映射结果结合输出距离的思路并借助现成脚本完成环境搭建、模型训练与推理可视化为自动驾驶、无人机或智能监控中的距离感知实践提供可复用的工程起点。1. 单目测距一张图为什么能算出三维距离很多人第一次看到「YOLOv5 单目测距」这个组合直觉反应是怀疑一个摄像头、一张二维图像凭什么能反推出目标离你多少米毕竟人眼判断距离靠的是双目视差单目看起来天生缺了一半信息。但工程上它确实能跑而且跑得还不差——这背后的关键是把「像素坐标」和「物理世界坐标」用相机成像几何硬绑在一起再用一个已知的物理尺寸当锚点。换句话说单目测距不是玄学它是在做一道几何题已知相机焦距、已知目标在图像里的像素高度、已知目标的真实高度求距离。YOLOv5 在这里的角色是「把目标框出来」测距模块负责「把框的像素尺寸翻译成米」。这套思路在三维测距场景里被反复验证也是 yolov5 单目方案能落地到边缘设备的核心原因不需要深度相机、不需要标定板阵列一个普通 USB 摄像头加一段 Python 就能跑通。值得搞懂的地方在于它的误差来源非常集中搞清楚了误差从哪来你就知道什么场景能用、什么场景会翻车。2. 成像几何像素高度怎么变成物理距离2.1 针孔模型下的相似三角形单目测距的数学底座是针孔相机模型。把相机想象成一个暗箱光线穿过光心打在成像平面上物体在现实中的高度 H、在图像上的像素高度 h、相机焦距 f以像素为单位、物体到相机的距离 D四者构成一对相似三角形H / D h / f D (H * f) / h这个公式是整个方案的命根子。H 是目标的真实高度比如一个人按 1.7 米算一辆车按 1.5 米算f 是相机内参里的焦距像素值h 是 YOLOv5 检测框的像素高度。三个已知量里H 是「假设值」f 是「标定值」h 是「检测值」——误差就从这三个口子往里钻。常见的实现思路是先用棋盘格标定一次拿到 f之后 f 固定不变H 按目标类别查表h 每帧从检测框实时取。所以同一套代码测人准、测车可能偏因为 H 的假设和真实值对不上。2.2 焦距 f 到底怎么来的f 不是镜头包装上印的那个毫米数而是「焦距毫米数 ÷ 传感器像元尺寸」得到的像素焦距。很多人直接拿手机参数里的等效焦距去套结果测出来差一大截血泪经验就在这。正确的做法是用张正友标定法跑一组棋盘格图像OpenCV 的calibrateCamera会直接吐出内参矩阵其中fx、fy就是我们要的像素焦距。如果实在没法标定退而求其次可以用「已知距离反推 f」把目标放在一个已知距离 D0 处量出它的像素高度 h0反解 f D0 * h0 / H。这个方法精度一般但应急够用。import cv2 import numpy as np # 棋盘格标定拿到内参矩阵fx/fy 即像素焦距 criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) objp np.zeros((6 * 9, 3), np.float32) objp[:, :2] np.mgrid[0:9, 0:6].T.reshape(-1, 2) # 棋盘格物理坐标单位格 objpoints, imgpoints [], [] for path in image_paths: img cv2.imread(path, 0) ret, corners cv2.findChessboardCorners(img, (9, 6), None) if ret: objpoints.append(objp) corners2 cv2.cornerSubPix(img, corners, (11, 11), (-1, -1), criteria) imgpoints.append(corners2) # 返回的 mtx 里 mtx[0][0]fx, mtx[1][1]fy单位是像素 ret, mtx, dist, rvecs, tvecs cv2.calibrateCamera(objpoints, imgpoints, img.shape[::-1], None, None) fx mtx[0][0]这段代码的关键在最后一行mtx[0][0]就是 fx。注意objp里的坐标单位是「格」不是毫米所以标定出来的平移量单位也是格但 fx、fy 是纯像素量纲不受影响。标定图像建议 15 张以上、覆盖画面各个角落否则畸变系数估不准边缘目标的测距会系统性偏大。2.3 检测框高度 h 的取值陷阱YOLOv5 输出的框是(x1, y1, x2, y2)h y2 - y1。听起来简单但这里有个容易被忽略的细节框的高度包含了目标头顶到脚底的完整投影如果目标被遮挡、或者只露出上半身h 就偏小算出来的 D 会偏大——距离被高估。另一个坑是框的抖动相邻帧 h 差几个像素距离就能跳几十厘米。常见的平滑手段是对最近 N 帧的 h 做滑动平均或卡尔曼滤波代价是响应变慢。我一般会这么理解测距的实时性和稳定性是一对矛盾静态场景可以重滤波动态避障场景宁可要原始值也不要滞后。3. YOLOv5 检测与测距的耦合方式3.1 检测输出到测距输入的字段映射YOLOv5 的推理结果经过 NMS 之后每条检测包含类别、置信度、框坐标。测距模块真正需要的只有三样类别决定 H、框高度 h、以及可选的框中心点用于横向偏移补偿。耦合方式有两种常见写法一种是在检测循环里直接算距离代码紧凑但职责混在一起另一种是把检测结果序列化成列表测距模块独立消费方便单测和替换。工程上我更倾向后者因为测距逻辑一旦要加俯仰角补偿、多目标排序混在检测循环里会越来越难维护。# 检测结果 - 测距输入的结构化映射 CLASS_HEIGHT {0: 1.70, 2: 1.50, 5: 0.80} # 类别ID: 真实高度(米)按实际场景改 def detect_to_distance(detections, fx): results [] for det in detections: cls_id int(det[5]) conf float(det[4]) x1, y1, x2, y2 det[:4] h_pixel y2 - y1 if h_pixel 0 or cls_id not in CLASS_HEIGHT: continue H CLASS_HEIGHT[cls_id] D (H * fx) / h_pixel # 核心测距公式 results.append({ cls: cls_id, conf: conf, box_h: h_pixel, distance_m: round(D, 2) }) return sorted(results, keylambda r: r[distance_m]) # 按距离排序近的优先CLASS_HEIGHT是这张表决定了测距的「先验」类别 ID 必须和你的模型训练时的类别顺序一致改模型忘了改这张表是高频翻车点。h_pixel 0的过滤是防御性写法NMS 偶尔会吐出退化框。按距离排序在多目标场景里很实用避障逻辑通常只关心最近的那个。3.2 相机俯仰角带来的系统性偏差上面的公式默认相机光轴水平。但实际部署时摄像头往往有俯仰角目标不在画面中心时像素高度和真实投影高度之间会差一个余弦因子。俯仰角越大、目标越偏离中心误差越明显。补偿思路是把框中心点的纵向偏移也纳入计算用D H * f / (h * cos(θ) offset * sin(θ))这类修正项θ 是俯仰角。这个修正不是必须的画面中心附近误差可以忽略但如果你的摄像头装在两米高、向下俯拍不补这一项近处目标会系统性偏近。常见的实现思路是先不做补偿跑一版看误差分布是否随目标纵向位置漂移漂移明显再加。3.3 多目标下的距离排序与去重一帧里检测到五个人测距模块输出五个距离业务层往往只需要「最近的那个」或者「距离小于阈值的所有目标」。这里有个细节YOLOv5 的 NMS 已经去掉了重叠框但同一目标在不同尺度上可能被重复检出尤其小目标表现为两个框高度差异大、距离差一倍。处理办法是在测距之后再做一次基于距离和框 IoU 的合并保留置信度高的那个。别小看这一步避障场景里一个幽灵近距离目标会直接触发急停属于典型的「检测没问题、测距背锅」的排查难点。4. 避坑与排查测距误差从哪来4.1 现象同一目标距离跳变超过 30%原因通常是检测框高度抖动叠加 H 假设值与真实值不符。解决分两步先对 h 做 5 帧滑动平均压抖动再核对CLASS_HEIGHT里该类别的真实高度是否和场景匹配。如果目标经常弯腰、蹲下固定 H 本身就是错的这类场景单目测距天然不可靠得换思路。4.2 现象整体距离系统性偏大或偏小八成是 fx 用错了。检查标定得到的 fx 是否和实际分辨率匹配——标定用 1280×720推理却把图 resize 到 640×640fx 必须按比例缩放否则差一倍。另一个可能是标定图像太少畸变系数不准边缘区域偏差被放大。4.3 现象近处准、远处飘这是单目测距的物理宿命距离越远同样的像素误差对应的距离误差越大因为 D 和 h 是反比关系h 小的时候 D 对 h 极其敏感。远处目标 h 只有二三十像素抖 2 个像素距离就差好几米。解决方向不是调参而是接受精度上限或者对远处目标降级为「远/近」二分类不报具体米数。4.4 现象换了个摄像头精度全崩内参是跟摄像头绑定的换镜头、换分辨率、甚至改了对焦fx 都变了。血泪经验把标定参数和摄像头型号、分辨率一起写进配置文件换硬件强制重标别想着复用旧参数。4.5 现象夜间或逆光下测距乱跳本质是检测框质量下降h 不可信测距只是把检测的误差放大了。排查顺序永远是先看检测框稳不稳再看测距公式别一上来就怀疑几何算错了。检测不稳的时候测距模块加再多滤波也是治标。5. 设计取舍单目测距的边界在哪单目测距这套方案的价值不在于它多精确而在于它用极低的硬件成本换来了「够用」的三维距离感知。它的精度天花板由三个假设锁死目标真实高度已知且稳定、相机内参标定准确、目标完整可见。三个假设任何一个破了误差就会非线性放大。所以我在选型时的判断标准很朴素如果业务能容忍 10%20% 的相对误差、目标类别固定、相机位置固定单目方案性价比极高如果要求厘米级、目标形态多变、或者需要绝对尺度那就该上双目或深度相机别在单目上硬调。方案硬件成本精度适用场景单目 已知高度低10%20% 相对误差固定类别、中近距离双目立体中厘米级近距通用避障深度相机高毫米级近距精细操作还有一个常被忽略的取舍是「测距频率」。YOLOv5 在边缘设备上跑 1530 FPS测距本身几乎不耗时瓶颈全在检测。如果业务只要 5 FPS 的距离更新完全可以把检测降频、测距模块用上一帧的框做插值省下来的算力留给别的模块。这种解耦在嵌入式部署里很常见但代价是快速移动目标会有滞后得按场景权衡。验证方法上我习惯用「已知距离打点」把目标放在 1 米、2 米、3 米、5 米处各测 30 帧看均值偏差和方差画一条误差随距离变化的曲线。这条曲线比任何单点精度都有说服力它能直接告诉你这套系统在几米以内可信。从那以后我每次部署单目测距都强制先跑一遍这条误差曲线再决定业务阈值怎么设而不是拍脑袋写个「小于 2 米报警」。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →