ARTICLE DETAIL

资讯详情

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

双目视觉测尺寸实战:标定、立体匹配与深度计算

双目视觉测尺寸实战:标定、立体匹配与深度计算 简介在计算机视觉领域如何从二维图像中恢复物体的三维尺寸是工业测量与机器人感知的核心问题。单目视觉因缺失深度信息难以直接测距而双目视觉通过模拟人眼利用左右相机成像的视差计算深度再结合像素坐标反推真实尺寸实现了非接触式高精度测量。这一过程涉及相机标定、极线校正、立体匹配与三维重建等多个经典环节其中视差图的准确性直接决定深度与尺寸的精度。OpenCV 提供了完整的工具链使得基于普通摄像头搭建测尺寸系统成为可能。本文从双目测距原理出发详细梳理了张正友标定、SGBM 立体匹配、视差后处理以及目标轮廓反投影的完整工程实践并针对精度调优给出可复现的参数与避坑经验适合课程设计、毕业设计或入门机器人视觉项目参考。 说实话大学里期末大作业和毕业设计里双目视觉测尺寸这个题目出现频率比我预想的高得多。一方面是因为它听起来足够硬核有算法、有数学、有OpenCV答辩时能撑场面另一方面是它的本质其实很朴素——就是先想办法知道物体离相机多远再结合图像里物体的像素大小反推出真实尺寸。我做这个项目时最大的感受是难点不在测尺寸本身而在能不能稳定地算出准确的深度尺寸测量只是最后多乘一个比例的算术题。这篇内容适合正在做课设、毕设或者纯粹想玩一玩计算机视觉的同学。我会把整个项目的架构、标定流程、核心算法、代码实现、调试踩坑全部捋一遍代码和参数都是可以直接抄去改的看完你大概能少走两周弯路。1. 项目整体设计与核心思路拆解1.1 为什么选双目视觉而不是单目加参照物先理清一个很多人没想明白的问题单目相机是拍不出真实尺寸的。单目相机的成像过程把三维世界投影到了二维平面这一过程中深度信息被丢掉了。一张白纸上画一个方块你根本不知道它实际是5厘米还是5米除非你在场景里放一个已知尺寸的参照物例如尺子、硬币、标定板。这种做法不是不能做但换一个距离就要重新标定而且参照物的识别精度受遮挡、角度、光照影响很大论文里几乎拿不出手。双目视觉的路线更正统它模仿人眼通过两个相隔固定距离的相机同时拍摄同一个物体找到左右图像中同一个空间点的对应关系利用视差同一物体在左右图里像素位置的差异来计算深度。这里的核心公式很简洁Z (f * b) / d其中 Z 是物体到相机的深度f 是焦距像素单位b 是两个相机光心之间的水平距离基线d 是视差。这个公式看上去简单但整套系统的工程难点全在怎么让 d 算得准。视差算不准深度就飘深度一飘尺寸测量就全废了。我当时选择这个方向的原因很实际第一OpenCV 原生支持双目标定和立体匹配不需要自己从零写数学库第二它包含了相机标定、极线校正、立体匹配、三维重建、轮廓检测这些经典模块每个都能在课程论文里单独展开写内容量天然充足第三硬件成本极低两个几十块钱的USB摄像头就能跑通整个流程。1.2 项目模块划分与开发流程我建议把所有代码按功能拆成几个独立脚本不要全部堆在一个 main 里。我当时是这么划分的capture.py负责采集左右相机的标定图片和待测物体图像calibrate.py单目标定、双目标定、极线校正输出标定参数文件stereo_match.py读取标定参数对左右图做校正计算视差图并生成深度图measure.py目标检测、轮廓提取、三维反投影、尺寸计算输出测量结果utils.py公共工具函数比如图像显示、时间统计、结果保存开发流程上也有一套固定的顺序千万别跳步先采集标定图片再做立体标定再验证极线校正效果然后调立体匹配参数最后才开始写测量逻辑。我见过不少同学上来就套一个完整工程把标定、匹配、测量全放在一起跑结果图都没显示出来就报错最后连是标定问题还是匹配问题都分不清。核心流程可以概括为六个字标定、校正、匹配、重建。标定解决相机内部参数和两相机相对位置是什么的问题校正解决左右两张图怎么才能变成严丝合缝的行对齐的问题匹配解决左右图里的像素点谁跟谁是一对的问题重建解决已知匹配关系后三维坐标怎么算的问题。尺寸测量只是重建之后一个非常自然的后续步骤。2. 关键前置相机标定与极线校正没有这一步后面全是废的2.1 相机标定到底在标什么相机标定解决的问题是把一个三维空间点投影到像素平面中间经历了什么变换。这里面有两类参数一类叫内参包括焦距 fx、fy主点 cx、cy以及畸变系数 k1、k2、p1、p2有些模型还有 k3另一类叫外参描述相机在世界坐标系中的旋转和平移。对于双目系统还有两个相机之间的相对旋转矩阵 R 和平移向量 T。这两个参数直接决定了基线的方向和长度而基线长度又直接出现在深度公式 Z f * b / d 里。所以如果标定出的 T 不准确后面的测量精度就会系统性偏移。标定的经典方法是张正友标定法核心流程是拍摄多张不同姿态的棋盘格照片提取棋盘格角点利用角点的世界坐标以棋盘格为单位建立的坐标和像素坐标之间的对应关系求解内外参数。OpenCV 提供了cv2.findChessboardCorners、cv2.calibrateCamera、cv2.stereoCalibrate这些现成接口算法细节不用自己写但你要理解每一步在做什么否则出了问题根本没法排查。2.2 标定板与图片采集的细节标定板可以直接用 A4 纸打印棋盘格我用的模式是 9x6 的角点数格子边长 20mm。打印后我专门量了实际边长因为打印机缩放会导致边长不是精确的 20mm这会影响后续所有测量的绝对精度。采集标定图片是最容易被忽略但最影响结果的一步。我踩过一个很大的坑一开始图省事把棋盘格贴在墙上相机固定不动只平移棋盘格拍了几张结果标定出来的焦距和实际差很多视差图也乱七八糟。后来查资料才明白标定需要棋盘的姿态足够丰富包括不同角度、不同距离、不同倾斜方向而且棋盘要出现在画面的不同位置包括边缘这样算法才能稳定地解出畸变和内外参。我的做法是把棋盘格贴在硬纸板上一只手拿着纸板变换姿态左右相机同步拍摄总共采集了 25 组左右的图片对。每组图片要求两台相机都能完整看到棋盘格且棋盘在画面中占比不低于三分之一。拍摄环境要光线均匀避免棋盘上出现反光否则角点检测会失败。采集代码大概长这样import cv2 cap_left cv2.VideoCapture(0) cap_right cv2.VideoCapture(1) count 0 while count 30: ret_l, frame_l cap_left.read() ret_r, frame_r cap_right.read() if not ret_l or not ret_r: continue gray_l cv2.cvtColor(frame_l, cv2.COLOR_BGR2GRAY) gray_r cv2.cvtColor(frame_r, cv2.COLOR_BGR2GRAY) found_l, _ cv2.findChessboardCorners(gray_l, (9, 6), None) found_r, _ cv2.findChessboardCorners(gray_r, (9, 6), None) if found_l and found_r: cv2.imwrite(fcalib/left_{count:02d}.jpg, frame_l) cv2.imwrite(fcalib/right_{count:02d}.jpg, frame_r) count 1 print(fcaptured {count}/30) # 实时显示方便调整姿态 view cv2.hconcat([frame_l, frame_r]) cv2.imshow(calib view, view) if cv2.waitKey(1) 0xFF ord(q): break cap_left.release() cap_right.release()注意代码逻辑是两张图都能检测到完整棋盘才保存这能过滤掉大量脏数据。实测下来30 组配对里有几组棋盘在画面边缘导致角点检测断裂的我手动删掉了最终保留了约 24 组质量较好的。2.3 单目标定与立体标定的代码实现拿到图片对之后先做单目标定再做立体标定。单目标定得到每台相机各自的内参和畸变系数立体标定得到两相机之间的旋转矩阵 R 和平移向量 T。代码如下import cv2 import numpy as np import glob CHESS_COLS 9 CHESS_ROWS 6 SQUARE_SIZE 20.0 # 实际测量值单位mm # 世界坐标系下的棋盘角点坐标 objp np.zeros((CHESS_COLS * CHESS_ROWS, 3), np.float32) objp[:, :2] np.mgrid[0:CHESS_COLS, 0:CHESS_ROWS].T.reshape(-1, 2) * SQUARE_SIZE obj_points [] img_points_left [] img_points_right [] left_images sorted(glob.glob(calib/left_*.jpg)) right_images sorted(glob.glob(calib/right_*.jpg)) assert len(left_images) len(right_images) for left_path, right_path in zip(left_images, right_images): left cv2.imread(left_path, cv2.IMREAD_GRAYSCALE) right cv2.imread(right_path, cv2.IMREAD_GRAYSCALE) ret_l, corners_l cv2.findChessboardCorners(left, (CHESS_COLS, CHESS_ROWS), None) ret_r, corners_r cv2.findChessboardCorners(right, (CHESS_COLS, CHESS_ROWS), None) if not (ret_l and ret_r): continue criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corners_l cv2.cornerSubPix(left, corners_l, (5, 5), (-1, -1), criteria) corners_r cv2.cornerSubPix(right, corners_r, (5, 5), (-1, -1), criteria) obj_points.append(objp) img_points_left.append(corners_l) img_points_right.append(corners_r) # 单目标定 ret_l, mtx_l, dist_l, rvecs_l, tvecs_l cv2.calibrateCamera( obj_points, img_points_left, left.shape[::-1], None, None) ret_r, mtx_r, dist_r, rvecs_r, tvecs_r cv2.calibrateCamera( obj_points, img_points_right, right.shape[::-1], None, None) # 双目标定 flags cv2.CALIB_FIX_INTRINSIC ret_s, mtx_l, dist_l, mtx_r, dist_r, R, T, E, F cv2.stereoCalibrate( obj_points, img_points_left, img_points_right, mtx_l, dist_l, mtx_r, dist_r, left.shape[::-1], RNone, TNone, flagsflags, criteriacriteria) print(Left camera matrix:\n, mtx_l) print(Right camera matrix:\n, mtx_r) print(Stereo R:\n, R) print(Stereo T:\n, T)这里我用了cv2.CALIB_FIX_INTRINSIC标志意思是让立体标定只优化 R 和 T不修改单目标定得到的内参。这是比较稳妥的做法因为单目标定如果本身就不准强行让立体标定一起重新优化内参容易把整体误差摊匀但每个参数都不准。需要说明的是如果你的时间紧张或者自认为单目标定拍得不太理想可以直接去掉flags让 OpenCV 用默认模式联合优化效果通常会好一些。这一点可以自行测试对比换不同的 flags 跑一遍看重投影误差哪个小就用哪个。标定完成后我需要检查标定质量。最直观的方法是看重投影误差哪个值越小越好另一个更实用方法是做极线校正然后直接在界面上叠加半透明颜色看左右图的同一行是否对齐。如果棋盘格的横线在两张图中完美处于同一水平行说明标定结果基本可用。2.4 立体校正把两张图对齐到同一行立体校正用cv2.stereoRectify计算左右相机的校正变换矩阵然后用cv2.initUndistortRectifyMap和cv2.remap产生校正后的图像。这一步是为了让左右图像的极线平行在同一行上这样立体匹配时只需要在水平方向搜索对应像素计算量大幅下降匹配精度也更高。# 图像尺寸需要自己设置要与实际采集分辨率一致 image_size (1280, 720) R1, R2, P1, P2, Q, roi1, roi2 cv2.stereoRectify( mtx_l, dist_l, mtx_r, dist_r, image_size, R, T, alpha0) map1_l, map2_l cv2.initUndistortRectifyMap( mtx_l, dist_l, R1, P1, image_size, cv2.CV_32FC1) map1_r, map2_r cv2.initUndistortRectifyMap( mtx_r, dist_r, R2, P2, image_size, cv2.CV_32FC1) left_rect cv2.remap(left_img, map1_l, map2_l, cv2.INTER_LINEAR) right_rect cv2.remap(right_img, map1_r, map2_r, cv2.INTER_LINEAR)校正完后把左右图并排显示用鼠标点几个特征点检查它们是否在同一水平线上。如果没有就回到标定环节排查常见原因包括图片对数量太少、拍摄姿态过于单一、棋盘格表面不平整、相机分辨率设置不一致。3. 立体匹配从两张图到一张视差图3.1 SGBM 算法与关键参数选择立体匹配是整个项目中最核心也最玄学的部分。OpenCV 里常用的算法是 SGBMSemi-Global Block Matching半全局块匹配。它的核心思想是对每个像素在一个视差范围内寻找左右图中最相似的块同时引入平滑约束让相邻像素的视差值不出现太大突变。SGBM 的原始论文Heiko Hirschmuller2008里用了很多信息论和动态规划的东西但我们用 OpenCV 的工程实现不需要完全吃透论文重点是理解几个调参手段minDisparity最小视差值通常设为 0numDisparities视差搜索范围必须是 16 的倍数。范围越大能测的近距物体越近但计算越慢错误匹配也越多blockSize匹配块大小必须是奇数。块越大视差图越平滑但边缘和细节丢失越严重P1、P2平滑惩罚参数控制视差变化的代价。P2 建议大于 P1通常经验公式是P1 8 * 3 * blockSize^2P2 32 * 3 * blockSize^2uniquenessRatio唯一性比例用于去除模糊匹配值越大匹配结果越严格disp12MaxDiff左右一致性检查的最大允许差异用于剔除错误点speckleWindowSize滤除细小斑点噪声的窗口大小设为 0 表示关闭speckleRange斑点范围内允许的视差变化量我实际调参后的配置如下sgbm cv2.StereoSGBM_create( minDisparity0, numDisparities128, blockSize9, P18 * 3 * 9 ** 2, P232 * 3 * 9 ** 2, disp12MaxDiff1, uniquenessRatio10, speckleWindowSize100, speckleRange32, preFilterCap63, modecv2.STEREO_SGBM_MODE_SGBM ) disp sgbm.compute(left_rect, right_rect).astype(np.float32) / 16.0注意compute返回的视差值是整数乘以16所以要除以16转成真正的浮点视差。这一步漏掉的话后续深度计算会差16倍属于新手最容易犯的错。3.2 视差图后处理与深度映射裸的 SGBM 视差图通常有大量噪声尤其是纹理稀少区域比如纯色物体表面。两个经典的后处理手段第一个是左右一致性检查。用同一个匹配器把左右图互换再计算一次视差然后对比两幅图的视差结果差异超过阈值的点认为是不可靠点并置为无效。OpenCV 里disp12MaxDiff参数就能做一部分但它检查的是右图视差与左图视差的一致性效果有限。第二个是cv2.ximgproc.createDisparityWLSFilter加权最小二乘滤波。它利用左图作为引导图像在平滑噪点的同时尽量保留边缘细节。使用 WLS 需要安装带 contrib 模块的 OpenCV 版本# 注意不要同时装 opencv-python否则两个包会冲突 pip install opencv-contrib-pythonWLS 的使用方法# 先创建一个右匹配器用于生成右视差图 right_matcher cv2.ximgproc.createRightMatcher(sgbm) disp_right right_matcher.compute(right_rect, left_rect).astype(np.float32) / 16.0 wls_filter cv2.ximgproc.createDisparityWLSFilter(sgbm) wls_filter.setLambda(8000) wls_filter.setSigmaColor(1.5) disp_filtered wls_filter.filter(disp, left_rect, disparity_map_rightdisp_right)setLambda控制平滑强度值越大越平滑但边缘可能被牺牲setSigmaColor控制颜色相似度的影响值越大越能保留颜色相近区域的边缘。我试过从 3000 到 12000 的 lambda最后停在 8000视觉上噪点明显减少边缘也没有糊掉。视差图生成之后用cv2.reprojectImageTo3D配合标定得到的 Q 矩阵把视差图转换成三维点云points_3d cv2.reprojectImageTo3D(disp_filtered, Q) # points_3d 形状与图像一致每个像素位置存 (X, Y, Z)单位与标定板边长一致mm这套流程跑完后你可以在每个像素位置读到该点相对于左相机坐标系的三维坐标。有了三维坐标尺寸测量就是纯粹的数学计算。4. 尺寸测量从三维坐标到毫米刻度4.1 逐点反投影而不是中心深度统一缩放很多网上的简化实现是这么做的先检测物体的最小外接矩形得到矩形的像素宽高单位像素再取物体中心的深度 Z然后用像素宽高乘以 Z / f得到物理尺寸。这个方法在物体很小、距离很远、物体表面基本在同一深度时误差还能接受但一旦物体距离相机很近或者物体本身有一定厚度误差就很明显了因为物体上不同位置的深度值其实不一样。严谨的做法是对物体的每个关键点直接做三维反投影。以物体轮廓的角点为例每个角点在左图中有像素坐标 (u, v)对应的深度 Z 从points_3d[v, u, 2]取到然后反投影到相机坐标系下的三维坐标def pixel_to_3d(u, v, depth, camera_matrix): fx camera_matrix[0, 0] fy camera_matrix[1, 1] cx camera_matrix[0, 2] cy camera_matrix[1, 2] x (u - cx) * depth / fx y (v - cy) * depth / fy return np.array([x, y, depth])这里的camera_matrix是校正后的左相机内参P1的左上 3x3 部分。然后算两个三维点的欧氏距离就是真实尺寸def distance_3d(p1, p2): return np.linalg.norm(p1 - p2)我实际测量时对一个矩形物体取minAreaRect返回的四个顶点像素坐标分别反投影到三维然后计算四条边的长度把相对的两条边取平均作为长和宽。这样比只取中心深度再缩放要准得多答辩时也更有说服力。4.2 边缘检测、轮廓提取与角点获取尺寸测量的第一步是把目标物体从图像里分离出来。我的做法有点暴力但好用把左图转灰度、高斯模糊、Canny 边缘检测然后findContours取最大外轮廓最后minAreaRect求最小外接矩形。import cv2 import numpy as np gray cv2.cvtColor(left_rect, cv2.COLOR_BGR2GRAY) blur cv2.GaussianBlur(gray, (5, 5), 0) edges cv2.Canny(blur, 50, 150) contours, _ cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: raise ValueError(no contour found) contour max(contours, keycv2.contourArea) rect cv2.minAreaRect(contour) # ((cx, cy), (w, h), angle) box cv2.boxPoints(rect) box np.int0(box)minAreaRect返回的是旋转矩形对倾斜放置的物体也能正确处理。拿到四个角点后把它们按顺序重新组织确保是上左下右这样的顺序然后逐点反投影计算边长。需要注意的是Canny 的两个阈值50 和 150不是固定死的需要根据实际光照条件微调。阈值太低会出现大量伪边缘阈值太高则可能把物体边缘断成好几段导致轮廓提取不完整。我建议在调试阶段写一个带滑动条的小窗口实时调节 Canny 阈值找到当前场景下最干净的边缘。另外一个常见的坑是如果物体和背景颜色相近边缘检测会把物体和背景当成同一个连通域导致外接矩形特别大。解决办法是把物体放到一个颜色对比强烈的背景上比如黑色绒布上放浅色盒子这样 Canny 结果干净很多。4.3 完整测量流程与精度评估测量流程可以封装成一个函数def measure_object(left_rect, right_rect, sgbm, Q, camera_matrix, wls_filterNone): # 1. 计算视差图 disp sgbm.compute(left_rect, right_rect).astype(np.float32) / 16.0 if wls_filter is not None: disp_right right_matcher.compute(right_rect, left_rect).astype(np.float32) / 16.0 disp wls_filter.filter(disp, left_rect, disparity_map_rightdisp_right) # 2. 三维重建 points_3d cv2.reprojectImageTo3D(disp, Q) # 3. 边缘检测 轮廓提取 gray cv2.cvtColor(left_rect, cv2.COLOR_BGR2GRAY) blur cv2.GaussianBlur(gray, (5, 5), 0) edges cv2.Canny(blur, 50, 150) contours, _ cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) contour max(contours, keycv2.contourArea) rect cv2.minAreaRect(contour) box cv2.boxPoints(rect) # 4. 4个角点反投影并计算边长 def to_3d(pt): u, v int(round(pt[0])), int(round(pt[1])) depth points_3d[v, u, 2] if depth 0 or not np.isfinite(depth): return None return pixel_to_3d(u, v, depth, camera_matrix) pts3d [to_3d(pt) for pt in box] if any(p is None for p in pts3d): return None w1 distance_3d(pts3d[0], pts3d[1]) h1 distance_3d(pts3d[1], pts3d[2]) w2 distance_3d(pts3d[2], pts3d[3]) h2 distance_3d(pts3d[3], pts3d[0]) width_mm (w1 w2) / 2.0 height_mm (h1 h2) / 2.0 return width_mm, height_mm我在实验时对一个尺寸为 85.2mm x 52.6mm 的卡片进行测量在 40 厘米左右的距离一共测了 10 次测得的结果大概在 83.8~86.5mm 的范围内波动宽度平均误差约 1.2mm高度平均误差约 0.8mm。对于实验室级别的双目视觉装置来说这个精度在合理范围内。精度评估建议这样做用游标卡尺或钢尺量出物体的真实尺寸和测量结果对比计算绝对误差和相对误差然后拍摄多组不同距离、不同角度下的数据分析误差随距离的变化趋势。把结果整理成表格放在毕设论文或大作业报告的实验结果与分析章节非常有说服力。5. 调试实录精度上不去的那些坑与排查方法5.1 常见问题速查表调试过程我踩过的坑比我愿意承认的还要多。下面这个表是我根据实际经历整理的基本覆盖了双目测尺寸最常见的几类问题现象可能原因检查与解决思路视差图大面积黑色空洞左右图没有校正对齐或纹理区域太少先看极线校正是否OK给物体贴上纹理纸增大 numDisparities物体边缘视差跳变严重blockSize 太小或者 P2 太小边缘约束不够适当增大 blockSize增大 P1、P2测量结果整体偏大或偏小基线 b 或焦距 f 不准标定板尺寸设置错了重新测量棋盘格实际边长重新标定检查代码是否漏了除以16近距离测量误差巨大视差搜索范围不够匹配失败增大 numDisparities将 minDisparity 设为负值同一物体不同位置测出的尺寸不一样相机不是严格平行安装或基线太短重新做立体标定检查 T 向量增加基线长度标定图片有部分无法检测角点棋盘反光、模糊、遮挡、角点被切到画面边界重新拍摄保证棋盘完整可见降低曝光两个摄像头采集的图像亮度不同摄像头自动曝光导致左右图亮度不一致固定曝光参数或者手动设定相同的曝光值5.2 精度上不去的根本原因分析双目视觉有个绕不开的物理原理深度误差与距离的平方成正比与基线长度和焦距成反比。公式可以近似写成delta_Z ≈ (Z^2 / (f * b)) * delta_d举个例子假设焦距 f 600 像素基线 b 60mm视差误差 delta_d 0.3 像素。在 Z 300mm 时深度误差约 0.75mm在 Z 600mm 时深度误差约 3mm在 Z 1000mm 时深度误差约 8.3mm。这说明物体离相机越远测距误差会急剧放大尺寸测量自然也就越不准。所以做这个项目时我强烈建议把被测物体放在离相机 30~60 厘米的范围内并且基线尽量拉开一些8~10厘米。基线变大可以用更大的视差范围但也要注意如果两个相机视角的公共区域变小物体需要在画面中居中放置否则可能超出双目公共视野。还有一个很多初学者忽略的问题USB 摄像头无法做到硬件级的帧同步。左右相机各自独立采样拍静止物体没问题拍运动的物体会出现左右图匹配错位导致视差图出现大面积错误。如果必须测量运动物体要么换工业相机使用硬件同步要么只对静止物体进行测量。我在项目里直接选择了只测静止物体并在文档里明确说明了这个限制老师并没有因此扣分反而觉得边界意识清晰。5.3 关于光照和物体纹理的避坑经验光照和纹理对称量的影响往往比算法参数还大。我当时用的是办公室普通荧光灯左右两个摄像头自动曝光后左右图亮度差异明显导致匹配效果不好。后来手动将曝光时间和增益固定成完全相同的值图像一致性立刻改善视差图质量提高了一个档次。如果摄像头支持手动设置曝光和增益在 OpenCV 里可以通过 VideoCapture 的属性设置但很多免驱USB摄像头并不支持。一种折中办法是实际使用中尽量保持环境光照均匀避免物体上出现高光阴影。另外物体表面如果太光滑比如金属盒子、玻璃瓶会因为反光导致左右图匹配特征混乱最有效的办法就是贴上一张有随机纹理的纸。我实验室里检测金属件时普遍会先贴一圈哑光胶带再测效果立竿见影。6. 项目源码结构与期末文档撰写要点6.1 代码模块如何组织才加分很多同学交代码时喜欢把一个几百行的脚本命名为main.py或者final.py老师打开一看头都大了。这个项目你只要花点心思把代码模块化就显得非常专业。我的目录结构如下project/ ├── calib/ │ ├── left_00.jpg │ ├── right_00.jpg │ └── ... ├── config/ │ └── stereo_params.npz ├── src/ │ ├── capture.py │ ├── calibrate.py │ ├── stereo_match.py │ ├── measure.py │ └── utils.py ├── results/ │ ├── disp_vis.png │ ├── measure_result.csv │ └── ... ├── README.md └── requirements.txtconfig/stereo_params.npz是标定结果的保存文件用np.savez保存mtx_l、mtx_r、dist_l、dist_r、R、T、Q、P1等矩阵。measure.py启动时直接加载不用每次重复标定。requirements.txt里写清楚依赖库和版本避免评审老师换环境时跑不起来。README 里要写明运行顺序、环境要求、目录说明、已知限制。6.2 文档与答辩内容怎么准备期末大作业或毕设文档最忌讳写成用户手册。老师想看到的不是我用了OpenCV的SGBM函数而是你自己的思路和取舍为什么选双目方案、标定的理论依据是什么、SGBM 参数是怎么试出来的、精度误差为什么这么大、有哪些可以改进的方向。我建议文档至少包含以下几章绪论研究背景、国内外现状、研究内容与目标系统原理相机成像模型、坐标系变换、双目测距原理、相机标定原理、SGBM 匹配原理系统设计硬件选型、软件架构、模块流程系统实现标定实现、匹配实现、尺寸测量实现配合关键代码和运行结果截图实验与分析标定误差、视差图效果、尺寸测量结果与误差分析、误差来源讨论总结与展望完成了什么、还存在什么问题、如何改进其中实验与分析是核心章节要把测量数据做成表格并与真实尺寸对比。如果你能做到在不同距离下各测10次计算均值、标准差、相对误差这篇文档的质量已经超过大部分同期作业。答辩时老师最常问的几个问题我提前把答案准备好为什么视差图上有洞答纹理缺失区匹配可信度低被左右一致性检查过滤。如何提高测量精度答增大基线、提高相机分辨率、使用质量更好的工业相机、加入亚像素匹配和滤波。为什么不用深度学习答传统双目视觉方案不需要训练数据可解释性强适合嵌入式部署深度学习做双目匹配效果好但需要GPU和数据本项目目标是验证经典理论。写在最后的实践经验这个项目我前后断断续续做了一周多大部分时间其实不是花在写代码上而是花在调标定、调 SGBM 参数、排除环境问题上。如果你有同学第一次跑出来一张惨不忍睹的视差图不要慌95% 的情况不是代码问题是标定没做好或者光照条件太差。先把标定质量和极线校正验证通过再谈后面那些花活。最后分享一个实用小技巧所有关键中间结果——标定重投影误差、校正后左右图、原始视差图、滤波后视差图、轮廓与角点图——都保存成图片文件用日期命名。这在调试时能帮你快速定位哪一步引入了误差期末写报告时素材也齐了一举两得。等代码调稳定之后整个运行流程压缩到几秒钟就能完成心里非常踏实。本文还有配套的精品资源点击获取
返回列表