当前位置: 首页 > news >正文

双目相机的标定,视差图,深度图,点云生成思路与实现。

该文档记录从双目相机标定到点云生成的所有过程,同时会附上代码。

代码直接能跑。https://github.com/stu-yzZ/stereoCamera

目录

大致思路如下:

一、相机标定

1、相机参数介绍

2、单目相机标定

3、双目相机标定

二、图片畸变矫正

三、极线矫正

1、极线矫正

2、投影矩阵Q

3、图片检查

四、SGBM局部匹配算法计算视差图,并填充

1、设置立体匹配算法SGBM

2、WLS+视差图空洞填充

五、通过视差图计算深度图并可视化

六、通过视差图和Q矩阵计算每个二维坐标对应的三维坐标,同时获取颜色。

七、点云的显示

反思:


大致思路如下:

首先标定双目相机,获取每个相机的内参,同时对相机进行标定,获取相机的相对位置参数,也就是外参。

然后通过相机拍图片并对图片进行矫正(畸变矫正和立体矫正)然后通过立体匹配算法计算视差图,通过视差图计算深度图,有了视差图很多问题都可以解决了,通过视差图可以获得点云信息。

点云数据太大了 我电脑跑不动,但大致可以看到将图片像右旋转45°即和图片视角一致。

一、相机标定

相机标定获取相机参数,用来矫正图。并且要使用相机参数对拍摄的图片进行矫正。相机标定非常重要,所有的计算都是基于相机参数进行的,如果误差较大,后面所有步骤都会受到影响。我自己从淘宝上买了个200左右的双目相机,标定之后结果一直很差,非常影响后续的视差图和深度图的计算。

但是标定的过程一定要自己动手做,只有亲自动手,才能知道一些细节。由于硬件限制(姑且认为是吧),我转换思路,用别人标定好的参数和图片进行视差图和深度图的实现。使用https://vision.middlebury.edu/stereo/data/,这个数据集MiddleburyStereoDatasets进行后续步骤。

1、相机参数介绍

拿单目相机举例,四个坐标系之间需要变换,主要是参数有相机内参和外参。

四个坐标系:世界坐标系->相机坐标系->图像坐标系->像素坐标系

内参:共有11个参数变量。其中,相机的内部参数有5个:焦距,像主点坐标,畸变参数;相机的外部参数有6个:旋转,平移。

2、单目相机标定

需要准备一个标定板,一台相机,尽可能多的拍一下照片,张正友标定法。但是标定出高精度的结果太难了,我还是暂时跳过了这个精度的要求,暂时先把整条思路打通。

3、双目相机标定

双目相机标定除了求解每个摄像头的内参外,还需要求解两个摄像头之间的 相对位置姿态,即 外参(旋转矩阵和平移向量)。其中平移矩阵的(以我选择的artroom1的参数为例)是这样的self.T = np.array([[-536.62], [0.0], [0.0]]),其中第一个元素是对应的你自己相机的基线长度,也就是两个相机镜头的距离,单位是mm。如果是自己标定的话也可以通过这个参数判断自己标定误差大小。

二、图片畸变矫正

使用畸变参数对图片进行矫正

cv.undistort该函数可以实现畸变矫正功能。双目相机的话需要对左右两张图像都进行矫正操作。

void undistort( InputArray src, //输入原图OutputArray dst,//输出矫正后的图像InputArray cameraMatrix,//内参矩阵InputArray distCoeffs,//畸变系数InputArray newCameraMatrix=noArray() );

三、极线矫正

非常重要的一步,将立体匹配从二维降到一维。(对极约束,是将搜索空间约束到像平面内的一条直线上)

1、极线矫正

这篇文章从原理讲的很细,会让人有生畏的感觉https://zhuanlan.zhihu.com/p/466758105。你也可以跳过没懂的地方,首先你要知道匹配的意思就是要从针对左视图中的某一个像素,在右试图中找到对应的匹配像素,如果不做立体矫正的话,需要从右视图的整个图片中搜索,如果做了立体匹配,可以将匹配过程从整张图片(二维空间)降低到一维空间,极线矫正之后空间中点在左右视图中的投影在同一条直线上。

下面代码展示了从读取图片到极线矫正,并检测矫正情况的过程。

# 读取图像imgl = cv.imread('1_L.jpg')imgr = cv.imread('1_R.jpg')high, wide = imgl.shape[0:2]# 读取相机参数config = stereoCamera()# 消除图像畸变imgl_qb = cv.undistort(imgl, config.cam_matrix_l, config.distortion_l)imgr_qb = cv.undistort(imgr, config.cam_matrix_r, config.distortion_r)# 极线校正map1x, map1y, map2x, map2y, Q = getRectifyTransform(high, wide, config)imgl_jx, imgr_jx = rectifyImage(imgl_qb, imgr_qb, map1x, map1y, map2x, map2y)# print("Print Q!")# print(Q)# 绘制等间距平行线,检查效果line = draw_line(imgl_jx, imgr_jx)其中draw_line函数为:
def draw_line(img1, img2):height = max(img1.shape[0], img2.shape[0])width = img1.shape[1] + img2.shape[1]output = np.zeros((height, width, 3), dtype=np.uint8)output[0:img1.shape[0], 0:img1.shape[1]] = img1output[0:img2.shape[0], img1.shape[1]:] = img2line_interval = 50  # 直线间隔for k in range(height // line_interval):cv.line(output, (0, line_interval * (k + 1)),(2 * width, line_interval * (k + 1)),(0, 255, 0), thickness=2, lineType=cv.LINE_AA)# plt.imshow(output, 'gray')# plt.show()return output

2、投影矩阵Q

Q矩阵在后面生成3D点云的时候要用到。暂不多解释。

3、图片检查

极线矫正之后的图片进行检查,这个过程很难界定怎么样算是好的,或许肉眼看着在同一个平面但是在像素层面上来看并没有对齐,总的来说这个结果也是和标定结果强相关。我自己标定的结果打印出来感觉没有差很多,但是最后视差图和深度图还是效果很差。下面这是我使用数据集中的参数和图片显示的结果,(数据集中的图片已经是畸变矫正和立体匹配之后的图片,所以他们肯定极线对齐的)

四、SGBM局部匹配算法计算视差图,并填充

通过opencv获取视差图

1、设置立体匹配算法SGBM

SGBM属于局部匹配算法,我调试下来的感觉就是泛化性很低,甚至图片场景变化大的话基本上一张图片对应一套参数,计算量先不谈,我22款拯救者y9000P还能带动,SGBM算法原理我没有太深究,主要的参数调整网上有很多讲解的,可以自己看,比较重要的就是windowssize和最大最小视差,视差值对应图片的深度也就是拍照的距离。窗口大小会影响视差图的平滑与否,可以自己设置调试下。且最大最小视差可以通过拍照的距离计算出来。根据深度计算公式,已知最大最小深度也就是拍照的最近最远距离,可以计算出视差值。我使用的数值来自于数据集中提供的。

同时需要注意视差图的精度问题。

def opencv_SGBM(left_img, right_img, use_wls=False):blockSize = 11paramL = {"minDisparity": 0,              #表示可能的最小视差值。通常为0,但有时校正算法会移动图像,所以参数值也要相应调整"numDisparities": 170,          #表示最大的视差值与最小的视差值之差,这个差值总是大于0。在当前的实现中,这个值必须要能被16整除,越大黑色边缘越多,表示不能计算视差的区域"blockSize": blockSize,"P1": 8 * 3 * blockSize * blockSize,          #控制视差图平滑度的第一个参数"P2": 32 * 3 * blockSize * blockSize,         #控制视差图平滑度的第二个参数,值越大,视差图越平滑。P1是邻近像素间视差值变化为1时的惩罚值,#p2是邻近像素间视差值变化大于1时的惩罚值。算法要求P2>P1,stereo_match.cpp样例中给出一些p1和p2的合理取值。"disp12MaxDiff": 1,            #表示在左右视图检查中最大允许的偏差(整数像素单位)。设为非正值将不做检查。"uniquenessRatio": 10,          #表示由代价函数计算得到的最好(最小)结果值比第二好的值小多少(用百分比表示)才被认为是正确的。通常在5-15之间。"speckleWindowSize": 50,       #表示平滑视差区域的最大窗口尺寸,以考虑噪声斑点或无效性。将它设为0就不会进行斑点过滤,否则应取50-200之间的某个值。"speckleRange": 1,              #指每个已连接部分的最大视差变化,如果进行斑点过滤,则该参数取正值,函数会自动乘以16、一般情况下取1或2就足够了。"preFilterCap": 31,"mode": cv.STEREO_SGBM_MODE_SGBM_3WAY}matcherL = cv.StereoSGBM_create(**paramL)# 计算视差图dispL = matcherL.compute(left_img, right_img)# WLS滤波平滑优化图像if use_wls:paramR = paramLparamR['minDisparity'] = -paramL['numDisparities']matcherR = cv.StereoSGBM_create(**paramR)dispR = matcherR.compute(right_img, left_img)# dispR = np.int16(dispR)lmbda = 80000sigma = 1.0filter = cv.ximgproc.createDisparityWLSFilter(matcher_left=matcherL)filter.setLambda(lmbda)filter.setSigmaColor(sigma)dispL = filter.filter(dispL, left_img, None, dispR)#双边滤波dispL = cv2.bilateralFilter(dispL.astype(np.float32), d=9, sigmaColor=75, sigmaSpace=75)# 除以16得到真实视差(因为SGBM算法得到的视差是×16的)dispL[dispL < 0] = 1e-6dispL = dispL.astype(np.int16)dispL = dispL / 16.0return dispL

2、WLS+视差图空洞填充

wls叫做基于加权最小二乘法的保边缘平滑滤波器。目的是对图像进行平滑处理,代码在上,主要是对视差图进行平滑处理。

这篇文章讲解了基于积分的空洞填充,也包括整个双目视觉的大致流程,也包括一些细节的内容,比如提到了精度相关的知识,我受益匪浅,https://www.cnblogs.com/riddick/p/8486223.html这篇博文是2018年发表的,不得不感慨……

空洞填充可以使得视差图更加的平滑和高质量。但是需要较大算力支持。

五、通过视差图计算深度图并可视化

通过公式计算每个像素(可计算像素)的深度坐标,同时生成深度图并可视化。

1、计算深度图并可视化

直接根据深度计算公式对视差图进行计算,但是要确定深度的单位,z=(f*b)/d,其中f和d的单位是像素,b(baseline)的单位是米,计算出来的z深度单位也是米。

六、通过视差图和Q矩阵计算每个二维坐标对应的三维坐标,同时获取颜色。

1、生成点云信息并保存

看代码即可。没有什么逻辑。

七、点云的显示

1、点云(6d)显示

数据太大了,可以在线展示点云,但是没有颜色信息,看起来不是很直观。

反思:

1、我在这个过程遇到了困扰我很久的问题,就是视差图生成了(虽然差一些),但是深度图一直显示不出来,用颜色映射出来都是红色的。我解决问题的思路一直在视差图转深度图的转换上了,其实最后才意识到源头在于视差图中计算出有大量的无限接近0的数值存在,导致深度无穷远,所以深度图中显示全红,同时也是看到了Middlebury数据集中给出的有效的视差范围,我才意识到这个问题,所以我想说的是如果遇到同样的问题可以将视差图中的数值分布打印成直方图,看看0附近的值是不是非常多,并且确定你自己图像的邮箱视差范围,在深度生成过程中将小于最小有效值的视差值都赋值为最小有效值。这样可以保证视差图转深度图没有问题。

相关文章:

双目相机的标定,视差图,深度图,点云生成思路与实现。

该文档记录从双目相机标定到点云生成的所有过程&#xff0c;同时会附上代码。 代码直接能跑。https://github.com/stu-yzZ/stereoCamera 目录 大致思路如下&#xff1a; 一、相机标定 1、相机参数介绍 2、单目相机标定 3、双目相机标定 二、图片畸变矫正 三、极线矫正…...

【H2O2|全栈】MySQL的基本操作(三)

目录 前言 开篇语 准备工作 案例准备 多表查询 笛卡尔积 等值连接 外连接 内连接 自连接 子查询 存在和所有 含于 分页查询 建表语句 结束语 前言 开篇语 本篇继续讲解MySQL的一些基础的操作——数据字段的查询中的多表查询和分页查询&#xff0c;与单表查询…...

2、C++命名空间

命名空间 命名空间是一种用来避免命名冲突的机制; 原理是将一个全局的作用域分成一个个命名空间&#xff0c;每个命名空间是个单独的作用域,从而有效避免命名冲突。 注意&#xff1a;命名空间定义在全局 命名空间定义格式 使用&#xff1a; …...

Elemenu-UI时间日期单个组件,限制当前日期之后的时间

element的时间日期组件&#xff0c; type"datetime" &#xff0c;当你设置了:picker-options"pickerOptions"之后 pickerOptions: { disabledDate(time) { return time.getTime() > Date.now(); }, }, 会发现&#xff0c;他只会限制日期&#xff0c;但不…...

flutter修改状态栏学习

在flutter中如何动态更改状态栏的颜色和风格。 前置知识点学习 AnnotatedRegion AnnotatedRegion 是 Flutter 中的一个小部件&#xff0c;用于在特定区域中提供元数据&#xff08;metadata&#xff09;以影响某些系统级的行为或外观。它通常用于改变系统 UI 的外观&#xff…...

解决Unity编辑器Inspector视图中文注释乱码

1.问题介绍 新创建一个脚本&#xff0c;用VS打开编辑&#xff0c;增加一行中文注释保存&#xff0c;在Unity中找到该脚本并选中&#xff0c;Inspector视图中预览的显示内容&#xff0c;该中文注释显示为乱码&#xff0c;如下图所示&#xff1a; 2.图示解决步骤 按上述步骤操作…...

关于csgo的游戏作弊与封禁

关于csgo的游戏作弊与封禁 一.关于作弊 什么叫作弊&#xff1f; 1.换肤&#xff0c;换库存 2.各种参&#xff08;回溯&#xff0c;自瞄&#xff0c;透视&#xff0c;急停&#xff0c;连跳&#xff0c;假身&#xff0c;子弹跟踪等&#xff09; 3.某一部分更改游戏内存&…...

严格单元测试造就安全软件

在信息技术迅速发展的今天&#xff0c;软件在各个行业中扮演着至关重要的角色&#xff0c;尤其是在汽车行业&#xff0c;其中软件的可靠性和安全性直接影响到人们的生命安全。软件缺陷所带来的潜在风险不容小觑&#xff0c;尤其在涉及到自动驾驶和车辆控制等关键系统时&#xf…...

ubuntu 根分区逻辑卷扩容

1、虚拟机关机通过管理界面给磁盘扩容。 rootcurtis:/home/curtis/git_code# pvdisplay--- Physical volume ---PV Name /dev/vda3VG Name ubuntu-vgPV Size <239.00 GiB / not usable 0Allocatable yes (but full)PE…...

如何查看电脑生产日期

查看电脑的生产日期通常可以通过以下方法实现&#xff0c;具体方式取决于操作系统和电脑类型&#xff1a; 方法 1&#xff1a;检查电脑 BIOS 生产日期通常记录在 BIOS 中。可以通过以下步骤查看&#xff1a; 重启电脑并进入 BIOS&#xff1a; 启动时按下特定的键&#xff08;…...

MAC M1 mysql 8.0 如何修改root用户密码

关闭mysql服务 使用brew方式安装&#xff0c;可以通过一下命令关闭 brew services stop mysql使用安装包安装的方式 可以选择&#x1f34e;->系统偏好设置->最下方单机MySQL图标->stop mysql server 启动 MySQL 到安全模式 sudo mysqld_safe --skip-grant-tables …...

漫画之家系统:Spring Boot框架下的漫画版权保护

摘 要 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;各行各业相继进入信息管理时代&a…...

在 MacOS 上为 LM Studio 更换镜像源

在 MacOS 之中使用 LM Studio 部署本地 LLM时&#xff0c;用户可能会遇到无法下载模型的问题。 一般的解决方法是在 huggingface.co 或者国内的镜像站 hf-mirror.com 的项目介绍卡页面下载模型后拖入 LM Studio 的模型文件夹。这样无法利用 LM Studio 本身的搜索功能。 本文将…...

Nginx配置https(Ubuntu、Debian、Linux、麒麟)

Ubuntu操作系统&#xff0c;Debian系统底层是Ubuntu&#xff0c;差异不大 ubuntu 安装nginx 1.安装依赖 sudo apt-get update sudo apt-get install gcc sudo apt-get install libpcre3 libpcre3-dev sudo apt-get install zlib1g zlib1g-dev sudo apt-get install openssl lib…...

「Mac畅玩鸿蒙与硬件40」UI互动应用篇17 - 照片墙布局

本篇将带你实现一个简单的照片墙布局应用&#xff0c;通过展示多张图片组成照片墙效果&#xff0c;用户可以点击图片查看其状态变化。 关键词 UI互动应用照片墙布局Grid 布局动态图片加载用户交互 一、功能说明 照片墙布局应用的特点&#xff1a; 动态加载多张图片组成网格布…...

VMware Workstation 安装Ubuntu 系统(图文步骤)

之前一直在讲Ubuntu Linux的用户和组 链接&#xff1a; Linux专栏 今天来讲讲Ubuntu 系统基础的安装步骤&#xff01;&#xff01;&#xff01; 废话少说&#xff0c;马上开始&#xff01; 文章目录 前言准备安装环境先下载Ubuntu 镜像 详细安装步骤如下新建虚拟机默认使用 15.…...

mybatis用pagehelper 然后用CountJSqlParser45,发现自己手写的mapper查询效率很慢

如题 效率慢疑惑 效率慢 分页查询,发现效率很慢,然后发现是比较复杂的sql,CountJSqlParser45它不会帮忙优化掉,就是select多少字段它count的时候也还是这么多字段 框架里的用法是这样的 所以去看了CountJSqlParser45里面的代码,发现如果有group之类的,它就不帮忙把count优化…...

【优选算法 二分查找】二分查找入门详解:二分查找 & 在排序数组中查找元素的第一个和最后一个位置

二分查找 题目描述 题目解析 暴力解法 我们可以从左往右遍历一次数组&#xff0c;如果存在 target 则返回数组的下标&#xff0c;否则返回 -1&#xff1b; 时间复杂度 O(N)&#xff0c;因为没有利用数组有序的特点&#xff0c;每次比较只能舍弃一个要比较的数&…...

WPF编写工业相机镜头选型程序

该程序满足面阵和线阵的要求。 前端代码 <Window x:Class"相机镜头选型.MainWindow" Loaded"Window_Loaded"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml…...

网络安全内容整理二

网络嗅探技术 网络监听 网络监听&#xff0c;也称网络嗅探(Network Sniffing)&#xff1a;在他方未察觉的情况下捕获其通信报文、通信内容的技术 网卡的工作模式&#xff1a; 1.广播模式(Broadcast Mode)&#xff1a;网卡能够接收网络中的广播信息 2.组播模式(Multicast Mo…...

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在 GPU 上对图像执行 均值漂移滤波&#xff08;Mean Shift Filtering&#xff09;&#xff0c;用于图像分割或平滑处理。 该函数将输入图像中的…...

C# 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

MySQL账号权限管理指南:安全创建账户与精细授权技巧

在MySQL数据库管理中&#xff0c;合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号&#xff1f; 最小权限原则&#xf…...

用机器学习破解新能源领域的“弃风”难题

音乐发烧友深有体会&#xff0c;玩音乐的本质就是玩电网。火电声音偏暖&#xff0c;水电偏冷&#xff0c;风电偏空旷。至于太阳能发的电&#xff0c;则略显朦胧和单薄。 不知你是否有感觉&#xff0c;近两年家里的音响声音越来越冷&#xff0c;听起来越来越单薄&#xff1f; —…...

NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合

在汽车智能化的汹涌浪潮中&#xff0c;车辆不再仅仅是传统的交通工具&#xff0c;而是逐步演变为高度智能的移动终端。这一转变的核心支撑&#xff0c;来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒&#xff08;T-Box&#xff09;方案&#xff1a;NXP S32K146 与…...

Mysql中select查询语句的执行过程

目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析&#xff08;Parser&#xff09; 2.4、执行sql 1. 预处理&#xff08;Preprocessor&#xff09; 2. 查询优化器&#xff08;Optimizer&#xff09; 3. 执行器…...

redis和redission的区别

Redis 和 Redisson 是两个密切相关但又本质不同的技术&#xff0c;它们扮演着完全不同的角色&#xff1a; Redis: 内存数据库/数据结构存储 本质&#xff1a; 它是一个开源的、高性能的、基于内存的 键值存储数据库。它也可以将数据持久化到磁盘。 核心功能&#xff1a; 提供丰…...

基于鸿蒙(HarmonyOS5)的打车小程序

1. 开发环境准备 安装DevEco Studio (鸿蒙官方IDE)配置HarmonyOS SDK申请开发者账号和必要的API密钥 2. 项目结构设计 ├── entry │ ├── src │ │ ├── main │ │ │ ├── ets │ │ │ │ ├── pages │ │ │ │ │ ├── H…...

OCR MLLM Evaluation

为什么需要评测体系&#xff1f;——背景与矛盾 ​​ 能干的事&#xff1a;​​ 看清楚发票、身份证上的字&#xff08;准确率>90%&#xff09;&#xff0c;速度飞快&#xff08;眨眼间完成&#xff09;。​​干不了的事&#xff1a;​​ 碰到复杂表格&#xff08;合并单元…...

电脑桌面太单调,用Python写一个桌面小宠物应用。

下面是一个使用Python创建的简单桌面小宠物应用。这个小宠物会在桌面上游荡&#xff0c;可以响应鼠标点击&#xff0c;并且有简单的动画效果。 import tkinter as tk import random import time from PIL import Image, ImageTk import os import sysclass DesktopPet:def __i…...