OpenCV相机标定与3D重建(60)用于立体校正的函数stereoRectify()的使用

news2025/1/21 16:23:13
  • 操作系统:ubuntu22.04
  • OpenCV版本:OpenCV4.9
  • IDE:Visual Studio Code
  • 编程语言:C++11

算法描述

为已校准的立体相机的每个头计算校正变换。
cv::stereoRectify 是 OpenCV 中用于立体校正的函数,它基于已知的相机参数和相对位置(通过 cv::stereoCalibrate 或其他方法获得),计算出两个相机的投影矩阵和重映射变换,使得从两个相机获取的图像能够被矫正为仿佛它们是并排且光学轴平行的状态。这样做的目的是为了简化后续的视差图计算。

函数原型

void cv::stereoRectify	
(
	InputArray 	cameraMatrix1,
	InputArray 	distCoeffs1,
	InputArray 	cameraMatrix2,
	InputArray 	distCoeffs2,
	Size 	imageSize,
	InputArray 	R,
	InputArray 	T,
	OutputArray 	R1,
	OutputArray 	R2,
	OutputArray 	P1,
	OutputArray 	P2,
	OutputArray 	Q,
	int 	flags = CALIB_ZERO_DISPARITY,
	double 	alpha = -1,
	Size 	newImageSize = Size(),
	Rect * 	validPixROI1 = 0,
	Rect * 	validPixROI2 = 0 
)		

参数

  • 参数cameraMatrix1:第一个相机的内参矩阵。
  • 参数distCoeffs1:第一个相机的畸变参数。
  • 参数cameraMatrix2:第二个相机的内参矩阵。
  • 参数distCoeffs2:第二个相机的畸变参数。
  • 参数imageSize:用于立体校准的图像尺寸。
  • 参数R:从第一个相机坐标系到第二个相机坐标系的旋转矩阵,详见 stereoCalibrate。
  • 参数T:从第一个相机坐标系到第二个相机坐标系的平移向量,详见 stereoCalibrate。
  • 参数R1:第一个相机的输出3x3校正变换(旋转矩阵)。该矩阵将未校正的第一个相机坐标系中的点转换为校正后的第一个相机坐标系中的点。更技术性地说,它执行了从未校正的第一个相机坐标系到校正后的第一个相机坐标系的基础变换。
  • 参数R2:第二个相机的输出3x3校正变换(旋转矩阵)。该矩阵将未校正的第二个相机坐标系中的点转换为校正后的第二个相机坐标系中的点。同样地,它执行了从未校正的第二个相机坐标系到校正后的第二个相机坐标系的基础变换。
  • 参数P1:第一个相机在新的(校正后)坐标系中的输出3x4投影矩阵,即它将校正后的第一个相机坐标系中的点投影到校正后的第一个相机的图像中。
  • 参数P2:第二个相机在新的(校正后)坐标系中的输出3x4投影矩阵,即它将校正后的第一个相机坐标系中的点投影到校正后的第二个相机的图像中。
  • 参数Q:输出4×4视差到深度映射矩阵(见 reprojectImageTo3D)。
  • 参数flags:操作标志,可以是零或 CALIB_ZERO_DISPARITY。如果设置了该标志,则函数会使每个相机的主点在校正后的视图中具有相同的像素坐标。如果没有设置该标志,函数可能会沿水平或垂直方向(取决于极线的方向)移动图像,以最大化有用图像区域。
  • 参数alpha:自由缩放参数。如果它是 -1 或未指定,函数将执行默认缩放。否则,参数应在 0 和 1 之间。alpha=0 表示校正后的图像被缩放和移动,使得只有有效的像素可见(校正后没有黑色区域)。alpha=1 表示校正后的图像被减少和移动,使得来自原始图像的所有像素都保留在校正后的图像中(不丢失源图像像素)。任何中间值都会产生这两个极端情况之间的中间结果。
  • 参数newImageSize:校正后的新图像分辨率。应该传递给 initUndistortRectifyMap(见 OpenCV 示例目录中的 stereo_calib.cpp 样本)。当传递 (0,0)(默认值)时,它被设置为原始 imageSize。设置为更大的值可以帮助保留原始图像中的细节,尤其是在存在较大径向畸变的情况下。
  • 参数validPixROI1:可选输出矩形,在校正后的图像中包含所有有效像素的区域。如果 alpha=0,则 ROI 覆盖整个图像。否则,它们可能较小(见下图)。
  • 参数validPixROI2:同上,适用于第二个相机。

该函数计算每个相机的旋转矩阵,这些矩阵(虚拟地)使两个相机的图像平面成为同一平面。因此,这使得所有的极线平行,从而简化了密集立体对应问题。函数以 stereoCalibrate 计算的矩阵作为输入,并提供两个旋转矩阵以及两个新坐标系中的投影矩阵作为输出。根据相机的相对位置,函数区分以下两种情况:
水平立体
第一个和第二个相机视图主要沿 x 轴相对偏移(可能有小的垂直偏移)。在校正后的图像中,左右相机中的对应极线是水平的并且具有相同的 y 坐标。P1 和 P2 的形式如下:
P1 = [ f 0 c x 1 0 0 f c y 0 0 0 1 0 ] \texttt{P1} = \begin{bmatrix} f & 0 & cx_1 & 0 \\ 0 & f & cy & 0 \\ 0 & 0 & 1 & 0 \end{bmatrix} P1= f000f0cx1cy1000
P2 = [ f 0 c x 2 T x ⋅ f 0 f c y 0 0 0 1 0 ] , \texttt{P2} = \begin{bmatrix} f & 0 & cx_2 & T_x \cdot f \\ 0 & f & cy & 0 \\ 0 & 0 & 1 & 0 \end{bmatrix} , P2= f000f0cx2cy1Txf00 ,
Q = [ 1 0 0 − c x 1 0 1 0 − c y 0 0 0 f 0 0 − 1 T x c x 1 − c x 2 T x ] \texttt{Q} = \begin{bmatrix} 1 & 0 & 0 & -cx_1 \\ 0 & 1 & 0 & -cy \\ 0 & 0 & 0 & f \\ 0 & 0 & -\frac{1}{T_x} & \frac{cx_1 - cx_2}{T_x} \end{bmatrix} Q= 10000100000Tx1cx1cyfTxcx1cx2
其中 Tx 是相机之间的水平偏移,如果设置了 CALIB_ZERO_DISPARITY,则cx1=cx2。

垂直立体
第一个和第二个相机视图主要沿垂直方向相对偏移(可能有一点水平偏移)。在校正后的图像中,极线是垂直的并且具有相同的 x 坐标。P1 和 P2 的形式如下:
P1 = [ f 0 c x 0 0 f c y 1 0 0 0 1 0 ] \texttt{P1} = \begin{bmatrix} f & 0 & cx & 0 \\ 0 & f & cy_1 & 0 \\ 0 & 0 & 1 & 0 \end{bmatrix} P1= f000f0cxcy11000
P2 = [ f 0 c x 0 0 f c y 2 T y ⋅ f 0 0 1 0 ] , \texttt{P2} = \begin{bmatrix} f & 0 & cx & 0 \\ 0 & f & cy_2 & T_y \cdot f \\ 0 & 0 & 1 & 0 \end{bmatrix}, P2= f000f0cxcy210Tyf0 ,
Q = [ 1 0 0 − c x 0 1 0 − c y 1 0 0 0 f 0 0 − 1 T y c y 1 − c y 2 T y ] \texttt{Q} = \begin{bmatrix} 1 & 0 & 0 & -cx \\ 0 & 1 & 0 & -cy_1 \\ 0 & 0 & 0 & f \\ 0 & 0 & -\frac{1}{T_y} & \frac{cy_1 - cy_2}{T_y} \end{bmatrix} Q= 10000100000Ty1cxcy1fTycy1cy2

其中 Ty 是相机之间的垂直偏移,如果设置了 CALIB_ZERO_DISPARITY,则cy1=cy2。

可以看到,P1 和 P2 的前三列实际上会成为新的“校正”相机矩阵。这些矩阵连同 R1 和 R2 可以传递给 initUndistortRectifyMap 来初始化每个相机的校正映射。

示例截图
下面是来自 stereo_calib.cpp 示例的截图。一些红色水平线通过对应的图像区域,这意味着图像已经很好地进行了校正,这是大多数立体对应算法所依赖的。绿色矩形是 roi1 和 roi2。可以看到,它们内部都是有效的像素。
在这里插入图片描述

代码示例

#include <iostream>
#include <opencv2/opencv.hpp>
#include <vector>

using namespace cv;
using namespace std;

// 生成测试图像函数
void generateTestImages( Size imageSize, Mat& img1, Mat& img2 )
{
    img1 = Mat::zeros( imageSize, CV_8UC3 );
    img2 = Mat::zeros( imageSize, CV_8UC3 );

    // 在第一张图像上画水平线,在第二张图像上画稍微偏移的水平线模拟立体图像
    for ( int y = 50; y < imageSize.height; y += 50 )
    {
        line( img1, Point( 0, y ), Point( imageSize.width, y ), Scalar( 0, 255, 0 ), 2 );
        line( img2, Point( 0, y + 10 ), Point( imageSize.width, y + 10 ), Scalar( 0, 255, 0 ), 2 );
    }
}

int main()
{
    // 假设我们已经有以下数据(来自 stereoCalibrate 或其他来源)
    Mat cameraMatrix1 = ( Mat_< double >( 3, 3 ) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1 );

    Mat cameraMatrix2 = ( Mat_< double >( 3, 3 ) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1 );

    Mat distCoeffs1 = Mat::zeros( 5, 1, CV_64F );  // 简化的畸变系数模型
    Mat distCoeffs2 = Mat::zeros( 5, 1, CV_64F );

    // 假定的旋转和平移矩阵(根据实际情况调整)
    Mat R = ( Mat_< double >( 3, 3 ) << 0.999, 0.001, -0.044, -0.002, 0.998, 0.061, 0.044, -0.061, 0.998 );

    Mat T = ( Mat_< double >( 3, 1 ) << 60.0, 0.0, 0.0 );  // 假设两个相机沿X轴平移了60个单位

    Size imageSize( 640, 480 );  // 图像尺寸

    // 输出变量
    Mat R1, R2;                       // 校正后的旋转矩阵
    Mat P1, P2;                       // 新的投影矩阵
    Mat Q;                            // 视差到深度映射矩阵
    Rect validPixROI1, validPixROI2;  // 有效像素区域

    // 操作标志和自由缩放参数
    int flags         = CALIB_ZERO_DISPARITY;
    double alpha      = -1;
    Size newImageSize = imageSize;  // 使用原始图像尺寸

    // 执行立体校正
    stereoRectify( cameraMatrix1, distCoeffs1, cameraMatrix2, distCoeffs2, imageSize, R, T, R1, R2, P1, P2, Q, flags, alpha, newImageSize, &validPixROI1, &validPixROI2 );

    cout << "Rectification matrices for the first camera:\n" << R1 << endl;
    cout << "Rectification matrices for the second camera:\n" << R2 << endl;
    cout << "Projection matrix for the first camera:\n" << P1 << endl;
    cout << "Projection matrix for the second camera:\n" << P2 << endl;
    cout << "Disparity-to-depth mapping matrix:\n" << Q << endl;

    // 初始化重映射
    Mat map1x, map1y, map2x, map2y;
    initUndistortRectifyMap( cameraMatrix1, distCoeffs1, R1, P1, newImageSize, CV_32FC1, map1x, map1y );
    initUndistortRectifyMap( cameraMatrix2, distCoeffs2, R2, P2, newImageSize, CV_32FC1, map2x, map2y );

    // 生成一对测试图像
    Mat img1, img2;
    generateTestImages( imageSize, img1, img2 );

    // 应用重映射
    Mat rectifiedImg1, rectifiedImg2;
    remap( img1, rectifiedImg1, map1x, map1y, INTER_LINEAR );
    remap( img2, rectifiedImg2, map2x, map2y, INTER_LINEAR );

    // 显示结果
    imshow( "Original Image 1", img1 );
    imshow( "Original Image 2", img2 );
    imshow( "Rectified Image 1", rectifiedImg1 );
    imshow( "Rectified Image 2", rectifiedImg2 );

    waitKey( 0 );  // 等待按键关闭窗口

    return 0;
}

运行结果

在这里插入图片描述
命令行输出:

Rectification matrices for the first camera:
[0.9990327505785522, 0.002838302200161476, -0.0438806029137316;
 -0.001498542304358349, 0.9995325828506972, 0.03053473744403527;
 0.04394675917987673, -0.03043944579710149, 0.9985700388541362]
Rectification matrices for the second camera:
[1, 2.782197548109867e-17, -9.101507439329915e-16;
 -4.437342568756724e-17, 0.9995349355631407, -0.03049446816700398;
 9.047190730013251e-16, 0.03049446816700397, 0.9995349355631405]
Projection matrix for the first camera:
[521, 0, 340.8940467834473, 0;
 0, 521, 249.6826610565186, 0;
 0, 0, 1, 0]
Projection matrix for the second camera:
[521, 0, 340.8940467834473, 31260;
 0, 521, 249.6826610565186, 0;
 0, 0, 1, 0]
Disparity-to-depth mapping matrix:
[1, 0, 0, -340.8940467834473;
 0, 1, 0, -249.6826610565186;
 0, 0, 0, 521;
 0, 0, -0.01666666666666667, 0]

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

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

相关文章

ARP 表、MAC 表、路由表、跨网段 ARP

文章目录 一、ARP 表1、PC2、路由器 - AR22203、交换机 - S57004、什么样的设备会有 ARP 表&#xff1f; 二、MAC 表什么样的设备会有 MAC 表&#xff1f; 三、路由表什么样的设备会有路由表&#xff1f; 四、抓取跨网段 ARP 包 所谓 “透明” 就是指不用做任何配置 一、ARP 表…

深度学习 · 手撕 DeepLearning4J ,用Java实现手写数字识别 (附UI效果展示)

引言 随着人工智能技术的不断发展&#xff0c;手写数字识别已经成为深度学习领域的一个经典案例。不管是老牌的机器学习模型还是现代的神经网络架构&#xff0c;手写数字识别总是大家学习和实战的起点之一。而对于我们日常使用的Java开发者来说&#xff0c;借助DeepLearning4J…

天机学堂5-XxlJobRedis

文章目录 梳理前面的实现&#xff1a;Feign点赞改进 day07-积分系统bitmap相关命令签到增加签到记录计算本月已连续签到的天数查询签到记录 积分表设计签到-->发送RabbitMQ消息&#xff0c;保存积分对应的消费者&#xff1a;**消费消息 用于保存积分**增加积分查询个人今日积…

2024 年度学习总结

目录 1. 前言 2. csdn 对于我的意义 3. 写博客的初衷 3.1 现在的想法 4. 写博客的意义 5. 关于生活和博客创作 5.1 写博客较于纸质笔记的优势 6. 致 2025 1. 前言 不知不觉, 来到 csdn 已经快一年了, 在这一年中, 我通过 csdn 学习到了很多知识, 结识了很多的良师益友…

使用Chrome和Selenium实现对Superset等私域网站的截图

最近遇到了一个问题&#xff0c;因为一些原因&#xff0c;我搭建的一个 Superset 的 Report 功能由于节假日期间不好控制邮件的发送&#xff0c;所以急需一个方案来替换掉 Superset 的 Report 功能 首先我们需要 Chrome 浏览器和 Chrome Driver&#xff0c;这是执行数据抓取的…

[操作系统] 进程的调度

进程切换概念 时间⽚&#xff1a;当代计算机都是分时操作系统&#xff0c;没有进程都有它合适的时间⽚(其实就是⼀个计数 器)。时间⽚到达&#xff0c;进程就被操作系统从CPU中剥离下来。 死循环是如何运行&#xff1f; 当一个进程代码为死循环&#xff0c;它并不会一直占据C…

Biotin sulfo-N-hydroxysuccinimide ester ;生物素磺基-N-羟基琥珀酰亚胺酯;生物素衍生物;190598-55-1

一、生物素及其衍生物的概述 生物素衍生物是指在生物素&#xff08;Vitamin H或B7&#xff09;分子基础上进行化学修饰得到的衍生化合物。这些衍生化合物在生物医学研究、临床诊断和药物开发等领域有着广泛的应用。 生物素&#xff08;Biotin&#xff09;是一种水溶性维生素&a…

Jenkins-Pipeline简述

一. 什么是Jenkins pipeline&#xff1a; pipeline在jenkins中是一套插件&#xff0c;主要功能在于&#xff0c;将原本独立运行于单个或者多个节点的任务连接起来&#xff0c;实现单个任务难以完成的复杂发布流程。Pipeline的实现方式是一套Groovy DSL&#xff0c;任何发布流程…

Linux系统下安装配置Nginx(保姆级教程)

目录 前言 安装配置Nginx 一.下载依赖 二.下载Nginx 1. 访问官网?&#xff0c;获取需要的Nginx版本 2. 将文件下载到Linux系统 3. 解压文件 4. 解压成功后&#xff0c;当前文件夹会出现一个nginx-1.26.1文件夹&#xff0c;进入到文件夹内 5. 配置nginx 6.?编译并安…

《Linux服务与安全管理》| 邮件服务器安装和配置

《Linux服务与安全管理》| 邮件服务器安装和配置 目录 《Linux服务与安全管理》| 邮件服务器安装和配置 1.在Server01上安装dns、postfix、dovecot和telnet&#xff0c;并启动 2&#xff0e;在Server01上配置DNS服务器&#xff0c;设置MX资源记录 3&#xff0e;在server1上…

WPS数据分析000001

目录 一、表格的新建、保存、协作和分享 新建 保存 协作 二、认识WPS表格界面 三、认识WPS表格选项卡 开始选项卡 插入选项卡 页面布局选项卡 公式选项卡 数据选项卡 审阅选项卡 视图选项卡 会员专享选项卡 一、表格的新建、保存、协作和分享 新建 ctrlN------…

2025年免费量化交易软件——PTrade(含开通攻略)

量化交易软件&#xff0c;为广大投资者提供了一个便捷、高效的投资工具。 本文重点为大家介绍一款2025年好用的免费量化交易软件&#xff1a;PTrade量化&#xff0c;并详解其功能、特点、开通方法等。 一、PTrade的概念 PTrade是恒生电子开发的一款交易终端软件&#xff0c;旨…

【数据结构篇】顺序表 超详细

目录 一.顺序表的定义 1.顺序表的概念及结构 1.1线性表 2.顺序表的分类 2.1静态顺序表 2.2动态顺序表 二.动态顺序表的实现 1.准备工作和注意事项 2.顺序表的基本接口&#xff1a; 2.0 创建一个顺序表 2.1 顺序表的初始化 2.2 顺序表的销毁 2.3 顺序表的打印 3.顺序…

mysql查缺补漏

auto increment&#xff1a;自增序列&#xff0c;在字段后作为约束使用 comment&#xff1a;备注信息&#xff0c;用于在创建字段后或创建表的语句最后. 数值类型&#xff1a; 字符串类型&#xff1a; 日期类型&#xff1a; desc table_name&#xff1a;查询表结构 sho…

C++ 面向对象(继承)

三、继承 3.1 继承的概念 基于一个已有的类 去重新定义一个新的类&#xff0c;这种方式我们叫做继承 关于继承的称呼 一个类B 继承来自 类 A 我们一般称呼 A类&#xff1a;父类 基类 B类: 子类 派生类 B继承自A A 派生了B 示例图的语法 class vehicle // 车类 {}class …

JAVA-IO模型的理解(BIO、NIO)

前言 &#xff08;本文是作者学习制作rpc框架时&#xff0c;一些自用的笔记&#xff0c;并不会完整详细的介绍某个模块&#xff0c;会写大概的流程及一些相关概念&#xff0c;供日后复习使用~&#xff09; IO模型 先理解基本的IO流程&#xff1a; 应用A把消息发送到 TCP发送缓…

【Spring】原型 Bean 被固定

问题描述 在定义 Bean 时&#xff0c;有时候我们会使用原型 Bean&#xff0c;例如定义如下&#xff1a; Service Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class ServiceImpl { }然后我们按照下面的方式去使用它&#xff1a; RestController public class Hello…

奉加微PHY6230兼容性:部分手机不兼容

从事嵌入式单片机的工作算是符合我个人兴趣爱好的,当面对一个新的芯片我即想把芯片尽快搞懂完成项目赚钱,也想着能够把自己遇到的坑和注意事项记录下来,即方便自己后面查阅也可以分享给大家,这是一种冲动,但是这个或许并不是原厂希望的,尽管这样有可能会牺牲一些时间也有哪天原…

Python脚本实现通过JLink烧录Hex文件

1 安装JLink驱动程序 驱动安装包下载路径&#xff1a;https://www.segger.com/downloads/jlink/ 选择对应的版本下载&#xff1a; 将下载的安装文件双击进行安装。 2 安装 pylink 包 pip install pylink3 查询 JLink 设备的 serial number 将JLink通过USB线插入电脑。 w…

【Qt】04-Lambda表达式

前言一、概念引入二、使用方法2.1 基本用法代码示例2.2 捕获外部变量2.3 参数列表 三、完整代码mywidget.cppsecondwidget.cppmywidget.hsecondwidget.h 总结 前言 一、概念引入 Lambda表达式&#xff08;Lambda Expressions&#xff09;是C11标准引入的一种匿名函数对象&…