SLAM 求解IPC算法

基础知识:方差,协方差,协方差矩阵


方差:描述了一组随机变量的离散程度

        方差= 每个样本值 与 全部样本的平均值 相差的平方和  再求平均数,记作:

        例如:计算数字1-5的方差,如下

去中心化:为了后续计算的方便,会对样本进行去中心化处理,方法是将全部样本按照平均值平移

例如:1-5每个数字都向负方向移动3(平均值)个单位,计算方差后结果依然是2


协方差:协方差描述了不同特征之间的相关情况,通过计算协方差,可以判断不同特征之间的关联关系。协方差=m个样本的(特征a-均值ua )乘以(特征b - 均值 ub)的乘积累加到一起再除以m-1

        例如1:一组数据点(1,1)(2,2)(3,3)(4,4)(5,5)他们的协方差计算如下

        例如2:同理

        例如3:同理

为了更方便的计算协方差,同样的也可以将数据去中心化处理

总之:协方差表示了不同特征之间的相关情况,想个特征值之间的协方差>0,则正相关,<0则负相关,=0则不相关


协方差矩阵:计算了不同维度的协方差,他是一个对对称矩阵,由方差和协方差两部分组成,其中,对角线上的元素是各个随机变量的方差,非对角线上的元素为两两随机变量之间的协方差。

在计算协方差矩阵时,需要将m个样本的特征按照列向量的方式,保存在矩阵中,然后计算矩阵和矩阵转置的乘积,再除以m,得到协方差矩阵

        例如:m个样本,每个样本有a和b两个特征,将这些样本按照列向量的方式,保存到矩阵x中,计算m个样本的协方差矩阵,他等于x乘以x的转置,再除以m。


1.SVD求解ICP方法C++代码展示,总结起来分为3步

#include<iostream>
#include<vector>
#include<eigen>
using namespace std;
//函数用于估计两组三维点集之间的旋转矩阵 R 和平移向量 t
//通过这段代码,可以实现对两组三维点集之间的姿态关系进行估计和计算,其中旋转矩阵R_用于描述旋转关系,平移向量t_用于描述平移关系
void pose_estimation_3d3d(const vector<Point3f>& pts1,
                          const vector<Point3f>& pts2,
                          Mat& R, Mat& t)
{
    // 计算两组三维点的质心
    Point3f p1, p2;
    int N = pts1.size();
    for (int i=0; i<N; i++)
    {
        p1 += pts1[i];
        p2 += pts2[i];
    }
    p1 /= N;
    p2 /= N;

    // 对每个减去质心,得到新的点集q1,q2
    vector<Point3f> q1(N), q2(N);
    for (int i=0; i<N; i++)
    {
        q1[i] = pts1[i] - p1;
        q2[i] = pts2[i] - p2;
    }

    // 计算协方差矩阵3x3 q1*q2^T
    Eigen::Matrix3d W = Eigen::Matrix3d::Zero();
    for (int i=0; i<N; i++)
    {
        W += Eigen::Vector3d(q1[i].x, q1[i].y, q1[i].z) * Eigen::Vector3d(q2[i].x,
                q2[i].y, q2[i].z).transpose();
    }
    cout << "W=" << W << endl;

    // SVD on W  对矩阵 W 进行奇异值分解(SVD)得到 U 和 V 矩阵。
    Eigen::JacobiSVD<Eigen::Matrix3d> svd(W, Eigen::ComputeFullU | Eigen::ComputeFullV);
    Eigen::Matrix3d U = svd.matrixU();
    Eigen::Matrix3d V = svd.matrixV();
    cout << "U=" << U << endl;
    cout << "V=" << V << endl;
    //根据计算出的 U 和 V 矩阵计算旋转矩阵 R 和平移向量 t。
    Eigen::Matrix3d R_ = U * (V.transpose());
    Eigen::Vector3d t_ = Eigen::Vector3d(p1.x, p1.y, p1.z) - R_ * Eigen::Vector3d(p2.x, p2.y, p2.z);//p1 p2分别为两组数据的中心点
    //将计算得到的旋转矩阵 R 和平移向量 t 转换为 OpenCV 的 Mat 类型。
    // convert to cv::Mat
    R = (Mat_<double>(3, 3) <<
            R_(0, 0), R_(0, 1), R_(0,2),
            R_(1, 0), R_(1, 1), R_(1,2),
            R_(2, 0), R_(2, 1), R_(2,2));
    t = (Mat_<double>(3, 1) << t_(0, 0), t_(1, 0), t_(2, 0));
}

经过上面的步骤,其实就可以得到R和T了,但是,这时候就出现了一个问题——结果不准确。在算法实现中,如果出现了求解值不准确的情况,那么一般做法就是——多求几次,也就是迭代!可以参考如下:

  • 从B点云中一一找到A中点的对应距离最近点,构成最近点集C
  • 把C点集存入Eigen矩阵中,和A点云去中心化后,求SVD分解,得到R矩阵和T向量(一个旋转一个平移)
  • 开始迭代,通过R×A+T得到新的点云A1
  • 重新执行1到3步骤,这次是从B中找A1的最近点
  • 求得到的点云An和它的最近点集Cn的平均距离dst,当dst小于设定的阈值时,跳出循环

如果发现还不准确,那么有可能是它的迭代条件——也就是平均距离dst判断出错了,出现这种原因一般就是点云中出现了离散点,导致某两点的距离出现了异常,带动了整个dst判断出错。解决方案如下(很管用):

  • 遍历A点找寻最近点,如果A中的某个点Ai和它的最近点距离大于某个阈值,则剔除,不参与接下来的计算。
  • 从B点云中一一找到A中点的对应距离最近点,构成最近点集C
  • 把C点集存入Eigen矩阵中,和A点云去中心化后,求SVD分解,得到R矩阵和T向量(一个旋转一个平移)
  • 开始迭代,通过R×A+T得到新的点云A1
  • 重新执行1到4,每次执行都要剔除一下离散点。
  • 求得到的点云An和它的最近点集Cn的平均距离dst,当dst小于设定的阈值时,跳出循环

2.非线性优化求解ICP c++代码展示

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/calib3d/calib3d.hpp>
using namespace cv;
 
#include <Eigen/Core>
#include <Eigen/SVD>
#include <Eigen/Dense>

#include <chrono>
#include <sophus/se3.hpp>
 
#include <g2o/core/base_vertex.h>
#include <g2o/core/base_unary_edge.h>
#include <g2o/core/sparse_optimizer.h>
#include <g2o/core/block_solver.h>
#include <g2o/core/solver.h>
#include <g2o/core/optimization_algorithm_gauss_newton.h>
#include <g2o/solvers/dense/linear_solver_dense.h>
using namespace std;
//定义VertexPose顶点              //顶点为6个优化变量,每个类型为SE3d(表示三维空间中的刚体变换,即旋转和平移)
class VertexPose : public g2o::BaseVertex<6, Sophus::SE3d> {
    public:
        EIGEN_MAKE_ALIGNED_OPERATOR_NEW;

        // 设置初始化的更新值 
        virtual void setToOriginImpl() override { 
            _estimate = Sophus::SE3d();
        }

        // left multiplication on SE3
        virtual void oplusImpl(const double *update) {
            Eigen::Matrix<double, 6, 1> update_eigen;//前三个元素表示平移在 x、y、z 轴上的分量,后三个元素表示旋转的绕 x、y、z 轴的旋转量
            update_eigen << update[0], update[1], update[2],
                            update[3], update[4], update[5];
            _estimate = Sophus::SE3d::exp(update_eigen) * _estimate;//exp 将update_eigen向量转换成SE3d 类型的刚体变换
        }

        virtual bool read(std::istream &in) override {return true;}
        
        virtual bool write(std::ostream &out) const override { return true;}
};
//定义边 一元边,连接一个顶点VertexPose ,和一个包含三维向量的观测
class EdgeProjectXYZRGBDPoseOnly : public g2o::BaseUnaryEdge<3, Eigen::Vector3d, bcv::VertexPose> 
{
    public:
        EIGEN_MAKE_ALIGNED_OPERATOR_NEW;

        EdgeProjectXYZRGBDPoseOnly(const Eigen::Vector3d &point) : _point(point) {}

        virtual void computeError() override {
            const VertexPose* p = static_cast<const VertexPose*> (_vertices[0]);
            //真实观测值 _measurement 与 估计观测值 p->estimate() * _point之间的误差
            _error = _measurement - p->estimate() * _point;//将顶点的估计值所代表的变换作用于点 _point,得到的新的位置信息
        }

        //linearizeOplus 函数实现了对雅可比矩阵的线性化操作
        virtual void linearizeOplus() override {
            VertexPose *p = static_cast<VertexPose*> (_vertices[0]);//从图优化中获取与当前边相连的顶点
            Sophus::SE3d T = p->estimate();//获取顶点的估计值(优化变量,用于计算位姿变换)
            Eigen::Vector3d xyz_trans = T * _point;//通过估计的值 计算当其点_point转换后的坐标

            //雅可比矩阵从 (0,0) 开始的 3×3 子矩阵(前三行前三列),设置为负的单位矩阵,表示误差函数对位姿变量的平移部分的导数
            _jacobianOplusXi.block<3, 3>(0, 0) = -Eigen::Matrix3d::Identity();
            //雅可比矩阵的前三行后三列部分,利用 Sophus 库的 hat 操作将向量 xyz_trans 转换为反对称矩阵,通常表示误差函数对位姿变量的旋转部分的导数
            _jacobianOplusXi.block<3, 3>(0, 3) = Sophus::SO3d::hat(xyz_trans);
        }

        bool read(std::istream &in) { return true; }

        bool write(std::ostream &out) const { return true; }

    protected:
        Eigen::Vector3d _point;
};
//定义求解器
void ICPSolver::NLOSolver(std::vector<cv::Point3f> &pts1,
                std::vector<cv::Point3f> &pts2,
                cv::Mat &R, cv::Mat &t)
{
    typedef g2o::BlockSolverX BlockSolverType;//优化问题求解器
    typedef g2o::LinearSolverDense<BlockSolverType::PoseMatrixType> LinearSolverType;//稠密线性方程求解类型
    // new一个 g2o优化器 采用高斯牛顿优化算法
    auto solver = new g2o::OptimizationAlgorithmGaussNewton(
        g2o::make_unique<BlockSolverType>(g2o::make_unique<LinearSolverType>())
    );
    //构建优化问题的图模型
    g2o::SparseOptimizer optimizer; // graph model
    optimizer.setAlgorithm(solver); // set solver
    optimizer.setVerbose(true); // print info

    //添加顶点
    bcv::VertexPose *p = new VertexPose();
    p->setId(0);//顶点id
    p->setEstimate(Sophus::SE3d());//初始估计值
    optimizer.addVertex(p);

    //添加边
    for(size_t i = 0; i < pts1.size(); i++) {
        bcv::EdgeProjectXYZRGBDPoseOnly *e = new bcv::EdgeProjectXYZRGBDPoseOnly(
            Eigen::Vector3d(pts2[i].x, pts2[i].y, pts2[i].z)
        );
        e->setVertex(0, p);//将上一步的顶点设置为边e的第一个顶点,本次只有一个顶点
        e->setMeasurement(Eigen::Vector3d(pts1[i].x, pts1[i].y, pts1[i].z));//设置了边的测量值(实际位置)
        e->setInformation(Eigen::Matrix3d::Identity());//设置边的信息矩阵为单位矩阵,表示边的置信度
        optimizer.addEdge(e);
    }

    auto t1 = std::chrono::system_clock::now();
    optimizer.initializeOptimization();//初始化优化器
    optimizer.optimize(100);//迭代次数
    auto t2 = std::chrono::system_clock::now();
    auto d = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
    std::cout << "duration: " << d << " ms" << std::endl;

    std::cout << "after optim:\n";
    std::cout << "T=\n" << p->estimate().matrix() << std::endl;
    
    Eigen::Matrix3d R_ = p->estimate().rotationMatrix();//estimate()提取估计值,rotationMatrix()提取旋转矩阵
    Eigen::Vector3d t_ = p->estimate().translation();//提取平移向量
    std::cout <<"det(R_)=" << R_.determinant() << std::endl;
    std::cout <<"R_R_^T=" << R_ * R_.transpose() << std::endl;
    std::cout << "R:\n" << R_ << std::endl;
    std::cout << "t:\n" << t_ << std::endl;
    R = (cv::Mat_<double>(3, 3) <<
        R_(0, 0), R_(0, 1), R_(0, 2),
        R_(1, 0), R_(1, 1), R_(1, 2),
        R_(2, 0), R_(2, 1), R_(2, 2)
    );
    t = (cv::Mat_<double>(3, 1) << t_(0, 0), t_(1, 0), t_(2, 0));
}


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

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

相关文章

.NET 异步编程(异步方法、异步委托、CancellationToken、WhenAll、yield)

文章目录 异步方法异步委托async方法缺点CancellationTokenWhenAllyield 异步方法 “异步方法”&#xff1a;用async关键字修饰的方法 异步方法的返回值一般是Task<T>&#xff0c;T是真正的返回值类型&#xff0c;Task<int>。惯例&#xff1a;异步方法名字以 Asy…

键盘映射工具KeyTweak的使用,把F9和F10改为 Home、End

如果你的笔记本没有Home、End键 对于写文字和写代码影响还是比较大的 下面使用键盘映射工具KeyTweak 把F9和F10分别改为 Home、End 然后点击ok 电脑重启后 就生效了 很好用 完美解决 小尺寸笔记本 的按键少的烦恼 可以自己再琢磨琢磨 去映射 符合自己需求的按键 软件下载链接&…

[PwnThyBytes 2019]Baby_SQL

[PwnThyBytes 2019]Baby_SQL 查看源码发现 下载源码&#xff0c;首先观察index.php 首先进入index.php&#xff0c;会执行session_start();启动session这里通过foreach将所有的环境变量的值都遍历了一遍&#xff0c;并且都使用了addslashes()进行转义&#xff0c;然后就定义了…

【智能家居】东胜物联提供软硬一体化智能家居解决方案,助企业提高市场占有率

背景 随着智能家居市场的不断壮大&#xff0c;越来越多的消费者开始享受到它带来的便捷和效益。现在&#xff0c;他们可以通过远程或语音控制设备进行个性化设置&#xff0c;比如调节照明和温度&#xff0c;让生活变得更加舒适和智能化。 根据SPER市场研究&#xff0c;预计秘…

【计算机网络_网络层】IP协议

文章目录 1. IP的基本概念1.1 什么是IP协议1.2 为什么要有IP协议 2. IP的协议格式3. 网段划分&#xff08;重要&#xff09;3.1 为什么要进行网段划分3.2 网段划分的规则3.2.1 古老的划分方案3.2.2 现代的划分方案 4. 特殊的IP地址5. 解决IP地址的数量限制问题6. 私有IP和公网I…

BUUCTF-Misc10

秘密文件1 1.打开附件 是一个流量包 2.Wireshark 用Wireshark打开 右键追踪tcp追踪流&#xff0c;发现一个以.rar结尾的压缩包 3.foremost 用foremost分离文件 发现有一个rar的文件夹 文件夹内有个加密的压缩包 4.ARCHPR 用ARCHPR工具对压缩包进行解密 5.得到flag [BJDCTF2…

搭建基于 Snowflake 的 CI/CD 最佳实践!

Snowflake 提供了可扩展的计算和存储资源&#xff0c;和基于 SQL 的界面 Snowsight&#xff0c;方便用户进行数据操作和分析。然而&#xff0c;如果用户想将自己的 CI/CD 流程与 Snowflake 集成时&#xff0c;会发现一些不便之处&#xff08;尤其相比其 SnowSight 优秀的查询能…

【Linux】进程排队的理解进程状态的表述僵尸进程和孤儿进程的理解

一、进程排队的理解 进程不是一直运行的&#xff0c;进程可能会在等待某种软硬件资源。即使把进程加载到CPU中&#xff0c;也不是一直会运行的。而进程排队&#xff0c;一定是在等待某种软硬件资源&#xff08;可以是CPU&#xff0c;键盘&#xff0c;磁盘&#xff0c;网卡等等设…

六种GPU虚拟化:除了直通、全虚拟化 (vGPU)还有谁?

在大类上计算虚拟化技术有这3种&#xff1a; 软件模拟、直通独占(如网卡独占、显卡独占)、直通共享&#xff08;如vCPU 、vGPU&#xff09;。但对于显卡GPU而言我总结细化出至少这6种分类&#xff1a; 第一种、软件模拟&#xff08;eg sGPU&#xff09;, 又叫半虚拟化。第二种…

Spark 3.5.0 特性速览

介绍 Spark 3系列已经发布了第六版3.5.0&#xff0c;目前最新3.5.1。 使用最广泛的大数据可扩展计算引擎。数以千计的公司&#xff0c;包括 80% 的财富 500 强企业&#xff0c;都在使用 Apache Spark。来自业界和学术界的 2000 多名开源项目贡献者。 Apache Spark 3.5.0 是…

英伟达GTC2024大会开幕,发布机器人003计划,引领具身智能新时代

一、背景 在全球科技创新的前沿阵地&#xff0c;2024年3月的英伟达GPU技术大会&#xff08;GTC&#xff09;再次成为全球瞩目的焦点。在此次盛会上&#xff0c;英伟达公司创始人兼首席执行官黄仁勋先生不仅展示了其公司在加速计算和生成式AI领域的最新突破&#xff0c;更震撼发…

耳机壳UV树脂制作私模定制耳塞需要什么样的设备和技术?

制作私模定制耳塞需要使用到一些特定的设备和技术&#xff0c;包括但不限于以下内容&#xff1a; 耳模制作工具&#xff1a;用于获取用户耳型的耳模制作工具&#xff0c;如硅胶、橡皮泥等。需要使用熟练的手法和技术&#xff0c;确保耳模的准确性和稳定性。UV树脂&#xff1a;…

HCIA——30奈奎斯特定理、香农定理

学习目标&#xff1a; 计算机网络 1.掌握计算机网络的基本概念、基本原理和基本方法。 2.掌握计算机网络的体系结构和典型网络协议&#xff0c;了解典型网络设备的组成和特点&#xff0c;理解典型网络设备的工作原理。 3.能够运用计算机网络的基本概念、基本原理和基本方法进行…

Laravel框架项目首页内容修改

#Laravel# 安装Laravel框架成功后运行项目&#xff0c;看到下面这个图就说明安装框架成功了 需要根据自己的需求修改页面时&#xff0c;先找到首页的文件 首页对应的页面文件为项目根目录下的resources/views/welcome.blade.php文件 <!DOCTYPE html> <html lang&quo…

如何从零开始拆解uni-app开发的vue项目(一)

uni-app项目分析: 背景:最近接手一个前同事留下的半拉子项目,出拿过来觉得很简单;当我看到app.vue的时候很确定是vue项目,心里不怎么慌,果断安装node.js,然后就去npm ;安装VS code,事实并不是我期盼的那样,或者说根本就不能运行。 报错:应用vs code打开文件,输入命…

数据库只追求性能是不够的!

那些成功的数据库公司没有一家是通过性能比竞争对手更快而成功的。 作者&#xff1a;JORDAN TIGANI&#xff0c;DuckDB 公司 MotherDuck 联合创始人&CEO 本文和封面来源&#xff1a;https://motherduck.com/&#xff0c;爱可生开源社区翻译。 本文约 4500 字&#xff0c;预…

3D模型优化服务+三维可视化+数字孪生+元宇宙=眸瑞科技

眸瑞科技&#xff1a;老子云平台AMRT3D数字孪生引擎 老子云概述 老子云3D可视化快速开发平台&#xff0c;集云压缩、云烘焙、云存储云展示于一体&#xff0c;使3D模型资源自动输出至移动端PC端、Web端&#xff0c;能在多设备、全平台进行展示和交互&#xff0c;是全球领先、自…

使用甘特图实现高效时间规划

甘特图虽然看似简单,却蕴含着规划时间的奥秘。它将复杂的工序分解成逻辑严密的任务链条,每个短小的条形图块都清晰地道出一个任务的起始、持续和终止。就像指挥家挥舞手中的棒,每个动作都精确拍着节奏,确保各个乐手分工合作、行云流水。择一个好用的甘特图制作工具,会让你事半功…

GPT-4与Claude3、Gemini、Sora:AI领域的技术创新与突破

【最新增加Claude3、Gemini、Sora、GPTs讲解及AI领域中的集中大模型的最新技术】 2023年随着OpenAI开发者大会的召开&#xff0c;最重磅更新当属GPTs&#xff0c;多模态API&#xff0c;未来自定义专属的GPT。微软创始人比尔盖茨称ChatGPT的出现有着重大历史意义&#xff0c;不亚…

C++文件操作详解

C 通过以下几个类支持文件的输入输出&#xff1a; ofstream: 写操作&#xff08;输出&#xff09;的文件类 (由ostream引申而来) ifstream: 读操作&#xff08;输入&#xff09;的文件类(由istream引申而来) fstream: 可同时读写操作的文件类 (由iostream引申而来) 打开文件(…
最新文章