深入理解SLAM中的3D-3D位姿估计:基于SVD与图优化的实现

深入理解SLAM中的3D-3D位姿估计:基于SVD与图优化的实现

slambook slambook 项目地址: https://gitcode.com/gh_mirrors/sl/slambook

概述

本文主要探讨计算机视觉与SLAM领域中一个核心问题:如何从两组3D点对应关系中估计相机运动(即旋转矩阵R和平移向量t)。我们将基于gaoxiang12/slambook项目中的pose_estimation_3d3d.cpp实现,详细解析3D-3D位姿估计的两种主要方法:SVD分解法和图优化法。

3D-3D位姿估计问题定义

给定两组3D点集pts1和pts2,其中pts1中的每个点与pts2中的点一一对应,我们的目标是找到一个刚体变换(R,t),使得对于所有i,有:

pts1[i] = R * pts2[i] + t

这个问题在SLAM中非常常见,特别是在RGB-D SLAM或激光SLAM中,当我们能够直接获取场景的三维信息时。

实现流程解析

1. 特征提取与匹配

代码首先通过ORB特征检测器和描述子提取器在两幅图像中找到匹配的特征点:

Ptr<FeatureDetector> detector = ORB::create();
Ptr<DescriptorExtractor> descriptor = ORB::create();
Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("BruteForce-Hamming");

ORB(Oriented FAST and Rotated BRIEF)是一种快速且具有旋转不变性的特征点检测与描述算法,非常适合实时SLAM系统。

2. 3D点云构建

对于每个匹配的特征点,利用深度图信息将其转换为3D点:

Point2d p1 = pixel2cam(keypoints_1[m.queryIdx].pt, K);
float dd1 = float(d1)/5000.0;
pts1.push_back(Point3f(p1.x*dd1, p1.y*dd1, dd1));

这里pixel2cam函数将像素坐标转换为相机坐标系下的归一化坐标,再结合深度值得到3D坐标。

3. SVD求解位姿

核心算法是通过SVD分解求解最优的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 = p1 / N; p2 = p2 / N;
    
    // 去中心化
    vector<Point3f> q1(N), q2(N);
    for(int i=0; i<N; i++) {
        q1[i] = pts1[i] - p1;
        q2[i] = pts2[i] - p2;
    }
    
    // 计算W矩阵
    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();
    }
    
    // SVD分解
    Eigen::JacobiSVD<Eigen::Matrix3d> svd(W, Eigen::ComputeFullU|Eigen::ComputeFullV);
    Eigen::Matrix3d U = svd.matrixU();
    Eigen::Matrix3d V = svd.matrixV();
    
    // 处理反射情况
    if(U.determinant() * V.determinant() < 0) {
        for(int x=0; x<3; ++x)
            U(x,2) *= -1;
    }
    
    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);
    
    // 转换为OpenCV格式输出
    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));
}

这个实现基于以下数学原理:

  1. 计算两组点集的质心p1和p2
  2. 对点集去中心化得到q1和q2
  3. 计算矩阵W = Σ(q1 * q2^T)
  4. 对W进行SVD分解:W = UΣV^T
  5. 最优旋转矩阵R = UV^T(需处理反射情况)
  6. 最优平移向量t = p1 - R*p2

4. 图优化精修

SVD解法给出了一个闭式解,但我们可以进一步使用图优化来精修结果:

void bundleAdjustment(const vector<Point3f>& pts1, const vector<Point3f>& pts2, Mat& R, Mat& t)
{
    // 初始化g2o优化器
    typedef g2o::BlockSolver<g2o::BlockSolverTraits<6,3>> Block;
    Block::LinearSolverType* linearSolver = new g2o::LinearSolverEigen<Block::PoseMatrixType>();
    Block* solver_ptr = new Block(linearSolver);
    g2o::OptimizationAlgorithmGaussNewton* solver = new g2o::OptimizationAlgorithmGaussNewton(solver_ptr);
    g2o::SparseOptimizer optimizer;
    optimizer.setAlgorithm(solver);
    
    // 添加顶点(位姿)
    g2o::VertexSE3Expmap* pose = new g2o::VertexSE3Expmap();
    pose->setId(0);
    pose->setEstimate(g2o::SE3Quat(
        Eigen::Matrix3d::Identity(),
        Eigen::Vector3d(0,0,0)
    ));
    optimizer.addVertex(pose);
    
    // 添加边(观测)
    for(size_t i=0; i<pts1.size(); i++) {
        EdgeProjectXYZRGBDPoseOnly* edge = new EdgeProjectXYZRGBDPoseOnly(
            Eigen::Vector3d(pts2[i].x, pts2[i].y, pts2[i].z));
        edge->setVertex(0, dynamic_cast<g2o::VertexSE3Expmap*>(pose));
        edge->setMeasurement(Eigen::Vector3d(pts1[i].x, pts1[i].y, pts1[i].z));
        edge->setInformation(Eigen::Matrix3d::Identity()*1e4);
        optimizer.addEdge(edge);
    }
    
    // 执行优化
    optimizer.initializeOptimization();
    optimizer.optimize(10);
}

自定义的边类EdgeProjectXYZRGBDPoseOnly定义了误差计算和雅可比矩阵:

class EdgeProjectXYZRGBDPoseOnly : public g2o::BaseUnaryEdge<3, Eigen::Vector3d, g2o::VertexSE3Expmap>
{
public:
    virtual void computeError() {
        const g2o::VertexSE3Expmap* pose = static_cast<const g2o::VertexSE3Expmap*>(_vertices[0]);
        _error = _measurement - pose->estimate().map(_point);
    }
    
    virtual void linearizeOplus() {
        // 雅可比矩阵计算...
    }
    
protected:
    Eigen::Vector3d _point;
};

技术要点深入

1. SVD解法的数学基础

3D-3D配准问题可以转化为最小二乘问题:

min Σ||(R*p2_i + t) - p1_i||²

通过去中心化,我们可以将问题分解为:

  1. 先求解旋转R:min Σ||R*q2_i - q1_i||²
  2. 再求解平移t:t = p1 - R*p2

旋转子问题可以转化为:

max tr(R^T * W), 其中 W = Σ(q1_i * q2_i^T)

这个最大化的解就是W的SVD分解结果。

2. 图优化的优势

虽然SVD给出了闭式解,但图优化有以下优势:

  • 可以方便地加入各种约束
  • 能处理异常值(通过鲁棒核函数)
  • 可以与其他图优化问题统一框架
  • 迭代优化可能得到更精确的结果

3. 实际应用中的注意事项

  1. 深度图处理:深度值需要正确缩放(代码中除以5000.0)
  2. 特征匹配质量:错误的匹配会导致估计失败,需要良好的特征匹配和筛选
  3. 点对数量:至少需要3个非共线点对
  4. 退化情况:当所有点共面时,解可能不唯一

实验结果分析

程序输出包括:

  1. SVD求解的R和t
  2. 优化后的R和t
  3. 随机选取几个点验证变换的正确性

通过比较SVD结果和优化结果,可以验证两种方法的一致性,通常图优化会给出更精确的结果。

总结

本文详细解析了3D-3D位姿估计的两种实现方法:基于SVD的闭式解法和基于图优化的迭代解法。这两种方法在SLAM系统中都有广泛应用,SVD解法快速可靠,适合作为初始估计;图优化方法灵活强大,适合后续精修和与其他约束联合优化。

理解这些基础算法对于深入SLAM系统开发至关重要,它们是点云配准、回环检测等高级功能的基础。通过本代码的学习,读者可以掌握3D几何处理的核心思想,并能够扩展到更复杂的SLAM问题中。

slambook slambook 项目地址: https://gitcode.com/gh_mirrors/sl/slambook

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

单迅秋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值