Opencv计算机视觉编程攻略-第十节 估算图像之间的投影关系
目录
1. 计算图像对的基础矩阵
2. 用RANSAC 算法匹配图像
3. 计算两幅图像之间的单应矩阵
4. 检测图像中的平面目标
图像通常是由数码相机拍摄的,它通过透镜投射光线成像,是三维场景在二维平面上的投影,这表明场景和它的图像之间以及同一场景的不同图像之间都有着重要的关联。投影几何学是用数学术语描述和区分成像过程的工具。下文将介绍几种多视图图像中基本的投影关系,并解释如何在计算机视觉编程中将其投入应用。
光线从被摄景象发出并穿过前置孔径,被相机捕获,捕获到的光线触发相机后面的成像平面(或图像传感器)

进一步构建,世界坐标系-图像坐标系转换矩阵:

1. 计算图像对的基础矩阵
上述投影方程解释了真实的场景是如何投影到单目相机的成像平面上的。在同一场景的两幅图像之间的投影关系:可以移动相机,从两个视角拍摄两幅照片;也可以使用两个相机,分别对同一个场景拍摄照片,如果这两个相机被刚性基线分割,我们就称之为立体视觉。

沿着三维点X 和相机中心点之间的连线,可在图像上找到对应的点x,反过来,在三维空间中,与成像平面上的位置x 对应的场景点可以位于线条上的任何位置。这说明如果要根据图像中的一个点找到另一幅图像中对应的点,就需要在第二个成像平面上沿着这条线的投影搜索。这条虚线称为点x 的对极线。它规定了两个对应点必须满足的基本条件,即对于一个点,在另一视图中与它匹配的点必须位于它的对极线上,并且对极线的准确方向取决于两个相机的相对位置。

即所有的对极线都通过同一个点。这个点对应着一个相机中心点在另一个相机上的投影(上图中的e 和e')。这个特殊的点称为极点,矩阵F(称为基础矩阵)的作用就是把一个视图上的二维图像点映射到另一个视图上的对极线上。如果两幅图像之间有一定数量的已知匹配点,就可以利用方程组来计算图像对的基础矩阵,即假设点(x, y)的对应点为(x', y'),这两个视图间的基础矩阵为F,由于(x', y') 位于(x, y)和F 相乘得到的对极线上(用齐次坐标表示)。
这样的匹配项至少要有7 对,用OpenCV 函数cv::findFundamentalMat 计算基础矩阵时,将使用上一节获得的匹配点匹配项。
//1. 这些匹配项存储在cv::DMatch 类型的容器中,其中每个元素代表一个cv::keypoint 实例的索引。为了在cv::findFundamentalMat 中使用,需要先把这些关键点转换成cv::Point2f类型。为此可用下面的OpenCV 函数:
// 把关键点转换成Point2f
std::vector<cv::Point2f> selPoints1, selPoints2;
std::vector<int> pointIndexes1, pointIndexes2;
cv::KeyPoint::convert(keypoints1,selPoints1,pointIndexes1);
cv::KeyPoint::convert(keypoints2,selPoints2,pointIndexes2);//2. 结果是selPoints1 和selPoints2 这两个容器,容器的元素为两幅图像中相关像素的坐标。容器pointIndexes1 和pointIndexes2 包含这些被转换的关键点的索引。于是调用cv::findFundamentalMat 函数的方法为:
// 用7 对匹配项计算基础矩阵
cv::Mat fundamental= cv::findFundamentalMat(selPoints1, // 第一幅图像中的7 个点selPoints2, // 第二幅图像中的7 个点cv::FM_7POINT); // 7 个点的方法//3. 要想直观地验证这个基础矩阵的效果,可以选取一些点,画出它们的对极线。OpenCV 中有一个函数可计算指定点集的对极线。计算出对极线后,可用函数cv::line 画出它们。
// 在右侧图像上画出对极线的左侧点
std::vector<cv::Vec3f> lines1;
cv::computeCorrespondEpilines(selPoints1, // 图像点1, // 在第一幅图像中(也可以是在第二幅图像中)fundamental, // 基础矩阵lines1); // 对极线的向量// 遍历全部对极线
for (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));
}

2. 用RANSAC 算法匹配图像
如何进一步使用上一节介绍的极线约束,使图像特征的匹配更加可靠:在匹配两幅图像的特征点时,只接受位于对极线上的匹配项。若要判断是否满足这个条件,必须先知道基础矩阵,但计算基础矩阵又需要优质的匹配项。这看起来像是“先有鸡还是先有蛋”的问题,下图为原始匹配结果。

我们的目的是计算两个视图间的基础矩阵和优质匹配项。因此,所有已发现的特征点的匹配度都要用上一节的极线约束验证

通过单应矩阵可以将假设仅通过旋转变换得到得两张图片进行极线对齐,从而服务于下一步得视差估计。场景点中值为0 的坐标会消除掉投影矩阵的第三列,从而又变成一个3×3 的矩阵。这种特殊矩阵称为单应矩阵(homography),表示在特殊情况下(这里指纯旋转或平面目标),世界坐标系的点和它的影像之间是线性关系。此外,由于该矩阵是可逆的,所以只要两个视图只是经过了旋转,或者拍摄的是平面物体,那么就可以将一个视图中的像素点与另一个视图中对应的像素点直接关联起来。单应矩阵的格式为:

其中H 是一个3×3 矩阵。这个关系式包含了一个尺度因子,用s 表示。计算得到这个矩阵后,一个视图中的所有点都可以根据这个关系式转换到另一个视图。
// 用RANSAC 算法匹配特征点
// 返回基础矩阵和输出的匹配项
cv::Mat match(cv::Mat& image1,cv::Mat& image2, // 输入图像std::vector<cv::DMatch>& matches, // 输出匹配项std::vector<cv::KeyPoint>& keypoints1, // 输出关键点std::vector<cv::KeyPoint>& keypoints2) {// 1.检测特征点detector->detect(image1,keypoints1);detector->detect(image2,keypoints2);// 2.提取特征描述子cv::Mat descriptors1, descriptors2;descriptor->compute(image1,keypoints1,descriptors1);descriptor->compute(image2,keypoints2,descriptors2);// 3.匹配两幅图像描述子// (用于部分检测方法)// 构造匹配类的实例(带交叉检查)cv::BFMatcher matcher(normType, // 差距衡量true); // 交叉检查标志// 匹配描述子std::vector<cv::DMatch> outputMatches;matcher.match(descriptors1,descriptors2,outputMatches);// 4.用RANSAC 算法验证匹配项cv::Mat fundamental= ransacTest(outputMatches,keypoints1, keypoints2,matches);// 返回基础矩阵return fundamental;
}
RANSAC 算法旨在根据一个可能包含大量局外项的数据集,估算一个特定的数学实体。其原理是从数据集中随机选取一些数据点,并仅用这些数据点进行估算。选取的数据点数量,应该是估算数学实体所需的最小数量,对于基础矩阵,最小数量是8 个匹配对(实际上只需要7 个,但是8 个点的线性算法速度较快)。用这8 个随机匹配对估算基础矩阵后,对剩下的全部匹配项进行测试,验证其是否满足根据这个矩阵得到的极线约束。标识出所有满足极线约束的匹配项(即特征点与对极线距离很近的匹配项),这些匹配项就组成了基础矩阵的支撑集。RANSAC 算法背后的核心思想是:支撑集越大,所计算矩阵正确的可能性就越大。反之,如果一个(或多个)随机选取的匹配项是错误的,那么计算得到的基础矩阵也是错误的,并且它的支撑集肯定会很小。反复执行这个过程,最后留下支撑集最大的矩阵作为最佳结果:


通过极线约束后可以将特征点进行限制,找到高质量得匹配点,从而计算H矩阵,并基于H矩阵实现图像拼接,opencv里有内置得拼接函数
// 读取输入的图像
std::vector<cv::Mat> images;
images.push_back(cv::imread("parliament1.jpg"));
images.push_back(cv::imread("parliament2.jpg"));
cv::Mat panorama; // 输出的全景图
// 创建拼接器
cv::Stitcher stitcher = cv::Stitcher::createDefault();
// 拼接图像
cv::Stitcher::Status status = stitcher.stitch(images, panorama);

3. 检测图像中的平面目标
假定我们需要检测图像中的平面物体。这个物体可能是一张海报、一幅画等等。采取的方法是检测这个平面物体的特征点,然后试着在图像中匹配这些特征点。然后和前面一样,用鲁棒匹配方案来验证这些匹配项,但这次要基于单应矩阵。
将目标图像依次进行压缩像素采样,得到一系列类似金字塔的图像,从这些图像中就可以检测到特征点。
// 1. 初始化匹配器 使用两个快速得多尺度检测器
TargetMatcher tmatcher(cv::FastFeatureDetector::create(10),cv::BRISK::create());
tmatcher.setNormType(cv::NORM_HAMMING);
因为不知道图像中目标物体的大小,所以我们把目标图像转换成一系列不同的尺寸,构建成一个金字塔。除了这种方法,也可以采用尺度不变特征。在金字塔中,目标图像的尺寸按特定比例(属性scaleFactor,默认为0.9)逐层缩小。
detectTarget 接下来要执行三个步骤。
- 第一步,在输入图像中检测兴趣点。
- 第二步,将该图像与目标金字塔中的每幅图像进行鲁棒匹配,并把优质匹配项最多的那一层保留下来;如果这一层的匹配项足够多,就可认为已经找到目标。
- 第三步,使用得到的单应矩阵和cv::getPerspectiveTransform 函数,把目标中的四个角点重新投影到输入图像中。
// 检测预先定义的平面目标
// 返回单应矩阵和检测到的目标的4 个角点
cv::Mat detectTarget(
const cv::Mat& image, // 目标角点(顺时针方向)的坐标
std::vector<cv::Point2f>& detectedCorners) {
// 1.检测图像的关键点
std::vector<cv::KeyPoint> keypoints;
detector->detect(image, keypoints);
// 计算描述子
cv::Mat descriptors;
descriptor->compute(image, keypoints, descriptors);
std::vector<cv::DMatch> matches;
cv::Mat bestHomography;
cv::Size bestSize;
int maxInliers = 0;
cv::Mat homography;
// 构建匹配器
cv::BFMatcher matcher(normType);
// 2.对金字塔的每层,鲁棒匹配单应矩阵
for (int i = 0; i < numberOfLevels; i++) {
// 在目标和图像之间发现RANSAC 单应矩阵
matches.clear();
// 匹配描述子
matcher.match(pyrDescriptors[i], descriptors, matches);
// 用RANSAC 验证匹配项
std::vector<cv::DMatch> inliers;
homography = ransacTest(matches, pyrKeypoints[i],
keypoints, inliers);
if (inliers.size() > maxInliers) { // 有更好的H
maxInliers = inliers.size();
bestHomography = homography;
bestSize = pyramid[i].size();
}
}
// 3.用最佳单应矩阵找出角点坐标
if (maxInliers > 8) { // 估算值有效
// 最佳尺寸的目标角点
std::vector<cv::Point2f> corners;
corners.push_back(cv::Point2f(0, 0));
corners.push_back(cv::Point2f(bestSize.width - 1, 0));
corners.push_back(cv::Point2f(bestSize.width - 1,
bestSize.height - 1));
corners.push_back(cv::Point2f(0, bestSize.height - 1));
// 重新投影目标角点
cv::perspectiveTransform(corners, detectedCorners, bestHomography);
}
return bestHomography;
}

本节内容还需多多学习,如果感兴趣,请下载上传代码自行研究。代码资源
相关文章:
Opencv计算机视觉编程攻略-第十节 估算图像之间的投影关系
目录 1. 计算图像对的基础矩阵 2. 用RANSAC 算法匹配图像 3. 计算两幅图像之间的单应矩阵 4. 检测图像中的平面目标 图像通常是由数码相机拍摄的,它通过透镜投射光线成像,是三维场景在二维平面上的投影,这表明场景和它的图像之间以及同一…...
14.流程自动化工具:n8n和家庭自动化工具:node-red
n8n 安装 docker方式 https://docs.n8n.io/hosting/installation/docker/ #https://hub.docker.com/r/n8nio/n8n docker pull n8nio/n8n:latest docker rm -f n8n; docker run -it \ --network macvlan --hostname n8n \ -e TZ"Asia/Shanghai" \ -e GENERIC_TIME…...
图形渲染: tinyrenderer 实现笔记(Lesson 1 - 4)
目录 项目介绍环境搭建Lesson 1: Bresenham’s Line Drawing Algorithm(画线算法)Lesson 2: Triangle rasterization 三角形光栅化Scanline rendering 线性扫描Modern rasterization approach 现代栅格化方法back-face culling 背面剔除 Lesson 3: Hidde…...
大规模硬件仿真系统的编译挑战
引言: 随着集成电路设计复杂度的不断提升,硬件仿真系统在现代芯片设计流程中扮演着越来越重要的角色。基于FPGA(现场可编程门阵列)的商用硬件仿真系统因其灵活性、全自动化、高性能和可重构性,成为验证大规模集成电路设…...
Kotlin问题汇总
Kotlin问题汇总 真机安装调试 查看真机的Android版本,将build.gradle文件中的minSdk改为手机的Android版本,点Sync Now更新设置 apk安装失败 在gradle.properties全局配置中设置android.injected.testOnlyfalse Unresolved reference: 在activity_…...
记一次常规的网络安全渗透测试
目录: 前言 互联网突破 第一层内网 第二层内网 总结 前言 上个月根据领导安排,需要到本市一家电视台进行网络安全评估测试。通过对内外网进行渗透测试,网络和安全设备的使用和部署情况,以及网络安全规章流程出具安全评估报告。本…...
【8】搭建k8s集群系列(二进制部署)之安装work-node节点组件(kubelet)
一、下载k8s二进制文件 下载地址: https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG -1.20.md 注:打开链接你会发现里面有很多包,下载一个 server 包就够了,包含了 Master 和 Worker Node 二进制文件。…...
Sentinel-自定义资源实现流控和异常处理
目录 使用SphU的API实现自定义资源 BlockException 使用SentinelResource注解定义资源 SentinelResourceAspect 使用Sentinel实现限流降级等效果通常需要先把需要保护的资源定义好,之后再基于定义好的资源为其配置限流降级等规则。 Sentinel对于主流框架&#…...
使用 VIM 编辑器对文件进行编辑
一、VIM 的两种状态 VIM(vimsual)是 Linux/UNIX 系列 OS 中通用的全屏编辑器。vim 分为两种状态,即命令状态和编辑状态,在命令状态下,所键入的字符系统均作命令来处理;而编辑状态则是用来编辑文本资料&…...
visual studio 2022的windows驱动开发
在visual studio2022中,若在单个组件中找不到Windows Driver Kit (WDK)选项,可通过提升vs版本解决,在首次选择时选择WDM 创建好项目在Source Files文件夹中创建一个test.c文件,并输入以下测试代码: #include <ntdd…...
基于大数据的美团外卖数据可视化分析系统
【大数据】基于大数据的美团外卖数据可视化分析系统 (完整系统源码开发笔记详细部署教程)✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 该系统通过对海量外卖数据的深度挖掘与分析,能够为美团外卖平台提供运营决策支…...
C/C++测试框架googletest使用示例
文章目录 文档编译安装示例参考文章 文档 https://github.com/google/googletest https://google.github.io/googletest/ 编译安装 googletest是cmake项目,可以用cmake指令编译 cmake -B build && cmake --build build将编译产物lib和include 两个文件夹…...
vue2打包部署到nginx,解决路由history模式下页面空白问题
项目使用的是vue2,脚手架vue-cli 4。 需求:之前项目路由使用的是hash,现在要求调整为history模式,但是整个过程非常坎坷,遇到了页面空白问题。现在就具体讲一下这个问题。 首先,直接讲路由模式由hash改为…...
如何将本地项目上传到Gitee的指定分支
在团队协作开发中,我们经常需要将本地项目代码上传到代码托管平台(如Gitee)的特定分支。本文将详细介绍从零开始完成这一过程的完整步骤,包含多种场景的解决方案和常见问题处理。 一、准备工作 1.1 安装Git 确保你的系统已安装…...
【数据结构】排序算法(中篇)·处理大数据的精妙
前引:在进入本篇文章之前,我们经常在使用某个应用时,会出现【商品名称、最受欢迎、购买量】等等这些榜单,这里面就运用了我们的排序算法,作为刚学习数据结构的初学者,小编为各位完善了以下几种排序算法&…...
AI随身翻译设备:从翻译工具到智能生活伴侣
文章目录 AI随身翻译设备的核心功能1. 实时翻译2. 翻译策略3. 翻译流程4. 输出格式 二、AI随身翻译设备的扩展功能1. 语言学习助手2. 旅行助手3. 商务助手4. 教育助手5. 健康助手6. 社交助手7. 技术助手8. 生活助手9. 娱乐助手10. 应急助手 三、总结四、未来发展趋势࿰…...
chromadb 安装和使用
简介 Chromadb 是一个开源的嵌入式向量数据库,专为现代人工智能和机器学习应用设计,旨在高效存储、检索和管理向量数据。以下是关于它的详细介绍: 核心特性 易于使用:提供了简洁直观的 API,即使是新手也能快速上手…...
【全球首发】DeepSeek谷歌版1.1.5 - 免费GPT-4级别AI工具
【全球首发】DeepSeek谷歌版1.1.5 - 免费GPT-4级别AI工具 资源简介 DeepSeek谷歌版1.1.5是目前全球领先的免费AI助手,性能超越国内主流AI产品,提供类似GPT-4的智能体验。 版本信息 最新版本:1.1.5(2024最新版)应用…...
LeetCode第132题_分割回文串II
LeetCode 第132题:分割回文串 II 题目描述 给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文。 返回符合要求的 最少分割次数 。 难度 困难 题目链接 点击在LeetCode中查看题目 示例 示例 1: 输入…...
LabVIEW 在故障诊断中的算法
在故障诊断领域,LabVIEW 凭借其强大的图形化编程能力、丰富多样的工具包以及卓越的功能性能,成为工程师们进行故障诊断系统开发的得力助手。通过运用各种算法,能够对采集到的信号进行全面、深入的分析处理,从而准确地诊断出系统中…...
SQL DB 数据类型
SQL DB 数据类型 引言 在数据库管理系统中,数据类型是定义和存储数据的方式。SQL(结构化查询语言)数据库中的数据类型决定了数据的存储格式、大小、取值范围以及如何处理数据。合理选择和使用数据类型对于确保数据库性能、数据完整性和应用程序的准确性至关重要。 SQL 数…...
Qt音频输出:QAudioOutput详解与示例
1. 简介 QAudioOutput是Qt多媒体框架中的一个关键类,它提供了将PCM(脉冲编码调制)原始音频数据发送到音频输出设备的接口。作为Qt多媒体组件的一部分,QAudioOutput允许开发者在应用程序中实现音频播放功能,支持多种音…...
springboot 启动方式 装配流程 自定义starter 文件加载顺序 常见设计模式
目录 springboot介绍 核心特性 快速搭建 Spring Boot 项目 方式一:使用 Spring Initializr 方式二:使用 IDE 插件 示例代码 1. 创建项目并添加依赖 2. 创建主应用类 3. 创建控制器类 4. 运行应用程序 配置文件 部署和监控 部署 监控 与其…...
Android学习之Material Components
以下是 Material Design 提供的核心控件列表(基于最新 Material Components for Android 库),按功能分类整理: 1. 基础按钮类 控件名称类名说明MaterialButtoncom.google.android.material.button.MaterialButton遵循 Material 规…...
sentinel新手入门安装和限流,热点的使用
1 sentinel入门 1.1下载sentinel控制台 🔗sentinel管理后台官方下载地址 下载完毕以后就会得到一个jar包 1.2启动sentinel 将jar包放到任意非中文目录,执行命令: java -jar 名字.jar如果要修改Sentinel的默认端口、账户、密码ÿ…...
Ubuntu 22 Linux上部署DeepSeek R1保姆式操作详解(Xinference方式)
一、安装步骤 1.基础环境安装 安装显卡驱动、cuda,根据自己硬件情况查找相应编号,本篇不介绍这部分内容,只给出参考指令,详情请读者自行查阅互联网其它参考资料。 sudo apt install nvidia-utils-565-server sudo apt install…...
ANTLR 实战_从零开始构建自定义语言解析器
1. 引言 1.1 什么是 ANTLR ANTLR(Another Tool for Language Recognition)是一个强大的解析器生成器,用于构建语言解析器、编译器和解释器。 1.2 ANTLR 的历史与发展 ANTLR 由 Terence Parr 创建,最初发布于 1995 年。经过多次版本更新,ANTLR 已成为构建解析器的首选工…...
CTF类题目复现总结-hashcat 1
一、题目地址 https://buuoj.cn/challenges#hashcat二、复现步骤 1、下载附件,解压得到What kind of document is this_文件; 2、用010 Editor打开What kind of document is this_文件,发现是office文件; 3、将后缀名改为ppt时…...
4月5日作业
需求: 1.按照图示的VLAN及IP地址需求,完成相关配置 2.要求SW 1为VLAN 2/3的主根及主网关 SW2为VLAN 20/30的主根及主网关,SW1和 SW2互为备份 3.可以使用super vlan 4.上层通过静态路由协议完成数据通信过程 5.AR1为企业出口路由器…...
Bert论文解析
文章目录 BERT:用于语言理解的深度双向转换器的预训练一、摘要三、BERT介绍BERT及其详细实现答疑:为什么没有标注的数据可以用来预训练模型?1. 掩码语言模型(Masked Language Model, MLM)2. 下一句预测(Nex…...
