☰
YOLOv8与OpenCV双目测距:从目标检测到精准距离估计的完整实现
2026/10/17 7:30:27 网站建设 项目流程

前一阵我接到一个需求:在园区低速场景里,不仅要认出前方的障碍物是什么,还要把它的距离报出来。障碍物可能是车、人,也可能是路口的交通灯,也就是YOLOv8预训练权重里常见的80类目标。我一开始天真地以为,目标检测已经这么成熟了,随便选一种单目方案就能搞定;真正做下来才发现,从“检测出目标”到“测得准距离”之间,还有一整条标定、立体重建、深度提取的链路要打通。这篇文章就把我基于YOLOv8和opencv双目测距算法的完整实现写出来,包括相机标定、立体校正、SGBM立体匹配、视差转深度、检测框对齐到目标,以及最后对车辆、行人、交通灯等目标的实测误差和踩坑经验。

这种“目标是什么+目标有多远”的组合需求,在很多场景里都会遇到。低速车防撞、智能监控、室外巡检、辅助泊车,本质上都需要先识别目标类别,再判断距离是否进入危险区域。适合看这篇文章的,应该是已经在用YOLOv8做过目标检测、但对双目测距还停留在“知道公式但不清楚怎么落地”阶段的开发者。我会把能直接抄的代码、参数和配置都放出来,也会把几处文档里不会写的东西单独拎出来讲。

1. 为什么我最终放弃单目和深度相机,选了双目方案

1.1 先说我当时的实际场景

这个项目最终应用在室内室外都有的园区:低速行驶的车,偶尔有行人横穿,路口有交通灯。系统需要在每帧画面里同时输出目标类别和目标距离,距离精度在近处5米内做到5%以内,10米之外尽量不要差出两三米,否则容易误报警或者漏报警。对于这种“不追求毫米级,但追求稳定和可信”的需求,关键不是某一帧算得飞快,而是长时间运行下不要频繁跳变。

所以我一开始并没有直接写算法,而是花了几天对比不同测距方案的硬件门槛和天然缺陷。这里面的坑比较典型:很多人上来就选单目方案,因为部署最简单;但单目尺度不确定这个问题,在真实场景里几乎无解。最终我整套系统用的是“普通双目相机+OpenCV标定+SGBM立体匹配+YOLOv8检测”,没有用激光雷达,也没有用带深度传感器的相机。原因很朴素:这套组合成本可控,不依赖主动光源,在户外和室内都能工作,而且80类预训练模型直接覆盖了车辆、行人、交通灯这些主要目标。

1.2 单目方案为什么先天吃亏

单目测距最大的问题是尺度缺失。一张二维图像里,一辆真实汽车和一个等比例的车模,可以投影出完全相同的像素尺寸;如果不知道目标的真实物理大小,就无法从像素高度反推出距离。某些单目方案会假设目标尺寸固定,比如“乘用车宽度约1.8米”“行人高度约1.7米”,然后通过检测框高度去换算距离。听起来可行,实际很脆弱:货车和小轿车宽度差很多,行人弯腰、蹲下、骑自行车时高度变化更大,检测框的上下边界又随姿态和遮挡频繁变化,最终距离结果经常在相邻两帧之间跳好几米。

还有一种单目方式是利用地平线或车道线消失点估算路面位置,再假设目标在地面上。这种办法在结构化道路里有点用,但目标一旦离开路面平面,比如行人站在路缘石后面,或者交通灯悬挂在半空,误差立刻失控。我给这个需求做单目预研时,10米外的车辆距离误差经常超过40%,而且没法通过调参消除,因为误差来源是几何层面的,不是模型精度问题。所以我才下了判断:只要不是纯空地平面场景,单目只能做“近大远小”的定性判断,不适合做定距告警。

1.3 深度相机也不是万能的

我也试过带主动光源的深度相机,比如结构光和ToF。结构光方案在室内近距离表现不错,但一到室外强光下就严重衰减,投射出去的编码图案几乎被太阳光淹没。ToF方案对远距离和中等距离的效果好一些,但容易受到多径反射干扰,黑色物体吸收光、镜面物体反光,都会让测距值出现明显跳变。另一点很现实:这类相机的视场角和帧率往往是固定的,想根据现场安装高度调整视野范围,限制比较多。

另一个问题是深度相机的深度图和YOLOv8的检测结果只在“像素坐标”上对齐,但不同传感器之间有时间戳不一致、视场不完全重合、分辨率不对齐的问题。我刚开始做同步时,目标在可见光图像里的位置和深度图里的深度值经常对不上,尤其目标一移动就错位。虽然这些问题可以通过硬件同步和标定解决,但综合下来并没有比普通双目省事,反而引入更多设备依赖。

1.4 双目的适用边界和选型结论

双目测距本质上是模拟人眼,用左右两个相机拍摄同一场景,通过同名点的像素位置差来恢复深度。它不需要主动光源,室内外都能用,普通工业相机或监控摄像头就能搭建。只要完成立体标定和校正,左右图像逐像素对齐后,再计算视差,就能把二维检测框和距离值绑定起来。

但我也要泼一盆冷水:双目不是万能的。它依赖场景纹理,纯色墙壁、白色车身、大面积天空这些弱纹理区域,立体匹配很容易错;距离越远,同样的视差误差会被放大得越厉害;两个相机之间的基线长度、镜头焦距也决定了系统的最远有效量程。所以后续所有工作都围绕一句话:把标定做扎实,把视差图质量控制在可接受范围,再把YOLOv8提供的语义信息用来修正深度提取的采样位置。这几个环节缺一个,最终距离都不稳。

2. 双目测距的几何原理:一句话公式 z = f·b/d

2.1 人眼的视差直觉

先举个非常容易理解的例子。你把一根手指竖在眼前,先闭左眼用右眼看,再闭右眼用左眼看,会发现手指相对于背景“跳”了一下。这个跳动就是视差。手指离眼睛越近,左右眼看到的相对位移越大;离得越远,跳动越不明显。双目相机做的事情,就是把左右两个摄像头当成两只眼睛,通过算法找到同一物体在两幅图像中的对应点,计算它在左右图像列坐标上的差值,也就是视差值。

立体校正之后,左右图像会做到行对齐,也就是说同一个空间点如果匹配成功,它的行坐标相同,只差一个列方向偏移。这个偏移量就是视差d,单位是像素。为什么要做行对齐?因为这样匹配搜索就能从“二维搜索”简化为“沿着同一行搜索”,计算量和误匹配率都会大幅下降。OpenCV里的stereoRectify和remap,就是专门为这个目的服务的。

2.2 公式推导与单位换算

双目测距最基本的公式是:

z = f × b / d

其中z是目标到相机基线的垂直距离,f是相机焦距,b是左右相机光心之间的基线长度,d是视差。需要注意单位:如果b用毫米,f用像素,那么z算出来就是毫米。很多人在这里容易搞混,因为相机内参里的fx单位本来就是像素,而标定板格子边长是毫米,两者相除后单位自然就统一到毫米了。

我举个例子。假设某对相机基线b=120毫米,左相机内参fx=800像素,某个目标在左右图中匹配到的视差d=20像素,那么距离z=800×120/20=4800毫米,也就是4.8米。这个公式非常简单,但背后有个隐含条件:左右相机必须已经完成立体校正,并且视差必须是同一空间点在两幅图中的精确列坐标差。如果直接用原始图像算,公式是不成立的。

2.3 基线与距离的关系:为什么长基线能看得更远

再看公式z = f·b/d就能明白,相同的视差d之下,基线b越长,测得的距离z越大。也就是说,如果想让系统在更远的距离上还有足够的视差分辨率,就应该加大两个相机之间的距离。反过来,基线太长也会导致近距离目标在左右视角差异过大,靠近图像边缘时容易彼此遮挡,近处的匹配难度反而上升。

还有一个更重要的误差传播关系:距离误差Δz和距离的平方成正比,和基线、焦距成反比。对公式求导可以得到 Δz ≈ z²/(f·b) × Δd,其中Δd是视差误差。这个关系说明,距离越远,同样的像素级视差误差对应到实际距离误差越大,而且是以平方速度放大的。这也是双目测距“远距离精度差”的根本原因,不是算法不给力,是物理几何决定的。

2.4 一像素视差误差到底意味着什么

我用实际参数算过一组数,基线120毫米,焦距800像素。在2米距离处,目标视差大约是48像素,1像素视差误差对应距离误差约42毫米。到了5米距离处,视差只有19.2像素,1像素误差对应约260毫米。到了10米距离处,视差只剩9.6像素,1像素误差对应距离误差已经超过1米。到了15米,单像素误差就会造成2.3米以上的抖动。

这说明什么?说明如果视差匹配精度只能做到整数像素级别,10米以外的数据基本只能用于“判断有没有目标”,不能用作精确测距。如果要改善远距离表现,要么增大基线,要么换更长焦距的镜头,要么用亚像素立体匹配算法。我当时项目里综合了安装空间和视场角,最终把有效量程定义在15米以内,并且对10米以上的结果做了非常保守的置信度过滤,宁可不出距离,也不报一个不可信的数字。

3. 标定与立体校正:80类目标测距绕不开的第一座山

3.1 标定到底在标什么

很多人以为相机标定只是“求一个内参矩阵”,其实对双目系统来说,标定要解决三类问题。第一类是单个相机自身的镜头畸变,包括径向畸变和切向畸变,这会让直线变弯、图像边缘变形。第二类是相机内参,也就是焦距、光心位置。第三类是左右相机之间的相对位姿关系,包括旋转矩阵R和平移向量T。这三类参数据如果有一个不准,后续立体校正就会残留误差,视差图看起来像模像样,但实际距离可能直接偏掉百分之十以上。

我见过不少项目把标定当成可有可无的参数获取步骤,随便拍几张棋盘格就跑,结果后面处理SGBM和YOLOv8融合时反复出现同一帧里左边目标有距离、右边目标没距离的怪问题。其实大多数情况下不是检测模型的毛病,而是左右图根本没有对齐。所以这一步我花的时间最多,也最值得。

3.2 棋盘格采集与内参计算

我用的标定板是常见的黑白棋盘格,内部角点尺寸是9×6,每个格子实际边长30毫米。这个物理边长非常重要,我习惯用游标卡尺量三次取平均,因为标定过程中它直接参与三维坐标构建,写错一个小数点,后面所有距离都会按比例漂移。采集图像时,我要求左右相机同时拍摄同一块标定板,数量一般20到30对,标定板要出现在画面的不同位置、不同角度、不同距离,既要有近处的俯拍,也要有远处的斜拍。

对应代码骨架如下:

import cv2 import numpy as np CHECKERBOARD = (9, 6) square_size = 30.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 objpoints = [] imgpoints_l = [] imgpoints_r = [] for left_path, right_path in image_pairs: left = cv2.imread(left_path) right = cv2.imread(right_path) gray_l = cv2.cvtColor(left, cv2.COLOR_BGR2GRAY) gray_r = cv2.cvtColor(right, 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: objpoints.append(objp) corners_l = cv2.cornerSubPix(gray_l, corners_l, (11, 11), (-1, -1), (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-4)) corners_r = cv2.cornerSubPix(gray_r, corners_r, (11, 11), (-1, -1), (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-4)) imgpoints_l.append(corners_l) imgpoints_r.append(corners_r)

采集时要特别注意标定板本身的平整度。打印纸容易翘边,我后来是贴在硬质铝板上使用的。还有就是不要让标定板反光,否则角点检测会时好时坏。这个环节每一对图像都要人工扫一眼,发现有重影、模糊、半块板子的图直接丢弃,不要指望算法能容忍脏数据。

3.3 立体校正确保左右图“行对齐”

得到左右相机的内参、畸变系数以及双目标定结果之后,就要做立体校正。OpenCV里通过cv2.stereoRectify可以得到左右视图的校正映射旋转矩阵,以及一个重投影矩阵Q。后面cv2.initUndistortRectifyMap配合cv2.remap,就能把原始左右图变成像素行列严格对齐的校正图。

ret, K1, D1, K2, D2, R, T, E, F = cv2.stereoCalibrate( objpoints, imgpoints_l, imgpoints_r, K1_init, D1_init, K2_init, D2_init, image_size, criteria=criteria ) R1, R2, P1, P2, Q, validPixROI1, validPixROI2 = cv2.stereoRectify( K1, D1, K2, D2, image_size, R, T, flags=cv2.CALIB_ZERO_DISPARITY, alpha=0 ) map1_l, map2_l = cv2.initUndistortRectifyMap(K1, D1, R1, P1, image_size, cv2.CV_32FC1) map1_r, map2_r = cv2.initUndistortRectifyMap(K2, D2, R2, P2, image_size, cv2.CV_32FC1) left_rect = cv2.remap(left_raw, map1_l, map2_l, cv2.INTER_LINEAR) right_rect = cv2.remap(right_raw, map1_r, map2_r, cv2.INTER_LINEAR)

这段代码里,P1和P2是校正后的投影矩阵,Q矩阵则把视差图转换为三维坐标,后面做深度提取时会用到。有一点容易被忽略:stereoRectify的image_size参数必须和实际输入图像分辨率完全一致,否则校正映射会错位。我早期犯过在调试窗口里缩小显示分辨率、却忘记修改image_size的错误,结果校正图看起来没问题,实际距离全部偏大。

3.4 如何判断标定和校正是否成功

判断标定质量不能只看重投影误差这一项数字。我的习惯是三个检查:第一,重投影误差最好在0.1像素以内,如果超过0.3像素,基本可以判定某组图像有问题或者标定板不平整。第二,校正后把左右图并排放到一起,找一个远处明显的竖直边缘,比如路灯杆,观察它在两幅图中的行坐标是否一致。如果上下错位超过两三个像素,后续视差计算会直接带进系统性误差。第三,在视差图上观察地面和物体边缘是否尖锐平滑,如果边缘出现大面积撕裂,多半是校正没做好或者相机在运行中有了位移。

另外还有一个很容易踩的坑:标定完成后,相机的安装位置和相对角度就不能再动了。双目标定得到的R和T是特定的物理安装关系,只要有一个相机被磕碰到,哪怕只偏移了半毫米,整个标定参数就要全部重来。所以我在项目里会把相机模组固定在一个金属支架上,并且在软件启动时做一次简单的校正自检,如果发现左右图极线错位变大,就直接提示重新标定,而不是继续输出不可信的距离。

4. YOLOv8目标检测与视差深度融合的实现流程

4.1 完整流水线

整个系统的在线处理流程可以拆成六步:采集左右原始图,对左右图分别remap校正,用SGBM计算校正后的视差图,把视差图转成深度图,用YOLOv8在左校正图上做目标检测,最后对每个检测框在深度图中采样并输出距离。这里有一个关键点:YOLOv8检测必须放在左校正图上,而不是原始左图,因为视差图和左校正图是像素级对齐的,检测框的坐标可以直接映射到深度图的同一位置。

我见过有人直接把YOLOv8跑在原始左图上,然后再单独算一张校正图的视差,结果检测框和深度图之间总是存在几个像素的偏移。近距离目标还好,远距离目标本身就只占几十个像素,偏移几个像素就会让采样区域完全落在背景上,距离自然不准。所以统一坐标系是第一步,不是优化项,是必须项。

4.2 关键代码骨架:检测、视差、距离一次打通

先给出一个最小可运行的骨架,假设已经提前算好了视差图disp和深度图depth:

from ultralytics import YOLO import cv2 import numpy as np model = YOLO("yolov8n.pt") baseline_mm = 120.0 fx = 800.0 left_rect = cv2.remap(left_raw, map1_l, map2_l, cv2.INTER_LINEAR) right_rect = cv2.remap(right_raw, map1_r, map2_r, cv2.INTER_LINEAR) disp = stereo.compute(left_rect, right_rect).astype(np.float32) / 16.0 depth = np.zeros_like(disp) valid = disp > 0 depth[valid] = fx * baseline_mm / disp[valid] results = model.predict(left_rect, imgsz=1280, conf=0.4, verbose=False) for box in results[0].boxes: cls = int(box.cls) conf = float(box.conf) x1, y1, x2, y2 = [int(v) for v in box.xyxy[0]] label = results[0].names[cls] if label in {"car", "truck", "bus", "person", "traffic light"}: center_x = (x1 + x2) // 2 bottom_y = y2 roi = depth[max(0, bottom_y - 10): bottom_y, max(0, center_x - 10): center_x + 10] valid_vals = roi[(roi > 0) & np.isfinite(roi)] if len(valid_vals) > 5: distance_mm = float(np.median(valid_vals)) print(label, conf, distance_mm / 1000.0)

这段代码有几个细节值得解释。第一,SGBM计算出的原始视差图通常是固定小数位数编码的,OpenCV的StereoSGBM默认把视差放大16倍存储,所以要用disp / 16.0还原成真实像素视差。这个缩放系数很多新手不知道,导致算出来的距离全都是16倍偏小。第二,深度转换时直接把无效视差置零,后面所有采样都要过滤掉零值和无穷值。第三,距离估计用中位数而不是平均值,因为ROI区域里只要混入几个错误匹配的离群点,平均值就会被拉偏很多,中位数能把离群点的影响压住。

4.3 为什么取检测框底部中心区域而不是整个框

对车辆和行人这类地面目标,我取的是检测框底部中心附近的一个小矩形区域。原因有两个:一是检测框底部通常对应车辆前缘或行人脚部,这个位置离目标与地面的接触点最近,几何意义更明确;二是车头、车身的中上部经常是光滑漆面或玻璃,在双目匹配里属于弱纹理甚至反光区域,视差不可靠,而被测目标底部附近往往有地面纹理、轮胎、腿部等特征,立体匹配的成功率更高。

如果贪方便直接取整个检测框的平均深度,结果往往会被背景严重污染。比如一辆车后面是远处的墙,检测框的上半部分有一半框到墙体,取全框平均值就会得到“车子距离很远”的错误结论。交通灯这类悬挂目标则是例外,它的有效区域高度集中在灯箱上,底部中心其实已经离开目标了,所以对交通灯我改成取检测框中心位置稍微扩大一圈的ROI,而不是底部。

4.4 深度有效性判断与置信度规则

深度采样不是拿到一个数就完事。我定了三个规则。第一,ROI内有效深度像素数量必须超过一定比例,少于阈值就认为该目标当前帧深度不可信,宁肯跳过也不输出错误距离。第二,ROI内深度值的标准差如果偏大,说明该区域存在明显误匹配或远近混叠,我会把置信度降级,而不是直接把中位数当最终结果。第三,检测框的置信度低于0.3时不做距离输出,因为此时检测框本身不稳定,采样到目标上的概率很低。

这三条规则叠加之后,会牺牲掉一部分帧的测距结果,但换来的是系统整体稳定性。我实际跑下来的体验是,偶尔缺一两帧并不会让告警逻辑崩溃,反而如果每一帧都强行输出一个有误差的距离,相邻帧之间跳来跳去,下游的逻辑很难判断到底该不该告警。稳定的低帧率输出比频繁跳变的“看似实时”更可靠。

5. SGBM参数整定:视差图质量直接决定测距精度

5.1 SGBM的核心参数和我的默认配置

OpenCV里双目立体匹配最常用的算法是SGBM,也就是半全局块匹配。它兼顾了速度和效果,参数不算多,但每一个都对结果影响很大。我整理了一套默认配置,后续再根据实际场景微调:

参数我的默认值作用说明
numDisparities160最大视差搜索范围,必须是16的倍数
blockSize5匹配窗口大小,奇数,越大越平滑
P18 * 3 * blockSize²小视差变化的平滑惩罚
P232 * 3 * blockSize²大视差变化的平滑惩罚
disp12MaxDiff1左右一致性检查的容忍度
uniquenessRatio10唯一性比率,抑制低置信度匹配
speckleWindowSize100剔除小连通噪声区域
speckleRange32连通区域内允许的视差变化
modeSGBM_3WAY三路动态规划,效果较好

blockSize对结果的影响很直观:窗口太小,纹理不足的区域会变成一片噪声;窗口太大,目标边缘会被抹平,前景和背景交界处的视差会互相污染。我的经验是先用blockSize=5跑一遍,观察边缘细节,再根据具体画面适当增大到7或9。P1和P2则控制平滑性,P2通常是P1的4倍左右,如果视差图上有太多细小空洞,可以考虑把P2再调大一些,但调太大会把小物体的真实视差直接抹掉。

5.2 从最近距离反推numDisparities

numDisparities是很多人不会算的参数。它表示算法在每条极线上搜索多少个视差级别,也就是最近能测到多近的目标。公式是:最大视差约等于 f × b / 最近距离。假设fx=800,b=120毫米,希望最近能测0.5米,那么最大视差=800×120/500=192像素。在OpenCV里numDisparities必须是16的倍数,所以取192或稍大一点的208。如果这个值设小了,近距离目标会全部显示为黑色空洞,测不出距离;如果设得过大,计算量上升,而且远距离处会增加误匹配。

我建议先根据公式算出理论值,再临时把视差图以伪彩色方式显示出来,观察最近目标的区域是否还有有效视差。如果最近目标区域亮起来了,说明范围设置合适。盲目的把numDisparities调到256或更高并不可取,因为在同一个速度要求下,搜索范围越大,匹配歧义越多,反而会产生更多误匹配点。

5.3 深度图滤波与帧间稳定

无论SGBM参数怎么调,原始深度图里总会有空洞和离群点。我常用的后处理流程是:先做一次左右一致性检查,通过disp12MaxDiff参数把不一致的像素置为无效;然后用中值滤波或者形态学闭运算填补小空洞;接着对时间轴做一阶低通滤波或者滑动中值滤波。帧间稳定性非常重要,因为距离告警最忌讳的就是同一目标在5米和8米之间来回跳。

这里要特别注意,时间滤波必须按检测框ID进行,不能对整幅深度图做时间平均。如果目标移动了,整幅图的平均方式会让旧位置的深度痕迹残留在新位置上,导致距离滞后。我的做法是维护一个目标跟踪列表,每个目标框内部只保留最近N帧的有效中位距离,输出时取这N帧的中位数,这样既平滑又不会明显滞后。如果目标短暂丢失又重新检测到,我会清空历史缓存,避免把很久之前的距离数据重新带入。

5.4 曝光与图像质量:被忽视的变量

让我很意外的是,很多项目的测距不稳,最后发现不是算法问题,而是两个相机的曝光参数不一致。左右相机如果自动曝光各自独立调整,同一物体的亮度就不同,立体匹配的代价函数会受影响,视差图在亮度差异大的区域会出现系统性偏移。我的做法是在系统初始化时手动设定左右相机的曝光时间、增益和白平衡,并且锁死自动调节。对于光照变化剧烈的场景,再定时根据左图亮度重新计算一组统一参数,同时写到左右两个相机上。

图像分辨率同样重要。原始分辨率越高,小目标和远距离物体的视差越可能撑到几个像素以上。我测试时用720p分辨率,15米处的汽车视差只有几个像素,SGBM在这么小的视差范围里很难稳定匹配;后来换成1080p甚至更高分辨率,情况明显改善。当然分辨率提高后计算量也上去了,所以我在实际部署里会结合YOLOv8的推理耗时,在帧率和分辨率之间选一个平衡点。

6. 实测结果与误差拆解:3米、10米、15米各差多少

6.1 在模拟项目X场景下的实测数据

我把整套系统搭在一个室内外综合测试场里,模拟项目X的园区低速预警场景。测试时车辆停在固定距离,系统输出连续200帧的距离中位数。相机的基线是120毫米,镜头焦距对应fx大约800像素,图像分辨率1280×720,YOLOv8使用nano权重。下表是其中一组比较有代表性的结果:

真实距离(m)输出中位数(m)相对误差观察现象
2.02.052.5%视差充足,非常稳定
5.05.122.4%轻微波动,可接受
8.07.861.8%视差匹配仍较好
12.011.206.6%误差开始明显放大
15.013.2012.0%视差只有几个像素,不可靠

这个表格说明两个问题。第一,5到8米范围内的测距精度已经能满足园区低速场景的使用需求。第二,一旦超过10米,误差从百分之几迅速增长到百分之十几,这正好印证了第2章的误差传播推导。所以我在系统里把10米以上的距离标记为“参考值”,不会直接驱动报警逻辑,而是结合目标运动趋势再做决策。

6.2 误差来源按影响程度排序

按我踩过的坑,误差来源排序大概是这样的。第一位是立体标定误差,只要左右相机的相对位姿标偏了,所有距离都会有一个随距离变化的系统性偏移,这种偏差很难靠后处理消除。第二位是SGBM误匹配,弱纹理和重复纹理区域会产生离群视差,尤其在白色车身、深色衣物、天空背景下最明显。第三位是深度采样位置不准确,检测框微小的左右抖动会让ROI从目标边缘滑到背景上。第四位才是YOLOv8检测框本身的位置回归误差。

理解了误差来源,就不难明白为什么我会在标定和视差图上花那么多时间。YOLOv8的检测框就算非常准,它也只是提供“目标在哪里”的信息;目标处的深度准不准,完全取决于标定、匹配和采样三者配合。很多人把精力全放在换更大的模型、调更高的检测精度上,对最终测距精度的提升其实非常有限。

6.3 车辆、行人、交通灯三类目标的差异化处理

不同类型目标在双目测距里有不同的性格。车辆是大目标,检测框稳定,但车身反光和纯色漆面容易造成视差空洞,所以我把采样区放在检测框底部中心附近的地面接触区域,并适当扩大ROI宽度来获取更多有效深度点。行人比车辆难一些,姿态变化多,检测框上下界会随手臂和腿的位置变化,底部中心点相对稳定,但行人衣物纹理弱,夜间更是难匹配,所以我对行人距离加了更严格的置信度过滤,宁可少报也不乱报。

交通灯是三类目标里最特殊的。它一般是悬挂的小目标,在高分辨率下也只有几十个像素,低分辨率下经常检测不到。即使检测到了,灯箱本身光滑、反光,SGBM很容易在它周围产生误匹配。我对交通灯的处理是:加大输入图像的推理尺寸,优先保证小目标召回;采样时不取底部而取检测框中心;对中心ROI的中位数距离再做一次时间平滑。这样一个流程下来,10米内的交通灯距离基本能用,10米外只能做是否存在判断。

6.4 我个人踩过最深的坑和最后一条建议

整个项目做下来,我最后悔的是早期花了大把时间调YOLOv8的模型参数和置信度阈值,却低估了标定和视差图质量的重要性。有一次某位同事不小心挪动了相机支架,我排查了半天SGBM参数、检测置信度、深度滤波逻辑,最后才发现是物理结构变了。从那以后我把“每次启动都检查极线对齐”写成了固定流程,从此再没有为类似的问题熬夜。

如果现在让我再搭一遍,我会按这个顺序来:先把两个相机固定得牢不可动,完成严格标定,用伪彩色视差图验证画面里每个目标的轮廓是否清晰;再跑通SGBM和深度转换,把静态目标的距离输出和卷尺实测值对比;最后再接YOLOv8的检测框做采样对齐。这样的顺序可以把每一层的问题隔离清楚,而不是把检测、匹配、标定三个环节的错误搅在一起,到时候根本不知道先调哪个。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询