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

news2024/12/23 12:28:44

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]);
		}
	}

    // 相机内参矩阵K
    Mat 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 和平移向量 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;
    
    // 三角法求解稀疏三维点云
    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.coloradmin.cn/o/1041316.html

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

相关文章

C语言学习(1)—— 环境安装和配置

运行C语言和C程序需要安装MinGW和VSCode。 一. 安装MinGW 1、进入官网下载MinGW&#xff1a;https://sourceforge.net/projects/mingw-w64/files/ 2、解压缩 3、配置环境变量 4、检查是否安装成功 二. 安装VSCode 1、进入官网下载VSCode&#xff1a;https://code.visualstud…

中国制造让苹果跪服,将再增加一家中国高科技供应商

日前产业链人士指出由于京东方的OLED面板有力地制衡韩国面板厂商三星和LGD&#xff0c;促使他们降价&#xff0c;而且技术也不错&#xff0c;因此正计划再引入一家中国OLED面板厂商&#xff0c;以进一步促进OLED面板的竞争。 早期苹果的OLED面板完全由三星供应&#xff0c;由此…

SR800-D 5G工业路由器:将无人驾驶汽车的通信能力提升到极限

​大家好&#xff01;欢迎来到今天星创易联的课堂&#xff0c;我是你们的通信老师&#xff0c;今天我们将讨论无人驾驶解决方案&#xff0c;其中包括SR800-D 5G工业路由器的运用。 首先&#xff0c;让我们聚焦于无人驾驶技术的重要性。无人驾驶汽车正在迅速崛起&#xff0c;这种…

【服务端 | Redis】如何使用redis 有序集合实现股票交易的订单表(价格优先、时间优先)

前两天倒腾redis的有序集合时&#xff0c;自己发现了一个问题&#xff0c;redis的有序集合在score相同的情况 下是如何排序的&#xff1f; 通过谷歌搜索&#xff0c;发现了一些线索&#xff0c;在score相同的情况下&#xff0c;redis使用字典排序&#xff0c;不过不是太明白什…

【红帽】跟着学习如何使用桌面访问命令行

今天我们分享一些红帽Linux的知识&#xff0c;记得关注&#xff0c;会一直更新~ ▶1、以student用户身份并使用student作为密码登录workstation 1.1.在workstation上&#xff0c;从GNOME登录屏幕中单击student用户帐户。系统提示输入密码时&#xff0c;请输入student。 1.2.…

Tuxera NTFS 2022 for Mac破解版百度网盘免费下载安装激活教程

Mac打不开移动硬盘”有多种原因&#xff0c;解决办法也不尽相同。它可能是安装的NTFS for Mac读写软件版本和当前macOS系统的兼容问题、或者是Mac没有正常连接硬盘等等。本篇文章就将为您罗列出导致“Mac打不开移动硬盘”的原因和解决办法。 为此不得不使用著名的Tuxera NTFS …

深入理解传输层协议:TCP与UDP的比较与应用

目录 前言什么是TCP/UDPTCP/UDP应用TCP和UDP的对比总结 前言 传输层是TCP/IP协议栈中的第四层&#xff0c;它为应用程序提供服务&#xff0c;定义了主机应用程序之间端到端的连通性。在本文章&#xff0c;我们将深入探讨传输层协议&#xff0c;特别是TCP和UDP协议的原理和区别…

【PHP手麻系统源码】手术麻醉相关的各项数据的记录、管理和应用

手术室麻醉信息管理系统是定位于手术室和麻醉科的科室级临床信息管理系统&#xff0c;主要用于与手术麻醉相关的各项数据的记录、管理和应用&#xff0c;实现医疗信息的共享及再利用&#xff0c;提高科室的整体信息化水平。   该系统将手术室内的各种设备&#xff08;如呼吸机…

云原生Kubernetes:K8S配置资源管理

目录 一、理论 1.Secret 2.Secret创建 3.Secret使用 4.Configmap 5.Configmap创建 6.Configmap使用 二、实验 1.Secret创建 2.Secret使用 3.Configmap创建 4.Configmap使用 三、问题 1.变量引用生成资源报错 2.查看pod日志失败 3.创建configmap报错 4.YAML创建…

【Redis】Redis做为缓存,MySQL如何与Redis保持数据一致

Redis的作用 一般情况下Redis是用来实现应用和数据库之间的一个读操作的缓存层&#xff0c;主要目的是减少数据库的io&#xff0c;还可以提升数据库io性能 方法一&#xff1a; 先更新MySQL数据库&#xff0c;再删除缓存&#xff0c;再从数据库查询到的最新的数据同步到redis…

二维空间 点绕点旋转公式

记录一下 点绕点旋转公式的推导 点A绕点B逆时针旋转贝塔角度 点A绕点B顺时针旋转贝塔角度 贝塔<阿尔法 点A绕点B顺时针旋转贝塔角度 贝塔>阿尔法

HTML+CSS综合案例一新闻详情

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>新闻详情</title><style>h1{text-align…

【渗透攻防】千变万化的WebShell

前言 WebShell就是以asp、php、jsp或者cgi等网页文件形式存在的一种命令执行环境&#xff0c;也可以将其称做为一种网页后门。本篇文章将带大家学习如何获取WebShell&#xff0c;如何隐藏WebShell&#xff0c;有攻必有防&#xff0c;最后带大家学习查杀WebShell。 目录 第一节…

服务接口调用OpenFeign_超时机制

超时机制 问题&#xff1a; 服务消费者在调用服务提供者的时候发生了阻塞、等待的情形&#xff0c;这个时候&#xff0c;服务消费者会一直等待下去。在某个峰值时刻&#xff0c;大呈的请求都在同时请求服务消费者&#xff0c;会造成线程的大呈堆积&#xff0c;势必会造成雪崩。…

SpringBoot 学习(六)Shiro

6. Shiro 6.1 简介 6.1.1 什么是Shiro Apache Shiro 是一个 Java 的安全权限框架。Shiro 可以应用在 JavaSE 和 JavaEE 环境中。Shiro 可以完成认证、授权、加密、会话管理、web 集成、缓存等。下载地址&#xff1a;http://shiro.apache.org/ 6.1.2 能力 ​ Authenticat…

玄子Share 设计模式 GOF 全23种 + 七大设计原则

玄子Share 设计模式 GOF 全23种 七大设计原则 前言&#xff1a; 此文主要内容为 面向对象七大设计原则&#xff08;OOD Principle&#xff09;GOF&#xff08;Gang Of Four&#xff09;23种设计模式拓展的两个设计模式 简单工厂模式&#xff08;Simple Factory Pattern&#x…

【Spring Boot】实战:实现Session共享

🌿欢迎来到@衍生星球的CSDN博文🌿 🍁本文主要学习实现Session共享 🍁 🌱我是衍生星球,一个从事集成开发的打工人🌱 ⭐️喜欢的朋友可以关注一下🫰🫰🫰,下次更新不迷路⭐️💠作为一名热衷于分享知识的程序员,我乐于在CSDN上与广大开发者交流学习。 💠我…

【LeetCode热题100】--240.搜索二维矩阵II

240.搜索二维矩阵II 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。每列的元素从上到下升序排列。 方法一&#xff1a;使用二分查找 class Solution {public boolean searchMatrix(int[…

做BI智能数据分析,奥威BI系统是专业的

威BI系统是一个全方位智能化、可视化的企业数据分析工具&#xff0c;注重满足企业的实际需求&#xff0c;提供贴合实际的解决方案&#xff0c;旨在帮助企业更好地利用数据&#xff0c;以实现商业价值。 注重企业数据的收集、整合和管理 对于企业来说&#xff0c;数据的来源和…

Mysql备份恢复、与日志管理

Mysql日志管理、备份与恢复 一、Mysql日志管理1.1、日志分类1.1.1、错误日志1.1.2 、通用查询日志1.1.3、 二进制日志1.1.4 、慢查询日志1.1.5 、配置日志 1.2、日志的查询 二、备份与恢复2.1、 数据备份的必要性2.2 、造成数据丢失的原因2.3、 数据库备份的分类2.3.1、 物理备…