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

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

代码直接能跑。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附近的值是不是非常多,并且确定你自己图像的邮箱视差范围,在深度生成过程中将小于最小有效值的视差值都赋值为最小有效值。这样可以保证视差图转深度图没有问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/487102.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Selenium:强大的 Web 自动化测试工具

Selenium&#xff1a;强大的 Web 自动化测试工具 在当今的软件开发和测试领域&#xff0c;自动化工具的重要性日益凸显。Selenium 就是一款备受欢迎的 Web 自动化测试工具&#xff0c;它为开发者和测试人员提供了强大的功能和便利。本文将详细介绍 Selenium 是什么&#xff0c…

基于 Spring Boot + Vue 的宠物领养系统设计与实现

引言 近年来&#xff0c;随着人们生活水平的提高&#xff0c;宠物逐渐成为许多家庭的重要成员。然而&#xff0c;宠物的流浪和弃养问题日益严重&#xff0c;这促使社会对宠物领养的需求不断增长。为解决宠物领养中信息不对称、领养流程复杂等问题&#xff0c;设计并实现一个基…

佑驾创新冲刺上市:交付进度延后,研发投入缩减,刘国清为实控人

近日&#xff0c;深圳佑驾创新科技股份有限公司&#xff08;MINIEYE&#xff0c;下称“佑驾创新”&#xff09;通过港交所聆讯并披露了聆讯后资料集&#xff08;即招股书&#xff09;。据贝多财经了解&#xff0c;佑驾创新获得了IPO备案通知书&#xff0c;拟在港交所上市。 对…

JS中的原型链与继承

原型链的类比 JS中原型链&#xff0c;本质上就是对象之间的关系&#xff0c;通过protoype和[[Prototype]]属性建立起来的连接。这种链条是动态的&#xff0c;可以随时变更。 这个就跟C/C中通过指针建立的关系很相似&#xff0c;比如&#xff0c;通过指针建立一个链表&#xf…

数据结构——图(遍历,最小生成树,最短路径)

目录 一.图的基本概念 二.图的存储结构 1.邻接矩阵 2.邻接表 三.图的遍历 1.图的广度优先遍历 2.图的深度优先遍历 四.最小生成树 1.Kruskal算法 2.Prim算法 五.最短路径 1.单源最短路径--Dijkstra算法 2.单源最短路径--Bellman-Ford算法 3.多源最短路径--Floyd-…

华为云云日志服务 HarmonyOS NEXT采集最佳实践

鸿蒙背景介绍 华为鸿蒙HarmonyOS系统是面向万物互联的全场景分布式操作系统&#xff0c;支持手机、平板、智能穿戴、智慧屏等多种终端设备运行&#xff0c;提供应用开发、设备开发的一站式服务的平台。2024 年 1 月 18 日正式推出 HarmonyOS NEXT 鸿蒙星河开发者预览&#xff…

在Ubuntu上使用IntelliJ IDEA:开启你的Java开发之旅!

你好&#xff0c;年轻的学徒&#xff01;&#x1f9d1;‍&#x1f4bb; 是时候踏上进入Java开发世界的史诗之旅了&#xff0c;我们的得力助手将是强大的IntelliJ IDEA。准备好了吗&#xff1f;出发吧&#xff01; 在我们开始之前&#xff0c;我们需要下载这个工具。但是&#…

图片预处理技术介绍4——降噪

图片预处理 大家好&#xff0c;我是阿赵。   这一篇将两种基础的降噪算法。   之前介绍过均值模糊和高斯模糊。如果从降噪的角度来说&#xff0c;模糊算法也算是降噪的一类&#xff0c;所以之前介绍的两种模糊可以称呼为均值降噪和高斯降噪。不过模糊算法对原来的图像特征的…

基础算法--双指针

两数之和 点击&#xff1a;题目链接 解法一&#xff1a;暴力解法 时间复杂度&#xff1a;O(N^2) 算法思路&#xff1a;两层for循环即可列出所有两个数字的组合&#xff0c;判断是否等于目标值 算法流程&#xff1a; 两层 for 循环&#xff1a; 外层 for 循环依次枚举第⼀个…

WPF+LibVLC开发播放器-音量控制和倍速控制

界面 界面上增加音量的控件和倍速控制控件 音量控制 主要也是一个Slider进度条控件来实现音量调节 我们这里设置默认的最大值为100&#xff0c;默认Value值也为100&#xff0c;默认声音开到最大 这里目前完全由前端控制音量调节&#xff0c;可以直接使用ValueChanged事件实…

【C语言练习(3) —水仙花数判断】

系列文章目录 文章目录 系列文章目录前言题目解题思路结果总结与反思 前言 通过水仙花数巩固之前学习知识点&#xff0c;锻炼自己的写敲代码能力&#xff0c;只有写才能发现问题、找问题、解决问题 题目 求出所有5位数中的所有水仙花数&#xff08;Lily Number&#xff09;&a…

【专题】2024年11月新能源汽车、智能汽车行业报告汇总PDF洞察(附原数据表)

原文链接&#xff1a;https://tecdat.cn/?p38520 随着科技的飞速发展与社会的持续变革&#xff0c;新能源汽车与智能汽车行业正步入全新的发展阶段&#xff0c;成为全球瞩目的焦点领域。本报告深入且全面地剖析了 2024 年 11 月该行业的多方面状况。从汽车消费市场来看&#…

【C++图论 BFS算法】2467. 树上最大得分和路径|2053

本文涉及知识点 C图论 CBFS算法 LeetCode2467. 树上最大得分和路径 一个 n 个节点的无向树&#xff0c;节点编号为 0 到 n - 1 &#xff0c;树的根结点是 0 号节点。给你一个长度为 n - 1 的二维整数数组 edges &#xff0c;其中 edges[i] [ai, bi] &#xff0c;表示节点 a…

Mysql | 尚硅谷 | 第02章_MySQL环境搭建

Mysql笔记&#xff1a;第02章_MySQL环境搭建 说明&#xff1a;本内容整理自尚硅谷B站MySQL视频>>尚硅谷B站MySQL视频 文章目录 Mysql笔记&#xff1a;第02章_MySQL环境搭建第02章_MySQL环境搭建 1. MySQL的卸载步骤1&#xff1a;停止MySQL服务步骤2&#xff1a;[软件](h…

【网络篇】TCP知识

TCP首部格式&#xff1f; 为什么需要 TCP 协议&#xff1f; TCP 工作在哪一层&#xff1f; IP 层是不可靠的&#xff0c;它不保证网络包的交付、不保证网络包的按序交付也不保证网络包中的数据的完整性。如果需要保障网络数据包的可靠性&#xff0c;那么就需要由上层&#xff0…

Spring Boot漫画之家:漫画爱好者的数字图书馆

2 系统开发环境 2.1 JAVA简介 JavaScript是一种网络脚本语言&#xff0c;广泛运用于web应用开发&#xff0c;可以用来添加网页的格式动态效果&#xff0c;该语言不用进行预编译就直接运行&#xff0c;可以直接嵌入HTML语言中&#xff0c;写成js语言&#xff0c;便于结构的分离&…

一次“okhttp访问间隔60秒,提示unexpected end of stream“的问题排查过程

一、现象 okhttp调用某个服务&#xff0c;如果第二次访问间隔上一次访问时间超过60s&#xff0c;返回错误&#xff1a;"unexpected end of stream"。 二、最终定位原因&#xff1a; 空闲连接如果超过60秒&#xff0c;服务端会主动关闭连接。此时客户端恰巧访问了这…

一、开启 GD32 单片机的学习之门

文章目录 一、开启GD32单片机的学习之门二、筑牢根基&#xff1a;GD32单片机基础知识全解析&#xff08;一&#xff09;单片机概述 三、开发环境搭建&#xff08;一&#xff09;软件下载与安装&#xff08;二&#xff09;安装GD32F450设备支持包&#xff08;三&#xff09;编译…

渗透测试---burpsuite(6)暴力破解与验证码识别绕过

声明&#xff1a;学习素材来自b站up【泷羽Sec】&#xff0c;侵删&#xff0c;若阅读过程中有相关方面的不足&#xff0c;还请指正&#xff0c;本文只做相关技术分享,切莫从事违法等相关行为&#xff0c;本人与泷羽sec团队一律不承担一切后果 视频地址&#xff1a;泷羽---bp&…

PSHuman 部署笔记

目录 github地址&#xff1a; 依赖项&#xff1a; xformers安装&#xff1a; 解决方法&#xff0c;安装xformers smpl_data下载&#xff1a; 推理步骤&#xff1a; SMPLDataset 香港科技大学提出了一种叫PSHuman的新框架。这个方法利用了一个多视角扩散模型的“先验知识…