
简介这份源码面向计算机、通信、人工智能、自动化等专业的学生与从业者提供基于Python的单目与双目视觉三维重建完整实现可用于课程设计、大作业或毕业设计参考。项目为个人毕设成果答辩评审分达98分代码经过调试测试确保可运行既适合零基础小白入门学习也便于进阶者在此基础上修改调整、扩展新功能。压缩包共41个文件约80.24MB以34张jpg实验图片、3个py核心脚本、2个txt说明文件及1个md文档、1个png示意图为主图片与脚本配合覆盖单目、双目两条重建流程便于对照理解算法输入输出。目前已有853人学习下载具备一定参考热度。读者可从中获取完整的单目与双目三维重建代码框架、实验素材与运行说明快速搭建可复现的视觉重建环境并借助现成结构梳理相机标定、立体匹配与深度恢复等关键环节的实现思路。1. 从两张照片到三维点云这套 Python 源码到底能跑出什么手里有一台普通 USB 摄像头或者一对标定过的双目模组想从零搭出一条「拍照 → 标定 → 校正 → 匹配 → 三角化 → 点云」的完整链路最省事的路径不是自己啃 OpenCV 文档而是找一份能直接跑通的参考实现。这套基于 Python 的单目双目视觉三维重建源码覆盖的正是这条链路单目部分用棋盘格做相机内参标定、去畸变、单目测距双目部分做立体标定、极线校正、SGBM 视差计算、深度图转点云最后用 Open3D 或 Matplotlib 把结果可视化出来。它适合两类人一类是刚接触双目立体视觉、需要一份能对照论文公式看代码的入门者另一类是做机器人、AGV、工业检测的工程师想快速验证某个相机模组能不能出可用深度。源码本身不依赖特殊硬件普通双目相机标定完就能跑这也是它比很多只给理论推导的资料更实用的地方。2. 环境与依赖把 OpenCV、Open3D 和标定板先对齐2.1 为什么这套源码对版本敏感视觉重建类项目最怕的不是算法难而是 OpenCV 版本一变cv2.stereoCalibrate的返回参数顺序、cv2.StereoSGBM_create的参数默认值、cv2.fisheye命名空间的位置都可能变。这套源码常见做法是锁定 OpenCV 4.x 的某个小版本因为 4.5 之后 SGBM 的uniquenessRatio和speckleWindowSize行为有调整直接跑老代码会出现视差图大面积空洞。Python 侧建议 3.83.10Open3D 用 0.15 以上NumPy 不要低于 1.21否则np.float这类老写法会直接报 AttributeError。我一般会先建独立虚拟环境避免和系统里已有的 cv2 冲突。python -m venv venv_recon source venv_recon/bin/activate # Windows 用 venv_recon\Scripts\activate pip install opencv-contrib-python4.8.1.78 pip install open3d0.17.0 numpy1.24.3 matplotlib3.7.2这里选opencv-contrib-python而不是opencv-python是因为部分源码会调用cv2.aruco或cv2.fisheye这些在 contrib 包里才完整。装完用下面三行验证别等到跑标定才报错。import cv2, open3d, numpy as np print(cv2.__version__, open3d.__version__, np.__version__) print(hasattr(cv2, StereoSGBM_create), hasattr(cv2, stereoCalibrate))如果StereoSGBM_create返回 False说明装成了精简版 OpenCV重建脚本会在视差计算那一步直接崩。标定板方面源码默认用 9×6 内角点、方格 25mm 的棋盘格这个参数写在配置区换板子必须同步改pattern_size和square_size否则标定出来的焦距会整体缩放后面测距全错。2.2 目录结构与数据准备拿到源码后先别急着跑主程序花两分钟看清目录。典型结构是calib/放标定图、data/放左右目原图、output/存视差和点云、根目录几个脚本分别对应单目标定、双目标定、重建。标定图建议左右目各拍 1520 张棋盘格在画面里要覆盖四个角和中心倾斜角度别太单一。很多人只拍正面 10 张结果畸变系数拟合不稳校正后边缘还是弯的。数据命名保持left_01.jpg、right_01.jpg成对脚本里按序号配对读取命名乱了会直接读空。提示标定图不要用手机翻拍屏幕上的棋盘格摩尔纹会让角点检测飘直接打印 A4 纸贴在硬板上最稳。3. 单目标定与测距内参、畸变和像素当量怎么算3.1 棋盘格角点检测与内参求解单目部分是整条链路的地基。核心就两步findChessboardCorners找角点calibrateCamera解内参和畸变。源码里通常会把角点做亚像素细化这一步不做的话重投影误差会大一个量级。下面是我常用的标定片段参数含义逐行说清。import cv2, numpy as np, glob pattern (9, 6) # 内角点数不是方格数 square 25.0 # 单格实际边长单位 mm 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 objpoints, imgpoints [], [] for f in sorted(glob.glob(calib/left_*.jpg)): img cv2.imread(f) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ok, corners cv2.findChessboardCorners(gray, pattern, None) if not ok: print(跳过, f); continue corners cv2.cornerSubPix(gray, corners, (11,11), (-1,-1), (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)) objpoints.append(objp); imgpoints.append(corners) ret, K, dist, rvecs, tvecs cv2.calibrateCamera( objpoints, imgpoints, gray.shape[::-1], None, None) print(重投影误差, ret, \n内参\n, K, \n畸变\n, dist.ravel())cornerSubPix的窗口 (11,11) 是搜索半径图越大可以适当加大终止条件 30 次迭代、0.001 精度是 OpenCV 官方示例的稳妥值。calibrateCamera返回的ret是平均重投影误差单位像素低于 0.5 算合格高于 1.0 说明标定图质量不行别硬用。K里fx、fy是焦距像素值cx、cy是主点后面单目测距全靠它们。3.2 单目测距的像素当量法单目测距常见两种做法已知物体实际尺寸反推距离或已知相机高度和俯角做地面投影。源码里多用第一种公式是距离 实际宽度 × 焦距 / 像素宽度。这里焦距取fx像素宽度是目标在图上占的像素数。实操时先用一个已知尺寸的物体比如 A4 纸宽 210mm在固定距离拍一张反算出这套相机的等效系数之后测同类目标直接套。def mono_distance(px_width, real_width_mm, fx): return real_width_mm * fx / px_width # 返回 mm # 例A4 纸宽 210mm图上占 320 像素fx800 print(mono_distance(320, 210, 800)) # 约 525mm这个方法的边界很清楚只对「已知实际尺寸、且正对相机」的目标有效目标一倾斜像素宽度就失真。所以单目测距在源码里更多是演示性质真正要稳定深度还得靠双目。把单目标定结果存成npz双目标定会直接复用左右目的K和dist作为初值这一步省不得。注意单目和双目的标定图分辨率必须一致混用不同分辨率会让stereoCalibrate报尺寸不匹配。4. 双目校正与 SGBM 视差从左右图到深度图4.1 立体标定与极线校正双目标定的目标是拿到右目相对左目的旋转R和平移T再用stereoRectify把两幅图校正到同一极线上。校正后同一个物点在左右图的 y 坐标基本一致视差只在 x 方向SGBM 才能算得准。这一步的输入是左右目各自的K、dist和成对的角点。ret, K1, d1, K2, d2, R, T, E, F cv2.stereoCalibrate( objpoints, imgpointsL, imgpointsR, K1, d1, K2, d2, gray.shape[::-1], flagscv2.CALIB_FIX_INTRINSIC) # 已有单目标定结果时固定内参 R1, R2, P1, P2, Q, _, _ cv2.stereoRectify( K1, d1, K2, d2, gray.shape[::-1], R, T, alpha0) mapLx, mapLy cv2.initUndistortRectifyMap(K1, d1, R1, P1, gray.shape[::-1], cv2.CV_32FC1) mapRx, mapRy cv2.initUndistortRectifyMap(K2, d2, R2, P2, gray.shape[::-1], cv2.CV_32FC1)CALIB_FIX_INTRINSIC表示信任单目标定结果只优化外参适合标定图质量好的情况如果双目同步性差可以去掉这个 flag 让内外参一起优化但耗时明显增加。alpha0表示校正后裁掉黑边alpha1保留全部像素但边缘有拉伸做点云我一般用 0。Q是重投影矩阵后面reprojectImageTo3D直接用它把视差转成三维坐标。4.2 SGBM 参数怎么调才不出空洞SGBM 是这套源码的深度核心参数调不好就是一张满是黑洞的视差图。关键参数有四个numDisparities必须是 16 的倍数决定搜索范围blockSize是匹配块大小奇数越大越平滑但边缘越糊uniquenessRatio控制匹配唯一性515 之间speckleWindowSize过滤小连通域噪点。下面是一组在 640×480 双目图上比较稳的配置。stereo cv2.StereoSGBM_create( minDisparity0, numDisparities128, # 16 的倍数覆盖最近到最远 blockSize5, # 3~11越大越平滑 P18*3*5**2, # 平滑惩罚常取 8*通道*blockSize^2 P232*3*5**2, # 必须大于 P1 uniquenessRatio10, speckleWindowSize100, speckleRange2, disp12MaxDiff1, modecv2.STEREO_SGBM_MODE_SGBM_3WAY) disp stereo.compute(grayL, grayR).astype(np.float32) / 16.0numDisparities128意味着最近能测到大约fx×基线/128的距离太近的物体会超出搜索范围变成空洞这时候要么加大numDisparities要么把相机往后退。P1/P2是视差平滑惩罚P2 必须大于 P1否则视差图会出现横向条纹。算完视差记得除以 16SGBM 输出的是定点数。disp12MaxDiff做左右一致性检查设 1 能去掉大部分误匹配但也会让遮挡区变空洞这是正常代价。4.3 视差转点云与可视化拿到视差后用reprojectImageTo3D配合Q矩阵生成三维点再按深度阈值裁掉太远和太近的噪点最后交给 Open3D 显示。import open3d as o3d points cv2.reprojectImageTo3D(disp, Q) mask (disp disp.min()) (disp disp.max()) (points[:,:,2] 2000) pts points[mask].reshape(-1, 3) pcd o3d.geometry.PointCloud() pcd.points o3d.utility.Vector3dVector(pts) o3d.visualization.draw_geometries([pcd])points[:,:,2]是 Z 方向深度单位跟标定时的square一致用 mm 就裁 2000mm 以内。mask 里同时限制视差范围能去掉背景和前景的飞点。点云出来如果整体倾斜多半是校正没做好或Q用错回头检查stereoRectify的输入顺序。提示SGBM 对光照敏感左右目曝光不一致时视差图会一半亮一半黑能手动锁曝光就锁。5. 避坑与排查跑不通时先看这五条5.1 标定重投影误差大角点总检测失败现象是findChessboardCorners频繁返回 False或calibrateCamera的 ret 超过 1.5。原因通常是棋盘格打印不平、光照反光、或者pattern_size填成了方格数而不是内角点数。解决把棋盘格贴到硬纸板上避免顶光直射确认 9×6 是内角点拍图时让棋盘格占画面 1/3 以上倾斜角度覆盖 ±30°。5.2 校正后左右图行不对齐现象是校正图上同一个角点在左右图 y 坐标差好几个像素SGBM 视差全是噪点。原因是双目标定时左右角点没有严格按同一物理点配对或者两目不同步导致运动物体错位。解决标定阶段用静止场景确保imgpointsL[i]和imgpointsR[i]是同一张棋盘格检查stereoCalibrate的输入顺序是左在前右在后。5.3 视差图大面积空洞现象是深度图中间一大片黑只有边缘有值。原因一般是numDisparities太小近处物体视差超出搜索范围或者blockSize太小纹理弱区域匹配不上。解决先把numDisparities加到 256 试再把blockSize提到 7 或 9如果还不行说明场景本身缺纹理得加投影纹理或换主动双目。5.4 点云尺度不对测距差一个系数现象是点云能出来但量出来的距离和实际差固定倍数。原因是标定时square_size填错比如实际 25mm 填成 30mm所有深度按比例缩放。解决拿卷尺量棋盘格单格实际边长重新标定或者直接在点云上量一个已知距离反推修正系数但治本还是重标。5.5 Open3D 显示卡死或点云全黑现象是draw_geometries窗口无响应或点云一片黑。原因是点数太多几十万以上或法线没算导致着色异常。解决先对点云做体素下采样pcd.voxel_down_sample(2.0)再estimate_normals如果还卡检查是不是把无效点NaN也塞进去了用np.isfinite过滤一遍。6. 进阶技巧把重建结果做成可复用的标定文件与批量流程跑通单张之后真正省时间的是把标定结果固化成文件后面换场景只做重建不做标定。我一般会把K1、d1、K2、d2、R、T、Q一起存成 npz重建脚本开头直接加载避免每次重复标定。np.savez(stereo_params.npz, K1K1, d1d1, K2K2, d2d2, RR, TT, QQ, sizegray.shape[::-1]) # 重建时 p np.load(stereo_params.npz) Q p[Q]; mapLx, mapLy cv2.initUndistortRectifyMap( p[K1], p[d1], cv2.Rodrigues(p[R])[0], p[K1], tuple(p[size]), cv2.CV_32FC1)批量处理时把校正映射mapLx/mapLy也缓存下来因为initUndistortRectifyMap每帧重算很费时。实测 640×480 的图缓存后单帧重建能从 80ms 降到 30ms 左右。另一个技巧是给 SGBM 加disp12MaxDiff和speckleWindowSize的组合过滤再对深度图做一次中值滤波点云噪点会明显减少代价是边缘稍微钝一点做测量就关掉滤波做展示就打开。验证重建精度有个土办法在场景里放一个已知尺寸的方块重建后量它的边长误差在 2% 以内说明标定和视差都靠谱。我最早跑这套源码时没做这步直接拿结果去测距差了将近 15%回头查是square_size填错血泪经验。从那以后我每次标定完都强制走一遍「量已知物」的验证再开始正式重建。希望帮到你。本文还有配套的精品资源点击获取