ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于YOLOv5和A4纸先验的单目测距系统设计与实现

2026/9/14 3:29:23 拓冰建站 浏览量
基于YOLOv5和A4纸先验的单目测距系统设计与实现 简介基于YOLOv5的单目测距系统源码毕业设计项目面向计算机视觉相关专业学生、毕业设计者及目标检测初学者解决利用单目摄像头实时检测目标并估算距离的问题。压缩包共79个文件约215.35MB包含28个py源码涵盖模型训练、推理、距离计算、数据预处理等、26个yaml配置模型结构、数据集与超参数、6个sh脚本、2个pt权重文件以及mp4演示视频、Dockerfile、xml标注、requirements依赖和说明文档目录模块清晰便于按需查阅。项目完整覆盖YOLOv5的数据准备、模型定制、训练、评估与部署流程附有coco128/argoverse等数据集配置和预训练权重其中集成了单目测距模块可直接运行detect脚本进行实践与二次开发。已有373人学习下载适合作为毕业设计参考也可用于快速掌握目标检测与单目测距的工程实现。1. 拿到压缩包先别急着训这个项目真正值钱的是 A4 纸先验单目测距一直被框定在“尺度不确定性”这个死结里没有深度传感器同一辆车在 5 米和 20 米处投影到 CMOS 上的物理尺寸差很多仅凭单帧像素无法直接还原真实距离。这套基于 YOLOv5 的毕业设计源码给的解法很务实——用已知物理尺寸的物体做尺度基准。项目中detect_A4.py的存在说明它把 A4 纸当作标准参照物长宽已知、到处都有、几乎零成本正好能塞进“先验尺寸 → 焦距比 → 距离”这条闭环。适合拿这个人入手的是两类人一类是计算机视觉方向的毕业生想把目标检测和空间几何结合起来做完整系统另一类是已经跑通 YOLOv5 检测、但还不会把 bounding box 转换成物理距离的工程师。压缩包里code目录下同时出现了kerman.py卡尔曼类的滤波与平滑、realsensedetect.py实时视频流检测和detect_A4.pyA4 测距主流程意味着这不是单一算法文件而是从检测到测距后处理的完整链路。接下来按工程落地的顺序拆模型怎么选、测距公式怎么落进代码、实时视频怎么调、最后一层误差怎么压。2. YOLOv5 模型选型与训练配置拿哪版权重、anchor 和超参怎么调2.1 为什么是这个项目选 YOLOv5 而不是 Faster R-CNN 或 YOLOv8单目测距对检测器的要求不是极限精度而是三个工程属性帧率、小目标稳定性、部署简单。Faster R-CNN 在 COCO 上的 AP 可能略高但两阶段结构在前向推理时的 ROI 池化和第二次分类开销在 CPU 或者低端 GPU 上很难跑到实时。YOLOv8 虽然新但它的解耦头和 anchor-free 策略在某些自定义小数据集上反而更难收敛尤其是 A4 纸这类背景占比小、形态单一的目标。YOLOv5 的 anchor-based 检测头配合多尺度特征图P3/P4/P5在 640×640 输入下对中小尺寸物体有天然的召回优势而且hyp.scratch.yaml里的数据增强配置已经被 Ultralytics 在 COCO 上验证过挑出来直接用不必从零调增广策略。这个压缩包里的models/yolov5s.yaml就是默认的 s 版本depth_multiple0.33、width_multiple0.50。对 A4 纸检测来说跑yolov5m或者yolov5l会浪费算力因为 A4 纸是平面物体纹理和形状特征都很简单不需要深层网络去提取复杂语义而yolov5s在单张 1080Ti 上能轻松跑到 80 FPS 以上把帧率预算留给后端的距离滤波。2.2 训练前的数据集准备不一定要从零标但要明白格式data/coco128.yaml已经给出了标准 COCO 格式的目录映射方式data/argoverse_hd.yaml则是针对自动驾驶数据集 Argoverse-HD 的配置。这两个 yaml 文件不是让你直接用而是提供结构参考训练自己的 A4 纸数据集时标注文件应放在labels/目录下每个 txt 文件名与图片名一致内容是class x_center y_center width height坐标全部做归一化。如果是单人毕设拍摄 A4 纸时带上标尺先用 LabelImg 框出纸张四个角生成矩形框再写脚本把它转成 YOLO txt 格式。类别的 id 不要用 0因为 COCO 里 0 是 person容易在后续分析时误判建议在数据配置里把 A4 纸这一类的 id 设为 1从coco.yaml的类别顺序看第 0 类 person 占据优先位置A4 纸作为新增类别排在后面更合理。2.3 超参数配置文件里真正值得动的几个开关hyp.scratch.yaml里有几十个超参数但针对 A4 纸检测注意力只需要放在 4 个位置参数默认值推荐调整方向理由lr00.010.005~0.02A4 纸类别的正样本数量少学习率大容易震荡mosaic1.00.5~0.8Mosaic 把 4 张图拼接A4 纸容易变形降低概率让几何特征稳定degrees0.05.0~10.0加一点旋转增广模拟手持摄像头角度变化fliplr0.50.5 保留水平翻转不改变 A4 纸的物理尺寸语义训练命令通常长这样python train.py \ --data data/a4.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --hyp hyp.scratch.yaml \ --project runs/train_a4逻辑说明--weights yolov5s.pt加载的是 COCO 预训练权重能提供通用的特征提取能力边缘、纹理A4 纸检测本质上只需要在最后一层新增输出头--img 640控制模型输入分辨率分辨率太高如 1280会显著增加推理延迟对测距场景不划算--hyp指定超参文件如果显存只有 8GB--batch 16在 640 分辨率下已经是上限。训练完看runs/train_a4/weights/best.pt的 mAP0.5 是否超过 0.95如果卡在 0.9 以下优先检查标注框是否紧贴纸张边缘YOLO 对标签框偏移比较敏感框大了或小了 10 个像素距离误差都会在后续计算中被放大。2.4 一个容易忽视的坑A4 纸的方向和纵横比会影响检测置信度A4 纸的宽高比是 1:1.414210mm×297mm当摄像头正对纸面时检测框几乎是标准的矩形置信度很高但倾斜超过 30° 时YOLOv5 的矩形框会把纸张连同桌面背景一起框进去导致 bounding box 宽度偏大。这个现象在测距阶段会直接带来距离偏小的误差因为宽度变大按相似三角形算出的距离就变小。训练阶段用degrees: 10.0增强倾斜鲁棒性代码处理时还需要配合离群点滤波——压缩包里的kerman.py就是在做这件事。3. 从 bounding box 到距离值针孔成像原理、焦距比与代码落位3.1 测距公式怎么推导像素宽度、真实宽度和焦距的关系单目测距不是从模型里直接回归距离那是深度估计网络做的事而是利用透视投影的几何关系。针孔相机模型下物体在图像上的像素宽度 $w_{pixel}$、真实物理宽度 $W_{real}$、相机焦距 $f$像素单位和物距 $D$ 满足$$ D \frac{f \times W_{real}}{w_{pixel}} $$这里的 $f$ 不是镜头标识上的 4mm 或 6mm而是焦距在像素坐标系下的表达$f_{pixel} f_{mm} / \mu$其中 $\mu$ 是单个像素的物理尺寸通常是 1.12μm 到 3μm 之间。很多初学者直接拿标称焦距算结果距离偏大或偏小原因就是没换算成像素质点。实际操作中更推荐用标定法直接得到 $f_{pixel}$在已知距离 $D_{known}$ 处放置 A4 纸检测出像素宽度 $w_{pixel}$反推 $f_{pixel} D_{known} \times w_{pixel} / W_{real}$。这个标定过程在项目启动时做一次把算出的 $f_{pixel}$ 存成常量写进配置。3.2 为什么用宽度而不是高度做测距基准从公式上看宽度和高度都行但工程上 A4 纸的宽度更稳。因为实际拍摄时摄像头与纸面之间往往存在俯仰角高度会因为透视收缩明显变小而宽度方向的收缩只有在水平偏航角存在时才体现。检测框的底部中心更贴近物体与地面的接触点正好对应到真实场景中可以视为基准的支撑面位置边框底部被杂物遮挡的概率相对较小靠近下方的像素也受镜头畸变影响更集中因此以底部矩形框宽度x2 - x1作为 $w_{pixel}$ 是最常见的处理方式。注意不要用检测框的[0]和[2]索引直接取坐标因为 YOLOv5 输出的是归一化坐标要先乘上图像宽度。YOLOv5 返回的结果中xyxy格式是未归一化的像素坐标输入--img 640时坐标范围在 0~640 之间直接拿x2 - x1即可作为像素宽度参与计算。3.3 YOLOv5 推理流程里插入距离计算的代码结构假设使用detect.py的模型加载方式拿到预测结果后需在非极大值抑制之后、可视化之前插入距离计算。核心代码如下import torch import numpy as np def estimate_distance(box, img_width, f_pixel, real_width_mm210.0): 利用针孔相机模型估算目标距离 box: [x1, y1, x2, y2] 像素坐标来自 YOLOv5 的 xyxy 输出 img_width: 输入图像宽度像素用于校验坐标合法性 f_pixel: 标定得到的焦距像素单位 real_width_mm: 目标真实宽度A4 纸为 210mm x1, y1, x2, y2 box pixel_width abs(x2 - x1) if pixel_width 5: return -1.0 # 框太小时信号不可信 # 像素宽度与真实宽度正比于像素焦距与真实距离之比 distance_mm (f_pixel * real_width_mm) / pixel_width return distance_mm / 1000.0 # 转成米 # 推理循环中的调用片段 # results.xyxy[0] 是当前帧所有检测框的张量 # 每行格式: [x1, y1, x2, y2, confidence, class_id] for det in results.xyxy[0]: cls_id int(det[5]) if cls_id 1: # 假设 1 是 A4 纸的类别 id box det[:4].cpu().numpy() dist estimate_distance(box, img_width640, f_pixel980.0) print(fA4 paper distance: {dist:.2f} m)逻辑说明estimate_distance的核心是(f_pixel * real_width_mm) / pixel_width这一步把“宽度在像素空间和物理空间的比例”还原成三角形相似关系。f_pixel是第 3.1 节标定得到的值这里举 980.0 作为示例正式使用时不要硬编码应该从标定文件读取。box[:4]提取的是坐标张量.cpu().numpy()是因为 YOLOv5 推理时数据可能在 GPU 上直接传给 numpy 函数会报类型错误。pixel_width 5的过滤条件是为了防止远距离目标框退化后产生异常大距离值。3.4 镜头的畸变影响为什么距离越近误差越小这个公式成立的前提是近似针孔模型真实镜头存在径向畸变和切向畸变。标定后的f_pixel实际上已经包含了畸变的平均影响但在画面边缘畸变导致像素宽度产生非线性伸缩。用cv2.undistort对每帧图像做去畸变处理可以让测距值更平滑import cv2 # 假设标定得到了相机内参矩阵 mtx 和畸变系数 dist # 这只在摄像头广角大、画面边缘变形明显时有必要做 map1, map2 cv2.initUndistortRectifyMap( mtx, dist, None, mtx, (img_width, img_height), cv2.CV_32FC1 ) frame_undistorted cv2.remap(frame, map1, map2, interpolationcv2.INTER_LINEAR)距离越远目标在图像上的像素宽度越小畸变带来的绝对误差占比越高。在 1 米距离时 A4 纸可能占 300 个像素宽即使畸变修正误差 2%算出的距离只有 2% 的波动到 10 米距离时像素宽度缩到 30 个像素同样 2 个像素的抖动带来 6%~10% 的距离误差。这也是后面引入滤波器的根本动机。4. 实时视频流测距realsensedetect.py 的工程化思路与参数调优4.1 从单张图片到视频流检测频率和测距频率的分离设计realsensedetect.py这类实时脚本和单帧detect.py的主要区别在于视频流里相邻帧高度相关目标检测可以以较低的频率运行而距离输出需要更高频率的更新。常见做法是检测线程以 15~20 FPS 运行测距线程以 30 FPS 运行检测框的位置通过线性插值或最近邻结果传给测距模块。这样做能显著降低 GPU 占用因为检测模型一次前向推理 640×640 分辨率图像的成本远高于一次简单的除法计算。import threading import queue import numpy as np class FrameProcessor: def __init__(self, model, f_pixel): self.model model self.f_pixel f_pixel self.detect_queue queue.Queue(maxsize4) self.distance_result 0.0 self.det_box None self.lock threading.Lock() def inference_worker(self): 检测线程从队列拿帧推理更新检测框和置信度 while True: frame self.detect_queue.get() results self.model(frame[None, ...], size640) # 从 results.xyxy[0] 中挑出最大置信度的 A4 纸框 with self.lock: self.det_box max( (d for d in results.xyxy[0] if int(d[5]) 1), keylambda x: x[4], defaultNone )逻辑说明inference_worker是独立线程它把当前帧推入模型做完整推理但只在拿到结果时更新共享变量det_box。测距主循环只需要读取这个共享变量不需要每次都重新跑模型这使视频流代码能在 CPU 上也能达到实时效果。queue的maxsize4限制了积压帧数防止检测速度跟不上输入帧率时内存被无限撑高。一个约定检测线程里不要放任何print或文件写入所有 IO 操作留在测距主线程否则帧率会被 IO 拖垮。4.2 RealSense 摄像头在单目模式下的标定注意事项realsensedetect.py这个名字虽然带 RealSense但单目测距场景下只使用它的 RGB 流不需要启用深度流。RealSense 的内参可以在运行时通过 SDK 直接获取这是它比普通 USB 摄像头方便的地方import pyrealsense2 as rs pipeline rs.pipeline() config rs.config() config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30) profile pipeline.start(config) # 获取内参这里返回的 fx 就是像素焦距 intr profile.get_stream(rs.stream.color).as_video_stream_profile().get_intrinsics() f_pixel intr.fx # 单位是像素直接用于测距公式说明intr.fx是 x 方向的焦距正常情况下fx和fy非常接近差异小于千分之一用fx作为测距公式里的 $f$ 即可。需要注意如果启用了 RealSense 的深度流并做了自动对齐RGB 流和深度流的分辨率要求可能不一致而测距代码只需要 RGB 的fx不要误把深度内参当作 RGB 内参使用。对普通 USB 摄像头则只能靠棋盘格标定推荐用cv2.calibrateCamera采集 15~20 张不同角度的棋盘格图得到内参矩阵mtx再取mtx[0, 0]作为f_pixel。4.3 实时测距主循环要不要每一帧都做距离计算上面的代码已经把检测和测距分离但实际做主循环时还有一个选择距离计算放不放阈值逻辑。在接近 5 米以上时 A4 纸的像素宽度可能低于 40 个像素单帧测距值波动达到 0.5 米以上。我一般会在主循环里加一个置信度和像素宽度的双重闸门# 推荐做法只在像素宽度高于阈值时才计算距离 MIN_PIXEL_WIDTH 50 # 低于该值测距结果抖动过大视为无效 while True: frame pipeline.wait_for_frames().get_color_frame() if not frame: continue img np.asanyarray(frame.get_data()) with processor.lock: det processor.det_box if det is not None: x1, y1, x2, y2 det[:4].cpu().numpy() pixel_w abs(x2 - x1) if pixel_w MIN_PIXEL_WIDTH: dist estimate_distance(det[:4], img_width640, f_pixelf_pixel) # 将 dist 送入 kerman.py 的平滑器替代直接输出 smoothed_dist smooth_distance(dist) print(fA4: {dist:.2f} m - smoothed: {smoothed_dist:.2f} m)逻辑说明MIN_PIXEL_WIDTH是根据实际摄像头焦距和视频分辨率决定的经验值。若标定f_pixel980、真实宽度 210mm像素宽度 50 对应距离约 4.1 米0.21m × 980 / 50px 4.116m。超过这个距离后 A4 纸在图像中的细节减少单帧检测框上下跳动幅度加大直接输出会让读者觉得系统不稳定。这里的smooth_distance就是kerman.py暴露的入口。保持距离计算和帧读取同步是为了保证测距结果的实时性不受检测线程里 NMS 耗时的抖动影响。5. 距离抖动的压平技巧用滤波、尺度校验框住误差边界5.1 误差链路上最脆弱的环节不是模型是坐标抖动把全套管线跑通后最常见的现象是目标在 3 米处不动测距输出却以 0.1 秒为周期在 2.7 米到 3.3 米之间跳。这个波动的不是测距公式本身而是 YOLOv5 每帧给出的 bounding box 有 1~2 个像素的随机偏移这部分偏移被除法放大后形成距离噪声。kerman.py在项目中的角色就是把这一层的跳变压平。它本质上是带置信度加权的平滑器置信度越高权重越大这是比单独用均值滤波更合理的设计因为低置信度帧往往伴随着检测框退化它们输出的距离值应该被压低贡献。关于kerman.py的具体实现可以按卡尔曼滤波或指数滑动平均两种方式组织。卡尔曼滤波要维护状态向量[distance, velocity]观测模型直接把distance映射到输出指数滑动平均更简单smoothed alpha * raw (1-alpha) * smoothed。对毕设场景我推荐先写指数滑动平均版本调参直观class DistanceSmoother: def __init__(self, alpha0.35): self.alpha alpha self.last_value None def update(self, raw_distance, confidence): # 置信度越低平滑系数越大越信任历史值 if confidence 0.5: alpha min(self.alpha * 1.5, 0.85) else: alpha self.alpha if self.last_value is None: self.last_value raw_distance else: self.last_value alpha * raw_distance (1 - alpha) * self.last_value return self.last_value代码说明alpha决定当前帧与历史帧的权重。alpha0.35意味着历史值占 65%能有效滤除高频抖动代价是目标快速移动时距离输出有约 3~5 帧的滞后。confidence 0.5时把alpha放大到 0.525目的是对低置信度帧更保守避免异常检测框导致距离突跳。5.2 动态校准窗口与离群点判定平滑滤波只能处理随机噪声处理不了瞬间的大跳变——比如某一帧检测框误框到背景里。这种离群点可以用滑动窗口内的中值偏差来判定剔除。from collections import deque import numpy as np class OutlierRejecter: def __init__(self, window_size7, threshold_m0.5): self.window deque(maxlenwindow_size) self.threshold threshold_m def filter(self, distance): if len(self.window) 3: self.window.append(distance) return distance median_dist np.median(self.window) if abs(distance - median_dist) self.threshold: # 用中值替代异常值而不是丢弃到没有输出 fixed median_dist else: fixed distance self.window.append(fixed) return fixed这里的threshold_m0.5表示超过当前中位数 0.5 米的帧会被替换为中位数这个值可以根据场景调整目标静止时调到 0.2 米更灵敏目标移动时调到 0.8 米避免把真实位移误判成异常。注意deque在未填满时直接返回原始距离前 3 帧不做过滤这是为了避免初始化阶段误伤。5.3 距离校验标尺验证法室内标定场景里放一把卷尺摄像头从 1 米移到 5 米每 0.5 米记录一次检测框中心是否对准 A4 纸中心、误差是否低于 15%。如果 1 米处误差大而 5 米处误差小说明焦距标定值偏小反过来则说明标定值偏大按线性比例修正f_pixel即可不需要重新拍摄。注意摄像头光轴必须尽量与纸面垂直倾斜视角下测距误差会迅速上升这种现象与检测置信度无关纯几何误差。5.4 A4 纸的实际摆放与先验库扩展一个把测距扩展到任意已知尺寸物体的技巧A4 纸作为先验物是入门方案但它有两个缺点纸张太轻遇风容易抖动远距离时像素宽度过小。把方法泛化时可以维护一个“先验尺寸库”known_objects.yaml # 目标名称: [实际宽度(米), 高度(米)] a4_paper: [0.210, 0.297] car_side: [1.8, 1.5] traffic_cone: [0.36, 0.7]推理时根据检测到的类别 id 索引这个 yaml把对应宽度传入estimate_distance测距公式本身不用改。训练时给这些目标分别建类但不建议把person放进去因为人的宽度变化太大正对、侧对差距明显用固定先验宽度测距的误差会很大。选参照物时要遵循两个条件宽度在直角视角下稳定、目标不能被压缩形变。这正是这套源码最核心的延伸方向。本文还有配套的精品资源点击获取