首页/新闻资讯/正文详情

双目摄像+YOLO测距:从像素框到真实米数的完整实现

发布时间:2026/9/24 18:14:50 来源:云帆数科 栏目:资讯中心
双目摄像+YOLO测距:从像素框到真实米数的完整实现
简介本资源是一套基于Python实现的双目摄像头测距与YOLO物体检测项目面向计算机视觉初学者、毕业设计选题学生及需要快速搭建视觉原型的开发者。项目将YOLOv5s预训练模型与双目视差测距结合可对视频中的目标进行识别并估算距离适用于无人驾驶、机器人导航、工业视觉检测与视频监控等场景。压缩包共105个文件约23.28MB包含23个py脚本、21个yaml配置、28个pyc缓存、15张jpg与3张png示例图、1个pt权重文件、1个mp4演示视频及ipynb教程覆盖训练、测试、检测、测距与视频处理等模块。已有221人学习下载。读者可据此获得一套可直接运行的完整工程理解YOLO检测与双目测距的衔接方式并借助中英文说明与Notebook教程快速上手适合作为毕设或课程实践的基础方案。1. 双目摄像YOLO测距从「像素框」到「真实米数」的那一步单目测距靠假设物体尺寸反推距离换一个场景就得重新标定误差能飘到让你怀疑人生。双目测距不一样它靠左右相机的视差直接算深度物理上更硬。但光有深度图没用你得知道「哪个像素属于哪个物体」——这就是 YOLO 进场的地方先检测框出目标再在框内取视差中位数算距离。这套组合在毕业设计里非常能打因为它同时覆盖了深度学习部署、相机标定、立体匹配、坐标变换四个技术点工作量够、展示效果直观、答辩时也好讲。我做过几版类似方案最深的体会是真正花时间的不是 YOLO 推理而是标定和视差图后处理。下面把我踩过的路完整拆一遍。2. 双目测距的几何原理与 YOLO 检测的衔接方式2.1 视差为什么能换算成距离双目测距的核心公式只有一个Z (f * B) / d其中Z是目标到相机的深度距离f是相机焦距像素单位B是两个相机光心之间的基线距离毫米或米d是同一个物点在左右两幅图中的横坐标差值也就是视差。这个公式来自相似三角形两个相机看到同一个点在各自图像上的位置不同位置差越大说明物体越近差越小说明越远。理解这个公式之后你会意识到几个关键约束。第一基线B越大同样视差下的测距精度越高但基线太大近处物体会超出共同视野所以常见双目模组的基线在 60mm 到 120mm 之间。第二焦距f和基线B必须通过标定得到不能拿出厂参数直接用因为装配误差会让实际值偏离标称值。第三视差d的精度直接决定距离精度而视差精度又取决于立体匹配算法的质量——这就是为什么廉价双目模组在纹理少的墙面上基本废掉。2.2 YOLO 检测框和深度图怎么对齐YOLO 输出的是(x1, y1, x2, y2, conf, cls)格式的检测框深度图输出的是每个像素的视差值。两者对齐的逻辑很直接在检测框内部取视差值的统计量通常是中位数然后代入公式算距离。但这里有个容易翻车的地方检测框内不全是目标物体可能包含背景。如果直接对框内所有像素取平均背景视差会把结果拉偏。我一般用两个策略组合一是只取框内视差置信度高的像素SGBM 会输出有效掩码二是取中位数而不是均值中位数对 outliers 更鲁棒。还有一个细节YOLO 检测的是 RGB 图深度图来自灰度图或红外图两者的分辨率可能不同。如果双目模组输出的是 1280x720 的左右图而 YOLO 输入是 640x640那检测框坐标要映射回原始分辨率再取视差。这个映射关系搞错测出来的距离会差一倍。2.3 坐标系转换从相机坐标到真实世界算出Z之后如果你还想知道物体在水平方向偏了多少、垂直方向偏了多少需要做坐标反投影X (u - cx) * Z / f Y (v - cy) * Z / f其中(u, v)是检测框中心点的像素坐标(cx, cy)是主点坐标同样来自标定。这样你就得到了物体在相机坐标系下的三维位置。如果相机是固定在某个平台上的再乘一个外参矩阵就能转到世界坐标系。实际做毕业设计时通常不需要做到世界坐标系能输出「前方 2.3 米偏左 0.4 米」就足够展示了。但如果你要做机器人抓取或者自动驾驶模拟这一步不能省。3. 环境搭建与代码复现从标定到实时测距3.1 标定先拿到 f、B、cx、cy 这四个数标定是整套流程的地基。我见过太多人跳过标定直接拿模组参数用结果测出来误差半米起步。标定分两步单目标定得到每个相机的内参和畸变系数双目标定得到两个相机之间的旋转和平移矩阵。用 OpenCV 做标定的最小代码import cv2 import numpy as np import glob # 棋盘格参数内角点数量根据你的标定板修改 CHESSBOARD (9, 6) SQUARE_SIZE 25 # 毫米 # 准备对象点 objp np.zeros((CHESSBOARD[0] * CHESSBOARD[1], 3), np.float32) objp[:, :2] np.mgrid[0:CHESSBOARD[0], 0:CHESSBOARD[1]].T.reshape(-1, 2) objp * SQUARE_SIZE objpoints [] # 3D点 imgpoints_l [] # 左图2D点 imgpoints_r [] # 右图2D点 left_images sorted(glob.glob(calib/left/*.png)) right_images sorted(glob.glob(calib/right/*.png)) for fname_l, fname_r in zip(left_images, right_images): img_l cv2.imread(fname_l) img_r cv2.imread(fname_r) gray_l cv2.cvtColor(img_l, cv2.COLOR_BGR2GRAY) gray_r cv2.cvtColor(img_r, cv2.COLOR_BGR2GRAY) ret_l, corners_l cv2.findChessboardCorners(gray_l, CHESSBOARD, None) ret_r, corners_r cv2.findChessboardCorners(gray_r, CHESSBOARD, None) if ret_l and ret_r: # 亚像素精化 criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corners_l cv2.cornerSubPix(gray_l, corners_l, (11, 11), (-1, -1), criteria) corners_r cv2.cornerSubPix(gray_r, corners_r, (11, 11), (-1, -1), criteria) objpoints.append(objp) imgpoints_l.append(corners_l) imgpoints_r.append(corners_r) # 单目标定 ret_l, mtx_l, dist_l, _, _ cv2.calibrateCamera(objpoints, imgpoints_l, gray_l.shape[::-1], None, None) ret_r, mtx_r, dist_r, _, _ cv2.calibrateCamera(objpoints, imgpoints_r, gray_r.shape[::-1], None, None) # 双目标定 ret, mtx_l, dist_l, mtx_r, dist_r, R, T, E, F cv2.stereoCalibrate( objpoints, imgpoints_l, imgpoints_r, mtx_l, dist_l, mtx_r, dist_r, gray_l.shape[::-1], flagscv2.CALIB_FIX_INTRINSIC ) # 立体校正 R1, R2, P1, P2, Q, _, _ cv2.stereoRectify( mtx_l, dist_l, mtx_r, dist_r, gray_l.shape[::-1], R, T, alpha0 ) # 保存参数 np.savez(stereo_params.npz, mtx_lmtx_l, dist_ldist_l, mtx_rmtx_r, dist_rdist_r, R1R1, R2R2, P1P1, P2P2, QQ) print(f基线 B {np.linalg.norm(T):.2f} mm) print(f左相机焦距 fx {mtx_l[0, 0]:.2f}, fy {mtx_l[1, 1]:.2f}) print(f左相机主点 cx {mtx_l[0, 2]:.2f}, cy {mtx_l[1, 2]:.2f})这段代码的逻辑是先分别对左右相机做单目标定拿到各自的内参矩阵和畸变系数然后用stereoCalibrate做双目标定得到两个相机之间的旋转矩阵R和平移向量TT的模长就是基线B最后用stereoRectify计算立体校正映射让左右图的极线对齐这样后续做立体匹配时只需要在同一行搜索对应点。参数说明CHESSBOARD是你标定板的内角点数不是方格数比如 10x7 的棋盘格内角点是 9x6。SQUARE_SIZE是每个格子的实际物理尺寸单位要和你想输出的距离单位一致用毫米的话最后算出来就是毫米。alpha0表示校正后图像会裁掉无效区域如果你不想裁就用alpha1但会引入黑边。标定采集时至少需要 15 到 20 组不同角度的图像标定板要覆盖画面各个区域包括四个角和中心。我一般会采集 30 组然后挑重投影误差小的用。3.2 立体匹配SGBM 参数怎么调标定完之后每一帧都要做立体匹配算视差图。OpenCV 提供了StereoSGBM参数比较多但真正影响测距精度的就那么几个import cv2 import numpy as np # 加载标定参数 params np.load(stereo_params.npz) mtx_l, dist_l params[mtx_l], params[dist_l] mtx_r, dist_r params[mtx_r], params[dist_r] R1, R2, P1, P2, Q params[R1], params[R2], params[P1], params[P2], params[Q] # 计算校正映射 img_size (1280, 720) map1_l, map2_l cv2.initUndistortRectifyMap(mtx_l, dist_l, R1, P1, img_size, cv2.CV_32FC1) map1_r, map2_r cv2.initUndistortRectifyMap(mtx_r, dist_r, R2, P2, img_size, cv2.CV_32FC1) # SGBM 参数 min_disp 0 num_disp 128 # 必须是16的倍数 block_size 5 stereo cv2.StereoSGBM_create( minDisparitymin_disp, numDisparitiesnum_disp, blockSizeblock_size, P18 * 3 * block_size ** 2, P232 * 3 * block_size ** 2, disp12MaxDiff1, uniquenessRatio10, speckleWindowSize100, speckleRange32, modecv2.STEREO_SGBM_MODE_SGBM_3WAY ) def compute_disparity(frame_l, frame_r): # 校正 rect_l cv2.remap(frame_l, map1_l, map2_l, cv2.INTER_LINEAR) rect_r cv2.remap(frame_r, map1_r, map2_r, cv2.INTER_LINEAR) # 转灰度 gray_l cv2.cvtColor(rect_l, cv2.COLOR_BGR2GRAY) gray_r cv2.cvtColor(rect_r, cv2.COLOR_BGR2GRAY) # 计算视差 disp stereo.compute(gray_l, gray_r).astype(np.float32) / 16.0 return disp, rect_l, rect_rnumDisparities决定了搜索范围值越大能测的最近距离越近但计算量也越大。128 的视差范围在 1280 宽度下大概能覆盖 0.5 米到 20 米左右具体取决于你的基线和焦距。blockSize影响匹配窗口大小太小噪声多太大边缘模糊5 到 11 之间比较常用。uniquenessRatio是唯一性检查值越大越严格能过滤掉误匹配但也会让视差图变稀疏10 到 15 是常见范围。P1和P2是平滑惩罚项控制视差图的平滑程度。经验公式是P1 8 * channels * blockSize^2P2 32 * channels * blockSize^2对于灰度图 channels1。如果你发现视差图太碎可以适当增大 P2。3.3 YOLO 检测与测距融合YOLO 部分用 ultralytics 库最省事模型可以用预训练的 yolov8n 或者自己训练的权重from ultralytics import YOLO import cv2 import numpy as np # 加载 YOLO 模型 model YOLO(yolov8n.pt) # 从标定参数中提取计算距离所需的量 params np.load(stereo_params.npz) P1 params[P1] f P1[0, 0] # 校正后的焦距 cx P1[0, 2] # 校正后的主点 x cy P1[1, 2] # 校正后的主点 y T params[T] if T in params else None # 基线从标定时保存的 T 向量模长获取这里假设已保存 B 120.0 # 毫米根据你的实际标定结果修改 def detect_and_measure(frame_l, frame_r): disp, rect_l, rect_r compute_disparity(frame_l, frame_r) # YOLO 检测输入左图 results model(rect_l, conf0.5, verboseFalse) for r in results: boxes r.boxes for box in boxes: x1, y1, x2, y2 map(int, box.xyxy[0].tolist()) cls_id int(box.cls[0]) conf float(box.conf[0]) label model.names[cls_id] # 在检测框内取有效视差的中位数 roi_disp disp[y1:y2, x1:x2] valid roi_disp[roi_disp 0] if len(valid) 10: continue median_disp np.median(valid) # 计算距离 if median_disp 0: Z (f * B) / median_disp # 毫米 Z_m Z / 1000.0 # 转米 # 计算物体中心的三维坐标 u_center (x1 x2) / 2 v_center (y1 y2) / 2 X (u_center - cx) * Z / f Y (v_center - cy) * Z / f # 绘制 cv2.rectangle(rect_l, (x1, y1), (x2, y2), (0, 255, 0), 2) text f{label} {Z_m:.2f}m cv2.putText(rect_l, text, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) return rect_l这段代码的关键逻辑是先对左右图做校正和立体匹配得到视差图然后用 YOLO 在左图上检测目标对每个检测框取框内有效视差的中位数代入公式算距离。conf0.5是置信度阈值你可以根据场景调整检测漏了调低误检多调高。valid roi_disp[roi_disp 0]这行过滤掉了无效视差SGBM 在纹理不足或遮挡区域会输出 0 或负值。有个细节要注意YOLO 输入是校正后的左图所以检测框坐标和视差图是对齐的不需要额外映射。但如果你用原始图做检测就得先做坐标变换。3.4 实时视频流处理与性能优化把上面串起来做成实时处理cap_l cv2.VideoCapture(0) # 左相机 cap_r cv2.VideoCapture(1) # 右相机 # 设置分辨率 cap_l.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap_l.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) cap_r.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap_r.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) while True: ret_l, frame_l cap_l.read() ret_r, frame_r cap_r.read() if not ret_l or not ret_r: break result detect_and_measure(frame_l, frame_r) cv2.imshow(Stereo YOLO, result) if cv2.waitKey(1) 0xFF ord(q): break cap_l.release() cap_r.release() cv2.destroyAllWindows()如果帧率不够优先降分辨率到 640x360SGBM 的计算量会降四倍。另一个优化是隔帧做立体匹配YOLO 每帧都跑但视差图两帧更新一次对静态场景影响不大。还可以把 YOLO 换成 yolov8n 或者更小的模型精度损失有限但速度提升明显。4. 避坑指南标定、匹配、测距中最容易翻车的五个点4.1 标定重投影误差大于 1 像素还硬用现象测出来的距离系统性偏大或偏小换个距离误差方向还不一样。原因标定图像质量差、棋盘格不平整、采集角度太单一导致内参和畸变系数不准。重投影误差超过 1 像素说明标定结果不可靠。解决重新采集标定图确保棋盘格平整可以贴在玻璃或亚克力板上覆盖画面各个区域和角度每组图左右相机同步采集。标定完后检查重投影误差单目标定控制在 0.3 像素以内双目标定控制在 0.5 像素以内。如果怎么都降不下来检查棋盘格内角点数量是否写错。4.2 基线 B 用了标称值而不是标定值现象距离在近处还行远处误差越来越大。原因模组出厂标称的基线是设计值实际装配会有偏差可能差几毫米。基线误差会直接按比例传递到距离上。解决用stereoCalibrate输出的T向量模长作为基线不要用规格书上的数字。如果T的模长和标称值差超过 5%说明标定有问题或者模组装配有问题。4.3 检测框内直接取平均视差现象同一个物体框大框小测出来的距离不一样背景复杂时误差特别大。原因检测框内包含背景像素背景视差和物体视差混在一起取平均结果被拉偏。解决只取有效视差像素大于 0 的然后用中位数而不是均值。如果背景占比很大可以进一步用 YOLO 的分割掩码替代检测框只取掩码内的视差。或者对检测框做收缩比如只取框中心 60% 的区域。4.4 SGBM 的 numDisparities 设太小导致近处物体测不到现象近处物体1 米以内视差图上一片黑算不出距离。原因numDisparities决定了最大视差搜索范围如果物体太近视差超过了设定值SGBM 就找不到匹配。解决增大numDisparities比如从 64 调到 128 或 256。但注意它必须是 16 的倍数。增大之后计算量会上升如果帧率不够就降分辨率。另一个办法是减小基线但这样远处精度会下降需要权衡。4.5 左右相机不同步导致运动物体测距抖动现象静止物体测距稳定一有运动物体距离就跳变。原因两个相机如果是分别读取的存在时间差运动物体在左右图中的位置对应不上视差计算错误。解决用支持硬件同步的双目模组或者用同一个采集卡同时采集两路。如果是 USB 相机尽量选同一型号同一固件的用cv2.VideoCapture分别读取时先各读几帧丢弃让缓冲区同步。软件层面可以对测距结果做滑动平均滤波但治标不治本。5. 把测距精度再压一截视差后处理与多帧融合技巧标定和 SGBM 调完之后单帧测距的误差大概在 5% 到 10% 左右对于毕业设计展示已经够用。但如果你想让数据更好看或者实际场景对精度要求更高还有几个技巧可以压误差。第一个是视差图的后处理。SGBM 输出的视差图边缘会有飞点我一般会做两步滤波先用cv2.medianBlur做中值滤波去掉椒盐噪声再用左右一致性检查剔除遮挡区域的误匹配。左右一致性检查的逻辑是左图算出的视差和右图算出的视差在对应点上应该一致不一致的就标记为无效。OpenCV 的StereoSGBM有disp12MaxDiff参数可以开启这个检查但更彻底的做法是分别计算左视差和右视差然后互相验证。第二个是多帧融合。对于静态或慢速运动的物体连续多帧的测距结果做滑动平均或者卡尔曼滤波能把随机误差压下去。我一般用 5 帧的滑动窗口取中位数响应速度够快抖动也明显减小。如果物体运动快就减到 3 帧。第三个是 ROI 加权。检测框内不同区域的视差可靠性不一样靠近框中心的像素通常更可靠边缘可能混入背景。我试过用高斯权重对框内视差做加权中位数中心权重高、边缘权重低比直接取中位数稳定一些。实现上就是生成一个和检测框同尺寸的高斯核然后对视差做加权排序取中位。第四个是距离分段校准。如果你有激光测距仪或者卷尺可以在几个已知距离上比如 1 米、2 米、3 米、5 米采集测距结果拟合一个校正曲线。双目测距在近距离和远距离的误差特性不一样分段校正比全局校正效果好。我一般会做一个二次多项式拟合把残差控制在 2% 以内。最后一个习惯每次换场景或者重新安装相机之后先拿一个已知距离的物体验证一下确认测距没跑偏再开始正式采集。这个习惯帮我省了很多事后返工的时间。希望帮到你。本文还有配套的精品资源点击获取

相关推荐

实拍交通标志数据集550张:txt/xml双格式,跑通yolov8训练
实拍交通标志数据集550张:txt/xml双格式,跑通yolov8训练

简介:这是一份面向深度学习目标检测方向的实拍交通标志数据集,适合正在做交通标志识别项目、准备YOLO系列算法训练或课程设计的学生与算法工程师使用。数据集共包含550张实拍图像,覆盖停止、提示、等待三类标志,并已同步提供txt与… · 2026/9/24 18:14:30

YOLOv5+RealSense D455单目测距实战:从检测框到毫米级深度值的精准映射
YOLOv5+RealSense D455单目测距实战:从检测框到毫米级深度值的精准映射

简介:本资源是一套基于YOLOv5-3.1与Intel RealSense D455深度相机实现的单目测距系统完整源码,面向计算机视觉初学者、嵌入式AI开发者及智能感知项目实践者,解决目标检测与距离估算融合落地的关键问题,适用于机器人避障、工业定位… · 2026/9/24 18:14:18

无人机目标检测数据集与YOLO训练教程:5000张标注图片+划分脚本实战
无人机目标检测数据集与YOLO训练教程:5000张标注图片+划分脚本实战

简介:一套面向无人机视觉应用的目标检测数据集,包含5000张真实场景高清图像,覆盖多种飞行环境与目标形态。数据使用LabelImg标注,标签质量较高,并同时提供VOC(xml)、COCO(json&#… · 2026/9/24 18:14:18

实时图像处理优化实战:从瓶颈定位到帧率与延迟的全面提升
实时图像处理优化实战:从瓶颈定位到帧率与延迟的全面提升

上个月在调一个实时图像检测项目,现场摄像头采集的画面在屏幕上明显“发飘”,鼠标去点画面里的目标物体,框总是慢半拍。客户的原话是:“能不能让这画面跟上手?”这个“跟上手”,翻译成技术指标就是两件事&a… · 2026/9/24 19:26:00

从RESTful设计到安全鉴权:Web开发者必懂的API实战指南
从RESTful设计到安全鉴权:Web开发者必懂的API实战指南

1. 从“会写代码”到“能交付系统”:Web开发到底卡在哪一关我见过太多开发者,写了好几年代码,CRUD 顺手拈来,前端框架玩得飞起,可真到了要独立做一个 Web 项目出来,还是会在第一步就懵住:数据从… · 2026/9/24 19:26:00

鸿蒙应用兼容性适配实战:从能跑到好跑的完整指南
鸿蒙应用兼容性适配实战:从能跑到好跑的完整指南

做鸿蒙适配这几年,我最大的感受是:让应用在 DevEco Studio 里编译通过、装上手机能打开,这只是起点,真正的硬仗全在“兼容性”上。同一个 APK 装在不同机型上,可能一个是流畅运行,另一个直接闪退&#xff1… · 2026/9/24 19:26:00

C#与Windows防火墙实战:实现《命运2》单人模式工具
C#与Windows防火墙实战:实现《命运2》单人模式工具

《命运2》的玩家社区里一直有个挺尴尬的现象:想安安静静刷个单人副本、做做任务、练练枪法,结果匹配系统硬是往你队伍里塞人,或者你想单挑某个活动却发现机制上根本不给你这个机会。Destiny 2 Solo Enabler 就是冲着这个痛点来的——它本质上… · 2026/9/24 19:26:00

SpringBoot+Vue月度绩效考核系统:从数据库设计到部署实战
SpringBoot+Vue月度绩效考核系统:从数据库设计到部署实战

1. 为什么选择绩效考核系统作为毕设/课设题目1.1 这个题目的真实定位:复杂度恰好落在能力圈内每年到了毕设选题季,总有人到处问"什么题目好过""什么题目工作量够"。我的经验是,题目并不是越新越高级越好,而是… · 2026/9/24 19:26:00

基于Matlab的ANN与DNN分类网络实现与部署实战
基于Matlab的ANN与DNN分类网络实现与部署实战

简介:面向Matlab初学与进阶者,资源基于MNIST手写数字数据集,演示从数据读取、归一化到网络训练与测试的完整流程。压缩包共5个文件,含2个.m脚本、2个csv数据文件和1个md说明文档:脚本分别实现ANN与DNN网络的构建和验证… · 2026/9/24 19:25:53

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13

1D-CNN时间序列建模实战:从Conv1d原理到工业落地
1D-CNN时间序列建模实战:从Conv1d原理到工业落地

简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26

柔软的L:汉语语流中被忽视的舌肌张力控制
柔软的L:汉语语流中被忽视的舌肌张力控制

1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码