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

前文:
1 基于SIFT图像特征识别的匹配方法比较与实现
2 OpenCV实现的F矩阵+RANSAC原理与实践

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.rhkb.cn/news/144447.html

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

相关文章

87、Redis 的 value 所支持的数据类型(String、List、Set、Zset、Hash)---->List相关命令

本次讲解要点&#xff1a; List相关命令&#xff1a;是指value中的数据类型 启动redis服务器&#xff1a; 打开小黑窗&#xff1a; C:\Users\JH>e: E:>cd E:\install\Redis6.0\Redis-x64-6.0.14\bin E:\install\Redis6.0\Redis-x64-6.0.14\bin>redis-server.exe redi…

Java编码技巧:验证码

目录 1.1、EasyCaptcha&#xff08;优选&#xff0c;支持种类多&#xff0c;样式多&#xff0c;使用简单&#xff09;1.1.1、作用1.1.2、官方信息1.1.3、使用案例1.1.4、依赖1.1.5、代码1.1.6、效果1.1.7、拓展 1.2、kaptcha1.2.1、作用1.2.2、官方信息1.2.3、使用案例1.2.4、依…

DE0开发板交通灯十字路口红绿灯VHDL

名称&#xff1a;基于DE0开发板的交通灯十字路口红绿灯 软件&#xff1a;Quartus 语言&#xff1a;VHDL 要求&#xff1a; 设计一个十字路口交通信号灯的控制电路。分为两种情况&#xff0c;正常状态和报警状态。 1.正常状态&#xff1a;要求红、绿灯按一定的规律亮和灭&a…

触觉智能 PurPle Pi OH(OpenHarmony)开发板

资料汇总 内容预览 产品介绍 PurPle-Pi OH 规格书​​​​​​ 系统编译 Purple-Pi-OH Linux SDK编译 Purple-Pi-OH OHOS SDK编译 使用手册 Purple-Pi-OH Ubuntu系统使用手册 常见FAQ 常见问题 官网 官网地址 Purple Pi OH介绍 Purple Pi OH作为一款兼容树莓派的开…

多个pdf合并成一个文件,3个方法合并pdf

如何把多个pdf合并成一个文件&#xff1f;在我们日常的工作中&#xff0c;经常会遇到一些需要处理的文件&#xff0c;其中包括PDF文件。特别是当我们需要将多个PDF文件合并成一个PDF文件时&#xff0c;会面临一些困难。这样的情况下&#xff0c;我们的阅读能力会受到限制&#…

软件测试中的测试工具和自动化测试

1. 测试工具 测试工具也分为不同人员使用的 开发人员&#xff1a;测试框架&#xff0c;编写测试用例&#xff1b;各类线上dump分析工具如windgb&#xff1b;开发时的集成IDE工具如Visual Studio&#xff0c;idea等等 面向不同测试需求的测试工具 软件测试是软件开发生命周期…

手摸手带你 在Windows系统中安装Istio

Istio简介 通过负载均衡、服务间的身份验证、监控等方法&#xff0c;Istio 可以轻松地创建一个已经部署了服务的网络&#xff0c;而服务的代码只需很少更改甚至无需更改。 通过在整个环境中部署一个特殊的 sidecar 代理为服务添加 Istio 的支持&#xff0c;而代理会拦截微服务…

Multisim14.0仿真(二十七)基于UC3842的反激式开关电源的设计及仿真

一、UC3842简介&#xff1a; UC3842为固定频率电流模式PWM控制器。它们是专门为OFF−线和直流到直流转换器应用与最小的外部组件。内部实现的电路包括用于精确占空比控制的修剪振荡器、温度补偿参考、高增益误差放大器、电流传感比较器和理想适合于驱动功率MOSFET的高电流温度极…

MySQL学习笔记24

MySQL的物理备份&#xff1a; xtrabackup备份介绍&#xff1a; xtrabackup优缺点&#xff1a; 优点&#xff1a; 1、备份过程快速、可靠&#xff08;因为是物理备份&#xff09;&#xff1b;直接拷贝物理文件。 2、支持增量备份&#xff0c;更为灵活&#xff1b; 3、备份…

第十四届蓝桥杯大赛软件赛决赛 C/C++ 大学 B 组 试题 C: 班级活动

[蓝桥杯 2023 国 B] 班级活动 【问题描述】 小明的老师准备组织一次班级活动。班上一共有 n n n 名&#xff08; n n n 为偶数&#xff09;同学&#xff0c;老师想把所有的同学进行分组&#xff0c;每两名同学一组。为了公平&#xff0c;老师给每名同学随机分配了一个 n n …

Eclipse环境基于HDFS的API进行开发

文章目录 IOUtils方式读取文件1.文件准备2.下载安装Eclipse3.打开eclipse&#xff0c;新建java项目&#xff0c;添加关于hadoop的一些包4.包内新建类进行开发5.利用打包的方式生成java jar包6.验证代码正确性 其它问题&#xff1a;Exception in thread “main“ java.lang.Unsu…

【前端】ECMAScript6从入门到进阶

【前端】ECMAScript6从入门到进阶 1.ES6简介及环境搭建 1.1.ECMAScript 6简介 &#xff08;1&#xff09;ECMAScript 6是什么 ECMAScript 6.0&#xff08;以下简称 ES6&#xff09;是 JavaScript 语言的下一代标准&#xff0c;已经在2015年6月正式发布了。它的目标&#xff…

基于微信小程序的刷题考试系统设计与实现(适用于各类考试类、答题类程序)

文章目录 前言系统主要功能&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计…

【IDEA】使用idea调试时查看对象集合的值

1、在实体类上添加toString方法 2、在要查看集合的地方右键View as→toString 3、View Text复制对象集合的值 4、复制map集合的值同理

[杂谈]-ESP32中的无线通信协议

ESP32中的无线通信协议 文章目录 ESP32中的无线通信协议1、ESP32 无线通信协议简介2、Bluetooth Low Energy (BLE)3、**Bluetooth Classic**4、**ESP-NOW**5、Wi-Fi&#xff08;客户端-服务器通信协议&#xff09;6、MQTT7、**LoRa**8、**GSM/GPRS/LTE**9、总结 ESP32是一个基于…

如何使用ArcGIS Pro直接获取道路中心线

以前使用ArcGIS获取道路中心线&#xff0c;需要先将面要素转换为栅格再获取中心线&#xff0c;现在我们可以通过ArcGIS Pro直接获取道路中心线&#xff0c;这里为大家介绍一下获取方法&#xff0c;希望能对你有所帮助。 新建地理数据库 在存储数据的文件夹上点击右键&#xff…

专栏更新情况:华为流程、产品经理、战略管理、IPD

目录 前言 01 华为流程体系入门课 CSDN学院 02 产品经理进阶课 CSDN学院 03 BLM 战略方法论进阶课 04 IPD 进阶 100 例专栏 作者简介 前言 已上线四大课程专栏更新情况&#xff1a; 01 华为流程体系入门课&#xff08;视频图文&#xff09;&#xff1b; 02 硬件产品经…

Linux文件查找,别名,用户组综合练习

1.文件查看: 查看/etc/passwd文件的第5行 [rootserver ~]# head -5 /etc/passwd root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin adm:x:3:4:adm:/var/adm:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologi…

使用YOLOv5的backbone网络识别图像天气 - P9

目录 环境步骤环境设置包引用声明一个全局的设备 数据准备收集数据集信息构建数据集在数据集中读取分类名称划分训练、测试数据集数据集划分批次 模型设计编写维持卷积前后图像大小不变的padding计算函数编写YOLOv5中使用的卷积模块编写YOLOv5中使用的Bottleneck模块编写YOLOv5…

信息安全:网络物理隔离技术原理与应用.

信息安全&#xff1a;网络物理隔离技术原理与应用. 随着网络攻击技术不断增强&#xff0c;恶意入侵内部网络的风险性也相应急剧提高。满足内外网信息及数据交换需求&#xff0c;又能防止网络安全事件出现的安全技术就应运而生了&#xff0c;这种技术称为“物理隔离技术” 基本原…