新闻详情

新闻详情

首页 / 资讯中心 / 详情

YOLOv5单目测距实战:相机标定、Python计算与误差控制

发布时间:2026/9/28 1:11:14来源:尧图网络
YOLOv5单目测距实战:相机标定、Python计算与误差控制
简介面向PyTorch初学者的YOLOv5单目测距项目适合正在学习目标检测与现实距离估算相结合的开发者。项目基于YOLOv5-6.1借助OpenCV和NumPy处理图像通过目标框高度与相机参数推算物体距离原理直观无需复杂数学推导便于从检测结果延伸出实用测距功能。压缩包共132个文件大小约13.91MB包含34个Python脚本、40个YAML/yml配置、PT模型权重、Jupyter教程、Docker部署文件及Shell辅助脚本覆盖训练、推理、后处理与可视化等环节。已有2287人学习代码组织清晰附带notebook可循序渐进地理解单目测距流程。读者可获得可运行的检测与测距源码、配置好的环境定义、模型权重以及从特征提取到距离计算的完整思路适合作为目标检测类课程设计或工程预研的参考。项目还自带示例图片与基础配置便于快速跑通实验并观察检测框对应的距离输出。1. 单目测距没有深度相机靠的是几何关系——YOLOv5 Python 把它变成可复现的工程单目测距最容易让人误会的一点是以为它像雷达一样直接给一个距离读数其实它靠的是几何换算YOLOv5 把目标框出来框的像素高度进入一个小孔成像公式再用 Python 算出距离。这个方案不需要双目、不需要深度相机一个普通 USB 摄像头就能做原理确实简单但工程化之后有一堆细节决定误差大小。它适合做低速碰撞预警、行人距离提示、安防区域入侵判断这类对厘米级精度不敏感的场景。这篇笔记会从成像原理讲到相机标定再到 YOLOv5 的检测框接入最后把那些让新手翻车的坑一次说清楚。2. 为什么单目能测距针孔模型里那一条简单的相似三角形2.1 一张二维图片没有深度但成像是线性的针孔相机的投影关系是一条直线。设目标实际高度为 H目标到相机的距离为 Z镜头焦距为 f则目标在像平面上的物理高度 h 满足H / Z h / f换句话说目标离得越近成像越大离得越远成像越小。这个比例是线性的所以只要我们知道目标的真实高度 H 和成像高度 h就能反推距离 Z。在代码里我们不会直接使用毫米单位的焦距而是用像素单位的焦距 fx。把物理高度和像素高度代入同一比例像素尺寸约掉之后公式变成了h_pixels fx * H / Z所以距离公式就是Z fx * H / h_pixels这就是整个单目测距的核心后面所有标定、检测、滤波都是为了让这个公式里的三个量尽量准确。2.2 距离公式推导目标像素高度反推距离假设相机标定得到 fx 1000面前是一个身高 1.7 m 的成年人YOLOv5 检测框高度为 170 像素那么Z 1000 * 1.7 / 170 10.0 m这就是最简单的单目测距。写成 Python 函数只有几行def distance_from_bbox_height(fx_pixel, real_height_m, h_pixel): 通过目标检测框高度估算距离。 fx_pixel: 相机内参 fx单位像素 real_height_m: 目标真实高度单位米 h_pixel: 检测框高度单位像素 if h_pixel 0: return -1.0 return fx_pixel * real_height_m / h_pixel # 示例fx1000行人高度1.7m框高170px dist distance_from_bbox_height(1000.0, 1.70, 170) print(dist) # 10.0这个函数里最值得注意的参数是fx_pixel。它不是镜头包装上写的“3.6mm”那种焦距而是把物理焦距换算成像素单位之后的内参值。real_height_m是一个先验量来自你对目标的类别判断。h_pixel来自目标检测框YOLOv5 返回的xyxy格式里ymax - ymin就是它。2.3 为什么这个方案“原理比较简单”却依然容易翻车公式简单不代表落地简单。这个相似三角形模型默认了几个前提第一目标的实际高度是已知且稳定的但行人高矮、车高车型都不同拍脑袋给一个高度值误差会直接传导到距离上。第二目标必须垂直于地面如果检测框里是一个蹲着的人或者被遮挡的目标框高代表的就不是真实高度。第三相机光轴要尽量和地面平行只要相机安装时有一点俯仰角上面的公式就会失真。第四相机内参要准确未标定的镜头 fx 偏差会让距离整体偏大或偏小。更重要的是距离越远像素高度变化对距离越敏感。一个 2 像素的检测框抖动在 20 m 处可能抖出几米误差。所以后面几章要解决的就是先把 fx 标定准再把 h_pixels 从检测框里稳定地取出来最后处理那些让公式失效的安装条件。3. 做单目测距能用的前提先把相机标定做了3.1 标定到底在标什么fx、fy、cx、cy相机内参矩阵是一个 3×3 矩阵写成这样[[fx, 0, cx], [ 0, fy, cy], [ 0, 0, 1]]其中 fx、fy 是焦距的像素单位cx、cy 是主点偏移。对单目测距的公式来说只需要 fx。为什么还要做整套标定因为标定同时会给出畸变系数。普通摄像头的镜头不是理想针孔模型画面边缘会有明显的桶形畸变目标越靠近图像边缘检测框高度越不可信。如果直接把未校正的图像送入 YOLOv5 测距得到的距离在画面中心还可以在四个角上会明显偏离真实值。所以我的习惯是先把标定得到的畸变系数存下来在测距管线的第一步用cv2.undistort校正整帧图像再送进 YOLOv5。这一步多花几毫秒但能省掉大量边缘测距不准的后续排查。3.2 棋盘格标定的图片采集与参数准备最常见的标定方法是张正友标定法OpenCV 里已经有完整实现。你需要一块打印出来的棋盘格尽量贴平在硬纸板或塑料板上然后用目标摄像头拍摄 1520 张不同位姿的图片。采集时让棋盘格出现在画面的中心、四个角、上下边缘并且每张的倾斜角度都有差异这样标定出的内参才覆盖整个画面。棋盘格参数有两个容易搞混的地方pattern_size是内角点数不是格子数。比如棋盘格每行有 9 个格子内角点就是 8 个每列有 7 个格子内角点就是 6 个。另外square_size是单个格子物理边长单位用米虽然它主要影响外参的尺度不影响 fx但填写实际尺寸可以避免以后做外参标定时再返工。3.3 用 OpenCV 跑通标定的最小 Python 脚本把采集到的图片放在calib/目录下脚本会逐张找角点、精化亚像素坐标最后用calibrateCamera解算内参import cv2 import numpy as np import glob import os pattern_size (7, 6) # 内角点数不是格子数 square_size 0.025 # 单个格子边长单位米 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) objp * square_size obj_points [] # 世界坐标系角点 img_points [] # 图像坐标系角点 images sorted(glob.glob(calib/*.jpg)) os.makedirs(calib/checked, exist_okTrue) for fname in images: img cv2.imread(fname) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, corners cv2.findChessboardCorners(gray, pattern_size, None) if not ret: continue criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corners_sub cv2.cornerSubPix(gray, corners, (5, 5), (-1, -1), criteria) obj_points.append(objp) img_points.append(corners_sub) draw_img img.copy() cv2.drawChessboardCorners(draw_img, pattern_size, corners_sub, ret) cv2.imwrite(os.path.join(calib/checked, os.path.basename(fname)), draw_img) ret, mtx, dist, rvecs, tvecs cv2.calibrateCamera( obj_points, img_points, gray.shape[::-1], None, None ) np.savez(calib_result.npz, mtxmtx, distdist) print(camera_matrix:) print(mtx) print(fx , mtx[0, 0])这段代码里size(5,5)是亚像素精化时搜索窗口的大小窗口太小容易漏掉角点太大则角点位置可能被邻居干扰。(30, 0.001)是迭代次数和精度阈值默认值够用。标定成功后mtx[0,0]就是测距公式里的fx_pixel。dist是畸变系数保存下来用于后续cv2.undistort。3.4 标定输出怎么用分辨率变化后 fx 要跟着缩放标定时用的分辨率是当时采集棋盘格的分辨率比如 1920×1080。如果你的 YOLOv5 测距管线后来换成了 1280×720 的帧或者为了提速把原图缩小fx必须按分辨率比例同步缩放否则距离会系统性偏大或偏小。缩放规则很简单fx_scaled mtx[0, 0] * (new_width / calib_width)cv2.undistort校正后的图像尺寸如果和标定尺寸不同同样要重新换算。一个常见误用是标定用 1920×1080代码里为了跑 YOLOv5 把帧 resize 成 640×640却忘记缩放fx最后测出来的距离差了 23 倍。这个问题排查起来很隐蔽因为目标检测框本身没错错的是相机参数和图像分辨率脱节了。3.5 不标定能玩吗临时验证可以但误差要心里有数。常见做法是根据镜头参数手动计算fx f_mm * image_width_px / sensor_width_mm比如镜头标称 6 mm传感器宽度 6.4 mm图像宽度 1920 像素那么 fx 6 * 1920 / 6.4 1800。这个值只能算近似因为实际镜头焦距和标称值有公差传感器尺寸也可能不精确。我见过用这个方法估算的 fx 和标定值差 10%20%对应距离误差也是 10%20%。如果只是随便玩玩没问题要做持续稳定的测距还是老老实实花半小时跑一次标定。4. 把 YOLOv5 接到测距管线里检测框的高度就是公式的 h_pixels4.1 环境与模型加载本地权重优先避免每次下载YOLOv5 的环境配置没有太多花活Python 3.83.10、PyTorch、OpenCV 装上就能跑。CPU 也能推理但逐帧测距会比较吃力有 NVIDIA GPU 会顺畅很多。模型我一般选yolov5s.pt精度和速度比较均衡如果想要更高精度可以换yolov5m.pt但帧率会明显下降。加载模型时优先把.pt权重文件和 YOLOv5 源码放在本地目录用sourcelocal避免每次初始化都去联网拉取import torch model torch.hub.load( ultralytics/yolov5, custom, pathyolov5s.pt, sourcelocal ) model.conf 0.45 # 置信度阈值 model.iou 0.45 # NMS 的 IoU 阈值 model.max_det 50 # 单张图最多保留 50 个目标这里的model.conf对测距影响很大。阈值调太低会把背景误检成目标距离文本乱跳阈值调太高远处不清晰的目标会被直接丢弃。我在室内场景用 0.45在低照度或远距离场景会降到 0.250.35再通过跟踪逻辑过滤误检。4.2 推理与后处理从模型输出里拿到干净的目标框官方模型的Results对象已经帮你把 letterbox 的坐标还原到原图分辨率了。直接调用pandas().xyxy[0]每一行就是一张检测结果import cv2 frame cv2.imread(street.jpg) results model(frame, size640) df results.pandas().xyxy[0] for _, row in df.iterrows(): x1, y1, x2, y2 row[xmin], row[ymin], row[xmax], row[ymax] conf row[confidence] cls_name row[name] print(cls_name, conf, x1, y1, x2, y2)xyxy是浮点数画框时需要转成int。name是 COCO 类别名比如person、car、bus。测距时我只对类别白名单里的目标计算距离其他类别直接跳过避免把路牌、树影拿去做距离换算。4.3 从检测框到测距目标真实高度配置表与换算函数不同目标要配不同的真实高度这个表是测距误差的主要来源之一。我常用的一套初始值类别真实高度/宽度 (m)说明person1.70成年人平均身高现场可调car1.50轿车高度SUV 建议改 1.70bus3.10城市公交大致高度truck3.60大型厢式货车高度这里要说明行人用高度比较合理因为行人直立时高度方向和图像竖直方向一致。车辆其实更适合用宽度判断因为车辆型号之间的高度差异比宽度更大。如果做车辆测距可以把配置表改成真实宽度把检测框宽度x2 - x1代入公式CLASS_SIZE_MAP { person: 1.70, # 高度 car: 1.80, # 宽度 bus: 2.50, # 宽度 truck: 2.60, # 宽度 } def estimate_distance(cls_name, bbox_h_pixel, bbox_w_pixel): size CLASS_SIZE_MAP.get(cls_name) if size is None: return -1.0 if cls_name person: return fx * size / max(bbox_h_pixel, 1) else: return fx * size / max(bbox_w_pixel, 1)加一个max(..., 1)是为了防止检测框高度为 0 时除零。映射表最好抽成配置文件因为现场的真实目标高度分布和你假设的往往不一样。4.4 完整的最小视频测距示例把上面的东西串起来就是一个能跑的逐帧测距脚本import torch import cv2 CLASS_HEIGHT_MAP { person: 1.70, car: 1.50, bus: 3.10, truck: 3.60, } FX 1008.56 # 用相机标定得到的 fx按当前分辨率缩放后填入 model torch.hub.load( ultralytics/yolov5, custom, pathyolov5s.pt, sourcelocal ) model.conf 0.45 model.iou 0.45 cap cv2.VideoCapture(0) if not cap.isOpened(): raise RuntimeError(camera open failed) while True: ret, frame cap.read() if not ret: break results model(frame, size640) df results.pandas().xyxy[0] for _, row in df.iterrows(): cls_name row[name] if cls_name not in CLASS_HEIGHT_MAP: continue x1 int(row[xmin]) y1 int(row[ymin]) x2 int(row[xmax]) y2 int(row[ymax]) h_pix max(y2 - y1, 1) dist FX * CLASS_HEIGHT_MAP[cls_name] / h_pix label f{cls_name} {dist:.2f}m cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, label, (x1, max(y1 - 10, 20)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow(YOLOv5 Distance, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段脚本每分钟推理多少帧取决于硬件。CPU 上通常 310 FPSGPU 上可以到 30 FPS 以上。如果卡顿明显可以隔帧测距只用最新一帧的目标框去更新历史路径。dist的计算用的是整条检测框的高度如果目标在画面底部被裁掉一半框高只剩一半距离会突然增大一倍这就是下一章要讲的坑。5. 单目测距必踩的 5 个坑从像素抖动到安装俯仰角5.1 坑一真实高度拍脑袋距离误差跟着拍脑袋现象所有行人都按 1.7 m 算一个实际身高 1.55 m 的人站在 7 m 处测出来约 7.68 m误差接近 10%。原因距离公式里 H 是线性因子H 错多少距离就错多少。目标真实高度不是一个稳定值但公式把它当成了常量。解决在场景里先采样统计目标高度分布。比如对固定工位、固定通道的行人可以取 P50 或 P90 身高作为默认值。车辆按类别分开配置轿车和 SUV 的高度不能共用同一个数。高度配置表做成配置文件让现场调试的人改文案就能调参不要写死在代码里。5.2 坑二标定焦距和检测分辨率不匹配现象标定用 1920×1080后来为了提速把视频帧 resize 成 1280×720测距结果整体偏大或偏小目标越远偏差越明显。原因fx的单位是像素图像分辨率缩放后像素密度变了fx必须同步缩放。很多人只记得检测框坐标要缩放忘了内参也要缩放。解决统一管线分辨率。如果做不到就按宽度比例换算fx_scaled fx_calib * current_width / calib_widthcalib_width是标定时候的图像宽度current_width是实际送进 YOLOv5 的帧宽度。注意如果用了cv2.undistort且输出尺寸与标定尺寸不同同样要先换算再测距。5.3 坑三相机俯仰角一歪相似三角形直接失效现象相机向下倾斜 3° 安装近距离 2 m 的目标测成 2.8 m远距离目标反而偏差变小整体曲线不是线性比例。原因针孔公式成立的前提之一是光轴与地面平行。一旦相机有俯仰角目标顶底两点到相机的深度距离不一致简单相似三角形就不再成立。解决最省事的做法是把相机支架调平让光轴尽量与地面平行残余 1° 以内的角度在几米到十几米范围内还能接受。如果安装场景必须俯视那就不能再用这个简单公式需要额外标定俯仰角再把目标底部的像素坐标反投影到地面平面求交。工程上我的习惯是先调平再谈测距除非这个项目预算充足否则不指望靠软件算法去弥补一个歪掉的镜头。5.4 坑四检测框抖动导致距离值原地横跳现象行人站在原地不动测距结果在 4.8 m 和 6.1 m 之间跳动画面上距离文字一直在变。原因YOLOv5 的检测框每帧可能有 23 像素的波动这个波动在近距离还好到了 15 m 开外框高只有几十像素2 像素波动就能让距离跳 10% 以上。解决不要直接平滑距离值因为距离和框高不是线性关系直接平滑距离会引入滞后。更好的做法是对检测框高度做一阶低通滤波alpha 0.4 smoothed_h alpha * raw_h (1 - alpha) * smoothed_halpha越大响应越快平滑越弱alpha越小距离越稳但目标快速移动时会拖尾。我一般先用 0.30.5再根据实际视频调试。要做到更好可以把检测框和距离都挂到跟踪 ID 上对每个 ID 单独维护一个平滑状态。5.5 坑五YOLOv5 默认输入尺寸改变后框坐标没缩放现象自己写预处理把帧直接 resize 成 640×640 送进模型拿到的框坐标在 640 坐标系里画回原图全错距离也完全偏离。原因官方模型内部用 letterbox 把原图等比例缩放并填充灰边模型输出的坐标会被还原到原图坐标系。如果绕过Results自己做预处理就必须手动把坐标映射回原图分辨率。解决最简单的办法是直接用官方model(frame, size640)的返回结果不要自己写 resize。如果确实要自己实现需要保存 letterbox 的缩放比例和填充偏移再反向计算scale original_width / (letterbox_width if original_width original_height else letterbox_height) # 对应每个坐标乘以 scale再减去填充偏移这里的细节很容易出错所以我建议新手不要重写 YOLOv5 的预处理直接消费官方推理结果。等跑通整个流程后再根据性能需求决定要不要自己接管。6. 把测距结果变得可信用一组实测数据反推焦距顺带验证误差与其把标定得到的fx当成不可质疑的真理我更建议在目标检测模型落地上线之前做一次“反推焦距”的静态验证。找一个人站在已知距离上记录 YOLOv5 检测框的高度然后用公式反推每个样本对应的fx取平均值作为测距用焦距。samples [ (5.0, 340), (10.0, 170), (15.0, 113), ] H 1.70 fx_list [] for dist_m, h_pix in samples: fx_list.append(dist_m * h_pix / H) eff_fx sum(fx_list) / len(fx_list) print(feffective fx {eff_fx:.1f} px)这里反推出来的eff_fx如果和标定值差得很远说明目标高度假设、安装俯仰角、YOLOv5 的框偏斜至少有一样存在问题。此时不要急着改平滑系数先现场核对这三个条件。做完反推后我建议把结果落成一张验证表而不是只看一帧的效果实际距离 (m)检测框高度 (px)计算距离 (m)5.03405.010.017010.015.011315.020.08520.0这张表里我用的是反推样本所以看起来完全吻合。真实工作时应该留一个不参与反推的距离点作为验证样本比如只用 5 m、10 m、15 m 算eff_fx再用 20 m 的检测框去验证误差能在 10% 以内这个测距管线才算真正闭环。我现在每换一个相机或者换一个安装位置都会先拍一段行人走动的视频把每帧的“类别、检测框高、距离”导出成 CSV看一眼距离—时间曲线是否平滑。如果曲线有规律地跳优先怀疑目标高度表如果随机跳优先处理框抖动如果整体偏一个固定比例优先检查fx是否和当前分辨率匹配。这个习惯帮我避免了很多“标定好了但落地还是翻车”的尴尬。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

佛山网站建设公司哪个性比价好些:3个坑教你避开拖工期 2026/9/28 2:45:17

佛山网站建设公司哪个性比价好些:3个坑教你避开拖工期

佛山网站建设公司哪个性比价好些:3个坑教你避开拖工期 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多佛山老板问哪家好,其实心里没底,怕被坑。 别光看报价,得看响应速度。 项目背景:一家陶瓷厂的“难产”网站…

阅读更多 →
如何把 AI Agent 接上真机:Mobile MCP 移动自动化服务器实操指南(含 30+ 控制工具) 2026/9/28 2:45:03

如何把 AI Agent 接上真机:Mobile MCP 移动自动化服务器实操指南(含 30+ 控制工具)

如何把 AI Agent 接上真机:Mobile MCP 移动自动化服务器实操指南(含 30 控制工具) 【免费下载链接】mobile-mcp Model Context Protocol Server for Mobile Automation and Scraping (iOS, Android, Emulators, Simulators and Real Devices)…

阅读更多 →
React Suite 图标扩展实战:在 rsuite 中接入 Font Awesome 图标 2026/9/28 2:45:03

React Suite 图标扩展实战:在 rsuite 中接入 Font Awesome 图标

前端UI组件 【免费下载链接】rsuite 🧱 A suite of React components . 项目地址: https://gitcode.com/gh_mirrors/rs/rsuite 点击查看 免费下载 React Suite(rsuite)内置了一套基于 rsuite/icons 的图标组件,但在实…

阅读更多 →
基于深度学习的面部表情识别系统实现:从PyTorch训练到毕设演示 2026/9/28 2:45:03

基于深度学习的面部表情识别系统实现:从PyTorch训练到毕设演示

简介:这套基于深度学习的面部表情识别系统完整毕业设计项目,面向计算机相关专业正在完成毕业设计、期末大作业或课程设计的学生,也适合希望上手图像分类、卷积神经网络实战的开发者,内容覆盖数据预处理、模型训练到结果评估的完整…

阅读更多 →
ADS版图优化实战:EM-Cosimulation驱动的物理约束建模 2026/9/28 2:45:03

ADS版图优化实战:EM-Cosimulation驱动的物理约束建模

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Arm Development Studio安装激活全攻略:从下载到调试一站式实操指南 2026/9/28 2:44:56

Arm Development Studio安装激活全攻略:从下载到调试一站式实操指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉