OpenCV实战——根据立体图像计算深度信息

news2024/12/23 9:14:11

OpenCV实战——根据立体图像计算深度信息

    • 0. 前言
    • 1. 立体视觉系统
    • 2. 计算深度信息
    • 3. 完整代码
    • 相关链接

0. 前言

人类可以用两只眼睛构建三个维度世界,而为机器人配备两个摄像头时,机器人同样也可以做到这一点,这称为立体视觉 (stereo vision)。安装在设备上的一对摄像机可以观察同一场景并由固定基线(即两个摄像机之间的距离)分隔。本节将介绍如何通过计算两个视图之间的深度对应关系根据两个立体图像计算深度图像。

1. 立体视觉系统

立体视觉系统通常由两个并排且朝向相同的相机组成,下图展示了这种立体视觉系统:

立体视觉系统
在这种理想配置下,相机仅在水平方向平移,因此所有极线都是水平的。这意味着对应点具有相同的 y y y 坐标,从而减少了在一维度上的匹配搜索。 x x x 坐标的差异取决于点的深度,无穷远处的点在图像中具有相同的坐标 ( x , y ) (x,y) (x,y),且这些点离立体视觉装置越近,它们的 x x x 坐标差异就越大,这可以由投影方程证明。当相机仅在水平方向移动时,第二个相机的投影方程如下:
S [ x ′ y ′ 1 ] = [ f 0 u 0 0 f v 0 0 0 1 ] [ 1 0 0 − B 0 1 0 0 0 0 1 0 ] [ X Y Z 1 ] S\left[ \begin{array}{ccc} x'\\ y'\\ 1\\\end{array}\right]=\left[ \begin{array}{ccc} f&0&u_0\\ 0&f&v_0\\ 0&0&1\\\end{array}\right]\left[ \begin{array}{ccc} 1&0&0&-B\\ 0&1&0&0\\ 0&0&1&0\\\end{array}\right]\left[ \begin{array}{ccc} X\\ Y\\ Z\\ 1\\\end{array}\right] S xy1 = f000f0u0v01 100010001B00 XYZ1
为了简单起见,我们假设两个相机具有相同的方形像素和校准参数。如果计算 x − x ′ x-x' xx 的差值(除以 s s s 以标准化齐次坐标)并忽略 z z z 坐标,可以得到以下等式:
Z = f ( x − x ′ ) B Z=f\frac {(x-x')} {B} Z=fB(xx)
其中, ( x − x ′ ) (x-x') (xx) 称为视差 (disparity)。为了计算立体视觉系统的深度图,必须估计每个像素的视差。本节,将介绍如何计算视差。

2. 计算深度信息

上一小节中展示的理想模型在现实中很难实现。即使它们可以准确定位,立体装置的摄像机也不可避免地会包含一些额外的平移和旋转。但我们可以对图像进行校正以产生水平线,可以通过使用鲁棒匹配算法来计算立体系统的基本矩阵来实现。例如,在以下图像上绘制核线:

绘制核线
OpenCV 提供了一个整流函数,它使用单应变换将每个相机的图像平面投影到完美对齐的虚拟平面上。

(1) 单应变换根据一组匹配点和一个基本矩阵进行计算。计算完成后,将在图像中应用这些单应性:

// 计算同形校正
cv::Mat h1, h2;
cv::stereoRectifyUncalibrated(points1, points2, fundamental, image1.size(), h1, h2);
// 通过扭曲矫正图像 
cv::Mat rectified1;
cv::warpPerspective(image1, rectified1, h1, image1.size());
cv::Mat rectified2;
cv::warpPerspective(image2, rectified2, h2, image1.size());

对于示例图像,校正后的图像对如下所示:

图像矫正
(2) 可以通过假设相机平行以及水平极线来计算视差图:

// 差异计算
cv::Mat disparity;
cv::Ptr<cv::StereoMatcher> pStereo = cv::StereoSGBM::create(0,   // 最小差异
                                                            32,  // 最大差异
                                                            5);  // 块大小
pStereo->compute(rectified1, rectified2, disparity);

(3) 可以将获得的视差图显示为图像。明亮的值表示高视差,高视差值对应于近端对象:

视差图

计算视差的质量主要取决于组成场景的不同对象,高纹理区域往往会产生更准确的视差估计,因为它们可以无歧义地匹配。此外,较大的基线会增加可检测深度值的范围,但扩大基线也会使视差计算更加复杂和不可靠。
当图像被正确校正后,搜索空间就可以与图像对齐。然而,在立体视觉中,我们通常需要密集的视差图。也就是说,我们希望将一幅图像的每个像素与另一幅图像的像素进行匹配。这比在一张图像中选择几个不同的点并在另一张图像中找到它们的对应点更具挑战性。因此,视差计算是一个复杂的过程,通常由四步组成:

  • 匹配误差计算
  • 误差汇总
  • 视差计算和优化
  • 视差改进

为一个像素分配视差是将一对点对应放入立体集合中,寻找最佳视差图通常是一个优化问题。从这个角度来看,匹配两个点的误差必须按照定义的度量进行计算,例如,可以是强度、颜色或梯度的绝对或平方差。在寻找最优解的过程中,匹配误差通常在一个区域上聚合,以应对局部噪声的影响。然后可以通过评估能量函数来估计全局视差图,该能量函数平滑视差图项,考虑可能的遮挡并强制执行唯一性约束。最后,通常应用后处理步骤以优化视差估计,例如检测平面区域或检测深度不连续性。
OpenCV 实现了许多视差计算方法,本节我们使用 cv::StereoSGBM 方法。最简单的方法是基于块匹配的 cv::StereoBM 函数。
最后,如果结合 cv::stereoCalibratecv::stereoRectify 函数进行完整的校准过程,则可以执行更准确的校正。然后,整流映射为相机计算新的投影矩阵,而非简单的单应性。

3. 完整代码

头文件 (robustMatcher.h) 完整代码参考基于随机样本一致匹配图像一节,主函数文件 (stereoMatcher.cpp) 完整代码如下所示:

#include <iostream>
#include <vector>
#include <numeric>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/objdetect/objdetect.hpp>
#include <opencv2/xfeatures2d.hpp>
#include <opencv2/viz.hpp>
#include "robustMatcher.h"

int main() {
    // 读取输入图像
    cv::Mat image1= cv::imread("1.png",0);
    cv::Mat image2= cv::imread("2.png",0);
    if (!image1.data || !image2.data)
        return 0; 
    // SIFT 检测器和匹配器
    RobustMatcher rmatcher(cv::xfeatures2d::SIFT::create(250));
    // 匹配两张图像
    std::vector<cv::DMatch> matches;
    std::vector<cv::KeyPoint> keypoints1, keypoints2;
    cv::Mat fundamental = rmatcher.match(image1, image2, matches,
            keypoints1, keypoints2);
    // 绘制匹配
    cv::Mat imageMatches;
    cv::drawMatches(image1, keypoints1, // 第一张图像及其关键点
            image2, keypoints2,         // 第二张图像及其关键点
            matches,                    // 匹配
            imageMatches,               // 结果图像
            cv::Scalar(255, 255, 255),
            cv::Scalar(255, 255, 255),
            std::vector<char>(),
            cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
    cv::namedWindow("Matches");
    cv::imshow("Matches", imageMatches);
    // 将关键点转换为 Point2f	
    std::vector<cv::Point2f> points1, points2;
    for (std::vector<cv::DMatch>::const_iterator it = matches.begin();
    it != matches.end(); ++it) {
        float x = keypoints1[it->queryIdx].pt.x;
        float y = keypoints1[it->queryIdx].pt.y;
        points1.push_back(keypoints1[it->queryIdx].pt);
        x = keypoints2[it->trainIdx].pt.x;
        y = keypoints2[it->trainIdx].pt.y;
        points2.push_back(keypoints2[it->trainIdx].pt);
    }
    // 计算同形校正
    cv::Mat h1, h2;
    cv::stereoRectifyUncalibrated(points1, points2, fundamental, image1.size(), h1, h2);
    // 通过扭曲矫正图像 
    cv::Mat rectified1;
    cv::warpPerspective(image1, rectified1, h1, image1.size());
    cv::Mat rectified2;
    cv::warpPerspective(image2, rectified2, h2, image1.size());
    cv::namedWindow("Left Rectified Image");
    cv::imshow("Left Rectified Image", rectified1);
    cv::namedWindow("Right Rectified Image");
    cv::imshow("Right Rectified Image", rectified2);
    points1.clear();
    points2.clear();
    for (int i = 20; i < image1.rows - 20; i += 20) {
        points1.push_back(cv::Point(image1.cols / 2, i));
        points2.push_back(cv::Point(image2.cols / 2, i));
    }
    // 绘制对极线
    std::vector<cv::Vec3f> lines1;
    cv::computeCorrespondEpilines(points1, 1, fundamental, lines1);
    for (std::vector<cv::Vec3f>::const_iterator it = lines1.begin();
    it != lines1.end(); ++it) {
        cv::line(image2, cv::Point(0, -(*it)[2] / (*it)[1]),
                cv::Point(image2.cols, -((*it)[2] + (*it)[0] * image2.cols) / (*it)[1]),
                cv::Scalar(255, 255, 255));
    }
    std::vector<cv::Vec3f> lines2;
    cv::computeCorrespondEpilines(points2, 2, fundamental, lines2);
    for (std::vector<cv::Vec3f>::const_iterator it = lines2.begin();
    it != lines2.end(); ++it) {
        cv::line(image1, cv::Point(0, -(*it)[2] / (*it)[1]),
            cv::Point(image1.cols, -((*it)[2] + (*it)[0] * image1.cols) / (*it)[1]),
            cv::Scalar(255, 255, 255));
    }
    cv::namedWindow("Left Epilines");
    cv::imshow("Left Epilines", image1);
    cv::namedWindow("Right Epilines");
    cv::imshow("Right Epilines", image2);
    // 绘制匹配
    cv::drawMatches(image1, keypoints1,
            image2, keypoints2,
            std::vector<cv::DMatch>(),
            imageMatches,
            cv::Scalar(255, 255, 255),
            cv::Scalar(255, 255, 255),
            std::vector<char>(),
            cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
    cv::namedWindow("A Stereo pair");
    cv::imshow("A Stereo pair", imageMatches);
    // 差异计算
    cv::Mat disparity;
    cv::Ptr<cv::StereoMatcher> pStereo = cv::StereoSGBM::create(0,   // 最小差异
                                                                32,  // 最大差异
                                                                5);  // 块大小
    pStereo->compute(rectified1, rectified2, disparity);
    //  绘制整流对 
    /*
    cv::warpPerspective(image1, rectified1, h1, image1.size());
    cv::warpPerspective(image2, rectified2, h2, image1.size());
    cv::drawMatches(rectified1, keypoints1,  // 1st image 
        rectified2, keypoints2,              // 2nd image
        std::vector<cv::DMatch>(),		
        imageMatches,		                // the image produced
        cv::Scalar(255, 255, 255),  
        cv::Scalar(255, 255, 255),  
        std::vector<char>(),
        2);
    cv::namedWindow("Rectified Stereo pair");
    cv::imshow("Rectified Stereo pair", imageMatches);
    */
	double minv, maxv;
	disparity = disparity * 64;
	cv::minMaxLoc(disparity, &minv, &maxv);
	std::cout << minv << "+" << maxv << std::endl;
	cv::namedWindow("Disparity Map");
	cv::imshow("Disparity Map", disparity);
	cv::waitKey();
	return 0;
}

相关链接

OpenCV实战(1)——OpenCV与图像处理基础
OpenCV实战(2)——OpenCV核心数据结构
OpenCV实战(3)——图像感兴趣区域
OpenCV实战(4)——像素操作
OpenCV实战(5)——图像运算详解
OpenCV实战(6)——OpenCV策略设计模式
OpenCV实战(7)——OpenCV色彩空间转换
OpenCV实战(8)——直方图详解
OpenCV实战(9)——基于反向投影直方图检测图像内容
OpenCV实战(10)——积分图像详解
OpenCV实战(11)——形态学变换详解
OpenCV实战(12)——图像滤波详解
OpenCV实战(13)——高通滤波器及其应用
OpenCV实战(14)——图像线条提取
OpenCV实战(15)——轮廓检测详解
OpenCV实战(16)——角点检测详解
OpenCV实战(17)——FAST特征点检测
OpenCV实战(18)——特征匹配
OpenCV实战(19)——特征描述符
OpenCV实战(20)——图像投影关系
OpenCV实战(21)——基于随机样本一致匹配图像
OpenCV实战(22)——单应性及其应用

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

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

相关文章

exe4j

exe4j是一种用于将Java程序打包成可执行文件&#xff08;.exe&#xff09;的软件工具。使用exe4j&#xff0c;开发人员可以将Java程序打包成可独立运行的.exe文件&#xff0c;并将所需的Java虚拟机&#xff08;JVM&#xff09;包含在内。exe4j提供了许多配置选项&#xff0c;可…

五、FM1288调试方案-调试原理

本篇只讲述调试原理,侧重流程、理论,不涉及细节,比如应该调哪一块、哪些寄存器这些。 文章目录 1. 结构框图1.1 回声消除原理1.2 硬件结构2. 调试方案2.1 uart串口调试2.2 I2C调试1. 结构框图 1.1 回声消除原理 回声消除的详细原理,牵涉到算法相关的东西,不太了解,只描…

二项分布的参数p的检验

设某事件发生的概率为p&#xff0c;做m次的独立检验&#xff0c;以X为发生的次数&#xff0c;则X服从二项分布B(m, p)&#xff0c;则针对X可以做出假设 定义一个合理的检验,&#xff0c;设置一个阈值C&#xff1a; F : 当 X < C时&#xff0c;接受H0&#xff0c;否则拒绝H0 …

[亲测有效] 如何实现vivo图案解锁

vivo是最受欢迎的智能手机品牌之一&#xff0c;拥有庞大的客户群。但是在使用vivo手机的过程中&#xff0c;难免会出现意外。其中最常见的是忘记密码。那么&#xff0c;如果您忘记了密码&#xff0c;如何解锁 vivo 手机呢&#xff1f;这是您需要知道的一切。本文将向您展示5种轻…

云原生应用环境中的权限提升

对于如今的现代数字应用程序&#xff0c;在操作事件期间管理访问权限对于确保企业的生产环境和基础架构安全都至关重要。一个被大家认可的基本安全原则是最小权限原则&#xff0c;基于该原则开发人员和运维人员应该具备尽可能小的权限&#xff0c;只访问必须的生产环境及数据&a…

牛客网剑指offer|中等题day2|JZ22链表中倒数最后k个结点(简单)、JZ35复杂链表的复制(复杂)、JZ77按之字形顺序打印二叉树(中等)

JZ22链表中倒数最后k个结点(简单) 链接&#xff1a;链表中倒数最后k个结点_牛客题霸_牛客网 /*** struct ListNode {* int val;* struct ListNode *next;* ListNode(int x) : val(x), next(nullptr) {}* };*/ class Solution { public:/*** 代码中的类名、方法名、参数名已经指…

CRM系统的在线演示是什么?有什么作用?

CRM系统在线演示的作用是帮助企业选择适合的CRM系统。在线演示可以让企业更好地了解CRM系统是如何工作的&#xff0c;以及它如何能使他们的业务受益。在线演示实质上是CRM系统的虚拟演示&#xff0c;您可以清楚的知道它是如何工作的&#xff0c;以及如何通过定制来满足某些业务…

解释水波特效处理

这篇博文译自以下这篇文章——The Water Effect Explained 由于这篇文章主要用Pascal语言进行描述的。因此我后面会添加一些注释&#xff0c;并结合Apple提供的ripple相关的Demo给出一些额外的遵守GNU11规范的C代码。 介绍 在计算机图形中的许多特效中&#xff0c;水特效是一…

ResourceManager HA 原理

简介 为了解决 Yarn 中 ResourceManager 的单点故障问题&#xff0c;在 Hadoop 2.4 中新增了 ResourceManager HA 的能力&#xff0c; 该文章基于 Hadoop 3.1.1 进行讲解。 1.1. 名词定义 全称简称备注ResourceManagerRmZookeeperZK ResourceManager Ha 架构 ResourceMana…

Linux shell编程 数组 ^ 数组排序

数组定义 数组内数据类型可以为数值也可以为字符串。 若字符串类型需要使用 " " 包含以免空格扰乱数组。 方法1 空格分隔直接定义数组 arr(10 20 30 40 50) arr1(zhangsan lisi wangwu) 方法2 指定元素下标定义&#xff0c;若跳过元素不设置会显示为空 arr([0]1…

Python 密码破解指南:10~14

协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 本文来自【OpenDocCN 饱和式翻译计划】&#xff0c;采用译后编辑&#xff08;MTPE&#xff09;流程来尽可能提升效率。 收割 SB 的人会被 SB 们封神&#xff0c;试图唤醒 SB 的人是 SB 眼中的 SB。——SB 第三定律 十、加…

震惊!如果患上植物神经紊乱,就会诱发胃肠神经功能紊乱!

植物神经系统和胃肠系统是人体内重要的调节系统&#xff0c;它们分别负责着许多生物过程的调控。当这两个系统出现紊乱时&#xff0c;会对人体健康产生不良影响。本文将从植物神经紊乱与胃肠神经功能紊乱的关系、症状、治疗办法和生活预防方法四个方面进行探讨。 一、植物神经紊…

GoAccess 网站日志分析

GoAccess是一个开源且免费的网站日志分析和交互式WEB日志查看器&#xff0c;可在 Linux 系统的终端中或通过浏览器运行。使用它可让系统管理员视化的查看统计报告&#xff0c;这对于SEO以及运维来说非常有价值。 GoAccess支持几乎所有Web 日志格式&#xff0c;包含&#xff1a;…

数据结构-图的遍历和应用(DAG、AOV、AOE网)

目录 *一、广度优先遍历(BFS) 广度优先生成树 广度优先生成森林 *二、深度优先遍历 深度优先生成树 深度优先生成森林 二、应用 2.1最小生成树 *Prim算法 *Kruskal算法 2.2最短路径 *BFS算法 *Dijkstra算法 *Floyd算法 *2.3有向无环图(DAG网) *2.4拓扑排序(AOV网)…

Java之线程安全

目录 一.上节回顾 1.Thread类常见的属性 2.Thread类中的方法 二.多线程带来的风险 1.观察线程不安全的现象 三.造成线程不安全现象的原因 1.多个线程修改了同一个共享变量 2.线程是抢占式执行的 3.原子性 4.内存可见性 5.有序性 四.解决线程不安全问题 ---synchroni…

【Win32绘图编程,GDI绘图对象】绘图基础,位图处理,绘图消息处理,画笔,画刷,文本绘制

这一篇文章分享本人学习win32绘图编程&#xff0c;其中包括GDI绘图对象&#xff0c;绘图基础&#xff0c;基本图形的绘制&#xff0c;画笔画刷的使用&#xff0c;文本绘制&#xff0c;以及文本字体的更改。 文章目录 一.绘图基础1.BeginPaint函数2.EndPaint函数3.颜色的使用 二…

8 集群管理

8 集群管理 8.1 集群结构 ES通常以集群方式工作&#xff0c;这样做不仅能够提高 ES的搜索能力还可以处理大数据搜索的能力&#xff0c;同时也增加了系统的容错能力及高可用&#xff0c;ES可以实现PB级数据的搜索。 下图是ES集群结构的示意图&#xff1a; 从上图总结以下概念…

SSM整合详细教学(下)

SSM整合详细教学&#xff08;下&#xff09; 五、SSM整合页面开发1 准备工作2 列表查询功能3 添加功能4 修改功能5 删除功能 六、拦截器1 拦截器简介问题导入1.1 拦截器概念和作用1.2 拦截器和过滤器的区别 2 入门案例问题导入2.1 拦截器代码实现【第一步】定义拦截器【第二步】…

从零开始搭建高效的文件服务器:FastDFS与Nginx完美结合,内网穿透实现公网访问

目录 前言 1. 本地搭建FastDFS文件系统 1.1 环境安装 1.2 安装libfastcommon 1.3 安装FastDFS 1.4 配置Tracker 1.5 配置Storage 1.6 测试上传下载 1.7 与Nginx整合 1.8 安装Nginx 1.9 配置Nginx 2. 局域网测试访问FastDFS 3. 安装cpolar内网穿透 4. 配置公网访问…

区间预测 | MATLAB实现QRBiLSTM双向长短期记忆神经网络分位数回归时间序列区间预测

区间预测 | MATLAB实现QRBiLSTM双向长短期记忆神经网络分位数回归时间序列区间预测 目录 区间预测 | MATLAB实现QRBiLSTM双向长短期记忆神经网络分位数回归时间序列区间预测效果一览基本介绍模型描述程序设计参考资料 效果一览 进阶版 基础版 基本介绍 MATLAB实现QRBiLS…