双目测距与YOLOV5融合:从像素坐标到毫米级目标定位的实践

发布时间:2026/10/21 5:57:37
双目测距与YOLOV5融合:从像素坐标到毫米级目标定位的实践 简介YOLOV5与双目相机结合的三维测距新版本面向自动驾驶、机器人导航、无人机避障等场景适合有一定深度学习基础的视觉工程师解决实时目标定位与距离估算问题。压缩包共141个文件约40.38MB含37个Python源码、40个YAML配置、预训练PT权重、IPYNB教程、MP4演示视频及Docker部署文件覆盖模型配置、训练到部署全流程。技术核心基于Mish激活与自适应锚框结合双目视差原理通过特征匹配与立体匹配生成深度图利用YOLO预处理减少误匹配提升鲁棒性。已有2420人学习下载包内提供完整代码、权重、样例数据和文档可快速复现并二次开发。1. 双目测距 YOLOV5把目标坐标从像素换算到毫米假设你正在做一台巡检机器人YOLOV5已经很利索地给出了画面里目标的位置但只有一个二维矩形框。底盘想知道“那个人离我几米”靠这个框答不上来。双目测距就是给检测结果补上第三个维度用左右两个相机同时拍摄利用同一目标在左右画面里的像素偏差视差还原出深度。这个资源包把新版YOLOV5检测、双目相机标定、SGBM立体匹配和测距串成一整条可运行管线目标用户是做机器人避障、安防联动、无人车绕障这类项目的开发者。跟着跑完之后你拿到的不是演示用的动画而是每一帧能写出“目标在哪个坐标、相距多远”的数。2. 从选型到标定把两路图像变成可计算的立体几何2.1 双目相机选型基线、分辨率与物理极限双目测距的根基是一个简单公式深度 焦距 × 基线 / 视差。基线和焦距的单位是长度单位视差单位是像素。这个公式决定了后面所有标定、匹配、三角测量的走向也是选型时唯一的物理依据。先看基线也就是左右两个相机光心的距离。基线越长同样一个视差值对应的深度变化越平缓远距离测距越稳定但近处左右视野重叠区域变小目标太近时直接进入左右图不重叠的盲区。我在实际里见过不少人为了测远距离把基线拉得很长结果最近可测距离也跟着变大机器人走近了反而测不到这是典型选型翻车。基线短则相反近处准、远处噪声大。下面这组参数是我在多个项目里反复调整后沉淀出来的起点值适合通用USB双目模组工作距离推荐基线推荐分辨率适用场景0.5–2m60mm左右720p桌面机械臂、近距离抓取1.5–6m80–120mm720p/1080p巡检机器人、安防联动5–20m150mm以上1080p室外远距离目标定位再说分辨率和焦距。720p和1080p之间的选择不只是清晰度问题还直接影响SGBM匹配计算量和带宽占用。1080p的左右两路图像同时跑匹配对CPU占用很大GPU资源富余的可以用否则还是720p更稳。像素焦距fx的大小决定测距灵敏度长焦镜头等效焦距大能测更远但视场角窄目标容易跑出画面。对大多数室内外通用场景3.6mm镜头配合720p是比较均衡的组合。结构安装也是选型的一部分。两个摄像头需要尽量保持光轴平行、同一水平面最省事的做法是用一块铝板或3D打印支架把两个模组固定在一个刚性结构上。如果存在几度的安装误差标定旋转矩阵可以补偿一部分但误差过大的后遗症是校正后的图像边缘失真明显后续匹配质量很难救。安装这块真不值得省钱两摄像头装歪了后面所有环节都会跟着吃亏。2.2 棋盘格标定采集样本的数量与角度控制光学镜头不是完美针孔模型有畸变左右相机的相对位姿也不是出厂时右手坐标系就对齐的。标定的作用就是把这些畸变和位姿关系算出来。标定输入的核心样本是棋盘格图像采集质量直接决定最终的测距精度。我一般会采集15到25对左右同步图像。太少不够约束内参和外参太多反而可能混入模糊或反光的坏图。采集时左右相机必须同时拍因为我后面要做双目标定需要知道同一时刻左右画面里棋盘格的对应关系。下面的脚本会实时检测棋盘格内角点左右两边都检测到完整棋盘时才允许保存避免某一侧角点被遮挡导致的数据“瘸腿”。import cv2 CHECKERBOARD (11, 8) # 内角点数11列、8行不是12x9的格子数 saved 0 target 20 cap_l cv2.VideoCapture(0) # 左相机 cap_r cv2.VideoCapture(1) # 右相机 criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) while saved target: ok_l, frame_l cap_l.read() ok_r, frame_r cap_r.read() if not (ok_l and ok_r): continue gray_l cv2.cvtColor(frame_l, cv2.COLOR_BGR2GRAY) gray_r cv2.cvtColor(frame_r, cv2.COLOR_BGR2GRAY) ret_l, corners_l cv2.findChessboardCorners(gray_l, CHECKERBOARD, None) ret_r, corners_r cv2.findChessboardCorners(gray_r, CHECKERBOARD, None) # 左右必须同时检测到完整棋盘才保存否则标定结果会学偏 if ret_l and ret_r: 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) cv2.drawChessboardCorners(frame_l, CHECKERBOARD, corners_l, ret_l) cv2.drawChessboardCorners(frame_r, CHECKERBOARD, corners_r, ret_r) key cv2.waitKey(1) 0xFF if key ord( ): # 空格保存这一对 cv2.imwrite(fcalib/left_{saved:02d}.png, frame_l) cv2.imwrite(fcalib/right_{saved:02d}.png, frame_r) saved 1 print(saved pairs:, saved)findChessboardCorners期望的是内角点数量也就是棋盘格纸里每行每列中间交叉点的个数。一张12×9格子的标定板内角点是11×8。这个数填错标定程序能跑但结果全是废的。cornerSubPix做亚像素细化把角点定位精度推到亚像素级如果省掉这一步重投影误差会明显变大。采集时的角度分布比数量更重要。只在一个距离、一个角度拍20张标定出来的内参在视野边缘区域全凭外推效果可想而知。正确做法是从左到右、从上到下缓慢移动棋盘格让它覆盖画面各个区域同时让棋盘格平面相对相机有大约15到30度的倾斜角。棋盘格在画面里至少要占三分之一以上太小了角点检测不稳定。另外标定板纸张要贴在硬质平板上软纸标定测出来的焦距会飘这是很多人都踩过的坑。2.3 双目标定与立体校正四个矩阵和一次重投影误差检查左右图的单目内参标定用刚才采集的同一批数据然后做双目标定。双目标定输出几个关键量旋转矩阵R、平移向量T、本质矩阵E、基础矩阵F以及总体重投影误差RMS。T向量中的模长就是物理空间里的基线长度单位取决于你建立标定板坐标系时用的尺寸单位所以棋盘格物理边长必须量准。import cv2 import glob import numpy as np CHECKERBOARD (11, 8) square_size_mm 24.0 # 必须用卡尺量实际边长不能只看标称值 objp np.zeros((CHECKERBOARD[0] * CHECKERBOARD[1], 3), np.float32) objp[:, :2] np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2) objp * square_size_mm # 所有坐标单位是毫米 objpoints [] imgpoints_l [] imgpoints_r [] for lf in sorted(glob.glob(calib/left_*.png)): rf lf.replace(left, right) img_l cv2.imread(lf) img_r cv2.imread(rf) 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, CHECKERBOARD, None) if not ret_l: continue imgpoints_l.append(cv2.cornerSubPix(gray_l, corners_l, (11, 11), (-1, -1), criteria)) ret_r, corners_r cv2.findChessboardCorners(gray_r, CHECKERBOARD, None) if not ret_r: continue imgpoints_r.append(cv2.cornerSubPix(gray_r, corners_r, (11, 11), (-1, -1), criteria)) objpoints.append(objp) # 单目标定先拿到内参初值 ret_l, K_l, D_l, _, _ cv2.calibrateCamera(objpoints, imgpoints_l, gray_l.shape[::-1], None, None) ret_r, K_r, D_r, _, _ cv2.calibrateCamera(objpoints, imgpoints_r, gray_r.shape[::-1], None, None) # 双目标定固定内参只优化外参收敛快且不容易漂 RMS, K_l, D_l, K_r, D_r, R, T, E, F cv2.stereoCalibrate( objpoints, imgpoints_l, imgpoints_r, K_l, D_l, K_r, D_r, gray_l.shape[::-1], criteriacriteria, flagscv2.CALIB_FIX_INTRINSIC) print(RMS:, RMS) # 小于0.3像素比较理想 print(Baseline mm:, np.linalg.norm(T)) # 基线长度单位mm这里的objp用了真实物理尺寸单位是毫米。如果square_size_mm填错焦距fx和基线T会同步错一个比例视差公式里两者相消时似乎能抵消一部分误差但reprojectImageTo3D输出XYZ的缩放比例就全错了。RMS小于0.3像素说明角点提取和模型拟合都不错。但注意RMS低不等于测距准这个关系在第5章专门说。双目标定完成后再做立体校正也就是把左右相机坐标系拉成纯平移关系让同名点处在同一水平扫描线上。这一步输出的映射矩阵是后面SGBM能不能正确匹配的前提。h, w gray_l.shape R1, R2, P1, P2, Q, roi1, roi2 cv2.stereoRectify( K_l, D_l, K_r, D_r, (w, h), R, T, alpha0.0, newImageSize(w, h)) map1_l, map2_l cv2.initUndistortRectifyMap(K_l, D_l, R1, P1, (w, h), cv2.CV_32FC1) map1_r, map2_r cv2.initUndistortRectifyMap(K_r, D_r, R2, P2, (w, h), cv2.CV_32FC1)alpha0表示校正时把畸变造成的无内容黑边裁掉有效区域由roi1和roi2给出alpha1保留全部原始像素但边缘会出现无效黑边。对测距来说alpha0更干净但后面YOLO检测框和深度图对齐时要把这两个ROI的偏移量记下来否则框在边缘区域会错位。Q矩阵是重投影矩阵后面用reprojectImageTo3D把视差图转成三维点云时直接依赖它。校正效果我自己习惯用一个粗糙但有效的办法验证把左右校正图叠成上下拼接图在画面上画几条水平线找画面中间的竖立物体边缘看它是否精确落在同一条水平线上。如果左右两幅图的行没有对齐后面匹配再努力也是白费。3. 立体匹配与深度生成SGBM参数的坑与WLS滤波3.1 SGBM参数初值与调优逻辑立体匹配算法把左右校正图上每个像素的同名点找出来得到视差图。OpenCV里最常用的SGBM实现是StereoSGBM_create参数相当多。新手常把参数来回试试得头疼最后还是雪花一片。原因在于没有理解每个参数控制的物理意义。下面这组参数是我在每个新双目项目上的起点。它不完全是最优解但给你一个稳定的基准再根据实际画面微调。import cv2 def create_sgbm(num_disparities64, block_size11): stereo cv2.StereoSGBM_create( minDisparity0, numDisparitiesnum_disparities, # 必须是16的整数倍 blockSizeblock_size, # 必须是奇数范围3~15 P18 * 3 * block_size ** 2, P232 * 3 * block_size ** 2, disp12MaxDiff1, uniquenessRatio10, speckleWindowSize100, speckleRange32, preFilterCap63, modecv2.STEREO_SGBM_MODE_SGBM_3WAY) return stereonumDisparities决定视差搜索范围。它越大能表达的深度范围越宽但近距离目标对应的视差值也可能超过搜索上限表现出来是目标被抹平同时计算量线性增长。对720p画面和1.5到6米工作距离64到96是个合理起点。blockSize是匹配窗口边长窗口变大可以让低纹理区域更容易找到匹配但会把物体轮廓磨圆。P1、P2是平滑惩罚项P1惩罚视差的小变化P2惩罚大跳变这组公式按blockSize自动缩放是OpenCV文档里的推荐比例。视差图如果出现一大块一大块的“石板”把P1和P2一起调大如果物体边缘一圈毛刺把P2调小。uniquenessRatio控制匹配唯一性默认10。重复纹理多的场景比如工地围挡波纹板这个值不合适会产生大量重复匹配噪声。speckleWindowSize和speckleRange负责滤除视差图里的小块噪声块speckleWindowSize100表示小于100个像素的孤立视差块会被清除speckleRange32是清除时允许的最大视差变化。SGBM输出的原始结果是定点数真实视差要除以16。这个细节很迷惑人我见过有人拿原始整数视差直接算深度结果所有距离都变成16倍。代码里一定要显式除以16stereo create_sgbm(96, 11) disp stereo.compute(img_l_rect, img_r_rect).astype(np.float32) / 16.03.2 WLS滤波与空洞填充裸的SGBM视差图在物体边界和低纹理区域会有一堆噪声。直接对它取深度目标边缘处会出现“锯齿状”跳变。OpenCV扩展模块里有一个WLS滤波方法它是目前效果相对均衡的后处理方案。# WLS滤波需要右图匹配器配合不能只对左图做 right_matcher cv2.ximgproc.createRightMatcher(stereo) wls_filter cv2.ximgproc.createDisparityWLSFilter(stereo) disp_l stereo.compute(img_l_rect, img_r_rect).astype(np.float32) / 16.0 disp_r right_matcher.compute(img_r_rect, img_l_rect).astype(np.float32) / 16.0 # filter 函数会把右图作为辅助置信场引导深度不连续处贴合左图颜色边缘 disp_wls wls_filter.filter(disp_l, img_l_rect, None, disp_r) disp_wls np.where(disp_wls 0, 0, disp_wls)WLS的核心思路是用左图灰度作为“置信场”把深度不连续约束到颜色边缘上。物体边界和背景颜色差异明显时深度边缘不会糊成一团。代价是多一道右图匹配耗时约增加一半换来的边缘质量对后续测距取值有明显帮助。滤波后仍然存在一些无效像素主要集中在遮挡区域和低纹理区域。直接丢弃这些0值即可因为后面YOLO框取深度时会用统计量而不是单像素值。如果想让深度图看起来更完整可以用一个简单的中值填洞操作对每个无效像素取周围半径5内的有效中值来填补。但注意填洞会让目标边缘产生一部分“背景深度泄漏”对单目可视化无所谓对测距会引入误差所以我在测距管线里宁可不填洞直接在取值阶段忽略无效像素。3.3 从视差到深度公式、Q矩阵与坐标单位换算有了视差图就可以生成深度。OpenCV提供的reprojectImageTo3D是最省事的办法它利用stereoRectify输出的Q矩阵把每个像素的视差投影到相机坐标系下的三维点云。points_3d cv2.reprojectImageTo3D(disp_wls, Q) depth_map points_3d[:, :, 2] # Z值单位与标定一致这里是mmQ矩阵的结构大致是前三行负责把像素坐标和视差转成相机坐标第四行的-1/Tx分量是把视差换算成Z值。直接解读Q矩阵的数学形式对工程应用帮助不大但有一个概念必须理解Z值是该点在相机坐标系下沿光轴方向的距离它的可靠性依赖于Q矩阵中包含的焦距和基线信息。如果项目只用图像中心附近的深度用公式 depth fx * baseline / disp 也能得到近似结果。但图像边缘区域的像素在针孔模型下与光轴存在夹角直接用视差公式会引入偏差reprojectImageTo3D则给出了相机坐标系下的完整三维坐标更准确。这也是推荐直接用它而不是手写除法的原因。一个常被忽视的细节如果SGBM的minDisparity不是0默认的Q矩阵期望视差图从0开始当你把minDisparity设为非负数值时视差图实际有一个起始偏移。处理方式是保持minDisparity0让所有有效视差都落在可搜索区间内否则深度会整体偏移误差在远距离时尤其明显。4. YOLOV5新版本推理与检测框坐标对齐4.1 新版推理脚本模型加载与输出解析新版YOLOV5仓库的推理输出已经很好用但把detect.py原封不动搬进自己的双目系统并不合适。我一般写一个独立的推理函数把模型加载、前处理、坐标还原封装好方便和深度图管线对接。import torch import cv2 # 从本地仓库加载权重第二个参数custom表示加载自训练的pt文件 model torch.hub.load(本地YOLOV5仓库路径, custom, pathyolov5s.pt, force_reloadFalse) model.conf 0.45 model.iou 0.45 frame cv2.imread(test_frame.png) results model(frame) # 直接吃BGR numpy数组内部自动转RGB boxes results.xyxy[0].cpu().numpy() # N x 6按置信度降序results.xyxy是一个列表每张图一个元素每一行是x1, y1, x2, y2, conf, cls。这里的坐标是相对输入图像的像素坐标。我刻意让输入保持原始分辨率不做letterbox缩放这样YOLO输出的坐标和remap后的深度图像素位置天然一致省掉一次坐标换算。代价是检测速度略慢但对离线处理或机器人低速巡检已经足够。如果环境无法联网把YOLOV5仓库整个clone到本地然后把第一个参数换成仓库目录即可。需要注意的是torch.hub.load第一次会创建缓存不要重复加载。模型加载失败时优先检查权重文件的来源版本是否和当前仓库代码匹配这个问题在第5章踩坑里细说。4.2 检测框坐标还原letterbox与ROI偏移如果你使用官方detect.py风格或者为了检测速度把输入resize到640情况会复杂一些。letterbox做法是把原始图像等比缩放后在两侧用灰色填充到固定尺寸保证目标比例不变。检测框坐标是在填充后的图上的必须还原回原始图像坐标系才能和深度图对齐。def scale_coords(img1_shape, coords, img0_shape): img1_shape: 输入网络的尺寸 (h1, w1)如 (640, 640) img0_shape: 原始图像尺寸 (h0, w0) h1, w1 img1_shape h0, w0 img0_shape gain min(h1 / h0, w1 / w0) pad_x (w1 - w0 * gain) / 2 pad_y (h1 - h0 * gain) / 2 coords coords.copy() coords[:, [0, 2]] - pad_x coords[:, [1, 3]] - pad_y coords[:, :4] / gain # 越界坐标裁回防止取ROI时数组越界 coords[:, [0, 2]] np.clip(coords[:, [0, 2]], 0, w0 - 1) coords[:, [1, 3]] np.clip(coords[:, [1, 3]], 0, h0 - 1) return coords这段逻辑对应YOLOV5仓库utils/general.py里内置的函数。gain是等比缩放的比例pad_x和pad_y是letterbox填充的灰色区域尺寸。如果忘记减去pad检测框会整体向右下偏移偏移量在图像边界处最明显。如果同时使用了stereoRectify的alpha0裁剪还要再叠加roi1的坐标偏移。这套“双重坐标变换”特别容易漏。4.3 深度取值策略中心点、底部点与ROI统计检测框给出了目标范围但目标在框内并不是均匀填充的。框的上部常常是背景天空或墙面直接对整个框求平均深度背景会把距离拉远。我一般用ROI统计的方法而不是简单地取中心点。def compute_target_distance(depth_map, box, margin0.15): x1, y1, x2, y2 [int(v) for v in box] h_box y2 - y1 w_box x2 - x1 # 截取框中间一条横带避开顶部背景和底部地面 y_start y1 int(h_box * margin) y_end y2 - int(h_box * margin) roi depth_map[y_start:y_end, x1:x2] valid roi[(roi 200) (roi 10000)] # 0.2m~10m单位mm if len(valid) 0: return 0.0 return float(np.percentile(valid, 30)) / 1000.0 # 返回米用30%分位数而不是均值是为了抵御框内少量极远像素的干扰。对地面上的行人和车辆底部附近的深度通常更接近真实物理距离因为目标底部与地面接触处遮挡少。对空中飞行物中心横带更合理。margin参数可以按目标类型调整行人用0.15车辆可以收到0.1。如果目标被部分遮挡ROI统计比中心点稳健很多。有了单帧距离做一个简单的一阶低通滤波可以明显改善抖动smoothed_dist 0.0 for box in boxes: dist compute_target_distance(depth_map, box) smoothed_dist 0.6 * smoothed_dist 0.4 * dist这个系数决定了响应的快慢。0.6的历史权重适合机器人低速行动如果目标在快速接近把0.6降到0.4会让输出更快跟上。5. 双目测距避坑标定、匹配与融合的五次翻车记录5.1 重投影误差只有0.123米外测距偏了30厘米现象双目标定RMS很漂亮0.1像素出头拿到真实场景里测3米远的箱子程序输出2.7米。原因RMS反映的是标定板角点的回投误差它衡量的是“标定模型自洽程度”不是“测距绝对精度”。当标定板是软纸、棋盘格物理尺寸量错、或者采集图像全部集中在同一距离时内参和外参会收敛到一组自洽但偏离真实光学模型的参数。物理基线长度其实是用格子边长作为“尺子”量出来的这把尺子错了基线、焦距全都跟着错。解决采图前用卡尺量棋盘格实际边长写入square_size_mm。采图覆盖远近不同距离和多个角度。标定完成后额外做一次“验证标定”——把标定板放在已知距离用卷尺跑完整立体匹配深度生成看输出距离与真实距离的偏差。偏差超过2%就要怀疑标定数据而不是急着调SGBM参数。5.2 黑色目标在视差图上一片空洞YOLO框内取不到深度值现象目标是一辆深色车或一个黑色背包YOLO检测框很准但深度图上对应区域全是0取深度时返回0.0。原因SGBM依赖局部纹理做匹配。深色表面反射率低左右图灰度值几乎相同匹配窗口找不到独特的位置再加上相机自动曝光会把深色物体压得更暗纹理信息近乎为零。解决先尝试把blockSize从11调到15同时降低P2让视差平滑度更高这能救回一部分弱纹理区域。如果还是不行退一步在YOLO框周围搜索最近的有效深度用目标相邻区域的值近似。最可靠的办法还是给相机补光或者换用带红外结构光的目标但这就属于硬件改动了成本另算。5.3 左右画面亮度不一致深度图一半“雪花”现象深度图左半边噪声很大右半边正常尤其当场景里有大面积窗户或侧光时。原因两个USB相机各自独立自动曝光左右画面亮度、白平衡不一致SGBM算法假设左右图对应像素灰度一致这个前提被破坏后会产生系统性误匹配。解决能关自动曝光就关掉手动固定两个相机的曝光和增益。关不了的话在SGBM之前对左右校正图做直方图匹配我通常用CLAHEclahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) img_l_rect clahe.apply(img_l_rect) img_r_rect clahe.apply(img_r_rect)需要注意CLAHE会增强对比度同时也会放大传感器噪声它不是银弹。用了CLAHE之后后续深度取值的阈值也要重新标定一遍。5.4 YOLO框贴近图像边缘深度忽远忽近现象目标在画面右侧边缘YOLO框画出来了但取到的距离抖动极大有时是0有时输出几百米。原因立体校正后图像边缘存在无效像素区域这部分没有正确视差。另外如果YOLO输入做了letterbox边缘坐标还原的误差也更大框贴边时ROI里充满无效像素统计值自然被带偏。解决两个措施同时做。第一从stereoRectify拿到roi1/roi2把它们作为有效深度区域YOLO框先与有效区域求交集再取值。第二取值阶段把深度图的无效掩膜应用在ROI上只统计掩膜内的有效点。边缘目标测距本身精度就有限至少要保证输出不出现极端跳变值。5.5 新版YOLOV5加载模型报错输出类别全是0现象torch.load权重时提示反序列化失败或者模型能加载但推理结果中类别编号全部是0。原因权重文件和当前仓库代码来自不同版本。YOLOV5的仓库在不同版本之间会调整检测头结构、类别映射甚至输出层格式旧权重里的dict结构和新代码不匹配时torch反序列化直接报错勉强加载成功的也会因为头部参数错位导致类别输出错乱。解决部署环境里锁定一个稳定版本。用torch.hub.load时显式指定ref标签比如v6.2不要每次拉最新main分支除非你愿意承担不兼容风险。自训练的权重更要保证训练和推理用的是同一版本代码和依赖。固定版本这个动作是我吃过几次亏之后养成的习惯。6. 进阶把测距结果投影回图像用参照物做端到端验证管线跑通后第一步不是急着测实际目标而是先做“参照物验证”。摆一个已知距离的物体比如在1.5米、3米、5米各放一个箱子跑一遍完整流程看程序输出距离和卷尺实测距离的偏差。这个验证能一次性暴露标定、校正、匹配、取值四个环节里的系统性问题比盲目调参效率高得多。# 完整测距主循环校正 - 视差 - YOLO - 取深度 - 画回图上 for frame_l, frame_r in camera_stream(): img_l_rect cv2.remap(frame_l, map1_l, map2_l, cv2.INTER_LINEAR) img_r_rect cv2.remap(frame_r, map1_r, map2_r, cv2.INTER_LINEAR) disp_l stereo.compute(img_l_rect, img_r_rect).astype(np.float32) / 16.0 points_3d cv2.reprojectImageTo3D(disp_l, Q) depth_map points_3d[:, :, 2] results model(img_l_rect) boxes results.xyxy[0].cpu().numpy() for box in boxes: dist compute_target_distance(depth_map, box) if dist 0: continue x1, y1, x2, y2 [int(v) for v in box[:4]] label f{results.names[int(box[5])]} {dist:.2f}m cv2.rectangle(img_l_rect, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img_l_rect, label, (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)这段代码里YOLO输入直接使用左相机校正图img_l_rect保证检测坐标系和深度图完全对齐。矩形框画在img_l_rect上标签显示目标类别和距离。调试时把输出视频保存成文件逐帧检查目标距离是否有跳跃比只盯终端打印的数字直观很多。验证测距精度时我会记录一组数据真实距离、程序输出距离、误差百分比。误差在2%以内说明管线整体状态良好超过5%就需要回头检查标定数据是否足够、棋盘格尺寸是否准确、SGBM参数是否把目标边缘磨平。这个验证过程不应该只在项目开始时做一次每换一组双目相机或者每调整一次安装支架都要重新走一遍。从那以后我每换一套双目硬件第一件事就是拍标定板、跑验证标定然后再谈检测和测距。次序一旦反过来后续排查成本会成倍增加。这套流程虽然谈不上优雅但确实是让我少走最多弯路的习惯希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询