
简介这是一套面向毕业设计、期末大作业及课程设计的Python双目立体视觉与三维重建项目覆盖从图像采集、立体校正、立体匹配到深度估计与点云重建的完整流程代码均带详细注释新手也可轻松上手。资源包共25个文件压缩后约33.78MB核心为14个Python源码文件涵盖标定配置、滤波优化、深度图生成、图片转视频等模块另有5个运行配置文件、3段结果演示视频以及1份图文并茂的项目说明文档便于对照理解与快速部署。目前已有285人学习下载是被导师认可的高分项目。读者可借此获得完整可运行的视觉方案从预处理到输出的全流程代码以及配套的演示录屏和工程笔记尤其适合需要完成类似课题或希望快速搭建双目重建系统的学生参考。1. 双目立体视觉与三维重建这个 Python 项目到底解决什么问题双目立体视觉和三维重建在毕业设计里是个很讨巧的题公式推导不深但做出来效果很唬人。真正动手才发现坑全在后半段——左右图拍好了SGBM 一跑全是雪花视差图出来了深度图却黑一块白一块。这份基于 Python 的双目立体视觉及三维重建项目代码就是冲着这个痛点去的。它把整个流程收成了一个完整可跑的工程stereoconfig 双目相机参数、SGBM 立体匹配、WLS 滤波、空洞填充、深度图生成、视频转帧和帧转视频的配套脚本全都在里面还带一份项目说明和代码注释。适合三类人要交期末大作业或课程设计的学生、想在答辩现场演示完整重建流程的同学、以及第一次接触双目视觉、想搞懂左右图到点云这条数据流的从业者。2. 先看懂工程结构从 stereoconfig 到 Depth.py一幅深度图是怎么算出来的2.1 文件清单这条工程里每个脚本是干什么的拿到压缩包先别急着跑 main.py。这份资源里脚本不少但各司其职先把链路理清楚后面调参时才知道该改谁。文件作用调用关系stereoconfig.py / 01 / 02双目相机参数内参、基线、畸变被 main.py、Depth.py 导入main.py主流程读取左右图 → SGBM 匹配 → 视差图核心脚本wls_filter.pyWLS 滤波把视差图的毛刺和噪点压掉在 main.py 中调用filter.py / filter02.py后处理滤波去孤立噪点可选接在 wls 之后fill.py空洞填充把视差图为 0 的区域补齐后处理dilation.py膨胀操作先扩张前景区域再做填充在 fill 之前Depth.py把视差图重投影成三维坐标/点云依赖 Q 矩阵mp42photo.py从视频里按间隔抽帧生成左右图序列数据准备photo2mp4.py把处理后的帧合成视频结果输出resize.py批量缩放图像尺寸数据准备tkvideo.pyTkinter 弹窗播放视频答辩演示辅助注意 .idea 目录和无后缀的 xml 文件是 PyCharm 工程配置对运行没影响忽略就行。README.md 是原作者的说明建议第一遍先读它。2.2 双目视觉的几何原理左右图差一点点深度差很多双目测距的核心不是图像本身而是左右两幅图上同一个目标点之间的“位置差”。假设左相机看到的点在像素坐标 x_left右相机看到同一个点在 x_right视差 d x_left - x_right。有了视差深度就能用 Z f × B / d 算出来f 是焦距像素单位B 是左右相机光心的距离叫基线。这个公式解释了两件事。第一视差越大物体越近第二d 在分母上所以近处物体对 d 的变化极其敏感远处物体视差趋近于 0深度值会飘。这也是为什么所有双目项目都会强调“基线要够宽、相机要尽量平行”stereoconfig 里的 Tx 就是基线 B 的数值。理解了这一点就明白 SGBM 输出的视差图质量直接决定了后面重建的成败。2.3 main.py 主流程从左右图到视差图的四步无论资源里的 main.py 实现细节如何变核心都不会跳出下面这个框架import cv2 import numpy as np from stereoconfig import stereo_config def compute_disparity(left_img, right_img, config): # 1. 转灰度SGBM 不处理彩色图 grayL cv2.cvtColor(left_img, cv2.COLOR_BGR2GRAY) grayR cv2.cvtColor(right_img, cv2.COLOR_BGR2GRAY) # 2. 构造 SGBM 立体匹配器参数全部来自 stereoconfig sgbm cv2.StereoSGBM_create( minDisparityconfig.min_disparity, numDisparitiesconfig.num_disparities, blockSizeconfig.block_size, P1config.p1, P2config.p2, disp12MaxDiffconfig.disp12_max_diff, uniquenessRatioconfig.uniqueness_ratio, speckleWindowSizeconfig.speckle_window_size, speckleRangeconfig.speckle_range, ) # 3. 计算视差图结果要除以 16 才是真实视差值 disparity sgbm.compute(grayL, grayR).astype(np.float32) / 16.0 return disparity这段代码的注释已经点出了关键SGBM 的原始输出是定点数除以 16 是为了还原成浮点像素视差这个细节很多新手会漏导致后面深度图整体被放大 16 倍。minDisparity 和 numDisparities 决定了你搜索视差的范围范围越大计算越慢但能覆盖的近处物体越全。这份资源的 stereoconfig.py 里已经把示例参数配好了你换自己的图像时优先改的是 numDisparities 和 blockSize而不是内参。main.py 拿到视差图之后还会继续做 WLS 滤波、空洞填充、生成深度图甚至调用 Depth.py 输出三维坐标。整条链路就是左右图 → 视差图 → 滤波/填充 → 深度图 → 点云每一步都在为下一步降噪。3. 跑通项目的最小步骤环境、命令行与输入输出3.1 Python 环境与 OpenCV 版本选型这个项目依赖的第三方库很少最核心的是 OpenCV。安装上有个经典坑官方opencv-python包是精简版很多双目后处理接口尤其 ximgproc 模块里的 WLS 滤波在它是找不到的。我一般直接装全家桶pip install opencv-python opencv-contrib-python numpy如果你在 VSCode 或 PyCharm 里配过 Python 环境这一步最常翻车的地方是装到了别的解释器里。终端里先执行python --version确认当前环境再 pip install。Python 版本不需要追新3.8 到 3.10 都稳OpenCV 4.x 配这些版本不会有什么脾气。装完验证一下python -c import cv2; print(cv2.__version__)能打印出版本号就过了。如果这里报错大概率是解释器没切对或者虚拟环境没激活别急着去改代码。3.2 用示例视频复现完整流程资源里自带 result0.mp4 和 output.mp4前者是原始采集的双目视频后者是跑完后的重建输出。复现流程分三步# 1. 从视频抽帧生成左右图序列 python mp42photo.py --video result0.mp4 --out photo_video --interval 30 # 2. 跑主流程生成视差图和深度图 python main.py --left photo_video/left --right photo_video/right --output photo_video/depth # 3. 把深度图序列合成输出视频 python photo2mp4.py --frames photo_video/depth --output output.mp4 --fps 30这三个命令就是整套资源的骨架。--interval 30表示每 30 帧抽一帧如果你的视频是 30fps相当于每秒取一帧。这个值别设太小否则相邻两帧几乎没位移SGBM 匹配不到有效视差也别太大否则目标在左右图之间的位移跳变太大容易出现撕裂。场景运动快就适当减小场景静止就加大。--fps 30控制输出视频的帧率要和抽帧率配合起来看。抽帧间隔 30、输出 30fps就是把 1 秒钟的视频内容摊到 30 帧输出视觉效果是慢放。如果只想看深度图效果可以把 fps 调高到 60快速翻页。3.3 答辩现场可视化tkvideo.py 的用法写论文的同学可能没意识到答辩演示时最尴尬的是现场打不开视频播放器、或者视频编码格式在教室电脑上不支持。tkvideo.py 这个脚本就是干这个的用 Tkinter 裸写一个视频播放窗口不依赖任何第三方播放器。from tkvideo import tkvideo # 传入视频路径和播放帧率 player tkvideo(output.mp4, 30, label) player.play()它的原理很简单用 OpenCV 读帧再通过 PIL 转成 Tkinter 能显示的 PhotoImage循环 update。好处是打包成 exe 后不需要客户机装解码器答辩现场插 U 盘就能跑。坏处是它没有进度条和音量控制遇到编码特殊的 mp4 可能花屏。我一般会把 output.mp4 同时压一份 H.264 编码的备用以防现场翻车。4. 改参数才能出好图SGBM、WLS 与空洞填充的调参细节4.1 StereoSGBM 关键参数从“全黑”到“有层次”很多同学第一次跑完视差图是全黑的原因八成是 numDisparities 设太小或尺寸不合法。SGBM 要求 numDisparities 是 16 的倍数blockSize 必须是奇数这两个约束是硬性的不满足直接报错或出空图。我常用的一组起点参数直接贴在下面参数推荐区间影响minDisparity0 或 -16小于 0 可以测量更近的目标但零视差区会被占用numDisparities64 ~ 256越大覆盖视差范围越广计算越慢blockSize5 ~ 15奇数越大越平滑太小全是噪点太大会丢边缘P1 / P2P18×通道数×blockSize²P2P1×4平滑惩罚P2 太小会让边缘破碎uniquenessRatio5 ~ 15误匹配抑制越大越挑speckleWindowSize50 ~ 200去小斑点0 表示关闭speckleRange1 ~ 2斑点内允许的视差波动disp12MaxDiff1左右一致性检查阈值-1 关闭这份资源的 stereoconfig.py 里应该已经给了一套能出图的参数但那是针对示例视频调的。换成你自己的双目摄像头第一条要调的就是 numDisparities。我一般先给 64 看整体形貌如果近处物体发黑视差超出搜索范围就翻倍到 128 或 256。blockSize 从 7 开始试边缘毛刺多就加大物体轮廓糊在一起就减小。4.2 wls_filter.py让深度图从雪花变成丝绸SGBM 直接输出的视差图噪点很多尤其纹理稀疏的区域基本是椒盐噪声的天下。WLSWeighted Least Squares滤波是 OpenCV 里最有效的视差图后处理手段它利用左图的颜色/梯度信息对视差图做引导滤波在保留边缘的同时把平坦区域抹平。import cv2 import numpy as np def wls_filter(disparity, left_img, sgbm, config): grayL cv2.cvtColor(left_img, cv2.COLOR_BGR2GRAY) grayR cv2.cvtColor(left_img, cv2.COLOR_BGR2GRAY) # 实际应传入右图 # 构造左右两个匹配器WLS 需要右视差图做一致性检查 right_matcher cv2.ximgproc.createRightMatcher(sgbm) disparity_right right_matcher.compute(grayR, grayL).astype(np.float32) / 16.0 # 创建 WLS 滤波器 wls cv2.ximgproc.createDisparityWLSFilter(sgbm) wls.setLambda(config.wls_lambda) # 平滑强度越大越平滑 wls.setSigmaColor(config.wls_sigma) # 颜色标准差控制边缘保护 filtered wls.filter(disparity, grayL, disparity_map_rightdisparity_right) return filtered注意这个实现里必须先createRightMatcher算出右视差图WLS 靠左右一致性来判断哪些点是错误匹配直接跳过这一步会报参数错误。lambda 是玄学参数默认值 8000 对很多场景偏大画面会糊成一片。我调的时候从 1000 起步慢慢加到边缘不再破碎为止sigmaColor 保持 1.0~2.0 之间它决定的是颜色差异的敏感度设太大边缘会被磨掉。WLS 最爽的一点是能救回来不少纹理稀疏区域的视差空洞因为它不信那些孤立点的匹配而用周围像素的深度趋势去推。但注意滤波不等于填充它只是让已有的视差值互相渗透真正的空洞区域还是得靠下一步。4.3 fill.py 与 dilation.py后处理顺序不能乱视差图上那些纯黑的点代表的是“没匹配上”常见于遮挡区域和弱纹理区域。把黑色区域直接填掉看着挺美但盲目填充会引入假深度。合理的顺序是先膨胀、后填充。dilation.py 做的事是用一个小尺寸核常见 3×3 或 5×5把前景区域向外扩一圈把那些孤立的黑色噪点吃掉。fill.py 再做真正的空洞填充思路是遍历黑色像素取周围非零视差值的统计值来填补通常取中位数能保留更多细节。这两步的先后顺序很关键。如果先 fill 后 dilation填充时会把大块空洞误判成有效区域膨胀后整个物体边缘往外涨一圈深度图看起来胖了点云也跟着变形。所以这条管线是固定死的原始视差 → WLS → dilation → fill。资源里的 main.py 大概率就是这个顺序你后处理效果不对优先检查是不是改乱了这一步。5. 避坑指南双目三维重建调试中的四个常见翻车点5.1 视差全黑、深度图起雾这两个高频现象现象一左右图看着正常SGBM 跑完视差图全黑。原因不外乎两个numDisparities 不是 16 的倍数使匹配器内部报错但不抛异常或者视差范围与实际场景不匹配——比如目标物体很近而 minDisparity 设成了正数导致所有像素的视差都超出搜索范围匹配不到任何点。解决方法是先在 stereoconfig 里把 minDisparity 改成 0numDisparities 从 64 开始逐级增大每改一次跑一帧看输出。同时用print(disparity.max())检查最大视差值如果一直是 0说明匹配器根本没有收到有效输入再回头检查图片路径是否正确。现象二深度图灰蒙蒙一片物体轮廓和背景分不开。这是最容易被误判成“代码写错”的情况其实只是 WLS 没调好。视差图本身可能是好的但 lambda 设太大把所有高低差都磨平了看起来就像一层雾。解决方法是把 lambda 降到 1000 以下跑一版对比如果边缘恢复了但噪点多再逐步往上加。另外检查是不是忘了在 wls.filter 里传右视差图缺了这个 WLS 会退化成普通滤波输出立刻变灰。5.2 OpenCV 版本差异、帧尺寸不一致这两个隐藏坑现象三运行到 WLS 相关代码时报module cv2 has no attribute ximgproc。原因基本可以锁定装的是精简版opencv-python而 ximgproc 模块只存在于opencv-contrib-python里。看似简单的缺失卡住不少第一次跑双目项目的人。解决方法是重新安装pip uninstall opencv-python opencv-contrib-python pip install opencv-contrib-python装完再验证cv2.ximgproc能正常引入。如果你在 OpenCV 4.5 以上版本里跑老代码还可能遇到StereoSGBM_create的接口签名变化老代码用cv2.SGBM_create的可以直接复制这份资源的写法新老接口在这个系列里是兼容的。现象四photo2mp4 合成的视频闪烁画面还会间歇性拉伸变形。这不是算法问题是输入图尺寸不统一。mp42photo 抽帧时如果源视频分辨率在中间有跳变或者抽出来包含彩色图、深度图混在一个目录里photo2mp4 拿到不同尺寸的帧硬编码就会出现这个现象。解决方法是抽帧后先跑一遍 resize.py把所有帧统一成同一个尺寸比如 640×480同时确认目录里只有同一类型的输出帧。深度图要显示彩色效果记得在合成前用cv2.applyColorMap转成伪彩色直接拿灰度图合成视频视觉冲击力差很多。6. 进阶把深度图导出为三维点云并用自己的手机照片验证6.1 从视差图到三维点Depth.py 的重投影逻辑前面所有工作都在处理二维信息最后一步才是真正跨进三维。Depth.py 里大概率做的是用reprojectImageTo3D把视差图结合 Q 矩阵转成三维坐标Q 矩阵长这样import numpy as np import cv2 # 以左相机为原点构造 Q 矩阵 # cx, cy 是左相机主点fx 是焦距Tx 是基线 Q np.float32([ [1, 0, 0, -cx], [0, 1, 0, -cy], [0, 0, 0, fx], [0, 0, -1 / Tx, 0] ]) points cv2.reprojectImageTo3D(disparity, Q)拿到的points是 H×W×3 的数组每个像素存着对应的 (X, Y, Z) 坐标。但要认清一个现实这是相机坐标系下的三维点不是真实尺度下的绝对坐标它的单位由 Tx 决定——如果 Tx 是米Z 就是米如果 Tx 是毫米Z 就是毫米。这也是为什么 stereoconfig 里 Tx 的数值不能随便抄必须和你自己的相机基线匹配。想可视化点云最省事的是用 Open3Dimport open3d as o3d # 把深度图转为点云再保存成 ply pcd o3d.geometry.PointCloud() pcd.points o3d.utility.Vector3dVector(points.reshape(-1, 3)) pcd.transform([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]]) o3d.io.write_point_cloud(result.ply, pcd)注意点云里会混入大量视差为 0 的无效点导出前先按 Z 值过滤掉离群点否则打开后屏幕一片噪点。这个导出与可视化链路是答辩时最能体现“三维重建完成度”的演示素材。6.2 用自己的手机照片重建时stereoconfig 必须改的三个地方用资源自带的视频跑通后下一步肯定是想用自己的场景试试。手机拍左右图听起来简单实际坑一踩一个准。核心改动集中在 stereoconfig.py 的三个数值class stereo_config: # 左相机内参fx, fy 是焦距像素cx, cy 是主点 left_camera_matrix [[fx, 0, cx], [0, fy, cy], [0, 0, 1]] # 基线左右相机光心的水平距离单位米 Tx 0.12 # 图像尺寸必须与你的输入图一致 image_size (640, 480)第一是图像尺寸很多手机拍出来的图是 4000×3000直接喂给 SGBM 会慢到怀疑人生先用 resize.py 缩到 640 宽再说。第二是焦距和主点手头没有标定板的话可以用相机厂商给的等效焦距按传感器宽度换算或者干脆用资源自带的参数先跑通流程——反正答辩演示的是方法不是标定精度。第三是 Tx 基线如果两张照片是用手平移拍的拿尺子量一下两次拍摄位置的水平距离填进去比瞎猜靠谱得多。有一点必须说透手机手持平移拍的双目左右图存在旋转和俯仰偏差SGBM 对这种非理想双目很敏感出来的视差图基本是花的。拿手机做实验时把手机固定在一个水平滑轨上平移才可能得到勉强能看的结果。从那以后我每次拿到一套新的双目素材都强制先走一遍固定流程检查 numDisparities 是否合法、确认图像尺寸一致、验证 WLS 右视差图存在、最后再谈重建效果。这套流程看起来机械但能砍掉至少一半的调试时间。这份项目的价值也在这里——它给了你一个带注释、可运行、前后链路完整的起点剩下的就是在它的骨架上替换数据和微调参数。希望帮到你。本文还有配套的精品资源点击获取