简介:这是一套面向计算机视觉初学者与毕业设计学生的双目测距与目标检测实战项目,基于Python编写,将YOLO目标检测与双目摄像头深度测距相结合,可对视频中的物体进行识别并估算距离,适用于无人驾驶、机器人导航、工业视觉检测与视频监控等场景。资源包共105个文件,约23.28MB,包含23个py脚本、21个yaml配置、28个pyc编译文件,以及jpg、png图像素材、sh运行脚本、md说明文档、ipynb教程、pt预训练权重与mp4测试视频等,覆盖模型训练、检测推理、测距计算与视频处理等完整模块。目前已有221人学习下载。项目提供可直接运行的代码与预训练模型,读者能借此掌握YOLO检测流程、双目视差测距原理及图像矫正匹配等关键步骤,并参考中英文说明与Notebook教程快速上手,适合作为毕业设计或课程实践的完整参考方案。
1. 双目摄像 + YOLO 测距:一套能跑通、能写进毕业设计的 Python 方案
很多人第一次做「视觉测距」时,会先想到单目测距:一张图、一个已知物体高度、一个相似三角形公式,代码不到二十行就能出结果。但真把摄像头架到桌面上,你会发现同一个杯子往前挪十厘米,算出来的距离能飘出半米——单目测距对物体真实尺寸和相机内参太敏感,稍微换个场景就翻车。双目摄像头的价值就在这里:它不依赖「这个物体到底多大」,而是靠左右两幅图里同一个点的像素差(视差)反推深度,理论上只要标定准、匹配稳,测距精度就能压到厘米级。再叠上 YOLO 做物体检测,你就能从「知道画面里有个东西」升级到「知道它在画面哪个位置、离我多远」。
这套方案适合三类人:正在找毕业设计题目的本科生,想用 Python 快速搭一个能演示、能答辩的视觉系统;做机器人、AGV、智能小车方向的工程师,需要一个低成本测距模块;以及刚学完 YOLO 检测、想再往前迈一步到 3D 感知的开发者。整条链路是:双目摄像头采集 → 双目标定 → YOLO 检测左右图目标 → 在目标框内做立体匹配求视差 → 视差转深度 → 输出物体三维坐标。下面按「先立住原理、再动手复现、最后讲坑」的顺序拆开讲,代码都能直接抄。
2. 双目测距的几何原理与 YOLO 检测的衔接点
2.1 视差为什么能换出深度:把公式拆到能自己推
双目测距的核心就一个公式:Z = f × B / d。Z 是深度(物体到相机的距离),f 是焦距(像素单位),B 是两个摄像头光心之间的距离(基线),d 是视差(同一个物点在左右图中横坐标的差值)。这个公式来自相似三角形:左右相机光心相距 B,成像平面在焦距 f 处,同一个空间点在左右成像平面上的横坐标差就是 d,三角形相似直接给出 Z 和 d 成反比。
理解这个反比关系很关键。基线 B 越大,同样视差 d 对应的深度分辨率越高,但 B 太大两只摄像头看到的画面重叠区域变小,匹配会变难;焦距 f 越大,远处物体也能有足够视差,但视野变窄。我一般建议桌面级 demo 用 B 在 6~12 厘米、分辨率 640×480 起步,这个组合在 0.3~3 米范围内表现比较稳。
视差 d 的精度直接决定深度精度。对公式求导能看出,深度误差和视差的平方成反比:ΔZ = Z² × Δd / (f × B)。也就是说物体越远,同样的视差误差带来的深度误差越大。这就是为什么双目在近处准、远处飘——不是算法不行,是几何决定的。做毕业设计时把这句话写进论文,答辩老师会觉得你真懂。
2.2 YOLO 在双目链路里到底干什么
YOLO 在这套系统里不是用来测距的,它的职责是把「对整幅图做立体匹配」缩小到「只对目标框做匹配」。整幅图做稠密立体匹配(比如 SGBM)会得到一张视差图,但背景纹理弱、重复纹理多的区域匹配噪声极大,直接拿来做测距不可靠。用 YOLO 先框出目标,再只在框内做匹配,等于给匹配算法划定了「这里一定有东西」的区域,鲁棒性提升非常明显。
衔接点在于:YOLO 在左图和右图分别检测,得到两组框,然后做左右框配对。配对逻辑是——同一个物体在左右图中的框,纵坐标基本一致(因为双目是水平放置的),横坐标差一个视差量。所以配对时优先看纵坐标重叠度,再看横坐标差是否在合理视差范围内。配对成功后,在左框和右框的重叠区域内取特征点做匹配,算出该目标的平均视差,代入公式得到深度。
这里有个容易忽略的点:YOLO 检测的是「物体」,而测距需要的是「同一个物理点在左右图的像素差」。所以不能直接拿框中心点的横坐标差当视差——框中心是检测框的几何中心,左右图里同一个物体的框中心不一定对应同一个物理点。正确做法是在框内提取特征点(ORB、SIFT 或简单的块匹配),逐点算视差再取中位数,这样抗噪。
2.3 从检测框到三维坐标的完整数据流
把整条链路串起来:左相机图像和右相机图像同时送入 YOLO,得到左框列表和右框列表;对每个左框,在右框列表里找纵坐标最匹配的框;配对后,在左框内用cv2.goodFeaturesToTrack提特征点,在右框对应区域用cv2.calcOpticalFlowPyrLK或cv2.matchTemplate做匹配;过滤掉视差为负或过大的异常点,取中位数作为该目标的视差 d;代入 Z = f × B / d 得到深度;再用相机内参把像素坐标反投影到相机坐标系,得到物体的 (X, Y, Z)。
这套流程里,标定质量决定下限,匹配质量决定上限。标定不准,f 和 B 都是错的,后面全白搭;匹配不稳,视差抖动,深度就跳。所以下一章先把标定做扎实。
3. 环境搭建与双目标定:把 f、B、畸变系数拿到手
3.1 Python 环境与依赖安装
先明确版本:Python 3.8~3.10 最稳,YOLO 用 ultralytics 包,OpenCV 用 4.5 以上。不建议用最新版 Python,某些科学计算包还没跟上。安装命令如下:
# 创建虚拟环境,避免污染系统 Python python -m venv stereo_yolo_env # Windows 激活 stereo_yolo_env\Scripts\activate # Linux / macOS 激活 source stereo_yolo_env/bin/activate # 安装核心依赖 pip install opencv-python==4.8.1.78 pip install opencv-contrib-python==4.8.1.78 pip install ultralytics pip install numpy pip install matplotlibopencv-contrib-python必须装,因为cv2.stereoCalibrate、cv2.StereoSGBM_create这些函数在 contrib 包里。ultralytics 会自动拉取 YOLOv8 的预训练权重,第一次运行会下载yolov8n.pt,大约 6MB。如果网络慢,可以手动下载后放到项目根目录,代码里指定路径即可。
提示:不要同时装
opencv-python和opencv-python-headless,会冲突。如果服务器没图形界面,用 headless 版本,但本地调试建议用完整版。
3.2 双目标定:棋盘格采集与参数求解
标定的目的是拿到左右相机的内参矩阵、畸变系数,以及两个相机之间的旋转矩阵 R 和平移向量 T。T 的模长就是基线 B。步骤是:打印一张棋盘格(建议 9×6 角点,方格边长 25mm),左右相机同时拍 15~20 组不同角度的照片,然后跑标定脚本。
import cv2 import numpy as np import glob # 棋盘格参数:内角点数量,不是方格数 CHESSBOARD = (9, 6) # 方格实际边长,单位毫米,根据你打印的尺寸改 SQUARE_SIZE = 25.0 # 生成棋盘格三维坐标(Z=0平面) 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 = [] # 三维点 imgpoints_l = [] # 左图角点 imgpoints_r = [] # 右图角点 left_images = sorted(glob.glob('calib/left/*.jpg')) right_images = sorted(glob.glob('calib/right/*.jpg')) for lpath, rpath in zip(left_images, right_images): img_l = cv2.imread(lpath) img_r = cv2.imread(rpath) 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) # 双目标定,固定内参只优化外参 flags = cv2.CALIB_FIX_INTRINSIC criteria_stereo = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 1e-5) 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], criteria=criteria_stereo, flags=flags ) # 基线就是平移向量的模长 baseline = np.linalg.norm(T) print(f"基线 B = {baseline:.2f} mm") print(f"左相机内参:\n{mtx_l}") print(f"右相机内参:\n{mtx_r}") # 保存标定结果 np.savez('stereo_calib.npz', mtx_l=mtx_l, dist_l=dist_l, mtx_r=mtx_r, dist_r=dist_r, R=R, T=T, baseline=baseline)这段代码的逻辑:先用单目标定给左右相机各算一套内参和畸变系数,再把这些作为初值传给stereoCalibrate,用CALIB_FIX_INTRINSIC固定内参只优化外参。这样做的原因是双目标定如果同时优化所有参数,容易过拟合到某几张图,反而降低泛化性。cornerSubPix做亚像素优化能把角点定位精度提到 0.1 像素级,对后续测距影响很大。
参数说明:CHESSBOARD是内角点数,不是方格数,9×6 的棋盘格有 8×5 个内角点,别填错;SQUARE_SIZE必须和你实际打印的方格边长一致,单位随意但全程统一;criteria_stereo的迭代次数和精度阈值,一般 100 次、1e-5 够用。
3.3 立体校正:让左右图行对齐
标定完还要做立体校正,目的是让左右图像的极线水平对齐——校正后,同一个物点在左右图中的纵坐标相同,匹配时只需要在同一行找,搜索空间从二维降到一维,速度和准确率都大幅提升。
# 读取标定结果 calib = np.load('stereo_calib.npz') mtx_l, dist_l = calib['mtx_l'], calib['dist_l'] mtx_r, dist_r = calib['mtx_r'], calib['dist_r'] R, T = calib['R'], calib['T'] image_size = (640, 480) # 和你采集时分辨率一致 # 计算校正映射 R1, R2, P1, P2, Q, roi1, roi2 = cv2.stereoRectify( mtx_l, dist_l, mtx_r, dist_r, image_size, R, T, flags=cv2.CALIB_ZERO_DISPARITY, alpha=0 ) # 生成映射表,后续每帧直接用 map1_l, map2_l = cv2.initUndistortRectifyMap(mtx_l, dist_l, R1, P1, image_size, cv2.CV_16SC2) map1_r, map2_r = cv2.initUndistortRectifyMap(mtx_r, dist_r, R2, P2, image_size, cv2.CV_16SC2) np.savez('stereo_maps.npz', map1_l=map1_l, map2_l=map2_l, map1_r=map1_r, map2_r=map2_r, Q=Q, P1=P1, P2=P2)alpha=0表示校正后只保留有效像素,边缘会被裁掉但不会引入黑色无效区域;CALIB_ZERO_DISPARITY让主点在左右图中横坐标一致,简化后续计算。Q是重投影矩阵,后面把视差图转成三维点云时要用。校正完建议用cv2.remap跑一帧看看,左右图同一行应该能对上同一个物体,如果对不上说明标定有问题,回去重拍。
4. YOLO 检测与双目匹配的代码实现
4.1 用 YOLOv8 在左右图上做检测
YOLOv8 的调用非常简洁,ultralytics 把预处理、推理、后处理都封装好了。下面这段代码同时处理左右图,返回检测框和类别:
from ultralytics import YOLO import cv2 import numpy as np # 加载预训练模型,首次运行会自动下载 model = YOLO('yolov8n.pt') def detect_stereo(img_l, img_r, conf=0.5): """对左右图分别检测,返回框列表""" results_l = model(img_l, conf=conf, verbose=False)[0] results_r = model(img_r, conf=conf, verbose=False)[0] boxes_l = results_l.boxes.xyxy.cpu().numpy() # [x1,y1,x2,y2] boxes_r = results_r.boxes.xyxy.cpu().numpy() cls_l = results_l.boxes.cls.cpu().numpy() cls_r = results_r.boxes.cls.cpu().numpy() return boxes_l, cls_l, boxes_r, cls_r # 读取校正后的左右图 calib_maps = np.load('stereo_maps.npz') map1_l, map2_l = calib_maps['map1_l'], calib_maps['map2_l'] map1_r, map2_r = calib_maps['map1_r'], calib_maps['map2_r'] cap_l = cv2.VideoCapture(0) # 左相机 cap_r = cv2.VideoCapture(1) # 右相机 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 # 先校正,再检测 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) boxes_l, cls_l, boxes_r, cls_r = detect_stereo(rect_l, rect_r, conf=0.5) # 后续匹配逻辑见下一节conf=0.5是置信度门限,调低会检出更多目标但误检增加,调高则漏检。室内场景我一般用 0.4~0.5,室外光照复杂时用 0.6。yolov8n.pt是最小的 nano 模型,速度最快,精度够 demo 用;如果要更准可以换yolov8s.pt或yolov8m.pt,但帧率会下降。
4.2 左右框配对:纵坐标对齐 + 类别一致
配对逻辑是这套系统的关键一步。校正后同一个物体在左右图的纵坐标基本一致,所以配对时先看类别是否相同,再看纵坐标重叠度,最后看横坐标差是否在合理视差范围内。
def match_boxes(boxes_l, cls_l, boxes_r, cls_r, max_disparity=200): """左右框配对,返回配对列表 [(idx_l, idx_r, disparity)]""" pairs = [] used_r = set() for i, (bl, cl) in enumerate(zip(boxes_l, cls_l)): best_j = -1 best_score = float('inf') for j, (br, cr) in enumerate(zip(boxes_r, cls_r)): if j in used_r: continue if cl != cr: # 类别必须一致 continue # 纵坐标中心差 cy_l = (bl[1] + bl[3]) / 2 cy_r = (br[1] + br[3]) / 2 dy = abs(cy_l - cy_r) # 横坐标差(左图物体应该在右图左边,所以 x_l > x_r) cx_l = (bl[0] + bl[2]) / 2 cx_r = (br[0] + br[2]) / 2 dx = cx_l - cx_r if dy > 30: # 纵坐标差太大,不可能是同一物体 continue if dx < 0 or dx > max_disparity: # 视差范围检查 continue # 综合评分:纵坐标差 + 视差偏离中值的程度 score = dy + abs(dx - 50) * 0.1 if score < best_score: best_score = score best_j = j if best_j >= 0: used_r.add(best_j) br = boxes_r[best_j] cx_l = (bl[0] + bl[2]) / 2 cx_r = (br[0] + br[2]) / 2 pairs.append((i, best_j, cx_l - cx_r)) return pairsmax_disparity=200是视差上限,对应最近测距距离。按 Z = f×B/d,如果 f=600 像素、B=60mm,d=200 时 Z=180mm,也就是最近能测 18 厘米。dy > 30是纵坐标容差,校正好的话这个值可以设小到 10,校正差就设大。评分函数里abs(dx - 50) * 0.1是经验项,假设典型视差在 50 左右,偏离太多降权,这个值根据你的基线调整。
4.3 框内特征匹配求视差,代入公式算深度
配对完成后,在左框内提特征点,在右框对应区域做匹配,过滤异常点后取中位数作为该目标的视差:
def compute_disparity(rect_l, rect_r, box_l, box_r): """在框内做特征匹配,返回中位数视差""" x1, y1, x2, y2 = map(int, box_l) # 左框区域 roi_l = rect_l[y1:y2, x1:x2] if roi_l.size == 0: return None # 在左框内提角点 corners = cv2.goodFeaturesToTrack( cv2.cvtColor(roi_l, cv2.COLOR_BGR2GRAY), maxCorners=50, qualityLevel=0.01, minDistance=5 ) if corners is None: return None # 右图搜索区域:左框向右扩展 max_disparity rx1 = max(0, x1 - 200) rx2 = min(rect_r.shape[1], x2 + 50) roi_r = rect_r[y1:y2, rx1:rx2] if roi_r.size == 0: return None disparities = [] for c in corners: px, py = c.ravel() # 左图全局坐标 gx = x1 + px gy = y1 + py # 在右图对应行做模板匹配 template = cv2.cvtColor(roi_l, cv2.COLOR_BGR2GRAY)[ max(0, int(py)-5):int(py)+6, max(0, int(px)-5):int(px)+6 ] if template.size == 0: continue search = cv2.cvtColor(roi_r, cv2.COLOR_BGR2GRAY) res = cv2.matchTemplate(search, template, cv2.TM_CCOEFF_NORMED) _, max_val, _, max_loc = cv2.minMaxLoc(res) if max_val < 0.7: # 匹配质量太差,丢弃 continue # 右图匹配点全局横坐标 match_x = rx1 + max_loc[0] + 5 d = gx - match_x if 0 < d < 200: disparities.append(d) if len(disparities) < 3: return None return float(np.median(disparities)) def disparity_to_depth(d, f, B): """视差转深度,单位与 B 一致""" if d <= 0: return None return f * B / dgoodFeaturesToTrack的qualityLevel=0.01控制角点质量,值越小提的点越多但噪声也越多;minDistance=5保证角点之间至少隔 5 像素,避免聚集。matchTemplate用TM_CCOEFF_NORMED归一化互相关,max_val < 0.7过滤掉匹配质量差的点。最后取中位数而不是平均值,是因为中位数对异常值不敏感——哪怕有几个点匹配错了,中位数依然稳。
焦距 f 从标定结果里取P1[0, 0](校正后的左相机焦距),基线 B 从np.linalg.norm(T)取。代入disparity_to_depth就得到深度。如果要三维坐标,用cv2.reprojectImageTo3D配合 Q 矩阵,或者手动反投影:X = (u - cx) × Z / f,Y = (v - cy) × Z / f。
5. 避坑与排查:双目 + YOLO 测距最容易翻车的五个地方
5.1 测距值整体偏大或偏小,但线性度还行
现象:测出来的距离和卷尺量的差一个固定比例,比如实际 1 米测出 1.2 米,实际 2 米测出 2.4 米。
原因:基线 B 或焦距 f 不准。最常见的是棋盘格打印时被打印机缩放,比如你设SQUARE_SIZE=25,实际打出来只有 24.3mm,导致标定出的 B 和 f 都偏小,深度整体偏大。
解决:打印后拿卡尺量一下实际方格边长,把真实值填进SQUARE_SIZE。另外检查stereoRectify后用的焦距是不是P1[0,0],不是原始mtx_l[0,0]——校正会改变焦距。
5.2 同一物体深度跳变严重,帧间抖动大
现象:物体静止不动,但输出的深度值在 ±20cm 范围内跳。
原因:框内特征点太少或匹配质量差,视差中位数不稳定。纹理弱的物体(白墙、纯色盒子)尤其明显。
解决:换用cv2.StereoSGBM_create做稠密匹配,在框内对视差图取中位数,比稀疏特征点稳。参数上把minDisparity=0、numDisparities=128、blockSize=5起步调。另外可以加时间滤波,对连续 5 帧的深度取中位数再输出。
5.3 YOLO 在右图漏检,导致配对失败
现象:左图检测到了目标,右图没检测到,配对列表为空。
原因:左右相机曝光不一致,或者目标在右图边缘被裁掉。双目相机如果自动曝光各自独立,左右图亮度差大,YOLO 在暗的那张上容易漏。
解决:把两个相机的曝光、增益、白平衡全部锁死,手动设成一样的值。如果目标在右图边缘,说明基线太大或目标太近,适当减小基线或拉远拍摄距离。也可以在右图用更低的conf阈值单独跑一次,提高召回。
5.4 校正后左右图行不对齐,匹配全乱
现象:校正后的左右图,同一个物体不在同一水平线上,纵坐标差几十像素。
原因:标定图像数量不够或角度太单一,导致外参 R、T 估计不准。常见于只拍了 5~6 组、且都是正对棋盘格的照片。
解决:重拍,至少 15 组,棋盘格要覆盖画面四个角和中心,倾斜角度在 ±30 度内变化。拍的时候左右相机必须同步触发,不能先拍左再拍右,否则运动物体标定会引入误差。
5.5 远处物体测距误差巨大,近处还行
现象:1 米内误差几厘米,3 米外误差半米以上。
原因:这是双目几何的固有特性,深度误差和 Z² 成正比。3 米处视差可能只有十几个像素,1 个像素的视差误差就带来几十厘米的深度误差。
解决:接受这个边界,别指望双目在远距离做高精度测距。如果必须测远,增大基线 B 或提高分辨率。工程上一般给深度值加一个置信度,视差小于某个阈值(比如 10 像素)时标记为「低置信」,不输出具体数值。
6. 把测距结果用起来:三维坐标输出与精度验证的实操技巧
走到这一步,你已经能拿到每个检测目标的深度 Z 了。但毕业设计或实际项目里,光有 Z 不够,通常还需要物体在相机坐标系下的 (X, Y, Z),甚至映射到世界坐标系。这一章讲两个进阶点:怎么把像素坐标反投影成三维坐标,以及怎么系统地验证你的测距精度。
先说反投影。校正后的图像,主点坐标和焦距从P1矩阵取:fx = P1[0,0]、fy = P1[1,1]、cx = P1[0,2]、cy = P1[1,2]。已知目标框中心像素 (u, v) 和深度 Z,反投影公式是:
def pixel_to_3d(u, v, Z, P): """像素坐标 + 深度 -> 相机坐标系三维点""" fx, fy = P[0, 0], P[1, 1] cx, cy = P[0, 2], P[1, 2] X = (u - cx) * Z / fx Y = (v - cy) * Z / fy return X, Y, Z # 示例:框中心在 (320, 240),深度 1.5 米 P1 = np.load('stereo_maps.npz')['P1'] X, Y, Z = pixel_to_3d(320, 240, 1500, P1) # 单位毫米 print(f"物体三维坐标: X={X:.1f}mm, Y={Y:.1f}mm, Z={Z:.1f}mm")注意单位统一:如果标定时SQUARE_SIZE用毫米,那 Z 就是毫米,X、Y 也是毫米。要转米就整体除以 1000。P1是校正后的左相机投影矩阵,不是原始内参矩阵,这点容易搞混。
再说精度验证。别只测一两个点就说「准」,要有系统方法。我一般这样做:把棋盘格立在已知距离处(用卷尺量,比如 500mm、1000mm、1500mm、2000mm、2500mm),每个距离测 30 帧,记录深度输出的均值和标准差。然后算三个指标:绝对误差(均值减真值)、相对误差(绝对误差除以真值)、重复性(标准差)。下面是一个验证脚本的框架:
def evaluate_depth(true_distances, measured_list): """true_distances: 真值列表(米) measured_list: 每个真值对应的多次测量列表""" print(f"{'真值(m)':<10}{'均值(m)':<10}{'绝对误差(m)':<12}{'相对误差':<10}{'标准差(m)':<10}") for true_d, measures in zip(true_distances, measured_list): arr = np.array(measures) mean = arr.mean() std = arr.std() abs_err = abs(mean - true_d) rel_err = abs_err / true_d print(f"{true_d:<10.2f}{mean:<10.3f}{abs_err:<12.3f}{rel_err:<10.2%}{std:<10.3f}") # 假设你采集了 5 个距离,每个距离 30 次测量 true_dists = [0.5, 1.0, 1.5, 2.0, 2.5] measured = [ [0.51, 0.49, 0.52, ...], # 0.5m 的 30 次测量 [1.02, 0.98, 1.01, ...], # 1.0m # ... ] evaluate_depth(true_dists, measured)跑完你会看到一张表,典型结果是这样的:0.5 米处相对误差 2% 以内,1 米处 3% 左右,2 米处可能到 8%,2.5 米处超过 10%。这不是算法差,是双目几何的物理极限。把这个表放进论文或报告,比只写「测距精度高」有说服力得多。
几个提升精度的小技巧,都是我踩坑后总结的:第一,标定用的棋盘格一定要平整,贴在硬板上,纸皱了角点检测会偏;第二,采集标定图时让棋盘格覆盖画面各个区域,别只放中间;第三,YOLO 的框稍微往外扩 5~10 像素再提特征点,避免框边缘切掉目标纹理;第四,如果目标类别固定(比如只测人),可以针对该类目标单独调conf和iou阈值,比通用设置效果好。
最后说一个我自己的习惯:每次改完标定参数或匹配算法,先别急着跑视频,拿一张静态的、已知距离的测试图跑一遍,看深度值对不对。静态图能排除运动模糊、帧同步这些干扰,快速定位是算法问题还是采集问题。这个习惯帮我省了无数次「以为是算法不行、其实是相机没同步」的冤枉路。
希望帮到你。
本文还有配套的精品资源,点击获取