ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

双目立体视觉系统实战:标定、SGBM匹配与深度图生成全解析

双目立体视觉系统实战:标定、SGBM匹配与深度图生成全解析 简介这是一套面向计算机、人工智能、自动化、电子信息等专业学生与科研人员的双目摄像头立体视觉系统毕业设计资源围绕相机标定、立体匹配与深度图生成三大核心环节展开可作为毕业设计、课程设计或项目立项演示的完整参考。压缩包共190个文件约2.58MB以34个Python脚本、11个C与11个H源码、22个JavaScript文件为主辅以38张jpg与12张png图像、11个json配置及若干工程文件覆盖算法实现、界面交互与数据配置等模块。资源内含设计文档与项目报告代码经测试可稳定运行便于复现与二次开发。目前已有39人学习下载。读者可借此掌握标定参数求解、匹配代价计算与视差转深度等关键流程并参考目录结构快速定位算法与工程代码适合在此基础上修改扩展功能或直接用于毕设与课设。1. 双目立体视觉系统从两个镜头到一张可信深度图很多人第一次做双目摄像头立体视觉系统都会卡在同一个地方标定跑完了匹配也出图了但深度图要么一片空洞要么近处准、远处飘换个场景直接崩。问题往往不在某一行代码而在整条链路的参数没有对齐——标定给的是相机之间的几何关系匹配给的是像素对应深度图只是把这两件事乘起来。任何一环有偏差最后都会以「深度图很脏」的形式暴露出来。这套方案要解决的就是把标定、匹配、深度图生成串成一条可复现的流水线用张正友标定法拿内参和畸变用立体标定拿外参和极线约束用 SGBM 或 BM 做稠密匹配再用 Q 矩阵重投影成三维点。适合做毕业设计、机器人避障、体积测量、抓取引导的从业者也适合已经会调 OpenCV 但深度图一直不稳的人。下面按我实际落地的顺序讲参数和坑都会给到。2. 标定先立住内参、外参与极线校正怎么一次做对2.1 为什么双目标定不能只标两个单目单目标定只回答「像素怎么对应到这条光线」双目标定还要回答「左右两条光线在空间里差多少」。这个差值就是基线也是深度的分母。常见做法是先用同一块标定板分别标左右目再做立体标定求 R、T最后用stereoRectify把两幅图拉到同一极线上。只标单目就去做匹配等于让两个各自为政的坐标系硬碰极线不水平SGBM 的搜索范围再大也找不到正确对应。张正友标定法的核心是平面棋盘格在多个位姿下提供单应约束解出内参后非线性优化畸变。双目版本多了一步把左右目的外参统一到同一世界坐标系得到右目相对左目的旋转和平移。这一步的精度直接决定深度图的尺度是否可信。2.2 采集标定图数量、角度和覆盖范围标定图不是越多越好而是位姿要散开。我一般拍 15 到 25 对棋盘格占画面 1/3 到 1/2覆盖四个角和中心同时要有明显倾斜。全是正对镜头的图畸变系数会解得很飘。# 目录结构建议 calib/ left/ left_01.png ... left_20.png right/ right_01.png ... right_20.png左右图必须同一时刻采集动态场景下用软触发或硬件同步。文件名按序号对齐后面读图才不会错位。棋盘格内角点数量要提前确认比如 9x6 表示每行 9 个内角、每列 6 个内角写错一个数整批图都检测失败。2.3 单目与双目标定的完整代码import cv2 import numpy as np import glob # 棋盘格内角点宽9 高6方格实际尺寸 25mm PATTERN (9, 6) SQUARE 25.0 objp np.zeros((PATTERN[0]*PATTERN[1], 3), np.float32) objp[:, :2] np.mgrid[0:PATTERN[0], 0:PATTERN[1]].T.reshape(-1, 2) * SQUARE criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-3) def collect(img_dir): objpoints, imgpoints [], [] for f in sorted(glob.glob(img_dir /*.png)): img cv2.imread(f) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ok, corners cv2.findChessboardCorners(gray, PATTERN, None) if not ok: print(skip, f) continue corners cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) objpoints.append(objp) imgpoints.append(corners) return objpoints, imgpoints, gray.shape[::-1] objL, imgL, shape collect(calib/left) objR, imgR, _ collect(calib/right) # 单目内参 _, K1, D1, _, _ cv2.calibrateCamera(objL, imgL, shape, None, None) _, K2, D2, _, _ cv2.calibrateCamera(objR, imgR, shape, None, None) # 双目立体标定 ret, K1, D1, K2, D2, R, T, E, F cv2.stereoCalibrate( objL, imgL, imgR, K1, D1, K2, D2, shape, flagscv2.CALIB_FIX_INTRINSIC, criteria(cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 100, 1e-5) ) # 极线校正 R1, R2, P1, P2, Q, roi1, roi2 cv2.stereoRectify( K1, D1, K2, D2, shape, R, T, alpha0 ) np.savez(stereo_params.npz, K1K1, D1D1, K2K2, D2D2, RR, TT, R1R1, R2R2, P1P1, P2P2, QQ) print(baseline mm , np.linalg.norm(T))逻辑说明stereoCalibrate里用CALIB_FIX_INTRINSIC固定单目结果只优化外参避免两套参数互相拉扯。alpha0表示校正后只保留有效像素边缘会被裁掉但不会引入黑色无效区如果做显示可以设alpha1保留全部视野。Q是后面reprojectImageTo3D用的重投影矩阵基线从T的模长读出单位跟SQUARE一致。参数说明cornerSubPix的窗口 11x11 适合 1080p 以下4K 图可以加到 15x15。stereoCalibrate的迭代阈值 1e-5 已经够用再小只是浪费时间。基线如果算出来跟卷尺量的差超过 2%先查棋盘格尺寸单位再查左右图是否严格同步。2.4 用重投影误差判断标定能不能用标定完不要急着做匹配先看两个数单目重投影误差和双目重投影误差。单目一般要小于 0.3 像素双目小于 0.5 像素。超过这个值深度图一定飘。# 单目重投影误差 mean_err 0 for i in range(len(objL)): imgpts, _ cv2.projectPoints(objL[i], rvecs[i], tvecs[i], K1, D1) mean_err cv2.norm(imgL[i], imgpts, cv2.NORM_L2) / len(imgpts) print(left reproj err , mean_err / len(objL))如果误差大优先删掉角点检测置信度低的图而不是盲目加图。我见过一批图里有两张棋盘格反光角点偏了 2 个像素整组外参就被带歪。校正后可以画极线检查同一物点在左右图的 y 坐标应该基本一致差超过 1 像素说明校正没生效。3. 立体匹配SGBM 参数怎么调才不空洞3.1 BM 和 SGBM 的选型边界OpenCV 里能直接用的稠密匹配主要是StereoBM和StereoSGBM。BM 快适合嵌入式实时但纹理弱的地方大片空洞SGBM 用半全局能量优化边缘和弱纹理都稳很多代价是慢。毕业设计或离线处理优先 SGBM实时避障且算力有限再考虑 BM 加后处理。SGBM 的核心是沿多个方向做动态规划把匹配代价的聚合从局部扩展到全局所以对重复纹理和无纹理区域比 BM 宽容。但它的参数比 BM 多调不好反而更差。3.2 匹配前的校正和预处理匹配必须在极线校正后的图上做否则搜索范围要设得很大噪声也跟着涨。用标定得到的R1、R2、P1、P2做remap左右图行对齐后再进 SGBM。import cv2 import numpy as np p np.load(stereo_params.npz) K1, D1, K2, D2 p[K1], p[D1], p[K2], p[D2] R1, R2, P1, P2, Q p[R1], p[R2], p[P1], p[P2], p[Q] left cv2.imread(scene/left.png) right cv2.imread(scene/right.png) h, w left.shape[:2] map1x, map1y cv2.initUndistortRectifyMap(K1, D1, R1, P1, (w, h), cv2.CV_32FC1) map2x, map2y cv2.initUndistortRectifyMap(K2, D2, R2, P2, (w, h), cv2.CV_32FC1) rectL cv2.remap(left, map1x, map1y, cv2.INTER_LINEAR) rectR cv2.remap(right, map2x, map2y, cv2.INTER_LINEAR)逻辑说明initUndistortRectifyMap把去畸变和极线校正合成一张映射表比先undistort再rectify少一次重采样边缘更干净。INTER_LINEAR是速度和质量的折中做亚像素测量可以换INTER_CUBIC但会慢一些。3.3 SGBM 关键参数逐项说明min_disp 0 num_disp 16 * 6 # 必须是16的整数倍 block 5 stereo cv2.StereoSGBM_create( minDisparitymin_disp, numDisparitiesnum_disp, blockSizeblock, P18 * block * block, P232 * block * block, disp12MaxDiff1, uniquenessRatio10, speckleWindowSize100, speckleRange2, preFilterCap63, modecv2.STEREO_SGBM_MODE_SGBM_3WAY ) disp stereo.compute(rectL, rectR).astype(np.float32) / 16.0参数说明numDisparities决定能测多近视差范围要覆盖最近物点的像素差设小了近处直接截断。blockSize越大越平滑但边缘越糊5 或 7 是常用值。P1、P2是平滑惩罚控制视差连续性P2一般是P1的 4 倍。uniquenessRatio过滤歧义匹配10 到 15 比较稳。speckleWindowSize去掉小连通噪声块100 适合 1080p。disp12MaxDiff做左右一致性检查设 1 能去掉大部分错误匹配。3.4 视差图转深度图与点云拿到视差后深度等于焦距乘基线除以视差。用Q矩阵可以直接重投影成三维点。# 视差转深度 f P1[0, 0] B np.linalg.norm(p[T]) depth f * B / (disp 1e-6) depth[disp min_disp 0.5] 0 # 无效视差置零 # 重投影成点云 points cv2.reprojectImageTo3D(disp, Q) mask disp min_disp 0.5 cloud points[mask] colors rectL[mask]逻辑说明reprojectImageTo3D内部就是用Q把 (x, y, disparity) 映射到 (X, Y, Z)比手写公式少踩坐标系坑。无效视差必须置零否则会生成飞到无穷远的点。点云可以存成 ply 用 MeshLab 看检查平面是否平、边缘是否重影。4. 避坑与排查深度图翻车的五条血泪记录4.1 深度图整体偏斜近处准远处飘现象近处物体深度对得上远处平面明显倾斜。原因极线校正没生效或外参 R 不准视差在垂直方向有残余。解决画极线检查 y 方向偏差重新做stereoRectify确认CALIB_FIX_INTRINSIC下内参没被污染。4.2 大片空洞纹理一弱就丢现象白墙、桌面大片无效视差。原因numDisparities不够或uniquenessRatio太高弱纹理区域匹配代价区分度低。解决先确认视差范围覆盖最近物点再把uniquenessRatio降到 5 到 10必要时加preFilterCap增强纹理。4.3 边缘重影物体轮廓有毛刺现象物体边缘深度跳变点云出现拖尾。原因blockSize太大或P2太小平滑过度。解决blockSize降到 5P2提到P1的 4 到 8 倍配合左右一致性检查disp12MaxDiff1。4.4 基线算出来和实测差很多现象深度尺度整体偏大或偏小。原因棋盘格尺寸单位写错或左右图不同步。解决确认SQUARE单位与实测一致动态场景用硬件同步静态场景也要避免拍摄间隔内移动标定板。4.5 点云颜色和位置对不上现象点云形状对但颜色错位。原因reprojectImageTo3D用的disp和取色的rectL尺寸或校正状态不一致。解决确保disp、Q、rectL都来自同一组校正参数mask 用同一个视差阈值。5. 进阶把深度图精度再压一档的验证习惯标定和匹配跑通只是及格线真正决定这套双目摄像头立体视觉系统能不能用于测量的是验证习惯。我一般会做三件事第一用已知尺寸的标定板放在不同距离量深度误差画误差随距离的曲线超过 2% 就回去查基线和焦距第二把点云导出后用平面拟合看平整度平面度差说明视差有系统性偏差第三换一个完全没参与标定的场景复测避免过拟合到标定环境。# 平面拟合检查点云平整度 import numpy as np def plane_fit(pts): centroid pts.mean(axis0) centered pts - centroid _, _, vh np.linalg.svd(centered) normal vh[-1] dist np.abs(centered normal) return dist.mean(), dist.max() mean_d, max_d plane_fit(cloud) print(plane mean err mm , mean_d, max , max_d)参数说明plane_fit用 SVD 解平面法向量mean_d反映整体噪声max_d反映离群点。平面度均值在 1 到 2 毫米以内算可用超过 5 毫米先查标定重投影误差。还有一个容易被忽略的点Q矩阵里的Q[2,3]和Q[3,2]跟基线、焦距直接相关换镜头或调基线后必须重新标定不能沿用旧参数。我吃过一次亏换了基线长度只改了T没重跑stereoRectify深度图整体缩放错了 8%排查了一下午才发现是Q没更新。标定参数最好和采集配置一起版本管理改一个就重跑全链路。希望帮到你。本文还有配套的精品资源点击获取
返回列表