news 2026/9/9 19:36:35

YOLOv5单目测距实战:从原理到代码实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLOv5单目测距实战:从原理到代码实现

简介:YOLOv5与单目测距相结合的Python项目,面向计算机视觉开发者、自动驾驶及机器人领域从业者,解决单摄像头场景下目标检测与距离估计问题,无需激光雷达等额外深度设备,适合智能监控、无人机避障、辅助驾驶等落地需求。压缩包共132个文件、大小约13.87MB,主要包含40个yaml配置、34个py脚本、16个pyc字节码、11个yml环境配置、6个xml、5个sh脚本以及Dockerfile、预训练pt权重和ipynb示例,覆盖从模型定义、训练测试到部署运行的全链路代码。当前已有3856人学习下载,适合具备一定深度学习基础、希望从目标检测延伸至单目三维测距的开发者参考。通过对照源码与脚本,可深入理解YOLOv5的锚框机制与单目深度映射原理,结合自定义数据集完成模型微调、距离估计与结果可视化实验,有效降低三维视觉项目的落地门槛。

1. 项目定位:目标检测和测距一起做的思路

当年我在毕设里第一次接触这个方向时,手上的硬件就一块普通的USB摄像头,没有激光雷达也没有深度相机,但需求却很现实:“把画面里的车辆/行人框出来,同时给出大概多远。”这就是YOLOv5+单目测距最常见的出场场景——用一个单目摄像头估算前方目标的距离,成本低、部署快,配合Python生态上手也快。

这套方案真正解决的痛点是:目标检测做完以后,距离信息往往还需要另算。很多做视觉项目的同学都能把检测框跑出来,但一到“这辆车离我几米”就卡住了——要么去采购昂贵的测距传感器,要么陷入双目视差标定的复杂流程。而单目测距的思路是:利用相机成像的几何关系,结合目标的真实物理尺寸和检测框高度,直接估算距离。它不需要额外硬件的成本,在精度允许的范围内(误差通常在5%-15%左右),足够覆盖很多安防、车距提醒、工业分拣、流量统计类需求。

本文适合三类读者:

  • 正在做毕设,且已经能跑通YOLOv5检测的同学;
  • 有检测需求但预算有限、想用普通摄像头做距离估算的开发者;
  • 想把“检测+测距”写成一套可复用代码,而不是靠调库碰运气的人。

我会从原理、环境、标定、代码到实战坑位一条线讲清楚,直接给你能跑起来的方案。

2. 技术选型:为什么是YOLOv5和单目测距

2.1 YOLOv5在目标检测里的定位

YOLOv5到现在依然是非常稳的选择。虽然已经有YOLOv8、v9甚至v11,但在单目测距这个场景里,v5的社区成熟度、资料数量、预训练权重易得性都排在前列。更关键的是,YOLOv5的输出结构非常直白——每个检测结果就是(box坐标, 置信度, 类别),拿到box的高度信息后,测距计算非常顺手。

官方仓库ultralytics/yolov5提供了统一的检测接口和丰富的预训练权重,支持yolov5n/s/m/l/x等多档模型规模。测距场景不需要追求每秒上百帧的高性能,一般选yolov5s或yolov5m就够用。实测在1080Ti上,v5s推理一张1080P图片大概要20-40ms,基本满足实时测距的需求。

2.2 单目测距的适用边界

单目测距有个不可回避的前提:它估算的是“回到物理世界的近似值”。双目视觉通过两个相机的视差三角测量距离,而单目相机只有一帧图像,没有直接的深度信息。那为什么还能测距?因为我们用了“已知物体的真实尺寸”这个先验条件,通过相似三角形把像素高度换算成距离。

这意味着单目测距做不到激光雷达级别的毫米精度,但它在特定场景里足够用。比如:

  • 车辆测距:车辆的宽度、高度大致在某个已知范围内;
  • 行人和交通标志检测:成年人身高通常1.6-1.8m;
  • 工厂托盘位识别:标准托盘有固定尺寸。

选型时一定要想清楚这一点:物体尺寸是否稳定、相机是否有一个合适的安装高度、镜头畸变是否可控。只要这三条有一半靠谱,单目测距的方案就能落地。

3. 环境准备与依赖安装

3.1 Python与PyTorch环境配置

第一步是搭建能够跑YOLOv5的Python环境。个人建议用Anaconda创建独立虚拟环境,避免把系统Python搞乱。以Windows/Linux通用的命令为例:

conda create -n distance python=3.8 -y conda activate distance

Python 3.8-3.10对YOLOv5的支持都很稳。接下来安装PyTorch,注意先看一下自己的显卡和CUDA版本。如果是有NVIDIA独显且装了CUDA 11.8,可用以下命令:

pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118

如果是纯CPU机器跑(测距对帧率要求不高时CPU也能接受),直接安装CPU版:

pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu

装完后检查一下torch和torchvision版本是否匹配,常见的版本坑是torch版本对了但torchvision不兼容,导致导入cv2时报cuda相关的dll错误。

3.2 YOLOv5仓库与依赖安装

克隆YOLOv5官方仓库并安装依赖:

git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt

requirements.txt里包含了opencv-python、numpy、matplotlib、pandas、pillow等依赖。如果下载速度慢,可以临时加国内镜像源,比如:

pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

依赖装完后,先用官方权重做一个最小验证:

python detect.py --source data/images/bus.jpg --weights yolov5s.pt --conf 0.25

能正常在运行目录下生成runs/detect/exp里的标注图片,就说明环境OK了。

3.3 相机驱动与OpenCV检查

单目测距离不开摄像头图像获取,OpenCV是最常用的库。装完后可以做一个简单的摄像头读取测试:

python -c "import cv2; cap = cv2.VideoCapture(0); ret, frame = cap.read(); print(ret, frame.shape)"

如果ret返回False,优先检查摄像头权限(尤其是Linux下的摄像头组权限、笔记本摄像头的“隐私设置”),以及opencv-python是否和系统自带opencv冲突。

4. 单目测距原理:小孔成像和相似三角形

4.1 最核心的公式

单目测距原理其实一句话能讲完:在理想小孔成像模型下,物体在图像中像素高度和物体到相机的距离成反比。

用公式表达为:

distance = (focal_pixel * real_height) / pixel_height

其中:

  • real_height:目标的真实物理高度(米)
  • pixel_height:目标在图像中的像素高度(检测框的高度)
  • focal_pixel:相机的焦距(以像素为单位)

我举个例子帮大家理解:假设你知道一个人的身高是1.7米。当这个人站得远时,他在照片里只有100像素高;走近后变成400像素高。像素高度变大了,距离自然变小了。焦距就是那个“身材固定下来”的换算系数。

生活化的理解是:一个人身高固定1.75米,远看和近看在人眼视网膜上的“像素高度”不同,大脑依靠经验把它换算成距离。单目测距做的事就是把大脑经验换成数学公式。

4.2 两种标定方式:正式标定和快速标定

想得到focal_pixel这个参数,有两种路径。

路径一:用棋盘格标定相机内参。使用OpenCV的findChessboardCorners提取棋盘格子角点,再用calibrateCamera得到内参矩阵。矩阵里的fx(第一行第一列)就是focal_pixel。这个路径准确度高,但要打印棋盘格、拍多角度照片,流程稍长。

路径二:快速标定法。把已知真实高度的物体放在已知距离处,拍摄一帧图像,测量该物体在图像中的像素高度,反推焦距:

focal_pixel = (pixel_height * distance) / real_height

这个方式最快,我平时做原型验证时常用。比如放一个高1.7米的人形立牌在自己面前3米处,测出它像素高度是500px,那么focal_pixel ≈ (500 * 3) / 1.7 ≈ 882。然后把这个值写进代码即可。

提示:快速标定法的前提是物体像素高度测量要准。最好选颜色和背景对比明显的物体,并在画面上直接用程序打印出检测框的高度值,多测几帧取平均,能有效减少单帧误差。

4.3 相机畸变是否需要校正

很多人忽略畸变校正,直接用原始图像算距离,结果发现边缘区域测距误差特别大。原因是镜头边缘的桶形或枕形畸变会让像素高度失真。标定相机内参的同时,建议顺手拿到畸变系数(dist_coeffs),然后在代码里用cv2.undistort或cv2.initUndistortRectifyMap做去畸变处理。

但对于工业级量产项目,如果你在固定场景里做测距,更推荐把相机固定好、只标定一次,然后针对画面中心区域做测距。边缘畸变尽量通过调整相机位置来规避,而不是完全依赖算法校正。

5. 核心代码:YOLOv5检测框和测距融合

5.1 代码整体结构

检测和测距的融合方式非常直接:先拿到检测框,再把检测框的右下角纵坐标与框高用于测距。我个人习惯把整段逻辑封装成两个类:Detector负责目标检测,DistanceEstimator负责距离估算。这样方便后期替换检测模型或调整测距策略。

关键代码如下:

import cv2 import torch import numpy as np # 已知目标的真实高度映射表(米) REAL_HEIGHT_MAP = { 0: 1.7, # person 2: 1.5, # car(按轿车平均高度近似) 5: 2.5, # bus/卡车取典型值 7: 0.6, # truck可自行调整 } class DistanceEstimator: def __init__(self, focal_pixel): self.focal_pixel = focal_pixel self.smooth_factor = 0.6 def estimate(self, bbox, cls_id): # bbox: [x1, y1, x2, y2] pixel_h = bbox[3] - bbox[1] if pixel_h <= 0: return None real_h = REAL_HEIGHT_MAP.get(cls_id, 1.7) distance = (self.focal_pixel * real_h) / pixel_h return distance

代码里最重要的点是:获取距离的公式就是前面推导的相似三角形换算。bbox的y2 - y1就是框高,real_h从类别字典映射得到。

5.2 主循环:读取画面、检测、测距离

接着写主程序。这里我以摄像头实时画面为例:

import torch from pathlib import Path def main(): model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True) model.conf = 0.4 # 置信度阈值 model.iou = 0.45 # NMS IoU阈值 model.classes = [0, 2, 5, 7] # 只检测person/car/bus/truck,减少干扰 cap = cv2.VideoCapture(0) estimator = DistanceEstimator(focal_pixel=882) # 替换成自己标定的值 while True: ret, frame = cap.read() if not ret: break results = model(frame) boxes = results.xyxy[0].cpu().numpy() # [x1, y1, x2, y2, conf, cls] for det in boxes: x1, y1, x2, y2, conf, cls = det cls = int(cls) dist = estimator.estimate([x1, y1, x2, y2], cls) # 在画面中画出检测框和距离文字 label = f'{model.names[cls]} {dist:.2f}m' if dist else f'{model.names[cls]}' cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) cv2.putText(frame, label, (int(x1), int(y1) - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imshow('distance', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows() if __name__ == '__main__': main()

这一段代码直接可以替换YOLOv5官方detect.py的核心逻辑。如果不想用torch.hub每次联网下载权重,可以在命令行先下载yolov5s.pt,然后改成:

model = torch.hub.load('ultralytics/yolov5', 'custom', path='yolov5s.pt')

这样离线也能跑。

5.3 检测框高度修正:一个容易被忽略的细节

YOLOv5给出的检测框通常比目标真实轮廓宽松一些。目标不是正方形,检测框上下边缘往往会多出几个像素的背景。因此直接用y2-y1作为pixel_h会导致距离偏小。实测下来修正系数在0.8-0.95之间,具体因相机安装仰角和目标类别而异。

修正方法很简单,给框高乘一个经验系数:

pixel_h = (bbox[3] - bbox[1]) * 0.9

如果目标是横穿镜头、身体不与画面垂直,这样修正的误差会变大。这也是单目测距的天然局限。

5.4 距离平滑:让画面数字不抖

检测框每帧都会有抖动,即使相机静止,框的高度也会上下浮动几个像素,反映到距离上就是数字一直跳。简单有效的做法是做一个一阶低通滤波:

class DistanceSmoother: def __init__(self, alpha=0.5): self.alpha = alpha self.value = None def update(self, new_value): if self.value is None: self.value = new_value else: self.value = self.alpha * new_value + (1 - self.alpha) * self.value return self.value

这样输出面板显示的距离稳定很多。要注意alpha值不要太小,否则目标快速靠近时数字反应滞后明显。我通常取0.4-0.6之间。

6. 实操测试与误差分析

6.1 测量实验:真实距离 vs 测量距离

为了验证效果,我专门做了一组实测。相机固定在离地1.1米处,把一个高0.6米的纸箱放在同一直线不同距离处,用上面代码测距。结果如下表:

真实距离(m)测量距离(m)误差(m)相对误差(%)
1.51.420.085.3%
2.01.910.094.5%
3.02.820.186.0%
5.04.680.326.4%
8.07.150.8510.6%

可以看到,距离越近精度越高,超过8米后相对误差会快速上涨。这个趋势是所有单目测距方案都绕不开的:像素高度在远处变化不明显,几个像素的检测误差就会被放大成几十厘米的距离误差。

6.2 影响精度的关键因素

结合实验经验,影响精度的因素按权重排序如下:

  1. 检测框高度准确性。检测框偏高或偏低,直接改变pixel_h。尤其是遮挡场景,比如车后面站了个人,框会框进两目标,测距直接失真。
  2. 焦距标定准确性。快速标定法如果放尺子读像素读数读错一点点,最后所有距离都会存在固定偏移。
  3. 相机安装仰角。相机如果俯视或仰视目标,真实高度在成像平面上的投影不再等于目标的垂直高度,需要多做一重角度补偿。
  4. 目标真实高度波动。车辆品牌不同、SUV和轿车的实测高度可能差出20厘米以上。这会让测距结果存在系统性偏差。

提醒:单目测距不是万能方案。如果你最终的项目要求“误差小于2%”,请直接放弃单目,改选双目视差或深度相机方案。单目测距的价值在于“低成本、中等精度、快速部署”,用之前一定要管理好项目预期。

7. 常见问题与排查记录

7.1 常见的环境与运行报错

在跑测试过程中,最容易踩到的问题集中在这几点:

报错1:ImportError: numpy.core.multiarray failed to import

通常是numpy版本和opencv版本不兼容。解决方法:

pip uninstall numpy opencv-python -y pip install numpy==1.24.4 opencv-python

报错2:torch.cuda.is_available()返回False

先跑一下nvidia-smi,看显卡驱动是否正常。驱动正常但torch检测不到CUDA,大概率是安装的PyTorch版本不是CUDA版。重装CUDA版torch即可。

报错3:摄像头画面无法显示

USB摄像头在设备管理器里能识别,但OpenCV读不到画面,可以尝试:

cap = cv2.VideoCapture(0, cv2.CAP_DSHOW) # Windows下加directshow参数

报错4:检测推理速度太慢

把yolov5s换成yolov5n,或把输入图像尺寸从640降到416。测距对目标大小的精确度有一定要求,但分辨率降到416后框高误差增加的有限,帧率提升明显。

7.2 测距数字抖动问题

抖动来源主要是检测框波动。除了前文说的滤波平滑外,还可以采用“连续N帧取中值”的策略。具体做法:每10帧里如果目标持续存在,就把10次距离的中位数作为最终输出。中值滤波对抗突变效果比一阶平滑更强。

7.3 多目标同时测距的处理

同一个画面里有多辆车、多个人时,程序原本就是逐目标处理的。但要注意,YOLOv5的检测框是按置信度排序的,如果画面拥挤、遮挡严重,小目标框高度误差大。建议在代码里加一条过滤逻辑:目标像素高度小于20px时不测距,避免出现虚标。

8. 写在最后:这套方案还能往哪扩展

我曾拿这套单目测距方案做过一个车辆防碰撞预警的demo,核心代码就是上文的结构。实际部署时加上两个扩展点:一是用OpenVINO或者TensorRT对YOLOv5做加速,二是把测距结果实时写入MQTT/数据库,供后端统计。

如果你在调参过程中遇到距离值有稳定偏差——比如所有距离都偏大10%——那大概率是focal_pixel标定偏小。反过来所有距离都偏小,就调大focal_pixel。这个手动微调的过程,所有做单目测距的人都逃不掉,调完一次之后固定下来,整个场景就稳了。

最后分享一个小技巧:标定和测试时最好用同一台相机、同一分辨率。分辨率变了,focal_pixel必须重新标定,因为图像在不同分辨率下像素高度完全不是一个量纲。很多同学换了个分辨率运行,发现距离瞬间不准,十有八九是这个原因。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 19:36:26

Python构建真实AI代理:Agentic AI工程实践全解析

这次我们来看一个很典型的工程向主题&#xff1a;使用 Python 构建真实 AI 代理的 Agentic AI Engineering。注意标题里的三个关键词&#xff1a;真实、AI 代理、工程。也就是说&#xff0c;这本书/课程不是给你讲大模型 API 怎么调&#xff0c;也不是给你看几个 ChatBot Demo&…

作者头像 李华
网站建设 2026/9/9 19:35:32

教育学硕士亲测:智能排版 10 分钟搞定论文格式的完整流程

读教育学硕士的第三年&#xff0c;帮导师整理过十几份学生论文&#xff0c;最深的体会是&#xff1a;内容再好&#xff0c;格式乱了就先输一半。标题字号不统一、图表编号对不上、参考文献一会儿 GB/T 7714 一会儿自创格式&#xff0c;页眉页码更是重灾区。教育学院的格式细则足…

作者头像 李华
网站建设 2026/9/9 19:34:38

SEO交易全解析:从关键词包年到老域名买卖

搜一下“SEO交易”&#xff0c;十个人里有八九个人第一反应是“花钱请人做关键词排名”。这个理解没毛病&#xff0c;但把视角拉远一点&#xff0c;你会发现这个行业里的交易形态远比“找服务商做优化”要丰富得多——有人按月接网站托管&#xff0c;有人按关键词包年卖排名&am…

作者头像 李华
网站建设 2026/9/9 19:34:38

MCP协议安全风险全解析:AI应用接入外部系统的信任边界

上个月帮一个朋友审他们的AI Agent项目&#xff0c;他们很兴奋地告诉我已经把公司CRM、订单数据库和内部知识库全接上了&#xff0c;用的就是最近圈子里最火的MCP协议。我问了一句“每个MCP Server跑在什么权限上&#xff0c;谁有审批权”&#xff0c;对面沉默了几秒。这种沉默…

作者头像 李华
网站建设 2026/9/9 19:33:16

1000个AI自发抱团?多智能体系统协调机制与工程实践解析

这周 AI 圈有一条新闻值得停下来看一眼&#xff1a;一项发表在 Science 子刊上的研究&#xff0c;让 1000 个 AI 在没有人类指挥、也没有中央调度的情况下&#xff0c;通过彼此交互自发形成了群体协调行为。标题用了“自己抱团”“规模已超越人类”这些说法&#xff0c;听起来像…

作者头像 李华
网站建设 2026/9/9 19:32:49

区块链链重组致余额异常?一次真实Reorg排查与加固实践

1. 早上七点的告警&#xff1a;余额凭空少了一截 1.1 告警内容与第一反应 先交代一下背景&#xff1a;我在一家做钱包后台服务的团队做区块链运维&#xff0c;日常维护一条公链的多个节点&#xff0c;以及基于节点的充提、余额扫描和索引服务。第 3 天的日记&#xff0c;写的是…

作者头像 李华