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=KT∗F∗K
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∗[R∣t]
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)的坐标从图像中提取了颜色信息,然后将颜色信息与三维点坐标关联起来,生成了带有颜色的稀疏点云
并将其存储在 pointCloud
和 pointColors
中;就可以根据需要进一步处理颜色信息
// 获取特征点的颜色信息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 !!!这样才能看到真正的颜色,也可以把点调大一点好看些
![]()


相关文章:

3 OpenCV两张图片实现稀疏点云的生成
前文: 1 基于SIFT图像特征识别的匹配方法比较与实现 2 OpenCV实现的F矩阵RANSAC原理与实践 1 E矩阵 1.1 由F到E E K T ∗ F ∗ K E K^T * F * K EKT∗F∗K E 矩阵可以直接通过之前算好的 F 矩阵与相机内参 K 矩阵获得 Mat E K.t() * F * K;相机内参获得的方式…...
在Springboot项目中使用Redis提供给Lua的脚本
在Springboot项目中使用Redis提供给Lua的脚本 在Spring Boot项目中,你可以使用RedisTemplate来执行Lua脚本。RedisTemplate是Spring Data Redis提供的一个Redis客户端,它可以方便地与Redis进行交互。以下是使用RedisTemplate执行Lua脚本的一般步骤&…...

分类预测 | MATLAB实现NGO-CNN北方苍鹰算法优化卷积神经网络数据分类预测
分类预测 | MATLAB实现NGO-CNN北方苍鹰算法优化卷积神经网络数据分类预测 目录 分类预测 | MATLAB实现NGO-CNN北方苍鹰算法优化卷积神经网络数据分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现NGO-CNN北方苍鹰算法优化卷积神经网络数据分类预测&…...

Linux或Centos查看CPU和内存占用情况_top只能查看对应的命令_如何查看具体进程---linux工作笔记062
一般我们都是用top去查看,但是top查看的结果,不能看出,具体是哪个程序占用的,这就很苦恼.. 其实如果有时间的话,再去专门看一下网络安全和linux脚本以及命令方面的,比较系统的看一下比较好.现在积累的都是工作中用到的,比较零散的知识. 如果用top,比如说这里的java,就只能知道…...
什么是DevOps
文章目录 一、概念二、地位三、目标四、要求五、具体手段 一、概念 是一组过程、方法与系统的统称,有助于打破开发、测试、运维、交付部门之间的壁垒,提高部门间的沟通协助能力。 二、地位 应成为公司的一种理念、文化、哲学。 三、目标 实现更加高…...
力扣每日一题
605. 种花问题 - 力扣(LeetCode) 动态规划 class Solution { public:bool canPlaceFlowers(vector<int>& flowerbed, int n) {int m flowerbed.size();if(1 m)return !flowerbed[0] > n;else if(2 m)return ((!flowerbed[0] &&…...

测试OpenCvSharp库的模板匹配功能
微信公众号“Dotnet讲堂”的文章《c#实现模板匹配,并输出匹配坐标》(参考文献1)中介绍了采用OpenCVSharp库实现模板匹配功能,也即在目标图片中定位指定图片内容的示例,本文参照参考文献1-4,学习并测试OpenC…...

网络编程day04(网络属性函数、广播、组播、TCP并发)
今日任务 对于newfd的话,最好是另存然后传入给分支线程,避免父子线程操作同一个文件描述符 ------------在tcp多线程服务端---------- 如果使用全局变量,或者指针方式间接访问,会导致所有线程共用一份newfd和cin,那么…...

HALCON支持GPU加速的算子有哪些?
参考例程get_operator_info。 get_opencl_operators这里可以查看到所有支持gpu加速的算子。 支持的算子列表: crop_rectangle1,deviation_image,mean_image,points_harris,gray_opening_shape, gray_dilat…...

MacBook Pro 电池电量限制充电怎么设置AlDente Pro for Mac最大充电限制工具
通过充电电量限制工具可以更好的保护MacBook Pro的电池,通过 AlDente Pro 您可以设置电池的最大充电百分比设置为 20% 至 100%,然后,它将保持在所需的电池百分比,然后再次使用电源适配器进行充电。 AlDent…...

毕业设计选题之Java+springboot线上蔬菜销售与配送系统(源码+调试+开题+lw)
💕💕作者:计算机源码社 💕💕个人简介:本人七年开发经验,擅长Java、Python、PHP、.NET、微信小程序、爬虫、大数据等,大家有这一块的问题可以一起交流! 💕&…...
【Leetcode】162.寻找峰值
一、题目 1、题目描述 峰值元素是指其值严格大于左右相邻值的元素。 给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。 你可以假设 nums[-1] = nums[n] = -∞ 。 你必须实现时间复杂度为 O(log n…...

SpringBoot集成MinIO8.0
一、安装MinIO 中文官网地址:https://www.minio.org.cn/download.shtml 官网地址:https://min.io/download 官网有相应的安装命令,可查看 建议引用相应版本的依赖 二、集成SpringBoot 1.引入依赖 <dependency><groupId>io.…...
蓝桥等考Python组别五级007
第一部分:选择题 1、Python L5 (15分) 表达式“not a > 0”等价于下面哪个表达式?( ) a < 0a == 0a <= 0a in 0正确答案:C 2、Python L5 (15分) 执行下面的程序,当用键盘输入10时,输出结果是( )。 n &...
【装机】通过快捷键设置BIOS从U盘启动
当要重装系统的时候,是否会遇到一个问题,进入bios的时候就开始凌乱了,因为不懂得怎么用bios设置u盘启动.不要着急,下面来一波小白装机教程 总的来讲,设置电脑从U盘启动一共有两种方法: 第一种:开机时候按快捷键,然后选择U盘启动第…...

关于操作系统与内核科普
关于操作系统与内核科普 一.什么是操作系统 操作系统是管理计算机硬件与软件资源的计算机程序。它为计算机硬件和软件提供了一种中间层。 操作系统是一种软件,主要目的有三种: 一.管理计算机资源,这些资源包括CPU,内存࿰…...
算法练习3——删除有序数组中的重复项
LeetCode 26 删除有序数组中的重复项 给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。 考虑 nums …...

《YOLOv5:从入门到实战》报错解决 专栏答疑
前言:Hello大家好,我是小哥谈。《YOLOv5:从入门到实战》专栏上线后,部分同学在学习过程中提出了一些问题,笔者相信这些问题其他同学也有可能遇到。为了让大家可以更好地学习本专栏内容,笔者特意推出了该篇专…...
[2023.09.25]:Rust编写基于web_sys的编辑器:输入光标再次定位的小结
前些天,写了探索Rust编写基于web_sys的WebAssembly编辑器:挑战输入光标定位的实践,经过后续的开发检验,我发现了一个问题,就是光标消失了。为了继续输入,用户需要再次使用鼠标点击。现在我已经弄清楚了导致…...

估计、偏差和方差
一、介绍 统计领域为我们提供了很多工具来实现机器学习目标,不仅可以解决训练集上的任务,还可以泛化。基本的概念,例如参数估计、偏差和方差,对于正式地刻画泛化、欠拟合和过拟合都非常有帮助。 二、参数估计 参数估计 是统计学…...

wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...

ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...

ZYNQ学习记录FPGA(一)ZYNQ简介
一、知识准备 1.一些术语,缩写和概念: 1)ZYNQ全称:ZYNQ7000 All Pgrammable SoC 2)SoC:system on chips(片上系统),对比集成电路的SoB(system on board) 3)ARM:处理器…...

Java数组Arrays操作全攻略
Arrays类的概述 Java中的Arrays类位于java.util包中,提供了一系列静态方法用于操作数组(如排序、搜索、填充、比较等)。这些方法适用于基本类型数组和对象数组。 常用成员方法及代码示例 排序(sort) 对数组进行升序…...

高端性能封装正在突破性能壁垒,其芯片集成技术助力人工智能革命。
2024 年,高端封装市场规模为 80 亿美元,预计到 2030 年将超过 280 亿美元,2024-2030 年复合年增长率为 23%。 细分到各个终端市场,最大的高端性能封装市场是“电信和基础设施”,2024 年该市场创造了超过 67% 的收入。…...