【OpenCV】双目相机计算深度图和点云
双目相机计算深度图的基本原理是通过两台相机从不同角度拍摄同一场景,然后利用视差来计算物体的距离。本文的Python实现示例,使用OpenCV库来处理图像和计算深度图。
1、数据集介绍
Mobile stereo datasets由Pan Guanghan、Sun Tiansheng、Toby Weed和Daniel Scharstein在2019-2021年期间创建的,使用了Roger Dai、Kyle Meredith、Tommaso Monaco、Nick Mosier和Daniel Scharstein在2017-2018年期间开发的结构化光采集系统。该系统采用安装在UR5机械臂上的移动设备(苹果iPod touch 6G);地基真差是使用[5]中描述的结构化照明管道的一个子集来计算的。这些数据集包括11个场景,在许多不同的照明条件和曝光(包括闪光灯和移动设备的“火炬”照明)下,从1-3个不同的观看方向拍摄。

数据集基本描述:
Dataset description
Each dataset consists of 2 views taken under several different illuminations and exposures. The files are organized as follows:
SCENE{1,2,3}/ -- scene imaged from 1-3 viewing directionsambient/ -- directory of all input views under ambient lighting{F,L,T}{0,1,...}/ -- different lighting conditions (F=flash, L=lighting, T=torch)im0e{0,1,2,...}.png -- left view under different exposuresim1e{0,1,2,...}.png -- right view under different exposurescalib.txt -- calibration informationim{0,1}.png -- default left and right view (typically ambient/L0/im{0,1}e2.png)disp{0,1}.pfm -- left and right GT disparities
Zip files containing the above files can be downloaded here. "all.zip" contains all 24 scenes (image pair, disparities, calibration file), but not the ambient subdirectories. The latter are available in separate zip files.Calibration file format
Here is a sample calib.txt file:
cam0=[1758.23 0 953.34; 0 1758.23 552.29; 0 0 1]
cam1=[1758.23 0 953.34; 0 1758.23 552.29; 0 0 1]
doffs=0
baseline=111.53
width=1920
height=1080
ndisp=290
isint=0
vmin=75
vmax=262
Explanation:cam0,1: camera matrices for the rectified views, in the form [f 0 cx; 0 f cy; 0 0 1], wheref: focal length in pixelscx, cy: principal pointdoffs: x-difference of principal points, doffs = cx1 - cx0 (here always == 0)baseline: camera baseline in mmwidth, height: image sizendisp: a conservative bound on the number of disparity levels;the stereo algorithm MAY utilize this bound and search from d = 0 .. ndisp-1vmin, vmax: a tight bound on minimum and maximum disparities, used for color visualization;the stereo algorithm MAY NOT utilize this information
To convert from the floating-point disparity value d [pixels] in the .pfm file to depth Z [mm] the following equation can be used:
Z = baseline * f / (d + doffs)
Note that the image viewer "sv" and mesh viewer "plyv" provided by our software cvkit can read the calib.txt files and provide this conversion automatically when viewing .pfm disparity maps as 3D meshes.
- 如果使用自己的双目相机,则需要知道相机两个摄像头的内参矩阵
cam0,1、基线baseline
2、Python代码
代码使用的双目图像数据是chess2
import cv2
import numpy as np
import time
import matplotlib.pyplot as pltdef load_images(left_path, right_path):left_image = cv2.imread(left_path, 0)right_image = cv2.imread(right_path, 0)return left_image, right_image#
def compute_disparity_map(left_image, right_image, cam0, cam1, doffs, ndisp, vmin, vmax, block_size=15):stereo = cv2.StereoSGBM_create(minDisparity=vmin, # 视差从0开始numDisparities=ndisp, # 视差范围数量,必须是16的倍数blockSize=block_size, # 匹配块的大小,奇数P1=8 * 3 * block_size ** 2, # 平滑惩罚项(第一级)P2=32 * 3 * block_size ** 2, # 平滑惩罚项(第二级),通常是P1的4倍disp12MaxDiff=1, # 左右视差检查的最大允许差异uniquenessRatio=10, # 唯一性比率阈值speckleWindowSize=100, # 斑点滤波器窗口大小speckleRange=32 # 斑点滤波器最大容许差异)disparity_map = stereo.compute(left_image, right_image).astype(np.float32) / 16.0return disparity_map# 转成深度图
def convert_disparity_to_depth(disparity_map, focal_length_px, baseline_mm):# 避免除以零的情况depth_map = (focal_length_px * baseline_mm) / disparity_mapdepth_map[disparity_map == 0] = 0 # 设置无效区域的深度为0return depth_map# 过滤最远平面
def filter_far_plane(depth_map, max_distance=1500): depth_map[depth_map > max_distance] = 0return depth_map#生成普通点云
def generate_point_cloud(depth_map, cam0, cx, cy):fx = cam0[0, 0]fy = cam0[1, 1]height, width = depth_map.shape# 创建网格u, v = np.meshgrid(np.arange(width), np.arange(height))# 计算 x, y, z 坐标z = depth_mapx = (u - cx) * z / fxy = (v - cy) * z / fy# 将坐标堆叠成点云数组point_cloud = np.stack((x, y, z), axis=-1)# 过滤掉无效点(深度值为 0 的点)valid_mask = z > 0point_cloud = point_cloud[valid_mask]return point_cloud#生成彩色点云
def generate_colored_point_cloud(depth_map, cam0, cx, cy): fx = cam0[0, 0]fy = cam0[1, 1]height, width = depth_map.shape# 创建网格u, v = np.meshgrid(np.arange(width), np.arange(height))# 计算x, y, z坐标x = (u - cx) * depth_map / fxy = (v - cy) * depth_map / fyz = depth_map# 将坐标堆叠成点云数组point_cloud = np.stack((x, y, z), axis=-1)# 归一化深度值valid_mask = depth_map > 0normalized_depth = np.zeros_like(depth_map)if valid_mask.any():normalized_depth[valid_mask] = (depth_map[valid_mask] - np.min(depth_map[valid_mask])) / (np.max(depth_map[valid_mask]) - np.min(depth_map[valid_mask]))# 使用jet colormap生成颜色colors = plt.cm.jet(normalized_depth)[:, :, :3] # 获取RGB颜色,忽略alpha通道# 将颜色值从 [0, 1] 转换为 [0, 255] 并转换为 uint8 类型colors = (colors * 255).astype(np.uint8)return point_cloud, colorsdef save_point_cloud_to_txt(point_cloud, file_path):point_cloud = np.hstack([point_cloud.reshape(-1, 3)])np.savetxt(file_path, point_cloud, fmt='%f %f %f', header='x y z', comments='')def save_point_cloud_to_txt_rgb(point_cloud, colors, file_path):points_with_colors = np.hstack([point_cloud.reshape(-1, 3), colors.reshape(-1, 3)])np.savetxt(file_path, points_with_colors, fmt='%f %f %f %f %f %f', header='x y z r g b', comments='')print(f"Colored point cloud saved to {file_path}")if __name__ == "__main__":left_image_path = 'Stereo/data/chess2/ambient/L0/im0e3.png' # 替换为你的左眼图像路径right_image_path = 'Stereo/data/chess2/ambient/L0/im1e3.png' # 替换为你的右眼图像路径left_image, right_image = load_images(left_image_path, right_image_path)# 双目相机参数cam0 = np.array([[1758.23, 0, 872.36], [0, 1758.23, 552.32], [0, 0, 1]])cam1 = np.array([[1758.23, 0, 872.36], [0, 1758.23, 552.32], [0, 0, 1]])doffs = 0baseline_mm = 124.86width = 1920height = 1080ndisp = 310isint = 0vmin=90vmax=280# 计算像素焦距fx = cam0[0, 0]fy = cam0[1, 1]cx = cam0[0, 2]cy = cam1[1, 2]start_time = time.time()disparity_map = compute_disparity_map(left_image, right_image, cam0, cam1, doffs, ndisp, vmin, vmax)depth_map = convert_disparity_to_depth(disparity_map, fx, baseline_mm)depth_map = filter_far_plane(depth_map, max_distance=depth_map.max()-1) # 过滤最远的平面# point_cloud = generate_point_cloud(depth_map, cam0, cx, cy)point_cloud, colors = generate_colored_point_cloud(depth_map, cam0, cx, cy)end_time = time.time()elapsed_time = end_time - start_timeprint(f"Elapsed Time = {elapsed_time:.4f} seconds")#cv2.imshow('depth',depth_map)#cv2.waitKey(0)#cv2.imwrite('D:/dataset/depth.jpg',depth_map)# 创建 1 行 3 列的子图布局plt.figure(figsize=(12, 4))# 显示第左图plt.subplot(1, 3, 1)plt.imshow(cv2.imread(left_image_path)[:, :, ::-1] ) # 反转通道顺序plt.title('left_image')plt.axis('off')# 显示右图plt.subplot(1, 3, 2)plt.imshow(cv2.imread(right_image_path)[:, :, ::-1] ) # 反转通道顺序plt.title('right_image')plt.axis('off')# 显示深度图plt.subplot(1, 3, 3)plt.imshow(depth_map, cmap='gray')plt.title('depth_map')plt.axis('off')# 调整子图间距plt.tight_layout()# 显示图像plt.show()# 保存最后一次迭代的点云到TXT文件output_file_path = 'point_cloud.txt'save_point_cloud_to_txt(point_cloud, output_file_path)#save_point_cloud_to_txt_rgb(point_cloud, colors, output_file_path)print(f"Point cloud saved to {output_file_path}")
3、运行结果
3.1 处理耗时
Elapsed Time = 1.3469 seconds
Point cloud saved to point_cloud.txt
3.2 左图、右图和计算的深度图

3.3 保存的点云文件point_cloud.txt

4、代码说明
函数 compute_disparity_map 的功能是计算双目立体视觉中的视差图(Disparity Map)。视差图是双目立体视觉中的关键输出,它表示左右图像中对应像素点的水平位移(视差),可以用来计算深度信息。

具体功能:
-
输入:
left_image和right_image:双目相机的左右图像。cam0和cam1:左右相机的内参矩阵(虽然函数中未直接使用,但通常在后续深度计算中会用到)。doffs:左右相机光心的水平偏移(通常用于校正后的图像)。ndisp:视差范围的数量(必须是 16 的倍数)。vmin和vmax:视差的最小值和最大值。block_size:匹配块的大小(奇数)。
-
输出:
disparity_map:视差图,表示每个像素点的视差值。
-
核心逻辑:
- 使用 OpenCV 的
cv2.StereoSGBM_create创建一个半全局块匹配(Semi-Global Block Matching, SGBM)立体匹配器。 - 调用
stereo.compute计算左右图像的视差图。 - 将视差图的值除以 16(OpenCV 的 SGBM 算法返回的视差图是 16 倍的实际值)。
- 使用 OpenCV 的
参数详解:
minDisparity=vmin:视差的最小值,通常为 0。numDisparities=ndisp:视差范围的数量,必须是 16 的倍数。例如,如果ndisp=64,则视差范围为[vmin, vmin + 64]。blockSize=block_size:匹配块的大小,必须是奇数。较大的块可以提高鲁棒性,但会降低细节。P1和P2:平滑惩罚项,用于控制视差图的平滑程度。P2通常是P1的 4 倍。disp12MaxDiff=1:左右视差检查的最大允许差异,用于过滤不匹配的点。uniquenessRatio=10:唯一性比率阈值,用于过滤非唯一的匹配点。speckleWindowSize=100:斑点滤波器窗口大小,用于去除小的噪声区域。speckleRange=32:斑点滤波器的最大容许差异。
视差图的用途:
视差图可以用于计算深度图(Depth Map),公式为:
Depth = f ⋅ B Disparity \text{Depth} = \frac{f \cdot B}{\text{Disparity}} Depth=Disparityf⋅B
其中:
- f f f 是相机的焦距(通常从相机内参矩阵中获取)。
- B B B 是基线长度(左右相机光心之间的距离)。
- Disparity \text{Disparity} Disparity 是视差值。
函数使用示例代码:
import cv2
import numpy as np# 假设 left_image 和 right_image 是左右图像
left_image = cv2.imread('left.png', cv2.IMREAD_GRAYSCALE)
right_image = cv2.imread('right.png', cv2.IMREAD_GRAYSCALE)# 相机内参和参数
cam0 = np.array([[1000, 0, 320], [0, 1000, 240], [0, 0, 1]]) # 左相机内参
cam1 = np.array([[1000, 0, 320], [0, 1000, 240], [0, 0, 1]]) # 右相机内参
doffs = 0 # 光心偏移
ndisp = 64 # 视差范围
vmin = 0 # 最小视差
vmax = 64 # 最大视差# 计算视差图
disparity_map = compute_disparity_map(left_image, right_image, cam0, cam1, doffs, ndisp, vmin, vmax)# 显示视差图
cv2.imshow('Disparity Map', disparity_map / ndisp) # 归一化显示
cv2.waitKey(0)
cv2.destroyAllWindows()
注意事项:
- 图像输入:左右图像需要是校正后的图像(即极线对齐)。
- 视差范围:
ndisp的选择需要根据场景的深度范围调整。 - 性能:SGBM 算法的计算复杂度较高,对于高分辨率图像可能需要较长时间。
如果有其他问题,欢迎评论!😊
相关文章:
【OpenCV】双目相机计算深度图和点云
双目相机计算深度图的基本原理是通过两台相机从不同角度拍摄同一场景,然后利用视差来计算物体的距离。本文的Python实现示例,使用OpenCV库来处理图像和计算深度图。 1、数据集介绍 Mobile stereo datasets由Pan Guanghan、Sun Tiansheng、Toby Weed和D…...
Uniapp 原生组件层级过高问题及解决方案
文章目录 一、引言🏅二、问题描述📌三、问题原因❓四、解决方案💯4.1 使用 cover-view 和 cover-image4.2 使用 subNVue 子窗体4.3 动态隐藏原生组件4.4 使用 v-if 或 v-show 控制组件显示4.5 使用 position: fixed 布局 五、总结Ἰ…...
iOS实现生物识别
1. info.plist中添加权限申请 <key>NSFaceIDUsageDescription</key> <string>APP would like to use Face ID</string> <key>NSBiometricUsageDescription</key> <string>APP would like to use Touch ID</string>2. 添加库 …...
React 初级教程
一、React 简介 React 是由 Facebook 开发的开源 JavaScript 库,用于构建用户界面(UI)。特点: 声明式编程:通过描述 UI 应该是什么样子(而不是操作 DOM)来构建界面。组件化:将 UI 拆分为独立可复用的组件。跨平台:支持 Web(React)、移动端(React Native)、VR 等。…...
【数据结构初阶第十节】队列(详解+附源码)
好久不见。。。别不开心了,听听喜欢的歌吧 必须有为成功付出代价的决心,然后想办法付出这个代价。云边有个稻草人-CSDN博客 目录 一、概念和结构 二、队列的实现 Queue.h Queue.c test.c Relaxing Time! ————————————《有没…...
萌新学 Python 之列表 list
list 列表:用中括号定义,元素写在中括号之间,元素之间使用逗号分隔 list1 [] # 空列表 list2 [1] # 元素为1个 list3 [a, b, c] print(type(list1), type(list2), type(list3)) # <class list> <class list> <class …...
250213-RHEL8.8-外接SSD固态硬盘
It seems that the exfat-utils package is still unavailable, even after enabling the RPM Fusion repository. This could happen if the repository metadata hasn’t been updated or if the package isn’t directly available in the RPM Fusion repository for RHEL 8…...
游戏引擎学习第99天
仓库:https://gitee.com/mrxiao_com/2d_game_2 黑板:制作一些光场(Light Field) 当前的目标是为游戏添加光照系统,并已完成了法线映射(normal maps)的管道,但还没有创建可以供这些正常映射采样的光场。为了继续推进&…...
Linux初始化 配置yum源
问题出现:(报错) 1 切换路径 2 备份需要操作的文件夹 3 更改 CentOS 的 YUM 仓库配置文件,以便使用阿里云的镜像源。 4 清除旧的yum缓存 5 关闭防火墙 6 生成新的yum缓存 7 更新系统软件包 8 安装软件包...
【笛卡尔树】
笛卡尔树 笛卡尔树定义构建性质 习题P6453 [COCI 2008/2009 #4] PERIODNICF1913D Array CollapseP4755 Beautiful Pair[ARC186B] Typical Permutation Descriptor 笛卡尔树 定义 笛卡尔树是一种二叉树,每一个节点由一个键值二元组 ( k , w ) (k,w) (k,w) 构成。要…...
Ubuntu启动geteck/jetlinks实战:Docker启动(无法登录)
参考: JetLinks 物联网基础平台 安装Docker Ubuntu下载安装Docker-Desktop-CSDN博客 sudo apt install -y docker-compose 下载源码 git clone https://github.com/jetlinks/jetlinks-community.git cd jetlinks-community 启动 cd docker/run-all sudo dock…...
Java String 类深度解析:内存模型、常量池与核心机制
目录 一、String初识 1. 字符串字面量的处理流程 (1) 编译阶段 (2) 类加载阶段 (3) 运行时阶段 2. 示例验证 示例 1:字面量直接赋值 示例 2:使用 new 创建字符串 示例 3:显式调用 intern() 注意点1: ⑴. String s1 &q…...
不到一个月,SQLite 3.49.0来了
距离 SQLite 3.48.0 发布不到一个月,SQLite 开发团队于 2025 年 2 月 6 日发布了 SQLite 3.49.0 版本。这更新速度的确让人感动,那么这个版本又有哪些更新呢? 查询优化器 新版本改进了自动索引(query-time index)优化…...
探索顶级汽车软件解决方案:驱动行业变革的关键力量
在本文中,将一同探索当今塑造汽车行业的最具影响力的软件解决方案。从设计到制造,软件正彻底改变车辆的制造与维护方式。让我们深入了解这个充满活力领域中的关键技术。 设计软件:创新车型的孕育摇篮 车辆设计软件对于创造创新型汽车模型至…...
ThreadPoolExecutor 详解
一、ThreadPoolExecutor 核心参数 构造函数如下: public ThreadPoolExecutor(int corePoolSize, // 核心线程数int maximumPoolSize, // 最大线程数long keepAliveTime, // 非核心线程空闲存活时间TimeUnit unit, // 存活时间单位BlockingQueue…...
TikTok走红全球:中国短视频平台以全新姿态登陆海外市场
在数字化浪潮中,短视频已经成为全球年轻人表达自我、分享生活的重要方式。TikTok,这个起源于中国的短视频平台,以其独特的魅力和创新的功能在全球范围内迅速走红。本文将探讨TikTok如何以全新姿态登陆海外市场,并分析其成功的关键…...
Qt的QTableWidget样式设置
在 Qt 中,可以通过样式表(QSS)为 QTableWidget 设置各种样式。以下是一些常见的样式设置示例: 1. 基本样式设置 tableWidget->setStyleSheet(// 表格整体样式"QTableWidget {"" background-color: #F0F0F0;…...
计算机毕业设计Python旅游评论情感分析 NLP情感分析 LDA主题分析 bayes分类 旅游爬虫 旅游景点评论爬虫 机器学习 深度学习
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...
渗透测试扫描器全解析:分类与介绍
目录 前言 操作系统扫描器:系统漏洞的 “侦察兵” 特点 代表性工具 网站 Web 漏洞扫描器:Web 应用的 “安全卫士” 特点 代表性工具 手机漏洞扫描器:移动设备的 “安全护盾” 特点 代表性工具 前言 在数字技术飞速发展的当下&…...
day9手机创意软件
趣味类 in:记录趣味生活(通用) 魔漫相机:真人变漫画(通用) 活照片:让照片活过来(通用) 画中画相机:与众不同的艺术 年龄检测仪:比一比谁更年轻…...
A001基于SpringBoot实现的小区物业管理系统
系统介绍 基于SpringBoot实现的小区物业管理系统是为物业管理打造的一款在线管理平台,它可以实时完成信息处理,对小区信息、住户等进行在线管理,使其系统化和规范化。 系统功能说明 1、系统共有物业、业主角色,物业拥有系统最高…...
【Uniapp】关于实现下拉刷新的三种方式
在小程序、h5等地方中,常常会用到下拉刷新这个功能,今天来讲解实现这个功能的三种方式:全局下拉刷新,组件局部下拉刷新,嵌套组件下拉刷新。 全局下拉刷新 这个方式简单,性能佳,最推荐…...
常见的 Web 攻击方式有哪些,如何防御?
一、XSS攻击(跨站脚本攻击) 攻击原理:恶意脚本通过用户输入注入页面,分为存储型(数据库持久化)、反射型(URL参数注入)、DOM型(客户端脚本修改) 防御方案&am…...
深入理解DeepSeek与企业实践(二):32B多卡推理的原理、硬件散热与性能实测
前言 在《深入理解 DeepSeek 与企业实践(一):蒸馏、部署与评测》文章中,我们详细介绍了深度模型的蒸馏、量化技术,以及 7B 模型的部署基础,通常单张 GPU 显存即可满足7B模型完整参数的运行需求。然而&…...
数据结构-链式二叉树
文章目录 一、链式二叉树1.1 链式二叉树的创建1.2 根、左子树、右子树1.3 二叉树的前中后序遍历1.3.1前(先)序遍历1.3.2中序遍历1.3.3后序遍历 1.4 二叉树的节点个数1.5 二叉树的叶子结点个数1.6 第K层节点个数1.7 二叉树的高度1.8 查找指定的值(val)1.9 二叉树的销毁 二、层序…...
【云安全】云原生- K8S etcd 未授权访问
什么是etcd? etcd 是一个开源的分布式键值存储系统,主要用于存储和管理配置信息、状态数据以及服务发现信息。它采用 Raft 共识算法,确保数据的一致性和高可用性,能够在多个节点上运行,保证在部分节点故障时仍能继续提…...
AI时代的前端开发:对抗压力的利器
在飞速发展的AI时代,前端开发工程师们面临着前所未有的挑战。项目周期不断缩短,需求变化日新月异,交付压力更是与日俱增,这使得开发人员承受着巨大的压力。如何提升对抗压能力,成为摆在每一位前端工程师面前的重要课题…...
在npm上传属于自己的包
前言 最近在整理代码,上传到npm方便使用,所以学习了如何在npm发布一个包,整理写成一篇文章和大家一起交流。 修改记录 更新内容更新时间文章第一版25.2.10新增“使用包”,“删除包的测试”25.2.12 1、注册npm账号 npm | Home 2…...
[Spring] Spring常见面试题
🌸个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 🏵️热门专栏: 🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 🍕 Collection与…...
适配器模式详解(Java)
一、引言 1.1 定义与类型 适配器模式是一种结构型设计模式,主要目的是将一个类的接口转换为客户期望的另一个接口。这种模式使得原本因为接口不匹配而不能一起工作的类可以一起工作,从而提高了类的复用性。适配器模式分为类适配器和对象适配器两种类型。类适配器使用继承关…...
