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

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_name1
       img_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.75
            pts1.emplace_back(keypoints1[m[0].queryIdx].pt); // 把第一幅图像中的匹配点坐标添加到pts1
            pts2.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

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

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

相关文章

Python学习笔记15 - 字符串

字符串是一个不可变的字符序列&#xff0c;另一个不可变的序列是元组 字符串的驻留机制 字符串的常用操作 字符串的查询 字符串的大小写转换 字符串内容 对齐操作的方法 字符串的劈分操作 字符串的判断 字符串替换 字符串合并 字符串的比较 字符串的切片 格式化字符串 字符串…

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之三 简单动态聚光灯效果

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之三 简单动态聚光灯效果 目录 Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之三 简单动态聚光灯效果 一、简单介绍 二、简单动态聚光灯效果实现原理 三、简单动态聚光灯效果…

JVM、maven、Nexus

一、jvm简介 1.应用程序申请内存时出现的三种情况&#xff1a; ①OOM:内存溢出&#xff0c;是指应用系统中存在无法回收的内存或使用的内存过多&#xff0c;最终使得程序运行要用到的内存大于能提供的最大内存。此时程序就运行不了&#xff0c;系统会提示内存溢出&#xff0c…

GPT4.5发布了?OpenAI终于发布正式版Turbo,重回AI王座第一

令人惊讶的是&#xff0c;短短三个月内&#xff0c;全球最强AI的称号又一次易主了&#xff01;几个月前&#xff0c;Claude3 Opus的性能全面超过了GPT-4&#xff0c;全球网友纷纷转向Claude3&#xff0c;并分享了他们对Claude3的惊艳体验。然而&#xff0c;OpenAI最近再次展示了…

Nginx健康检查

Nginx健康检查nginx_upstream_check_module nginx健康检查介绍: ​ 主动健康检查&#xff0c;nignx定时主动地去ping后端的服务列表&#xff0c;当发现某服务出现异常时&#xff0c;把该服务从健康列表中移除&#xff0c;当发现某服务恢复时&#xff0c;又能够将该服务加回健…

网格(mesh)生成算法

mesh网格生成算法 1. 简介2. Points clouds Vs Meshes3. 为什么要生成mesh网格以及生成mesh网格的难点4. 常见的mesh网格生成算法 1. 简介 基于 3D视觉的新兴应用场景蓬勃发展&#xff0c;3D点云越来越受到人们的广泛关注。点云有着广泛的应用领域包括机器人技术、3D图形、自动…

多模态 ——LLaVA 集成先进图像理解与自然语言交互GPT-4的大模型

概述 提出了一种大型模型 LLaVA&#xff0c;它使用 GPT-4 生成多模态语言图像指令跟随数据&#xff0c;并利用该数据将视觉和语言理解融为一体。初步实验表明&#xff0c;LLaVA 展示了出色的多模态聊天能力&#xff0c;在合成多模态指令上的表现优于 GPT-4。 在科学质量保证中…

Java中volatile关键字

保证了不同线程对这个变量进行操作时的可见性&#xff0c;即一个线程修改了某个变量的值&#xff0c;这新值对其他线程来说是立即可见的,volatile关键字会强制将修改的值立即写入主存。 1.volatile的可见性 一个典型的例子&#xff1a;永不停止的循环。 public class Forever…

全网最好的JVM总结:有生命周期的JVM

1.编译 1.1 java中编译器有哪些&#xff1f; 前端编译器 javac后台即时编译器 JIT编译器静态提前编译器 &#xff08;一步到位&#xff0c;直接把java编译成二进制&#xff09; 2.2 编译过程是怎么样&#xff1f; 解析与填充符号表&#xff0c;生成语法树 &#xff08;编译…

设计模式学习笔记 - 设计模式与范式 -行为型:17.中介模式:什么时候用中介模式?什么时候用观察者模式?

概述 本章学习 23 种经典设计模式中的最后一个设计模式&#xff0c;中介模式。和之前讲过的命令模式、解释器模式类似&#xff0c;中介模式也不怎么常用&#xff0c;应用场景比较特殊、有限&#xff0c;但是&#xff0c;跟它俩不同的是&#xff0c;中介模式理解起来并不难&…

Linux部署自动化运维平台Spug

文章目录 前言1. Docker安装Spug2 . 本地访问测试3. Linux 安装cpolar4. 配置Spug公网访问地址5. 公网远程访问Spug管理界面6. 固定Spug公网地址 前言 Spug 面向中小型企业设计的轻量级无 Agent 的自动化运维平台&#xff0c;整合了主机管理、主机批量执行、主机在线终端、文件…

【深度学习】多层感知机与卷积神经网络解析

引言&#xff1a; 在人工智能的宏伟画卷中&#xff0c;深度学习如同一笔瑰丽而深邃的色彩&#xff0c;为这幅画增添了无限的生命力和潜能。作为支撑这一领域核心技术的基石&#xff0c;多层感知机&#xff08;MLP&#xff09;和卷积神经网络&#xff08;CNN&#xff09;在模仿人…

文心一言 VS 讯飞星火 VS chatgpt (236)-- 算法导论17.3 2题

二、使用势能法重做练习17.1-3。练习17.1-3的内容是&#xff1a;假定我们对一个数据结构执行一个由 n 个操作组成的操作序列&#xff0c;当 i 严格为 2 的幂时第 i 个操作的代价为 i &#xff0c;否则代价为1。使用聚合分析确定每个操作的摊还代价。如果要写代码&#xff0c;请…

【漏洞复现】泛微E-Mobile 6.0 client.do存在命令执行漏洞

0x01 阅读须知 “如棠安全的技术文章仅供参考&#xff0c;此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等&#xff08;包括但不限于&#xff09;进行检测或维护参考&#xff0c;未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供…

关于Salesforce DevOps的理解

“DevOps”是一组结合了软件开发 &#xff08;Dev&#xff09; 和运营 &#xff08;Ops&#xff09; 的实践&#xff0c;可帮助团队更快、更可靠地构建、测试和发布软件。 DevOps 的核心理念包括持续集成&#xff08;Continuous Integration&#xff09;、持续交付&#xff08;…

Docker+Nginx部署vue项目

这篇文章给大家分享一下如何使用DockerNginx部署前端vue项目。 第一步&#xff1a;创建vue项目 执行这个命令&#xff0c;创建一个vue项目 npm create vue3将vue项目打包 npm run build此时会看到vue工程中生成了一个dist文件&#xff0c;我们将他上传到服务器中。 第二步…

步骤大全:网站建设3个基本流程详解

一.领取一个免费域名和SSL证书&#xff0c;和CDN 1.打开网站链接&#xff1a;https://www.rainyun.com/z22_ 2.在网站主页上&#xff0c;您会看到一个"登陆/注册"的选项。 3.点击"登陆/注册"&#xff0c;然后选择"微信登录"选项。 4.使用您的…

VMware Workstation部署最新版OpenWrt 23.05.3

正文共&#xff1a;1456 字 51 图&#xff0c;预估阅读时间&#xff1a;2 分钟 我们之前介绍了如何在VMware Workstation上安装OpenWrt&#xff08;软路由是啥&#xff1f;OpenWrt又是啥&#xff1f;长啥样&#xff1f;在VMware装一个瞅瞅&#xff09;&#xff0c;也介绍了如何…

LRUCache原理及源码实现

目录 LRUCache简介&#xff1a; LRUCache的实现&#xff1a; LinkedHashMap方法实现&#xff1a; 自己实现链表&#xff1a; 前言&#xff1a; 有需要本文章源码的友友请前往&#xff1a;LRUCache源码 LRUCache简介&#xff1a; LRU是Least Recently Used的缩写&#xf…

扣子Coze插件教程:如何使用Coze IDE创建插件

&#x1f9d9;‍♂️ 诸位好&#xff0c;吾乃斜杠君&#xff0c;编程界之翘楚&#xff0c;代码之大师。算法如流水&#xff0c;逻辑如棋局。 &#x1f4dc; 吾之笔记&#xff0c;内含诸般技术之秘诀。吾欲以此笔记&#xff0c;传授编程之道&#xff0c;助汝解技术难题。 &#…