☰
基于YOLOv5的单目测距系统:从像素到距离的毕业设计实战
2026/10/5 19:08:01 网站建设 项目流程

简介:本资源为基于YOLOv5的单目测距系统完整毕业设计项目,面向计算机相关专业正在做毕设、课程设计或期末大作业的学生,以及需要项目实战练习的学习者。项目经导师指导并认可,获得98分评价,可直接作为毕设使用。压缩包共78个文件,约215.32MB,以Python源码、YAML配置、Shell脚本、XML工程配置、JPG图片、MP4演示视频、Dockerfile、PyTorch权重文件及说明文档为主,涵盖模型训练、推理检测、距离测算与部署脚本等模块。资源包含全部项目源码与数据,经过严格调试,确保可运行,读者可据此掌握YOLOv5目标检测与单目测距的完整实现流程,理解权重加载、数据配置、检测脚本与测距逻辑之间的衔接方式,并借助演示视频与配置文件快速复现实验环境。目前已有180人学习,适合需要完整项目方案与排错参考的读者。

1. 单目测距这套方案,为什么成了毕业设计里的性价比之王

单目测距这件事,说白了就是只用一个普通摄像头,靠图像里的像素关系反推出目标离你有多远。它不像双目或者激光雷达那样需要额外硬件,一台笔记本加一个 USB 摄像头就能跑,这对预算有限、时间紧张的毕业设计来说几乎是刚需。而 YOLOv5 负责把画面里的目标框出来,测距模块再根据框的位置和相机参数算出距离,两者一拼,就是一个完整可演示、可写论文、可答辩的系统。这套组合之所以在毕业设计里反复出现,核心原因是它把深度学习检测和几何测距两条线都覆盖了,工作量够、创新点好写、演示效果直观。适合谁?适合做计算机视觉方向、嵌入式 AI 方向、机器人感知方向的本科生,也适合想快速搭一个测距 demo 验证想法的工程师。你拿到一份「基于 YOLOv5 的单目测距系统源码加全部数据」的压缩包,真正要搞清楚的不是怎么解压,而是这套东西的测距逻辑站不站得住、数据能不能复现、参数怎么调才不飘。

2. 单目测距的几何底子:从像素坐标到真实距离

2.1 针孔模型和相似三角形,测距的根就在这

单目测距不是玄学,它的数学基础是针孔相机模型。真实世界的一个点,经过镜头投影到成像平面,满足相似三角形关系。设目标真实宽度为 W,在图像里占的像素宽度为 w,相机焦距为 f(单位是像素),目标到相机的距离为 D,那么有:

D = (W × f) / w

这个公式就是整个测距系统的命根子。YOLOv5 给你的是边界框,框的像素宽度 w 直接能读出来,W 是你提前知道的目标真实尺寸,f 需要标定。三个量里,W 和 f 是常数,w 随目标远近变化,所以距离 D 和像素宽度 w 成反比。目标越远,框越小,算出来的 D 越大,逻辑自洽。

但这里有个容易被忽略的点:这个公式假设目标正对相机、没有俯仰角、没有镜头畸变。实际场景里摄像头往往有俯仰,目标也不一定正对,所以纯靠宽度测距在远距离会明显偏大或偏小。常见做法是加一个俯仰角修正,或者改用「目标接地点」的纵坐标来测距,后者在车道、地面目标上更稳。

2.2 焦距 f 到底怎么标,别拿镜头标称值糊弄

很多人翻车就翻在这里:直接拿镜头说明书上的焦距(比如 3.6mm)当 f 用。那个是物理焦距,单位是毫米,而公式里的 f 是像素焦距,单位是像素,两者差一个像元尺寸的换算。正确做法是用标定板或者已知距离的目标反推。

我一般用最简单的一张纸法:把相机固定,把一个已知宽度 W 的物体放在已知距离 D 处,拍一张,量出它在图像里的像素宽度 w,然后 f = (D × w) / W。多测几组取平均,比任何标称值都靠谱。

# 用已知距离和已知宽度反推像素焦距 f # W: 目标真实宽度(米), D: 实测距离(米), w: 图像中像素宽度(像素) def calibrate_focal(W, D, w): f = (D * w) / W # 单位: 像素 return f # 例: 一个 0.5 米宽的板子放在 3 米处, 图像里占 120 像素 f = calibrate_focal(0.5, 3.0, 120) print("像素焦距 f =", f) # 720 像素

这段代码就是把公式反过来用。参数说明:W 要量准,误差直接线性传到 f 上;D 用卷尺量,别用估计;w 建议在 YOLOv5 输出的框上多帧取中位数,单帧抖动大。标定完把 f 写进配置文件,后面测距直接读,不要每次重算。

2.3 用框宽还是用框高,选错了距离直接飘

YOLOv5 输出的框有宽有高,用哪个测距要看目标形状。对于车辆、行人这类宽度相对固定的目标,用框宽 w 更稳,因为宽度受遮挡影响小。对于锥桶、路标这类高度固定的目标,用框高更合适。最怕的是混用:标定用宽度,测距用高度,结果差一个长宽比,距离能差出百分之几十。

我的习惯是:先确定目标的哪个维度在真实世界里是已知且稳定的,就用那个维度。如果目标长宽都已知,可以两个都算,取加权平均,权重按检测框的置信度给。这样在目标部分出画或者被遮挡时,至少还有一个维度能兜底。

3. 把 YOLOv5 和测距模块接起来:从检测框到距离数字

3.1 检测输出里到底拿哪些字段

YOLOv5 推理完,你拿到的是每个目标的 [x1, y1, x2, y2, conf, cls]。测距只需要 x1、x2(算宽度)或者 y1、y2(算高度),外加 conf 做过滤。别小看 conf 过滤,低置信度的框往往框歪了,宽度量出来偏大或偏小,距离直接错。我一般把 conf 阈值设在 0.4 到 0.5 之间,太低噪声多,太高漏检。

# 从 YOLOv5 输出解析出用于测距的像素宽度 # det: 单条检测结果 [x1, y1, x2, y2, conf, cls] def get_pixel_width(det, conf_thres=0.45): x1, y1, x2, y2, conf, cls = det if conf < conf_thres: return None # 置信度不够, 丢弃 w_px = x2 - x1 # 像素宽度 return w_px # 结合标定好的 f 和已知真实宽度 W 算距离 def estimate_distance(w_px, f, W): if w_px is None or w_px <= 0: return None return (W * f) / w_px

逻辑说明:get_pixel_width 先做置信度门限,再算宽度,避免脏数据进测距。estimate_distance 就是核心公式的代码化。参数说明:conf_thres 按你的场景调,夜间或远距离可以降到 0.35;W 必须和标定时用的是同一个物理目标,换了目标要换 W。

3.2 多帧平滑,别让距离数字跳成心电图

单帧测距的原始值抖动很大,因为检测框本身每帧都在变。直接显示会像心电图一样跳,答辩时很难看。常见做法是加一个滑动窗口平均或者卡尔曼滤波。滑动窗口简单,几行代码就能压住抖动。

from collections import deque class DistanceSmoother: def __init__(self, window=7): self.buf = deque(maxlen=window) # 固定长度滑动窗口 def update(self, d): if d is not None: self.buf.append(d) if len(self.buf) == 0: return None return sum(self.buf) / len(self.buf) # 均值输出 smoother = DistanceSmoother(window=7) # 每帧: smoothed_d = smoother.update(raw_distance)

参数说明:window 越大越平滑但延迟越高,7 到 10 帧在 30fps 下延迟约 0.2 到 0.3 秒,肉眼可接受。如果目标快速靠近,窗口要调小,否则距离跟不上。卡尔曼滤波效果更好但调参麻烦,毕业设计用滑动窗口足够写进论文了。

3.3 把测距结果画回画面,演示才直观

光有数字不够,答辩时要让人一眼看到「框 + 距离」。用 OpenCV 把距离文字画在框上方,颜色按距离分档,近的红色、远的绿色,视觉冲击力强。

import cv2 def draw_result(frame, box, distance): x1, y1, x2, y2 = box cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) if distance is not None: text = f"{distance:.2f} m" # 距离越近颜色越红 color = (0, 0, 255) if distance < 3 else (0, 255, 0) cv2.putText(frame, text, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2) return frame

逻辑说明:先画框,再在框上方写距离。参数说明:putText 的坐标 (x1, y1-10) 是框左上角往上偏 10 像素,避免压住框线;字号 0.7 在 1080p 下清晰,720p 可以降到 0.5。颜色阈值 3 米按你的场景改,室内可以设 1.5 米。

4. 数据集和训练:让 YOLOv5 认得出你要测的目标

4.1 数据标注的四个硬要求

测距系统的精度,一半靠标定,一半靠检测框准。标注时要注意:框要贴紧目标边缘,留白会让宽度偏大、距离偏小;同一类目标在不同距离都要有样本,否则模型对远近目标的框质量不一致;遮挡样本要标,但遮挡超过一半的建议丢弃,因为宽度已经不可信;标注格式统一用 YOLO 的 txt,别混 VOC 的 xml。

常见做法是先用 labelImg 或 roboflow 标一批,训练一版,用模型跑一遍没标的数据,把漏检和框歪的挑出来补标,迭代两三轮,框质量会明显提升。

4.2 训练参数怎么设才不白跑

YOLOv5 的训练参数里,和测距最相关的是输入尺寸 imgsz 和框回归损失。imgsz 建议设 640,和推理时保持一致,否则框的像素宽度会因缩放而变,测距公式里的 w 就不对了。如果显存够,可以上 1280,小目标框更准,但速度下降。

# YOLOv5 训练命令示例 python train.py \ --data data/mydata.yaml \ # 数据集配置, 含类别和路径 --weights yolov5s.pt \ # 预训练权重, s 版速度精度平衡 --img 640 \ # 输入尺寸, 必须和推理一致 --batch 16 \ # 按显存调, 8G 显存用 16 --epochs 100 \ # 毕业设计 100 轮通常够 --device 0 # 用第一块 GPU

参数说明:weights 用 yolov5s 起步,精度不够再换 yolov5m;batch 爆显存就减半;epochs 看 loss 曲线,验证集 loss 不再降就可以停。训练完记得用 val.py 看 mAP 和框的回归质量,mAP 高不代表框准,要单独看框的宽高误差。

4.3 推理时 imgsz 必须和训练一致

这是血泪经验:训练用 640,推理用 1280,检测框的像素宽度会翻倍,测距结果直接除以 2,距离全错。因为测距公式里的 w 是图像像素宽度,图像被缩放了,w 就变了。解决办法要么推理也用 640,要么在测距前把框坐标按缩放比例还原回原始图像尺度。

# 推理时把框坐标还原到原始图像尺度 def rescale_box(box, infer_size, orig_size): # box: [x1, y1, x2, y2] 在 infer_size 尺度下 scale = orig_size / infer_size return [int(v * scale) for v in box] # 例: 推理 640, 原图 1920, 框宽 100 像素 -> 还原后 300 像素

参数说明:infer_size 是推理输入边长,orig_size 是原始图像边长,两者要对应同一维度。还原后再算 w,测距才准。

5. 避坑与排查:测距飘、框不准、距离跳的常见病根

5.1 距离整体偏大或偏小,先查 W 和 f

现象:所有距离都系统性偏大或偏小,比例差不多。原因:W 或 f 标定错了,或者标定和测距用的不是同一个目标。解决:重新标定 f,确认 W 是测距目标的真实宽度,两者单位统一(都用米)。如果偏大偏小是固定比例,基本就是这两个参数之一错了。

5.2 近距离准远距离飘,多半是俯仰角没修正

现象:3 米内误差小,5 米外误差迅速变大。原因:相机有俯仰角,远距离时目标在图像里的位置偏移,纯宽度公式失效。解决:加俯仰角修正,或者改用接地点纵坐标测距。简单修正可以在公式里乘一个和纵坐标相关的系数,但更稳的是重新标定一组带距离的样本,拟合一个修正曲线。

5.3 距离数字跳得厉害,检查框抖动和窗口大小

现象:同一静止目标,距离在正负 0.5 米内跳。原因:检测框每帧宽度变化,或者滑动窗口太小。解决:先看框是不是在抖,如果是,提高 conf 阈值或换更稳的模型;如果框稳但距离还跳,加大滑动窗口到 10 帧以上。另外检查图像有没有压缩伪影,低质量视频流会让框边缘不稳。

5.4 目标一出画距离就乱,加个有效性判断

现象:目标走到画面边缘,框被截断,距离突然变得很小。原因:框宽被截断,w 变小,距离算出来偏小。解决:判断框是否贴边,贴边的目标不输出距离,或者用框高兜底。这个判断几行代码就能加,能避免演示时出现离谱数字。

5.5 换了个摄像头结果全变,f 要重标

现象:同一套代码,换个摄像头距离全错。原因:不同摄像头的像素焦距 f 不同,分辨率也可能不同。解决:每换一个摄像头,重新标定 f,并确认分辨率一致。如果分辨率变了,还要考虑图像缩放对 w 的影响。这条没有捷径,标定是必须的。

6. 进阶技巧:用接地点测距和单目测距的边界

接地点测距是我更推荐的一种方式,尤其适合地面目标。原理是利用目标框底边中点的纵坐标 v,结合相机高度 H、俯仰角 θ 和焦距 f,通过几何关系算距离。公式大致是 D = H / tan(θ + arctan((v - v0) / f)),其中 v0 是光心纵坐标。这个方式对目标宽度不敏感,框宽抖动影响小,在车道线、行人、锥桶上比宽度法稳。

import math def distance_by_ground(v, v0, f, H, theta): # v: 目标框底边纵坐标, v0: 光心纵坐标 # f: 像素焦距, H: 相机离地高度(米), theta: 俯仰角(弧度) angle = theta + math.atan((v - v0) / f) if angle <= 0: return None # 目标在地平线以上, 无效 return H / math.tan(angle)

参数说明:H 用卷尺量相机镜头中心到地面的高度;theta 是相机光轴和水平面的夹角,可以用水平仪或者已知距离的目标反推;v0 通常是图像高度的一半,但标定后更准。这个方法的边界是目标必须在地面上,悬空目标不适用。

单目测距的天花板要清楚:它本质是几何估算,不是真值测量。远距离、大俯仰、目标尺寸不一致时误差会放大。毕业设计里把它做到 5 米内误差 10% 以内,已经是很扎实的结果。想再往上走,要么加双目,要么加雷达,但那就不是单目测距的范畴了。

我自己的习惯是:先用手动标定把 f 和 W 钉死,再用滑动窗口压抖动,最后用接地点法做交叉验证,两个方法结果差太多就说明标定或场景有问题。这套流程跑下来,答辩时被问「你这距离准不准」,你能拿出两组独立方法互相印证,比任何解释都有说服力。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询