
简介YOLOv5单目测距python是一套结合目标检测与单目深度估计的可运行工程面向计算机视觉开发者及自动驾驶、机器人导航、智能监控等场景使开发者无需深度传感器即可从单目图像中估算目标物体距离。压缩包共包含132个文件大小约13.87MB涵盖yaml/yml模型配置、Python训练与推理脚本、pyc编译文件、sh部署脚本、Dockerfile环境配置、Markdown说明文档、预训练pt权重和jpg示例图覆盖模型定义、训练测试、环境搭建与快速上手。目前已有3856人学习下载具有较高实用参考价值。工程基于YOLOv5-6.1目录组织附带tutorial.ipynb入门教程、bus.jpg测试图及Docker环境配置用户可获得从数据准备、模型训练到距离输出的完整源码和操作指引适合有一定深度学习基础、希望将测距功能集成到视觉项目中的开发者。 做了几年视觉项目YOLOv5单目测距这套组合我前后接过很多次类似的咨询毕设要做车辆测距、车间想做安全区域预警、实验室想给小车加个避障功能。大家的诉求其实很统一就是用一个普通USB摄像头把画面里目标的位置距离算出来。相比激光雷达动辄几千的成本单目方案只需要一个摄像头加一台能跑Python的设备综合成本和落地门槛都低很多。这篇文章把整套方案从原理、环境搭建、相机标定到最终的Python代码实现完整过一遍。我会重点讲清楚每个步骤背后的计算逻辑而不是只丢一段代码让你复制跑通。适合的目标人群是对YOLOv5目标检测有基础认识、想在此基础上做距离估计的同学或者正准备做相关毕设、工程原型的开发者。读完你不仅能复现还能根据实际场景调参。1. 项目拆解YOLOv5和单目测距各自的角色1.1 检测和测距是怎么耦合的先理清楚整个系统的数据流。YOLOv5负责从图像中找出目标输出每个目标的检测框坐标x1, y1, x2, y2、置信度和类别。但这只是告诉你“画面里哪里有目标”并没有回答“目标离你多远”。单目测距要做的事情就是基于这个检测框结合摄像头本身的参数推算目标在真实世界中的距离。检测框在这里面承担一个关键角色它给出了目标在图像中的位置和尺寸而这些像素信息正是单目测距公式的输入。比较常用的做法是取检测框的底边中点作为测距参照点。为什么要用底边而不是中心点因为检测框底边通常对应目标与地面的接触位置。对于行人、车辆这类直立物体底边中点可以近似认为是目标“脚底”的位置而这个位置刚好在地平面上。有了这个前提才能用几何关系把图像坐标换算成真实距离。如果取的是检测框中心点中心点的高度会随目标的高度和远近不断变化距离计算就会出现明显偏差。所以工程上建议统一使用 (x1x2)/2, y2 即底边中点作为测距的目标点。1.2 单目测距的三种主流实现思路单目测距没有深度相机那种像素级深度信息只能靠几何假设和先验知识反推。目前主流的做法有三种第一种是相似三角形测距法。已知目标的真实宽度或高度比如车辆宽度约1.8米再结合焦距通过比例关系计算距离。公式很简单距离 (真实宽度 × 焦距) / 像素宽度。这种方式实现最方便但缺点也很明显目标姿态变化、检测框不够精确时误差会放大。第二种是地面平面假设法本文推荐。假设目标底边接触地面已知相机安装高度和俯仰角通过针孔相机模型反推目标到底边的水平地面距离。这种方式不需要知道目标的长宽对车辆、行人等目标都适用配合YOLOv5的检测框底边点非常自然也是目前开源社区项目里最常采用的方式。第三种是基于PNP的位姿估计。需要已知目标的三维模型或关键点的对应关系通过求解相机外参获得目标的位置。精度最高但对模型要求也高通用性差跑毕设或者快速原型一般用不到。2. 环境搭建与依赖取舍2.1 Python环境和YOLOv5依赖安装要点不同教程推荐的Python版本差异很大我实测下来建议用Python 3.8到3.10之间的版本太高或太低都容易和PyTorch出现兼容性问题。PyTorch建议根据显卡CUDA版本选择1.10到2.x的稳定版本。如果你用的是纯CPU环境也能跑只是推理速度会慢不少后面我会讲CPU环境下的优化方案。YOLOv5的依赖安装最简单的做法是直接拉官方仓库git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt这里有个新手特别容易踩的坑requirements.txt会同时装opencv-python这个库的安装包比较大网络不好时容易超时。建议先单独装pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple另外import cv2装完之后记得验证一下命令行输入python -c import cv2; print(cv2.__version__)能正常输出版本号再继续下一步。很多人卡在环境问题上一两个小时90%都是opencv或torch版本不匹配导致的。2.2 选择哪种YOLOv5模型和运行方式YOLOv5官方提供了n/s/m/l/x五个尺寸的模型从n到x参数量逐级增加。单目测距项目对检测框的准确性要求较高太小或太大的框都会直接影响测距结果。如果是笔记本或台式机跑实时视频推荐yolov5s或yolov5m平衡速度和框精度。如果是树莓派这类边缘设备推荐yolov5n或yolov5s导出的ONNX模型。运行方式也有两类选择一是直接用官方detect.py脚本二是通过torch.hub在自定义Python脚本中加载模型。detect.py适合快速验证效果但如果要和测距逻辑集成必须走第二种方式把YOLOv5当成一个库调用。import torch model torch.hub.load(ultralytics/yolov5, yolov5s, force_reloadFalse)这里额外提醒一下force_reloadFalse首次运行会从网上下载权重之后会走本地缓存。如果你有自己训练好的权重用torch.hub.load(ultralytics/yolov5, custom, pathbest.pt)加载。自己训练的模型在特定场景下的检测框通常比官方预训练模型更准测距精度也会随之提升。3. 相机标定与测距公式推导3.1 用OpenCV做棋盘格标定获取相机内参单目测距的核心依赖之一是相机内参尤其是焦距fx、fy和主点坐标cx、cy。这些参数可以从相机出厂参数中得到但实际使用中摄像头可能存在畸变所以强烈建议自己做一次标定。标定方法用OpenCV的棋盘格方案。打印一张棋盘格比如8x6内角点用摄像头从不同角度拍15到20张照片然后用以下代码提取角点并计算内参import cv2 import glob import numpy as np # 棋盘格尺寸内角点数量 (横, 竖) pattern_size (8, 6) objp np.zeros((pattern_size[0] * pattern_size[1], 3), np.float32) objp[:, :2] np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2) objpoints [] imgpoints [] images glob.glob(calib_images/*.jpg) for fname in images: img cv2.imread(fname) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, corners cv2.findChessboardCorners(gray, pattern_size, None) if ret: objpoints.append(objp) corners2 cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)) imgpoints.append(corners2) ret, mtx, dist, rvecs, tvecs cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None) print(内参矩阵:\n, mtx)标定要诀是让棋盘格尽量覆盖画面各个区域尤其是边缘位置因为畸变在边缘最明显。计算完内参矩阵后顺便看一下重投影误差通常控制在0.1到0.3像素之间就算合格。内参矩阵中mtx[0][0]就是fxmtx[1][1]是fymtx[0][2]是cxmtx[1][2]是cy。3.2 测距公式从像素到底边地面距离有了相机内参还需要两个额外的安装参数相机光心离地面的高度 h_cam以及相机光轴与水平面的俯仰角 pitch。这两个参数可以用尺子量也可以用已知距离反推二选一。测距公式基于针孔相机模型。目标底边在图像中的行坐标 v_bottom对应像素在相机坐标系下的俯角可以表示为angle pitch atan((v_bottom - cy) / fy)其中 pitch 是相机俯仰角光轴相对水平面向下的角度(v_bottom - cy)/fy 是目标底边像素相对光轴的角度的正切值。两者相加就是目标底边相对相机水平视线的实际俯角。由于目标底边在地平面上结合相机高度用三角函数可得水平距离distance h_cam / tan(angle)这个公式的推导逻辑可以用一个生活场景来理解你站在高处向下看到地面上的一个点这个点的距离等于你的双眼高度除以你视线与水平面夹角的tan值。相机高度固定之后图像上目标的v坐标变化直接反映视线夹角的变化距离也就跟着出来了。代码实现如下import math def compute_distance(v_bottom, cam_h, pitch_deg, fy, cy): pitch math.radians(pitch_deg) r (v_bottom - cy) / fy angle pitch math.atan(r) if angle 0: return None return cam_h / math.tan(angle)如果你的相机安装基本水平pitch接近0还可以用简化公式 distance cam_h * fy / (v_bottom - cy)。实测下来当目标在20米以内、pitch小于10度时简化公式和完整公式的误差在工程可接受范围内。但摄像机如果有明显俯视角度必须用完整公式否则距离会整体偏大。4. YOLOv5检测与测距集成实战4.1 从检测结果提取测距关键点这步是重点。模型推理后拿到的结果是检测框坐标需要先解析出底边中点的像素坐标results model(frame) boxes results.xyxy[0].cpu().numpy() # x1, y1, x2, y2, conf, cls for box in boxes: x1, y1, x2, y2, conf, cls box if conf 0.5: continue bottom_x int((x1 x2) / 2) bottom_y int(y2) cls_name model.names[int(cls)] # 后续将 bottom_y 带入测距公式为什么用y2而不是y1或中心点的y因为YOLO的检测框是矩形包围盒目标站立于地面时包围盒的底边基本贴合目标与地面的接触位置。使用底边中点意味着我们认为该像素在世界坐标系中的高度近似为0这正好满足地面平面假设。这里有个容易忽视的细节检测框受目标姿态、遮挡等影响底边不一定完全贴地。比如行人弯腰、车辆被障碍物挡住底部底边像素会偏上或偏下导致测距误差。常见处理方法是只对特定类别person、car、truck等启用测距逻辑并配合多帧平滑来抑制跳变。4.2 完整集成代码视频流实时测距下面给出一套可直接运行的完整流程。代码按“读取视频帧 → YOLOv5推理 → 解析检测框 → 计算距离 → 绘制结果”的顺序组织import math import cv2 import torch def compute_distance(v_bottom, cam_h, pitch_deg, fy, cy): pitch math.radians(pitch_deg) r (v_bottom - cy) / fy angle pitch math.atan(r) if angle 0.02: return None return cam_h / math.tan(angle) # ---------- 参数配置 ---------- CAM_H 1.5 # 相机安装高度单位米 PITCH_DEG 5.0 # 相机俯仰角单位度 FY 700.0 # 相机内参fy来自标定 CY 360.0 # 相机主点cy来自标定 CONF_THRESH 0.5 # 检测置信度阈值 # ---------- 加载模型 ---------- device torch.device(cuda if torch.cuda.is_available() else cpu) model torch.hub.load(ultralytics/yolov5, yolov5s, force_reloadFalse) model.to(device).eval() # ---------- 视频流处理 ---------- cap cv2.VideoCapture(test_video.mp4) if not cap.isOpened(): print(无法打开视频源) exit() while cap.isOpened(): ret, frame cap.read() if not ret: break results model(frame) dets results.xyxy[0].cpu().numpy() for det in dets: x1, y1, x2, y2, conf, cls_id det if conf CONF_THRESH: continue bottom_x int((x1 x2) / 2) bottom_y int(y2) dist compute_distance(bottom_y, CAM_H, PITCH_DEG, FY, CY) label f{model.names[int(cls_id)]} {dist:.2f}m if dist else \ f{model.names[int(cls_id)]} ??? cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) cv2.circle(frame, (bottom_x, bottom_y), 4, (0, 0, 255), -1) cv2.putText(frame, label, (int(x1), int(y1) - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) cv2.imshow(YOLOv5 Mono Distance, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()如果跑的是本地图片序列或在线摄像头把cap cv2.VideoCapture(test_video.mp4)换成cap cv2.VideoCapture(0)即可0表示第一个USB摄像头。实际运行过程中距离数值会存在帧间抖动尤其是目标处在10米外的时候几个像素的框波动可能带来半米以上的距离跳变。建议对同一个目标的距离值做滑动平均或卡尔曼滤波。简单做法是维护一个距离列表每次取最近5帧的中位数作为最终输出能明显稳定画面显示和后续逻辑判断。5. 精度优化与常见问题排查5.1 误差来源与对应补偿策略用这套方案做测距误差来源主要集中在四个方面。第一是相机标定误差。fx和cy如果标定不准所有距离都会系统性偏移。解决方法是标定时多拍照片保证棋盘格覆盖画面边缘并检查重投影误差在0.3像素以内。如果条件允许把相机装到固定位置后再标定一次因为安装后的镜头角度和标定时可能存在微小差别。第二是安装参数不准。相机高度h_cam可以用尺子量但pitch角很难直接测准。一个非常实用的标定方法是反推法在相机正前方放置一个已知距离的目标或标记物量出它到相机光心的水平距离然后利用测距公式反解pitch。我在工程中经常用这个方法比用角度尺估得准得多。第三是检测框的底边不贴合真实接触点。比如车辆尾部被遮挡、行人抬脚等都会造成底边像素偏移。处理手段是只对置信度高的目标测距对不同类别设置不同置信度阈值目标截断在画面边缘时直接跳过因为边缘区域畸变和出框都会极大影响精度。第四是地面非绝对水平。摄像头安装的位置地面如果有坡度测距公式默认目标在地面的假设就会失效。这个在户外场景比较常见建议尽量选择平整地面布置相机否则就要在标定时额外引入地面倾角参数。5.2 实测精度与距离范围参考拿我个人做的测试来看这套方案在室内走廊场景、3到15米的范围内平均误差基本能控制在10%以内。5米内的误差可以做到5%左右15米外误差会逐步扩大到15%到20%。误差随距离增大的原因很好理解同等像素波动在远处对应更大的真实距离变化。这里建议做距离可视化输出时在画面中标记出置信度较低的可疑结果而不是直接把偏差很大的数字打上去。另外对超远距离大于30米的目标单目测距的参考意义已经不大不建议再参与业务逻辑判断。5.3 新手踩坑速查表现象常见原因解决办法测距数值整体偏大或偏小相机俯仰角pitch没有标定用已知距离反推pitch别靠目测同一个目标距离值剧烈跳动检测框底边不稳定加入滑动平均或卡尔曼滤波摄像头画面存在明显畸变没有做畸变校正标定后用cv2.undistort校正目标在近处距离准、远处不准单目测距固有误差限定测距范围在15米内使用测距返回Nonebottom_y位置高于图像中心太多或angle小于0只对画面下半区域目标测距CPU上跑实时视频特别卡模型过大或没调推理参数换yolov5n、缩小输入尺寸320、关闭验证集增强很多人在模型部署阶段发现自己的电脑带不动大模型这里多说一句纯CPU环境下可以把图像输入尺寸从640降到416甚至320推理速度会快不少代价是检测小目标的精度下降。若是树莓派这类设备导出ONNX再用OpenCV DNN模块或TensorRT/NCNN部署是更稳妥的方向。YOLOv5训练出的检测模型本身和测距公式是完全解耦的换推理后端不影响后续距离计算逻辑。结合这些经验我个人最推荐的做法是项目启动初期先用手头现有的手机或USB摄像头把整套链路跑通确认检测和测距结果符合预期后再针对实际业务场景标定相机、优化模型和滤波参数最后去考虑边缘设备的推理加速。把第一步跑通永远是性价比最高的事情。本文还有配套的精品资源点击获取