【opencv】示例-epipolar_lines.cpp 对极线

news/2024/4/29 23:15:32/文章来源:https://blog.csdn.net/cxyhjl/article/details/137578359

ee649ab3b854f79541811b3bd3514dcb.png

5fea5e2d6bebe83ddead508beac6bdb9.png

3052f8e425ecb29f4ab2a3d106085c28.png

这段代码总的功能是使用OpenCV库进行立体视觉的估计。它从命令行读取两个图像文件名,使用SIFT算法检测关键点并计算这些点的描述子,接着通过FLANN库进行快速近似最近邻搜索来找到匹配的关键点。然后使用RANSAC方法计算基础矩阵,找到内点,并绘制出对极线和对应的点。最后将处理后的图像显示出来,并且保存到文件。这个程序是处理和展示两个图像之间的对应关系和极线(epilines)的。

// 这个文件属于OpenCV项目的一部分。
// 它服从发现在顶级目录的LICENSE文件中的许可条款
// 以及在http://opencv.org/license.html 网址中的条款。#include "opencv2/calib3d.hpp" // 包含用于相机标定和三维重建的函数和类
#include "opencv2/highgui.hpp"  // 包含GUI功能,如显示和保存图像,处理鼠标事件等。
#include "opencv2/imgproc.hpp"  // 包含图像处理的功能如滤波,几何变换等。#include <vector> // 包含标准模板库的vector容器
#include <iostream> // 包含标准输入输出库using namespace cv; // 使用OpenCV命名空间中的所有成员// 主函数,程序的入口点
int main(int argc, char** argv) {std::string img_name1, img_name2; // 声明两个string变量,用来存放两幅图像的名字if (argc < 3) { // 如果命令行参数少于3个(包括程序本身的名称)CV_Error(Error::StsBadArg, // 抛出一个错误"Path to two images \nFor example: " // 并显示错误信息"./epipolar_lines img1.jpg img2.jpg");} else {img_name1 = argv[1]; // 把第一个图像的文件名赋给img_name1img_name2 = argv[2]; // 把第二个图像的文件名赋给img_name2}Mat image1 = imread(img_name1); // 读取第一幅图像Mat image2 = imread(img_name2); // 读取第二幅图像Mat descriptors1, descriptors2; // 定义两个Mat对象,用来存放两组特征描述std::vector<KeyPoint> keypoints1, keypoints2; // 定义两个 KeyPoint 类型的向量,用来存储两组关键点Ptr<SIFT> detector = SIFT::create(); // 创建SIFT检测器detector->detect(image1, keypoints1); // 在第一幅图像中检测关键点detector->detect(image2, keypoints2); // 在第二幅图像中检测关键点detector->compute(image1, keypoints1, descriptors1); // 计算第一幅图像中关键点的特征描述detector->compute(image2, keypoints2, descriptors2); // 计算第二幅图像中关键点的特征描述FlannBasedMatcher matcher(makePtr<flann::KDTreeIndexParams>(5), makePtr<flann::SearchParams>(32)); // 使用基于FLANN的匹配器,初始化为带有两个参数的构造函数// 获取k=2个最佳匹配以便可以应用由D.Lowe解释的比率测试std::vector<std::vector<DMatch>> matches_vector; // 定义一个DMatch向量的向量,用来存放匹配对matcher.knnMatch(descriptors1, descriptors2, matches_vector, 2); // 在两组描述子之间进行k邻近匹配std::vector<Point2d> pts1, pts2; // 定义两个Point2d向量,用于存放匹配点的坐标pts1.reserve(matches_vector.size()); // 为pts1预留足够大小的内存空间pts2.reserve(matches_vector.size()); // 为pts2预留足够大小的内存空间for (const auto &m : matches_vector) { // 遍历所有的匹配对// 对最佳匹配和次佳匹配应用Lowe比率测试if (m[0].distance / m[1].distance < 0.75) { // 如果最佳匹配与次佳匹配的距离比小于0.75pts1.emplace_back(keypoints1[m[0].queryIdx].pt); // 把第一幅图像中的匹配点坐标添加到pts1pts2.emplace_back(keypoints2[m[0].trainIdx].pt); // 把第二幅图像中的匹配点坐标添加到pts2}}std::cout << "Number of points " << pts1.size() << '\n'; // 输出匹配点的数量Mat inliers; // 定义一个Mat对象,去存放内点const auto begin_time = std::chrono::steady_clock::now(); // 记录RANSAC寻找基础矩阵开始的时间const Mat F = findFundamentalMat(pts1, pts2, RANSAC, 1., 0.99, 2000, inliers); // 使用RANSAC算法计算基础矩阵// 输出RANSAC算法寻找基础矩阵所花时间std::cout << "RANSAC fundamental matrix time " << static_cast<int>(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - begin_time).count()) << "\n";Mat points1 = Mat((int)pts1.size(), 2, CV_64F, pts1.data()); // 将pts1中的点转换为Mat格式用于进一步计算Mat points2 = Mat((int)pts2.size(), 2, CV_64F, pts2.data()); // 将pts2中的点转换为Mat格式用于进一步计算vconcat(points1.t(), Mat::ones(1, points1.rows, points1.type()), points1); // 把points1转置并添加一行1,生成齐次坐标vconcat(points2.t(), Mat::ones(1, points2.rows, points2.type()), points2); // 把points2转置并添加一行1,生成齐次坐标RNG rng; // 随机数生成器const int circle_sz = 3, line_sz = 1, max_lines = 300; // 定义绘制圆的大小,线的大小,以及最大线的数量std::vector<int> pts_shuffle (points1.cols); // 创建一个向量用来存放随机顺序的点的索引for (int i = 0; i < points1.cols; i++) // 初始化pts_shuffle向量pts_shuffle[i] = i;randShuffle(pts_shuffle); // 对点的索引进行随机排序int plot_lines = 0, num_inliers = 0; // 记录绘制的直线数和内点数double mean_err = 0; // 记录点到对极线的平均距离// 遍历点,绘制对极线和点,计算平均距离for (int pt : pts_shuffle) { // 遍历随机顺序的点if (inliers.at<uchar>(pt)) { // 如果是内点const Scalar col (rng.uniform(0,256), rng.uniform(0,256), rng.uniform(0,256)); // 随机生成颜色const Mat l2 = F     * points1.col(pt); // 用基础矩阵F和齐次坐标,计算第二个图像中的对极线const Mat l1 = F.t() * points2.col(pt); // 用基础矩阵F的转置和齐次坐标,计算第一个图像中的对极线// 提取对极线的参数double a1 = l1.at<double>(0), b1 = l1.at<double>(1), c1 = l1.at<double>(2);double a2 = l2.at<double>(0), b2 = l2.at<double>(1), c2 = l2.at<double>(2);// 对极线参数进行归一化const double mag1 = sqrt(a1*a1 + b1*b1), mag2 = (a2*a2 + b2*b2);a1 /= mag1; b1 /= mag1; c1 /= mag1; a2 /= mag2; b2 /= mag2; c2 /= mag2;// 如果没有绘制太多的线,就绘制对极线if (plot_lines++ < max_lines) {// 在第一个图像上绘制对极线line(image1, Point2d(0, -c1/b1),Point2d((double)image1.cols, -(a1*image1.cols+c1)/b1), col, line_sz);// 在第二个图像上绘制对极线line(image2, Point2d(0, -c2/b2),Point2d((double)image2.cols, -(a2*image2.cols+c2)/b2), col, line_sz);}// 在两幅图像上绘制内点circle (image1, pts1[pt], circle_sz, col, -1);circle (image2, pts2[pt], circle_sz, col, -1);// 计算点到对极线的平均距离mean_err += (fabs(points1.col(pt).dot(l2)) / mag2 + fabs(points2.col(pt).dot(l1) / mag1)) / 2;num_inliers++; // 内点数自增}}// 输出内点到对极线的平均距离和内点数std::cout << "Mean distance from tentative inliers to epipolar lines " << mean_err/num_inliers<< " number of inliers " << num_inliers << "\n";// 将两幅图像横向拼接hconcat(image1, image2, image1);const int new_img_size = 1200 * 800; // 定义新图像的大小// 调整图像大小,并保持原始的宽高比resize(image1, image1, Size((int) sqrt ((double) image1.cols * new_img_size / image1.rows),(int)sqrt ((double) image1.rows * new_img_size / image1.cols)));// 创建一个窗口并显示处理后的图像imshow("epipolar lines, image 1, 2", image1);imwrite("epipolar_lines.png", image1); // 保存图像到文件waitKey(0); // 等待用户按键
}

6b81b79652567269704abe5b24b0f130.png

FlannBasedMatcher matcher(makePtr<flann::KDTreeIndexParams>(5), makePtr<flann::SearchParams>(32)); // 使用基于FLANN的匹配器,初始化为带有两个参数的构造函数

ae1b6bb102ddd609d19edaf2573a0b4d.png

pts1.reserve(matches_vector.size());

1dbaebb1ee3d7cc741f4f0fae46a873b.png

const Mat F = findFundamentalMat(pts1, pts2, RANSAC, 1., 0.99, 2000, inliers);

35d296d062b2550995ccf62df6754c68.png

const auto begin_time = std::chrono::steady_clock::now(); 
std::cout << "RANSAC fundamental matrix time " << static_cast<int>(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - begin_time).count()) << "\n";

645f9051b9432703581dbeab9c3e2c4e.png

对极线的绘制:

for (int pt : pts_shuffle) {if (inliers.at<uchar>(pt)) {const Scalar col (rng.uniform(0,256), rng.uniform(0,256), rng.uniform(0,256));const Mat l2 = F     * points1.col(pt);const Mat l1 = F.t() * points2.col(pt);double a1 = l1.at<double>(0), b1 = l1.at<double>(1), c1 = l1.at<double>(2);double a2 = l2.at<double>(0), b2 = l2.at<double>(1), c2 = l2.at<double>(2);const double mag1 = sqrt(a1*a1 + b1*b1), mag2 = (a2*a2 + b2*b2);a1 /= mag1; b1 /= mag1; c1 /= mag1; a2 /= mag2; b2 /= mag2; c2 /= mag2;if (plot_lines++ < max_lines) {line(image1, Point2d(0, -c1/b1),Point2d((double)image1.cols, -(a1*image1.cols+c1)/b1), col, line_sz);line(image2, Point2d(0, -c2/b2),Point2d((double)image2.cols, -(a2*image2.cols+c2)/b2), col, line_sz);}circle (image1, pts1[pt], circle_sz, col, -1);circle (image2, pts2[pt], circle_sz, col, -1);mean_err += (fabs(points1.col(pt).dot(l2)) / mag2 + fabs(points2.col(pt).dot(l1) / mag1)) / 2;num_inliers++;}
}

1d4c49ac7ebbec1ad6a2a138b5a64c24.png

The End

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

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

相关文章

Python中大的一把锁

今天可以来讲解下GIL是个什么了。 GIL为什么是Python中大的一把锁&#xff1f; GIL是Global Interpreter Lock的缩写&#xff0c;翻译过来就是全局解释器锁。 从字面上去理解&#xff0c;它就是锁在解释器头上的一把锁&#xff0c;它使Python代码运行变得有序。 假如有一段…

基于FPGA轻松玩转AI

启动人工智能应用从来没有像现在这样容易&#xff01;受益于像Xilinx Zynq UltraScale MPSoC 这样的FPGA&#xff0c;AI现在也可以离线使用或在边缘部署、使用.可用于开发和部署用于实时推理的机器学习应用&#xff0c;因此将AI集成到应用中变得轻而易举。图像检测或分类、模式…

关于hive启动的相关问题记录

问题&#xff1a;初始化hive元数据报错 [atguiguhadoop102 software]$ schematool -initSchema -dbType mysql -verboseError: Table CTLGS already exists (state42S01,code1050) Closing: 0: jdbc:mysql://hadoop102:3306/metastore?useSSLfalse org.apache.hadoop.hive.me…

基于GAN的多变量时间序列污染训练集异常检测

论文地址&#xff1a;https://ieeexplore.ieee.org/document/9618824 论文源码&#xff1a;https://github.com/sxxmason/FGANomaly 期刊&#xff1a;IEEE Transactions on Knowledge and Data Engineering 多元时间序列异常检测在结构健康监测、智能运维、量化交易等诸多实际…

【Locust分布式压力测试】

Locust分布式压力测试 https://docs.locust.io/en/stable/running-distributed.html Distributed load generation A single process running Locust can simulate a reasonably high throughput. For a simple test plan and small payloads it can make more than a thousan…

推荐学习什么编程语言?

选择编程语言学习时&#xff0c;除了就业因素外&#xff0c;还可以考虑以下几个方面来决定学习哪些编程语言&#xff1a; 个人兴趣与目标&#xff1a;如果你对某个特定领域感兴趣&#xff0c;比如游戏开发、数据分析、人工智能等&#xff0c;可以选择与该领域紧密相关的编程语言…

MySql数据库从0-1学习-第三天多表设计学习

项目开发中,在进行数据库表结构设计时,会根据业务需求及业务模块之间的关系,分析并设计表结构,由于业务之间相互关联,所以各个表结构之间也存在着各种联系,基本上分为三种: 一对多(多对一)多对多一对一 一对多 需求:根据需求,完成部门和员工表的设计 一对多,很多人会使用外键,…

记录我第一场面了40min+的面试

中冶赛迪信息技术(重庆)有限公司 国企 首先3/24投递的&#xff0c;4/10打了电话问是否接受劳务派遣&#xff0c;我当时不知道劳务派遣什么意思&#xff0c;问了和售前售后是不是类似&#xff0c;得到了不大一样的回答&#xff0c;后面加了微信&#xff0c;定了11开始面试。 这…

【Ubuntu】 Github Readme导入GIF

1.工具安装 我们使用 ffmpeg 软件来完成转换工作1.1 安装命令 sudo add-apt-repository ppa:jonathonf/ffmpeg-3sudo apt-get updatesudo apt-get install ffmpeg1.2 转换命令 &#xff08;1&#xff09;直接转换命令&#xff1a; ffmpeg -i out.mp4 out.gif(2) 带参数命令&…

uos安装lxml避坑记录

环境&#xff1a;紫光电脑uos系统 python&#xff1a;系统自带3.7.3 条件&#xff1a;已打开开发者模式&#xff0c;可以自行安装应用商店之外的软件 一、pip3 install lxml4.8.0可以正正常下载&#xff0c;但出现如下错误 另&#xff1a;为什么是4.8.0&#xff1f;因为这个…

【前缀合】Leetcode 寻找数组的中心下标

题目解析 724. 寻找数组的中心下标 算法讲解 我们使用一个前缀和数组&#xff0c;一个后缀和数组 class Solution { public:int pivotIndex(vector<int>& nums) {// lsum[i] 表⽰&#xff1a;[0, i - 1] 区间所有元素的和// rsum[i] 表⽰&#xff1a;[i 1, n - …

计算机网络----第七天

交换机与路由器 路由器的作用&#xff1a; 作用&#xff1a;寻IP地址 路由转发 隔离广播域 交换和维护路由信息 路由器的特点&#xff1a; 特点&#xff1a;位于网络层 转发流量 实现从源地址到目的地址转发 支持丰富的接口类型 支持多种路由协议 支持数据链路层协议 交换…

机器学习——模型融合:平均法

机器学习——模型融合&#xff1a;平均法 在机器学习领域&#xff0c;模型融合是一种通过结合多个基本模型的预测结果来提高整体模型性能的技术。模型融合技术通常能够降低预测的方差&#xff0c;提高模型的鲁棒性&#xff0c;并在一定程度上提高预测的准确性。本文将重点介绍…

pyside6的QSpinBox自定义特性初步研究(二)

当前的需求是&#xff0c;蓝色背景的画面&#xff0c;需要一个相对应色系的QSpinBox部件。已有的部件风格是这样的&#xff0c;需要新的部件与之般配。 首先新建一个QDoubleSpinBox&#xff0c;并定义其背景色和边框&#xff1a; QDoubleSpinBox { color: white; border:1px…

【精选】发布应用到应用商店的基本介绍

摘要 本文旨在介绍如何在各大应用商店发布应用&#xff0c;包括市场选择、准备材料、上架步骤以及常见被拒原因及解决方法。通过详细的步骤和经验分享&#xff0c;帮助开发者顺利将应用推向市场。 引言 随着移动应用市场的不断发展&#xff0c;越来越多的开发者希望将他们的…

VScode代码查找、替换

VScode代码查找、替换 快捷方法按CtrlF &#xff08;Mac为CommandF&#xff09; 右上角出现的框就是查找框&#xff0c;可以输入想找的内容 点击左边的小尖儿&#xff0c;输入替换的内容后 按回车是替换一个&#xff0c;按Ctrl回车&#xff08;Command回车&#xff09;是全替换…

Vue通过自定义指令实现元素平滑上升的动画效果(可以自定义动画时间、动画效果、动画速度等等)。

1、演示 2、介绍 这个指令不是原生自带的&#xff0c;需要手动去书写&#xff0c;但是这辈子只需要编写这一次就好了&#xff0c;后边可以反复利用。 3、关键API IntersectionObserver IntersectionObserver 是一个用于监测元素是否进入或离开视口&#xff08;viewport&#x…

【Java】Java使用Swing实现一个模拟计算器(有源码)

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 今天翻了翻之前写的代码&#xff0c;发现自己之前还写了一个计算器&#xff0c;今天把我之前写的代码分享出来。 我记得那会儿刚学不会写&#xff0c;写的乱七八糟&#xff0c;但拿来当期末作业还是不错的哈哈。 直接上…

学透Spring Boot — 004. Spring Boot Starter机制和自动配置机制

如果你项目中一直用的是 Spring Boot&#xff0c;那么恭喜你没有经历过用 Spring 手动集成其它框架的痛苦。 都说 Spring Boot 大大简化了 Spring 框架开发 Web 应用的难度&#xff0c;这里我们通过配置 Hibernate 的两种方式来深刻体会这一点&#xff1a; 使用 Spring 框架集…

golang的引用和非引用总结

目录 概述 一、基本概念 指针类型&#xff08;Pointer type&#xff09; 非引用类型&#xff08;值类型&#xff09; 引用类型&#xff08;Reference Types&#xff09; 解引用&#xff08;dereference&#xff09; 二、引用类型和非引用类型的区别 三、golang数据类型…