OpenCV两张图片实现稀疏点云的生成

1 E矩阵

1.1 由F到E

E = K T ∗ F ∗ K E = K^T * F * K E=KTFK

E 矩阵可以直接通过之前算好的 F 矩阵与相机内参 K 矩阵获得

Mat E = K.t() * F * K;

相机内参获得的方式是一个较为复杂的方式,需要使用棋盘进行定位获得,我们这里直接使用了 OpenMVG 提供的现成的图片和 K 矩阵

1.2 直接使用函数

利用 openCV 提供的 findEssentialMat 函数可以直接得到 E 矩阵

Mat E = findEssentialMat(matchedPoints1, matchedPoints2, K, RANSAC, 0.999, 1.0, inliers);

2 相机姿态恢复

这一步可以使用 SVD 来通过 E 矩阵获取相对旋转矩阵 R平移向量 t

但是OpenCV直接提供了一个非常便捷的函数 —— recoverPose

其接受本质矩阵 E 、特征点的对应关系、相机的内参信息以及输出的相对旋转矩阵 R 和平移向量 t ;它会自动进行 SVD 分解和其他必要的计算,以恢复相对姿态信息

 //相机姿态恢复,求解R,t,投影矩阵Mat R, t;recoverPose(E, inlierPoints1, inlierPoints2, K, R, t);

3 相机投影矩阵

要构建相机的投影矩阵(也称为视图矩阵或外参矩阵),需要将旋转矩阵 R 和平移向量 t 合并到一起,投影矩阵通常表示为 3x4 的矩阵,其中旋转矩阵和平移向量都位于其中的适当位置

通常情况下,投影矩阵的形式如下:
P 1 = K ∗ [ R ∣ t ] P_1 = K * [R | t] P1=K[Rt]

P 2 = K ∗ [ I ∣ 0 ] P_2 = K * [I | 0] P2=K[I∣0]

实现代码如下:

// 创建两个相机的投影矩阵 [R T]
Mat proj1(3, 4, CV_32FC1);
Mat proj2(3, 4, CV_32FC1);// 设置第一个相机的投影矩阵为单位矩阵 [I | 0]
proj1(Range(0, 3), Range(0, 3)) = Mat::eye(3, 3, CV_32FC1);
proj1.col(3) = Mat::zeros(3, 1, CV_32FC1);// 设置第二个相机的投影矩阵为输入的旋转矩阵 R 和平移向量 T
R.convertTo(proj2(Range(0, 3), Range(0, 3)), CV_32FC1);
t.convertTo(proj2.col(3), CV_32FC1);// 转换相机内参矩阵 K 为浮点型
Mat fK;
K.convertTo(fK, CV_32FC1);// 计算投影矩阵 [K * [R|T]]
proj1 = fK * proj1;
proj2 = fK * proj2;

4 三角法得稀疏点云

4.1 三角法计算3D点

对于每对匹配的特征点,可以使用三角法来计算它们的三维坐标;这通常涉及到将两个视角下的像素坐标与相应的投影矩阵相结合,以恢复三维坐标

// 三角法求解稀疏三维点云
Mat point4D_homogeneous(4, inlierPoints1.size(), CV_64F);
triangulatePoints(proj1, proj2, inlierPoints1, inlierPoints2, point4D_homogeneous);

4.2 转换为非齐次坐标

函数 triangulatePoints 得到的 point4D_homogeneous 通常是齐次坐标,需要将它们转换为非齐次坐标,以得到真实的三维点坐标

// 将齐次坐标转换为三维坐标
Mat point3D;
convertPointsFromHomogeneous(point4D_homogeneous.t(), point3D);
cout << point3D << endl;

5 匹配颜色

将颜色信息与点云关联在一起

使用了内点(inliers)的坐标从图像中提取了颜色信息,然后将颜色信息与三维点坐标关联起来,生成了带有颜色的稀疏点云

并将其存储在 pointCloudpointColors 中;就可以根据需要进一步处理颜色信息

 // 获取特征点的颜色信息vector<Vec3b> colors1, colors2; // 颜色信息for (Point2f& inlierPoints : inlierPoints1){int x = cvRound(inlierPoints.x); // 关键点的x坐标int y = cvRound(inlierPoints.y); // 关键点的y坐标Vec3b color = img1.at<Vec3b>(y, x);colors1.push_back(color);}for (Point2f& inlierPoints : inlierPoints2){int x = cvRound(inlierPoints.x); // 关键点的x坐标int y = cvRound(inlierPoints.y); // 关键点的y坐标Vec3b color = img2.at<Vec3b>(y, x);colors2.push_back(color);}// 创建带颜色的点云数据结构vector<Point3f> pointCloud;vector<Vec3b> pointColors;// 关联颜色信息到点云for (int i = 0; i < point3D.rows; ++i){Point3f point = point3D.at<Point3f>(i);Vec3b color1 = colors1[i];Vec3b color2 = colors2[i];// 在这里可以根据需要选择使用哪个颜色,或者进行颜色插值等处理Vec3b finalColor = color1; // 这里示例使用第一个相机的颜色pointCloud.push_back(point);pointColors.push_back(finalColor);}

6 生成 ply 文件

手动输出点云 PLY 文件,并包括了 PLY 文件的头部信息以及点云数据的写入;这是一种创建包含颜色信息的 PLY 文件的有效方法

在 PLY 文件的头部信息中,指定了点的数量以及点的属性,包括点的坐标和颜色通道(蓝色、绿色、红色)然后,你循环遍历点云数据,将点的坐标和颜色信息写入PLY文件

其会生成一个包含点云和颜色信息的PLY文件,可以将其用于保存点云以进行可视化或进一步处理

特别注意:图片是RGB还是BGR的颜色通道,这里是RGB

color[0]color[1]color[2] 分别代表蓝色,绿色,红色通道

// 手动输出点云ply文件
ofstream plyFile(PLY_SAVE_PATH);// ply的头部信息
plyFile << "ply\n";
plyFile << "format ascii 1.0\n";
plyFile << "element vertex " << point3D.rows << "\n";
plyFile << "property float x\n";
plyFile << "property float y\n";
plyFile << "property float z\n";
plyFile << "property uchar blue\n";
plyFile << "property uchar green\n";
plyFile << "property uchar red\n";
plyFile << "end_header\n";// 写入点云数据
for (int i = 0; i < point3D.rows; ++i)
{Vec3b color = pointColors[i];const float* point = point3D.ptr<float>(i);plyFile << point[0] << " " << point[1] << " " << point[2] << " "<< static_cast<int>(color[0]) << " "<< static_cast<int>(color[1]) << " "<< static_cast<int>(color[2]) << endl;
}plyFile.close();   

7 完整测试代码

关于之前的阶段可以查看我之前的文章

// 定义图像文件路径和保存结果的路径
#define IMG_PATH1 "test_img\\images\\100_7105.jpg"
#define IMG_PATH2 "test_img\\images\\100_7106.jpg"
#define PLY_SAVE_PATH "test_img\\results\\output.ply"
#define K_NUM 2905.88, 0, 1416, 0, 2905.88, 1064, 0, 0, 1 // 3*3#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
#include <fstream>using namespace std;
using namespace cv;int main()
{// 阶段一------------------------------------------------------------------------------------// 读取两幅图像Mat img1 = imread(IMG_PATH1);Mat img2 = imread(IMG_PATH2);if (img1.empty() || img2.empty()){cout << "无法读取图像" << endl;return -1;}// 创建SIFT对象Ptr<SIFT> sift = SIFT::create();vector<KeyPoint> keypoints1, keypoints2;Mat descriptors1, descriptors2;// 检测关键点并计算描述子sift->detectAndCompute(img1, noArray(), keypoints1, descriptors1);sift->detectAndCompute(img2, noArray(), keypoints2, descriptors2);// 使用FLANN进行特征匹配FlannBasedMatcher matcher;vector<vector<DMatch>> matches;matcher.knnMatch(descriptors1, descriptors2, matches, 2);vector<DMatch> good_matches;for (int i = 0; i < matches.size(); ++i){const float ratio = 0.7f;if (matches[i][0].distance < ratio * matches[i][1].distance){good_matches.push_back(matches[i][0]);}}// 阶段二------------------------------------------------------------------------------------// 声明用于保存匹配点对的容器vector<Point2f> matchedPoints1, matchedPoints2;for (int i = 0; i < good_matches.size(); ++i){matchedPoints1.push_back(keypoints1[good_matches[i].queryIdx].pt);matchedPoints2.push_back(keypoints2[good_matches[i].trainIdx].pt);}// 进行基本矩阵F的估计并使用RANSAC筛选Mat F;vector<uchar> inliers;F = findFundamentalMat(matchedPoints1, matchedPoints2, inliers, FM_RANSAC);cout << F << endl;vector<Point2f> inlierPoints1;vector<Point2f> inlierPoints2;for (int i = 0; i < inliers.size(); ++i){if (inliers[i]){inlierPoints1.push_back(matchedPoints1[i]);inlierPoints2.push_back(matchedPoints2[i]);}}// 相机内参矩阵KMat K = (Mat_<double>(3, 3) << K_NUM);cout << K << endl;计算本质矩阵E//Mat E = findEssentialMat(matchedPoints1, matchedPoints2, K, RANSAC, 0.999, 1.0, inliers);Mat E = K.t() * F * K;cout << "Essential Matrix (E):" << endl;cout << E << endl;//相机姿态恢复,求解R,t,投影矩阵Mat R, t;recoverPose(E, inlierPoints1, inlierPoints2, K, R, t);cout << "recoverpose" << endl;cout << "R:" << R << endl;cout << "t:" << t << endl;// 创建两个相机的投影矩阵 [R T]Mat proj1(3, 4, CV_32FC1);Mat proj2(3, 4, CV_32FC1);// 设置第一个相机的投影矩阵为单位矩阵 [I | 0]proj1(Range(0, 3), Range(0, 3)) = Mat::eye(3, 3, CV_32FC1);proj1.col(3) = Mat::zeros(3, 1, CV_32FC1);// 设置第二个相机的投影矩阵为输入的旋转矩阵 R 和平移向量 TR.convertTo(proj2(Range(0, 3), Range(0, 3)), CV_32FC1);t.convertTo(proj2.col(3), CV_32FC1);// 转换相机内参矩阵 K 为浮点型Mat fK;K.convertTo(fK, CV_32FC1);// 计算投影矩阵 [K * [R|T]]proj1 = fK * proj1;proj2 = fK * proj2;// 三角法求解稀疏三维点云Mat point4D_homogeneous(4, inlierPoints1.size(), CV_64F);triangulatePoints(proj1, proj2, inlierPoints1, inlierPoints2, point4D_homogeneous);// 将齐次坐标转换为三维坐标Mat point3D;convertPointsFromHomogeneous(point4D_homogeneous.t(), point3D);cout << point3D << endl;// 获取特征点的颜色信息vector<Vec3b> colors1, colors2; // 颜色信息for (Point2f& inlierPoints : inlierPoints1){int x = cvRound(inlierPoints.x); // 关键点的x坐标int y = cvRound(inlierPoints.y); // 关键点的y坐标Vec3b color = img1.at<Vec3b>(y, x);colors1.push_back(color);}for (Point2f& inlierPoints : inlierPoints2){int x = cvRound(inlierPoints.x); // 关键点的x坐标int y = cvRound(inlierPoints.y); // 关键点的y坐标Vec3b color = img2.at<Vec3b>(y, x);colors2.push_back(color);}// 创建带颜色的点云数据结构vector<Point3f> pointCloud;vector<Vec3b> pointColors;// 关联颜色信息到点云for (int i = 0; i < point3D.rows; ++i){Point3f point = point3D.at<Point3f>(i);Vec3b color1 = colors1[i];Vec3b color2 = colors2[i];// 在这里可以根据需要选择使用哪个颜色,或者进行颜色插值等处理Vec3b finalColor = color1; // 这里示例使用第一个相机的颜色pointCloud.push_back(point);pointColors.push_back(finalColor);}// 手动输出点云ply文件ofstream plyFile(PLY_SAVE_PATH);// ply的头部信息plyFile << "ply\n";plyFile << "format ascii 1.0\n";plyFile << "element vertex " << point3D.rows << "\n";plyFile << "property float x\n";plyFile << "property float y\n";plyFile << "property float z\n";plyFile << "property uchar blue\n";plyFile << "property uchar green\n";plyFile << "property uchar red\n";plyFile << "end_header\n";// 写入点云数据for (int i = 0; i < point3D.rows; ++i){Vec3b color = pointColors[i];const float* point = point3D.ptr<float>(i);plyFile << point[0] << " " << point[1] << " " << point[2] << " "<< static_cast<int>(color[0]) << " "<< static_cast<int>(color[1]) << " "<< static_cast<int>(color[2]) << endl;}plyFile.close();   return 0;
}

最终效果:

特别提醒:用 meshlab 打开后记得在右侧的设置框中将 shading 改为None !!!这样才能看到真正的颜色,也可以把点调大一点好看些

image-20230925090744151
image-20230925090905231 100_7103

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

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

相关文章

网络编程-UDP协议(发送数据和接收数据)

需要了解TCP协议的&#xff0c;可以看往期文章 https://blog.csdn.net/weixin_43860634/article/details/133274701 TCP/IP参考模型 通过此图&#xff0c;可以了解UDP所在哪一层级中 代码案例 发送数据 package com.hidata.devops.paas.udp;import java.io.IOException; …

HTML+CSS综合案例二:CSS简介

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title> CSS简介</title><style>h1{color: #33…

Learn Prompt- Midjourney 图片生成:Image Prompts

Prompt 自动生成 前不久&#xff0c;Midjourney 宣布支持图片转 prompt 功能。 原始图片​ blueprint holographic design of futuristic Midlibrary --v 5Prompt 生成​ 直接输入 /describe 指令通过弹出窗口上传图像并发送&#xff0c;Midjourney 会根据该图像生成四种可…

机器学习小白理解之一元线性回归

关于机器学习&#xff0c;百度上一搜一大摞&#xff0c;总之各有各的优劣&#xff0c;有的非常专业&#xff0c;有的看的似懂非懂。我作为一名机器学习的门外汉&#xff0c;为了看懂这些公式和名词真的花了不少时间&#xff0c;还因此去着重学了高数。 不过如果不去看公式&…

渗透测试信息收集方法和工具分享

文章目录 一、域名收集1.OneForAll2.子域名挖掘机3.subdomainsBurte4.ssl证书查询 二、获取真实ip1.17CE2.站长之家ping检测3.如何寻找真实IP4.纯真ip数据库工具5.c段&#xff0c;旁站查询 三、端口扫描1.端口扫描站长工具2.masscan(全端口扫描)nmap扫描3.scanport4.端口表5.利…

短信登录功能如何实现?

简介&#xff1a; 在日常生活中我们登录/注册某些网站/APP是通常可以选择 密码登录和手机号登录。 为什么手机号发送后会有验证码返回呢&#xff1f; 网站如何识别我的验证码是否正确&#xff1f; 如果我的个人网站也想要实现短信登录功能&#xff0c;具体该如何实现&#xff1…

GiliSoft USB Lock v10.5.0 电脑USB设备管控软件

网盘下载 软件功能特性 禁止USB / SD驱动器 禁用从USB / SD磁盘读取&#xff0c;禁用写入USB / SD磁盘&#xff0c;阻止非系统分区。它不允许任何类型的USB / SD驱动器访问您的计算机&#xff0c;除非您授权它或它已在可信设备白名单。 CD锁&#xff0c;块媒体和蓝光光盘 禁用…

混合Rollup:探秘 Metis、Fraxchain、Aztec、Miden和Ola

1. 引言 混合Rollup为新的以太坊L2扩容方案&#xff0c;其分为2大类&#xff1a; 将乐观与ZK技术结合的混合Rollup同时支持公开智能合约 和 私人智能合约 的混合Rollup 本文将重点关注Metis、Fraxchain、Aztec、Miden和Ola这五大项目。 2. 何为混合Rollup&#xff1f; 混合…

ADC数模转化器

简介 • ADC &#xff08; Analog-Digital Converter &#xff09;模拟 - 数字转换器 • ADC 可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量&#xff0c;建立模拟电路到数字电路的桥梁 • 12 位逐次逼近型 ADC &#xff0c; 1us 转换时间 &#xff08;12位:分辨率…

pycharm中配置torch

在控制台cmd中安装好torch后&#xff0c;在pycharm中使用torch&#xff0c;需要进行简单设置即可。 在pycharm中新建一个工程&#xff0c;在file文件中打开setting 在setting中找到project interpreter编译器 找到conda environment的环境配置&#xff0c;设置好相应的目录 新…

1688-阿里巴巴批发网(获取商品的名称,价格,图片)

1688 item_get-获得1688商品详情 为了进行电商平台 的API开发&#xff0c;首先我们需要做下面几件事情。 1&#xff09;开发者注册一个账号 2&#xff09;然后为每个1688 应用注册一个应用程序键&#xff08;App Key) 。 3&#xff09;下载1688 API的SDK并掌握基本的API基础…

VSCode 和 CLion

文章目录 一、VSCode1、文档2、插件3、智能编写4、VSCode 与 C&#xff08;1&#xff09;安装&#xff08;2&#xff09;调试&#xff08;a&#xff09;使用 CMake 进行跨平台编译与调试&#xff08;b&#xff09;launch.json&#xff08;c&#xff09;传参 &#xff08;3&…

MAC word 如何并列排列两张图片

系统&#xff1a;MAC os 参考博客 https://baijiahao.baidu.com/s?id1700824516945958911&wfrspider&forpc 步骤1 新建一个word文档和表格 修改表格属性 去掉自动重调尺寸以适应内容 插入图片 在表格的位置插入对应的图片如下 去除边框 最终结果如下

数据链路层协议

文章目录 数据链路层协议0. 数据链路层解决的问题1. 以太网协议(1) 认识以太网(2) 以太网帧格式<1> 两个核心问题 (3) 认识MAC地址(4) 局域网通信原理(5) MTU<1> 认识MTU<2> MTU对IP协议的影响<3> MTU对UDP协议的影响<4> MTU对TCP协议的影响<…

Unity之Hololens开发如何实现UI交互

一.前言 什么是Hololens? Hololens是由微软开发的一款混合现实头戴式设备,它将虚拟内容与现实世界相结合,为用户提供了沉浸式的AR体验。Hololens通过内置的传感器和摄像头,能够感知用户的环境,并在用户的视野中显示虚拟对象。这使得用户可以与虚拟内容进行互动,将数字信…

PWN基础:从源文件到可执行文件

目录 编译原理 GCC编译过程 Preprocess阶段 File命令 Compile阶段 Assemble阶段 Link阶段 高级语言编写的程序想在操作系统运行&#xff0c;需要被翻译为机器指令&#xff0c;在按照可执行目标文件格式打包并以二进制形式存储在文件中 编译原理 编译器作用&#xff1a;…

蓝桥杯每日一题2023.9.25

4406. 积木画 - AcWing题库 题目描述 分析 在完成此问题前可以先引入一个新的问题 291. 蒙德里安的梦想 - AcWing题库 我们发现16的二进制是 10000 15的二进制是1111 故刚好我们可以从0枚举到1 << n(相当于二的n次方的二进制表示&#xff09; 注&#xff1a;奇数个0…

CMU15-213 课程笔记 04-Floating Point

文章目录 浮点数如何用二进制表示IEEE 浮点数标准IEEE 浮点数实现IEEE 浮点数在内存里 E exp - bias 计算指数M 1.xxx 尾数计算举例&#xff1a;对一个浮点数进行转换一些关于浮点数的计算等等 浮点数如何用二进制表示 计算机内部的浮点数不是这样存在内存里的&#xff08;至…

【Linux学习】03Linux用户和权限

Linux&#xff08;B站黑马&#xff09;学习笔记 01Linux初识与安装 02Linux基础命令 03Linux用户和权限 文章目录 Linux&#xff08;B站黑马&#xff09;学习笔记前言03Linux用户和权限认知root用户root用户&#xff08;超级管理员&#xff09;su和exit命令sudo命令 用户、用户…

Java面试被问了几个简单的问题,却回答的不是很好

作者&#xff1a;逍遥Sean 简介&#xff1a;一个主修Java的Web网站\游戏服务器后端开发者 主页&#xff1a;https://blog.csdn.net/Ureliable 觉得博主文章不错的话&#xff0c;可以三连支持一下~ 如有需要我的支持&#xff0c;请私信或评论留言&#xff01; 前言 前几天参加了…