从双目标定到立体匹配:pyton实践指南

news2024/11/15 9:11:36

文章目录

    • 前言
    • 标定
    • 立体匹配
      • 文章已经同步更新在3D视觉工坊啦,原文链接如下:

前言

立体匹配是计算机视觉中的一个重要领域,旨在将从不同角度拍摄的图像匹配起来,以创建类似人类视觉的3D效果。实现立体匹配的过程需要涉及许多步骤,包括双目标定、立体校正、视差计算等。在这篇文章中,将介绍如何使用Python实现立体匹配的基本步骤和技巧。

下面的代码实现了从相机标定到立体匹配的完整流程,下面将分别介绍各个函数的参数和输出。

标定

首先,该程序需要用到以下库:

numpy
cv2 (OpenCV)
os

在程序开头,需要定义一些变量来存储标定图片的路径、棋盘格参数、角点坐标等等。具体介绍如下:

path_left = "./data/left/"
path_right = "./data/right/"

path_left和path_right是左右相机标定图片文件夹的路径。

CHESSBOARD_SIZE = (8, 11)
CHESSBOARD_SQUARE_SIZE = 15  # mm

CHESSBOARD_SIZE是棋盘格内部角点的行列数,CHESSBOARD_SQUARE_SIZE是棋盘格内部每个小正方形的大小(单位为毫米)。

objp = np.zeros((CHESSBOARD_SIZE[0] * CHESSBOARD_SIZE[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:CHESSBOARD_SIZE[0], 0:CHESSBOARD_SIZE[1]].T.reshape(-1, 2) * CHESSBOARD_SQUARE_SIZE

objp是物理坐标系下每个角点的三维坐标,即棋盘格的位置。该变量在后续的相机标定以及立体匹配中都会被用到。

criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)

criteria是角点检测的终止准则,一般都使用这个默认值。

img_list_left = sorted(os.listdir(path_left))
img_list_right = sorted(os.listdir(path_right))

img_list_left和img_list_right分别是左、右图像的文件名列表,使用os.listdir()函数获取。

obj_points = []
img_points_left = []
img_points_right = []

obj_points、img_points_left和img_points_right分别是存储每个标定图片对应的物理坐标系下的角点坐标、左相机的像素坐标和右相机的像素坐标。这些变量同样在后续的相机标定和立体匹配中用到。

接下来,程序读取标定图片并检测角点。对于每幅图片,程序执行以下操作:

img_l = cv2.imread(path_left + img_list_left[i])
img_r = cv2.imread(path_right + img_list_right[i])
gray_l = cv2.cvtColor(img_l, cv2.COLOR_BGR2GRAY)
gray_r = cv2.cvtColor(img_r, cv2.COLOR_BGR2GRAY)

首先读取左右图像,然后将它们转换为灰度图像。

ret_l, corners_l = cv2.findChessboardCorners(gray_l, CHESSBOARD_SIZE, None)
ret_r, corners_r = cv2.findChessboardCorners(gray_r, CHESSBOARD_SIZE, None)

通过OpenCV的cv2.findChessboardCorners()函数检测左右图像上的棋盘格角点。这个函数的参数包括:

image:需要检测角点的灰度图像。
patternSize:内部角点的行列数,即(CHESSBOARD_SIZE[1]-1, CHESSBOARD_SIZE[0]-1)。
corners:用于存储检测到的角点坐标的数组。如果检测失败,则该参数为空(None)。
flags:检测时使用的可选标志。
这个函数的返回值包括:

ret:一个布尔值,用于指示检测是否成功。如果检测成功,则为True,否则为False。
corners:用于存储检测到的角点坐标的数组。
接下来是亚像素级别的角点检测。

cv2.cornerSubPix(gray_l, corners_l, (11, 11), (-1, -1), criteria)
cv2.cornerSubPix(gray_r, corners_r, (11, 11), (-1, -1), criteria)

这里使用了OpenCV的cv2.cornerSubPix()函数来进行亚像素级别的角点检测。这个函数的参数包括:

image:输入的灰度图像。
corners:用于存储检测到的角点坐标的数组。
winSize:每次迭代中搜索窗口的大小,即每个像素周围的搜索范围大小。
通常为11x11。
zeroZone:死区大小,表示怎样的对称性(如果有的话)不考虑。通常为(-1,-1)。
criteria:定义迭代停止的误差范围、迭代次数等标准,和以上的criteria一样。

img_points_left.append(corners_l)
img_points_right.append(corners_r)

如果检测到了左右图像上的角点,则将这些角点的坐标存储到img_points_left和img_points_right中。

cv2.drawChessboardCorners(img_l, CHESSBOARD_SIZE, corners_l, ret_l)
cv2.imshow("Chessboard Corners - Left", cv2.resize(img_l,(img_l.shape[1]//2,img_l.shape[0]//2)))
cv2.waitKey(50)

cv2.drawChessboardCorners(img_r, CHESSBOARD_SIZE, corners_r, ret_r)
cv2.imshow("Chessboard Corners - Right", cv2.resize(img_r,(img_r.shape[1]//2,img_r.shape[0]//2)))
cv2.waitKey(50)


在图片上标出检测到的角点,并在窗口中显示。这里使用了cv2.drawChessboardCorners()函数,该函数的参数包括:

img:需要标定角点的图像。
patternSize:内部角点的行列数,即(CHESSBOARD_SIZE[1]-1, CHESSBOARD_SIZE[0]-1)。
corners:存储检测到的角点坐标的数组。
patternfound:检测到角点的标记,即ret。

程序接下来对双目摄像机进行标定。

ret_l, mtx_l, dist_l, rvecs_l, tvecs_l = cv2.calibrateCamera(obj_points, img_points_left, gray_l.shape[::-1],None,None)
ret_r, mtx_r, dist_r, rvecs_r, tvecs_r = cv2.calibrateCamera(obj_points, img_points_right, gray_r.shape[::-1],None,None)

flags = 0
flags |= cv2.CALIB_FIX_INTRINSIC
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
ret, M1, d1, M2, d2, R, T, E, F = cv2.stereoCalibrate(
    obj_points, img_points_left, img_points_right,
    mtx_l, dist_l, mtx_r, dist_r,
    gray_l.shape[::-1], criteria=criteria, flags=flags)

这段代码首先对左右相机进行单独标定:

ret_l, mtx_l, dist_l, rvecs_l, tvecs_l = cv2.calibrateCamera(obj_points, img_points_left, gray_l.shape[::-1],None,None)
ret_r, mtx_r, dist_r, rvecs_r, tvecs_r = cv2.calibrateCamera(obj_points, img_points_right, gray_r.shape[::-1],None,None)

这里使用了OpenCV的cv2.calibrateCamera()函数对左右相机进行标定。这个函数的参数包括:

objectPoints:每幅标定图片对应的物理坐标系下的角点坐标。
imagePoints:每幅标定图片上检测到的像素坐标。
imageSize:标定图片的尺寸。
cameraMatrix:用于存储标定结果的内参数矩阵。
distCoeffs:用于存储标定结果的畸变系数。
rvecs:每幅标定图片的外参数矩阵中的旋转向量。
tvecs:每幅标定图片的外参数矩阵中的平移向量。
这个函数的返回值包括:

ret:一个标志位,表示标定是否成功。
cameraMatrix:用于存储标定结果的内参数矩阵。
distCoeffs:用于存储标定结果的畸变系数。
rvecs:每幅标定图片的外参数矩阵中的旋转向量。
tvecs:每幅标定图片的外参数矩阵中的平移向量。
然后对双目摄像机进行标定:

flags = 0
flags |= cv2.CALIB_FIX_INTRINSIC
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
ret, M1, d1, M2, d2, R, T, E, F = cv2.stereoCalibrate(
    obj_points, img_points_left, img_points_right,
    mtx_l, dist_l, mtx_r, dist_r,
    gray_l.shape[::-1], criteria=criteria, flags=flags)

这里使用了OpenCV的cv2.stereoCalibrate()函数进行双目摄像机标定。这个函数的参数包括:

objectPoints:每幅标定图片对应的物理坐标系下的角点坐标。
imagePoints1:每幅标定图片的左相机上检测到的像素坐标。
imagePoints2:每幅标定图片的右相机上检测到的像素坐标。
cameraMatrix1:左相机的内参数矩阵。
distCoeffs1:左相机的畸变系数。
cameraMatrix2:右相机的内参数矩阵。
distCoeffs2:右相机的畸变系数。
imageSize:标定图片的尺寸。
criteria:定义迭代停止的误差范围、迭代次数等标准。
flags:标定的可选标志。
这个函数的返回值包括:

ret:一个标志,表示标定是否成功。
cameraMatrix1:左相机的内参数矩阵。
distCoeffs1:左相机的畸变系数。
cameraMatrix2:右相机的内参数矩阵。
distCoeffs2:右相机的畸变系数。
R:旋转矩阵。
T:平移向量。
E:本质矩阵。
F:基础矩阵。

立体匹配

通过图像标定得到的参数进行立体匹配的整个流程,如下:

首先,我们需要读取左右两张图像:

img_left = cv2.imread("./left.png")
img_right = cv2.imread("./right.png")

其中,“./left.png” 和 “./right.png” 是放置左右图像的路径。这两幅图像是未经校正和矫正的图像。

接下来,通过图像标定得到相机的参数,根据得到的参数,将图像进行去畸变:

img_left_undistort = cv2.undistort(img_left, M1, d1)
img_right_undistort = cv2.undistort(img_right, M2, d2)

在上述代码中,M1、M2、d1、d2 是从双目相机标定中获得的参数。去畸变后的图像 img_left_undistort 和 img_right_undistort 可供之后的操作使用。

然后,进行极线校正,以实现左右图像在几何上的一致性:

R1, R2, P1, P2, Q, roi1, roi2 = cv2.stereoRectify(M1, d1, M2, d2, (width, height), R, T, alpha=1)
map1x, map1y = cv2.initUndistortRectifyMap(M1, d1, R1, P1, (width, height), cv2.CV_32FC1)
map2x, map2y = cv2.initUndistortRectifyMap(M2, d2, R2, P2, (width, height), cv2.CV_32FC1)
img_left_rectified = cv2.remap(img_left_undistort, map1x, map1y, cv2.INTER_LINEAR)
img_right_rectified = cv2.remap(img_right_undistort, map2x, map2y, cv2.INTER_LINEAR)

其中,R、T 是双目相机标定得到的旋转和平移矩阵, (width, height)是左右图像的尺寸。R1、R2 是左右图像的旋转矩阵,P1、P2 是左右图像的投影矩阵,Q 是视差转换矩阵,roi1、roi2 是矫正后的图像中可以使用的区域。

然后,将左右图像拼接在一起以方便观察:

img_stereo = cv2.hconcat([img_left_rectified, img_right_rectified])

接下来,需要计算视差图:

minDisparity = 0
numDisparities = 256
blockSize = 9
P1 = 1200
P2 = 4800
disp12MaxDiff = 10
preFilterCap = 63
uniquenessRatio = 5
speckleWindowSize = 100
speckleRange = 32
sgbm = cv2.StereoSGBM_create(minDisparity=minDisparity, numDisparities=numDisparities, blockSize=blockSize,
                             P1=P1, P2=P2, disp12MaxDiff=disp12MaxDiff, preFilterCap=preFilterCap,
                             uniquenessRatio=uniquenessRatio, speckleWindowSize=speckleWindowSize,
                             speckleRange=speckleRange, mode=cv2.STEREO_SGBM_MODE_SGBM_3WAY)

disparity = sgbm.compute(img_left_rectified,img_right_rectified)

上面的代码块定义了使用的视差算法的参数,并使用了 SGBM(Semi Global Block Matching)算法计算了原始的视差图。注意,由于使用的是16位的 SGBM 输出,因此需要将它除以16。

接下来,可以对视差图进行 WLS 滤波,减少视差空洞:

# 定义 WLS 滤波参数
lambda_val = 4000
sigma_val = 1.5

# 运行 WLS 滤波
wls_filter = cv2.ximgproc.createDisparityWLSFilterGeneric(False)
wls_filter.setLambda(lambda_val)
wls_filter.setSigmaColor(sigma_val)
filtered_disp = wls_filter.filter(disparity, img_left_rectified,  None, img_right_rectified)
filtered_disp_nor = cv2.normalize(filtered_disp, filtered_disp, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)

上述代码块中,WLS 滤波为视差图降噪,并进行平滑处理。 这里使用了 cv2.ximgproc.createDisparityWLSFilterGeneric 函数,创建一个生成 WLS 滤波器的对象 wls_filter,然后设置了滤波参数 lambda_val 和 sigma_val。filtered_disp 是经过滤波后的视差图。filtered_disp_nor 是经过归一化处理后的、用于显示的视差图。

最后,可以在窗口中显示原始视差图、预处理后的 WLS 滤波器的视差图:

cv2.imshow("disparity", cv2.resize(disparity_nor,(disparity_nor.shape[1]//2,disparity_nor.shape[0]//2)))
cv2.imshow("filtered_disparity", cv2.resize(filtered_disp_nor,(filtered_disp_nor.shape[1]//2,filtered_disp_nor.shape[0]//2)))
cv2.waitKey()
cv2.destroyAllWindows()

文章已经同步更新在3D视觉工坊啦,原文链接如下:

从双目标定到立体匹配:Python实践指南

欢迎大家加入知识星球,里面有很多大牛解答问题,还可以和小伙伴们一起讨论问题哦!请添加图片描述

请添加图片描述
请添加图片描述
请添加图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为OD机试真题B卷 Java 实现【统计每个月兔子的总数】,附详细解题思路

一、题目描述 有一种兔子,从出生后第3个月起每个月都生一只兔子,小兔子长到第三个月后每个月又生一只兔子。 例如:假设一只兔子第3个月出生,那么它第5个月开始会每个月生一只兔子。 一月的时候有一只兔子,假如兔子都…

dvwa靶场通关(七)

第七关:SQL Injection(sql注入) low 我们输入 1,出现报错信息,根据报错信息可知,查询语句是单引号闭合的字符型 接着判断字段数 1 order by 3# 报错 1 order by 2# 正常 所以字段数就是2 利用联合查询爆出数据库名…

农村小子背井离乡北漂的这些年

人生虽不尽人意、生活也并不完美、可生活依旧很美 1. 写在前面 由于工作变动及其他种种原因吧,很长的一段时间内没有再去写文章。始于2019年初夏,止于2020年初冬,再次落笔于2023年的夏季。恰好今天是端午节,祝大家端午安康 白驹过…

springboot第28集:springboot一些概念

DataScopeAspect 数据过滤处理 此切面在执行带有ControllerDataScope注解的方法之前进行数据权限过滤。首先获取当前登录用户,然后判断当前用户是否为超级管理员。如果不是超级管理员,则获取权限字符,默认使用上下文中的权限字符。接下来&am…

【计算机组成原理】2、二进制和十六进制转换,进制相减、内存地址偏移计算与容量计算

文章目录 一、进制转换1.1 二进制转十六进制1.2 十六进制转二进制 二、进制相减2.1 十六进制 三、内存地址偏移计算3.1 根据首末地址,求存储容量3.2 根据末地址 和 存储容量,求首地址 一、进制转换 1.1 二进制转十六进制 因 2 4 16 2^416 2416&#…

leetcode300. 最长递增子序列(动态规划-java)

最长递增子序列 leetcode300. 最长递增子序列题目描述解题思路代码演示: 二分法改进(N * logN)动态规划专题 leetcode300. 最长递增子序列 来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/longest-increasing-subsequenc…

C++标准输出

C将输入和输出看作字节流,输入时,程序从输入流中抽取字节,输出时,程序将自己插入到输出流中,流充当了程序与流源或流目标之间的桥梁,也就是说C通过流与硬件,文件相关联,流赋予了C程序…

[数字图像处理]第六章 彩色图像处理

第六章 彩色图像处理 引言 ​ 彩色图像处理可分为两个主要领域:全彩色处理和伪彩色处理。在第一类中, 通常要求图像用全彩色传感器获取,如彩色电视摄像机或彩色扫描仪。在第二类中,问题是对一种特定的单色灰度或灰度范围赋予一种…

设计模式之装饰者模式笔记

设计模式之装饰者模式笔记 说明Decorator(装饰)目录装饰者模式示例类图快餐类炒饭类炒面类装饰者类鸡蛋类培根类测试类 说明 记录下学习设计模式-装饰者模式的写法。JDK使用版本为1.8版本。 Decorator(装饰) 意图:动态地给一个对象添加一些额外的职责。 结构: 其中&#x…

【Neo4j教程之CQL命令基本使用】

🚀 Neo4j 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,C…

Python基础篇(二):入门基础必备知识

Python基础篇(一):如何使用PyCharm创建第一个Python项目(包含tools) 入门基础必备知识 1. 标识符2. 关键字2.1 关键字字典 3. 引号3.1 表示字符串3.2 在字符串中使用引号3.3 创建多行字符串3.4 在注释中使用引号 4. 编码5. 输入输出5.1 输入示例5.2 输出示例5.3 格…

王道计算机网络学习笔记(3)——数据链路层

前言 文章中的内容来自B站王道考研计算机网络课程,想要完整学习的可以到B站官方看完整版。 三:数据链路层 3.1:数据链路层功能概述 结点:主机、路由器 链路:网络中两个结点之间的物理通道,链路的传输介…

PolarFormer:Multi-camera 3D Object Detection with Polar Transformer——论文笔记

参考代码:PolarFormer 1. 概述 介绍:在仓库RoboBEV中总结了现有的一些bev感知算法在不同输入情况下的鲁棒性,在这些感知算法中PolarFormer拥有较为不错的泛化性能。这个算法的思想是将之前由直角坐标系栅格化构建bev网格,转换到由…

Unity之透明度混合与ps的透明度混合计算结果不一致

一、问题 前段时间学习shader时发现了一个问题,一张纯红色透明度为128的图片叠加在一张纯绿色的图片上得出的结果与ps中的结果不一致。网上查找了ps中的透明混合的公式为 color A.rgb*A.alpha B.rgb*(1-A.alpha)。自己使用代码在unity中计算了一下结果总是不对。…

Python--异常处理

Python--异常处理 <font colorblue>一、异常<font colorblue>二、异常处理语句<font colorblue>1、try...except语句<font colorblue>2、try...except...else语句<font colorblue>3、try...except...finally语句<font colorblue>4、raise语…

leetcode354. 俄罗斯套娃信封问题(动态规划-java)

俄罗斯套娃信封问题 leetcode354. 俄罗斯套娃信封问题题目描述:解题思路代码演示 动态规划专题 leetcode354. 俄罗斯套娃信封问题 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/russian-doll-envelopes 题目描述: 给你…

c++学习之模板

目录 一&#xff0c;模板的概述 二&#xff0c;函数模板 1.函数模板的定义 2.函数模板的注意事项 3.函数模板的重载 4.函数模板的局限性 三&#xff0c;类模板 1.类模板的概念 2.类模板定义方式 3.类模板的成员函数在类外实现 4.函数模板作为类模板的友元 5.在写程…

《大学英语4》期末考试复习(一)听力原文+答案速记

目录 Unit 1 Long conversation Passage 1 Unit 2 Long conversation Passage 1 Unit 3 Long conversation Passage 1 Unit 4 Long conversation Passage 1 Unit 5 Long conversation Passage 1 Unit 6 Long conversation Passage 1 Unit 7 Long conversat…

Java线程池小结

目录 一.什么是线程池 二.线程池的好处是什么 三.四种基本线程池及其使用 newCachedThreadPool newFixedThreadPool newScheduledThreadPool newSingleThreadExecutor 线程池的四种拒绝策略 选择一:AbortPolicy 选择二:CallerRunsPolicy 选择三:DiscardPolicy 选择四…

【Java】Java核心要点总结70

文章目录 1. volatile 如何保证变量的可⻅性&#xff1f;2. volatile 可以保证原⼦性么&#xff1f;3. synchronized 关键字4. synchronized 和 volatile 的区别5. synchronized 和 ReentrantLock 的区别 1. volatile 如何保证变量的可⻅性&#xff1f; 在Java中&#xff0c;使…