Sophus使用记录

2023-11-09

sophus库是一个基于Eigen的C++李群李代数库,可以用来方便地进行李群李代数的运算。

头文件

主要用到以下两个头文件

#include <sophus/so3.h>
#include <sophus/se3.h>

SO(3)的使用

SO(3)的定义

// Sophus::SO3可以直接从旋转矩阵构造
Eigen::Matrix3d R = Eigen::AngleAxisd(M_PI/2, Eigen::Vector3d(0,0,1)).toRotationMatrix();
Sophus::SO3d SO3_R(R); 
// 亦可从旋转向量构造
Sophus::SO3d SO3_v = Sophus::SO3d::exp({0, 0, M_PI/2}); 
// 或者四元数
Eigen::Quaterniond q(R); 
Sophus::SO3d SO3_q(q);

SO(3)的运算

// 旋转矩阵
Eigen::Matrix3d R0 = SO3_R.matrix();
// 旋转向量,也即李代数
Eigen::Vector3d so3 = SO3_R.log();
// hat为向量到反对称矩阵
Eigen::Matrix3d so3_hat = Sophus::SO3d::hat(so3);
// 相对的,vee为反对称到向量
Eigen::Vector3d so3_vee = Sophus::SO3d::vee(so3_hat);
// 更新
Eigen::Vector3d update_so3(1e-4, 0, 0);
Sophus::SO3d SO3_updated = Sophus::SO3d::exp(update_so3)*SO3_R;

// 两个SO(3)可以直接相乘
Sophus::SO3d SO3_mult = SO3_R * SO3_q;
// 也可以用对数映射
Eigen::Vector3d so3_mult = SO3_R.log() + SO3_q.log();
// 对数映射的反操作
Sophus::SO3d SO3_mult2 = Sophus::SO3d::exp(so3_mult);

SE(3)的使用

SE(3)的定义

// 从旋转矩阵和平移向量构造
Eigen::Matrix3d R = Eigen::AngleAxisd(M_PI/2, Eigen::Vector3d(0,0,1)).toRotationMatrix();
Eigen::Vector3d t(1,0,0);
Sophus::SE3d SE3_Rt(R, t);
// 从四元数构造
Eigen::Quaterniond q(R);
Sophus::SE3d SE3_qt(q, t);

SE(3)的运算

// 李代数se(3)是一个六维向量,方便起见先typedef一下
typedef Eigen::Matrix<double, 6, 1> Vector6d;
// SE(3)是一个4*4的矩阵,方便起见先typedef一下
typedef Eigen::Matrix<double, 4, 4> Matrix4d;
// 用对数映射获得它的李代数
Vector6d se3 = SE3_Rt.log();
// hat为向量到反对称矩阵
Matrix4d se3_hat = Sophus::SE3d::hat(se3);
// 相对的,vee为反对称到向量
Vector6d se3_vee = Sophus::SE3d::vee(se3_hat);
// 更新
Vector6d update_se3; update_se3.setZero(); // 平移在前,旋转在后
update_se3(0, 0) = 1e-4;
Sophus::SE3d SE3_updated = Sophus::SE3d::exp(update_se3)*SE3_Rt;

SE(3)、SO(3)与Eigen类型的相互转换

Eigen::Matrix3d R = Eigen::AngleAxisd(M_PI/2, Eigen::Vector3d(0,0,1)).toRotationMatrix();
Sophus::SO3d SO3_R(R);
Eigen::Vector3d t(1,0,0);
Sophus::SE3d SE3_Rt(R, t);
// 从SO(3)获得单位四元数
Eigen::Quaterniond q0 = SO3_R.unit_quaternion();
// 从SO(3)获得旋转矩阵
Eigen::Matrix3d R0 = SO3_R.matrix();
// 从SE(3)获得旋转矩阵
Eigen::Matrix3d R1 = SE3_Rt.matrix().block<3,3>(0,0);
// 从SE(3)获得单位四元数
Eigen::Quaterniond q1 = SE3_Rt.unit_quaternion();
// 从SE(3)获得平移向量
Eigen::Vector3d t1 = SE3_Rt.translation();

坐标变换

Eigen::Vector3d p1(1, 0, 0); // 令p1=(1,0,0)
Eigen::Vector3d p2 = SE3_Rt * p1; // 相当于R*p1+t

优化问题

空间中存在一组三维点云,已知在世界坐标系下的坐标 P w P_w Pw,以及这些点云在相机坐标系下的坐标 P c P_c Pc,求解相机相对于世界坐标系的位姿 T c w T_c^w Tcw

残差定义:

r i = R p i + t − q i \begin{aligned} r_{i} &=R p_{i}+t-q_{i} \end{aligned} ri=Rpi+tqi

目标函数如下:

min ⁡ R , t ∑ i = 1 N ∥ R p i + t − q i ∥ 2 2 \begin{aligned} \min _{R, t} \sum_{i=1}^{N}\left\|R p_{i}+t-q_{i}\right\|_{2}^{2} \end{aligned} R,tmini=1NRpi+tqi22

使用右扰动模型计算得到残差的雅克比矩阵如下:

∂ r ∂ δ θ = − R p ^ ∧ ∂ r ∂ δ p = I 3 × 3 \begin{aligned} \frac{\partial r}{\partial \delta \theta} &=-R \hat{p}^{\wedge} \\ \\ \frac{\partial r}{\partial \delta p} &=I_{3 \times 3} \\ \end{aligned} δθrδpr=Rp^=I3×3

使用左扰动模型计算得到残差的雅克比矩阵如下:

∂ r ∂ δ θ = − ( R p ^ ) ∧ ∂ r ∂ δ p = I 3 × 3 \begin{aligned} \frac{\partial r}{\partial \delta \theta} &=-(R\hat{p})^{\wedge} \\ \\ \frac{\partial r}{\partial \delta p} &=I_{3 \times 3} \\ \end{aligned} δθrδpr=(Rp^)=I3×3

使用Guass-Newton迭代求解即可,程序如下:

#include <iostream>

#include <Eigen/Core>
#include <Eigen/Dense>
#include <Eigen/Geometry>

#include "sophus/se3.hpp"
#include "sophus/so3.hpp"

int main(void){
    // 优化变量为李代数se(3)的平移向量
    typedef Eigen::Matrix<double, 6, 1> Vector6d;
    // 数据点
    std::vector<Eigen::Vector3d> pts1, pts2;
    // 随机数生成器
    std::default_random_engine generator;
    std::normal_distribution<double> noise(0., 1.);
    // 构造相机位姿,作为参考位姿
    Eigen::Matrix3d R = Eigen::AngleAxisd(M_PI/2, Eigen::Vector3d(0,0,1)).toRotationMatrix();
    Eigen::Vector3d t(1,0,0);
    Sophus::SE3d SE3_Rt(R, t);
    // 观测数据
    for (int i = 0; i < 100; ++i) {
        double x = noise(generator), y = noise(generator), z = noise(generator);
        pts1.push_back(Eigen::Vector3d(x, y, z)); // 相机坐标系下的点
        pts2.push_back(SE3_Rt * pts1[i]); // 世界坐标系下的点
    }
    // 开始Gauss-Newton迭代,初始位姿为参考位姿+扰动
    Sophus::SE3d SE3_estimate(R*Eigen::AngleAxisd(0.1, Eigen::Vector3d::UnitZ()).toRotationMatrix(), 
                            t+Eigen::Vector3d(0.1, 0.0, 0.0));
    enum {
        DLEFT = 0, // 左扰动
        DRIGHT = 1, // 右扰动
    };
    int disturb = DRIGHT;
    for (int iter = 0; iter < 100; ++iter) {
        Eigen::Matrix<double, 6, 6> H = Eigen::Matrix<double, 6, 6>::Zero();
        Eigen::Matrix<double, 6, 1> b = Eigen::Matrix<double, 6, 1>::Zero();
        double cost = 0;
        for (int i = 0; i < pts1.size(); ++i) {
            // compute cost for pts1[i] and pts2[i]
            Eigen::Vector3d p = pts1[i], pc = pts2[i]; // p为相机坐标系下的点,pc为世界坐标系下的点
            Eigen::Vector3d pc_est = SE3_estimate * p; // 估计的世界坐标系下的点
            Eigen::Vector3d e = pc_est - pc; // 残差
            cost += e.squaredNorm() / 2;
            // compute jacobian
            Eigen::Matrix<double, 3, 6> J = Eigen::Matrix<double, 3, 6>::Zero();
            if(disturb == DRIGHT){
                // 右扰动
                J.block<3,3>(0,0) = Eigen::Matrix3d::Identity();
                J.block<3,3>(0,3) = -SE3_estimate.rotationMatrix() * Sophus::SO3d::hat(p);
            } else{
                // 左扰动
                J.block<3,3>(0,0) = Eigen::Matrix3d::Identity();
                J.block<3,3>(0,3) = -Sophus::SO3d::hat(SE3_estimate.rotationMatrix() * p);
            }
            // compute H and b
            H += J.transpose() * J;
            b += -J.transpose() * e;
        }
        // solve dx 
        Vector6d dx = H.ldlt().solve(b);
        if (dx.norm() < 1e-6) {
            break;
        }
        // update estimation
        if(disturb == DRIGHT){
            // 右乘更新
            SE3_estimate.so3() = SE3_estimate.so3() * Sophus::SO3d::exp(dx.block<3,1>(3,0));
            SE3_estimate.translation() += dx.block<3,1>(0,0);
        } else{
            // 左乘更新
            SE3_estimate.so3() = Sophus::SO3d::exp(dx.block<3,1>(3,0)) * SE3_estimate.so3();
            SE3_estimate.translation() += dx.block<3,1>(0,0);
        }

        std::cout << "iteration " << iter << " cost=" << cost << std::endl;
    }
    std::cout << "estimated pose: \n" << SE3_estimate.matrix() << std::endl;
    std::cout << "ground truth pose: \n" << SE3_Rt.matrix() << std::endl;
}

bug记录

右乘se3的exp映射时,结果有问题,而左乘没问题

初步定位到问题,在求导时,不是对SE3求导,而是对平移向量和旋转向量分别求导,然后再组合起来,这和SE3求导结果不同.

暂时不管了,SE3右乘雅可比有点复杂,高翔书中也是分开求导和更新的,就这样吧。

// se3右乘更新, 有问题
SE3_estimate = SE3_estimate * Sophus::SE3d::exp(dx); 
// 分开更新,没问题
SE3_estimate.so3() = SE3_estimate.so3() * Sophus::SO3d::exp(dx.block<3,1>(3,0));
SE3_estimate.translation() += dx.block<3,1>(0,0);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Sophus使用记录 的相关文章

随机推荐

  • 嵌入式入门基础知识有哪些?

    嵌入式系统是指在特定应用领域内为满足特定要求而设计的计算机系统 通常被嵌入到设备中 具有实时性 可靠性 低功耗等特点 嵌入式系统应用广泛 例如 智能家居 智能手表 汽车控制系统 医疗设备等 在本篇博客中 我们将讨论嵌入式入门基础知识 包括嵌
  • 狂神说Mybatis笔记(全网最全)

    Mybatis 环境说明 jdk 8 MySQL 5 7 19 maven 3 6 0 IDEA 学习前需要掌握 JDBC MySQL Java 基础 Maven Junit 1 Mybatis简介 1 1 什么是MyBatis MyBat
  • 认识区块链,认知区块链— —数据上链

    上周末参加一次长沙本地胡子互联网俱乐部举办的区块链分享会 颇受启发 同时感谢俱乐部提供的这个交流平台 祝好 好吧 还是先把前些天对区块链的一点理解简单整理下 再回顾下上周末的参会纪要比较好 下篇给大家分享出来 个人区块链思考第一篇 认识区块
  • Yolov3计算准确率、误报率、漏检率等

    思想很简单 将标注的yolo数据转下格式 转为 类别 xmin ymin xmax ymax 转换valid后的信息 两个信息进行对比 完事 具体的 在终端执行 darknet detector valid cfg voc data cfg
  • 【SSM框架】之Spring

    SSM框架笔记 自用 Spring Spring Framework系统架构 Spring程序开发步骤 核心概念 IoC Inversion of Control 控制反转 使用对象时 由主动new产生对象转换为由外部提供对象 此过程中对象
  • 计算机毕业设计看这篇就够了(二)毕设流程

    本篇将为大家介绍计算机专业毕业设计流程 提前了解毕设流程可以让同学们从宏观角度去看毕设要做些什么样的事情 大概知道每个阶段要去做哪些工作 为后续毕设任务的真正开展打下心理预期 也不至于一脸懵 计算机毕设分为以下主流程 选题 确定导师 完成前
  • 【Proteus仿真】【STM32单片机】基于stm32的智能书桌设计

    文章目录 一 功能简介 二 软件设计 三 实验现象 联系作者 一 功能简介 系统运行后 默认为手动模式 当检测有人 可通过K2键开关灯 如果姿势不对 警示灯亮 否则灭 可通过K3和K4键调节桌子高度 按下K1键切换为自动模式 此时有人 且光
  • Sentinel原理与Demo

    Sentinel 是什么 随着微服务的流行 服务和服务之间的稳定性变得越来越重要 Sentinel 以流量为切入点 从流量控制 熔断降级 系统负载保护等多个维度保护服务的稳定性 Sentinel 具有以下特征 丰富的应用场景 Sentine
  • 【FreeRTOS(三)】任务状态

    文章目录 任务状态 任务挂起 vTaskSuspend 取消任务挂起 vTaskResume 挂起任务调度器 vTaskSuspendAll 取消挂起任务调度器 xTaskResumeAll 代码示例 任务挂起 取消任务挂起 代码示例 挂起
  • Docker help帮助文档

    1 查看 docker help 帮助 docker help 2 用法 docker 选项 命令 3 选项 客户端配置文件的配置字符串位置 默认为 root docker D 启用调试模式 H 要连接的主机列表守护进程套接字 l 设置日志
  • Centos7.3安装和配置Mysql5.7

    第一步 获取mysql YUM源 进入mysql官网获取RPM包下载地址 https dev mysql com downloads repo yum 点击 下载 右击 复制链接地址 https dev mysql com get mysq
  • 源码剖析transformer、self-attention

    原文链接 首先给大家引入一个github博客 这份代码是我在看了4份transformer的源码后选出来的 这位作者的写法非常易懂 代码质量比较高 GitHub Separius BERT keras Keras implementatio
  • 一步一步教你用idea上交代码到gitee(图文解释)

    一步一步教你用idea上交代码到gitee 图文解释 文章目录 一步一步教你用idea上交代码到gitee 图文解释 工具准备 具体操作 结语 工具准备 首先 我们进行代码的提交需要两个工具包 在我的上一篇中有讲 大家可以自行去提取 2条消
  • .net 批量注册服务

    假设我们需要注册xxxQuery服务 例如下图中的BarQuery和FooQuery 传统的做法是 services TryAddScoped
  • vscode 运行和调试 javascript 代码

    安装node 安装vscode 扩展包 code runer 配置vs code下有关F5的操作的文件 参考地址
  • 【Zabbix实战之运维篇】Zabbix监控Docker容器配置方法

    Zabbix实战之运维篇 Zabbix监控Docker容器配置方法 一 检查Zabbix监控平台状态 1 检查Zabbix各组件容器状态 2 奸诈Zabbix server状态 二 下载监控模板 1 进入Zabbix官网下载页面 2 查看下
  • 微信小程序中识别html标签的方法

    rich text组件 在微信小程序中有一个组件rich text可以识别文本节点或是元素节点 具体入下 需要识别的数据放在data中 然后放在nodes属性中即可
  • 编写程序:5类员工有对应封装类,创建Employee数组,若干不同的Employee对象,并实现增删改查功能(《黑马程序员》P144编程题加强版)

    文章目录 Employee类 SalariedEmployee类 HourlyEmployee类 SalesEmployee类 BasePlusSalesEmployee类 Test类 实现增删改查 原题 1 Employee 这是所有员工
  • 【python】深入了解Selenium-PageObject

    1 PageObject 定义 Page Object 简称PO 模式 是Selenium实战中最为流行 并且是自动化测试中最为熟悉和推崇的一种设计模式 在设计自动化测试时 把页面元素和元素的操作方法按照页面抽象出来 分离成一定的对象 然后
  • Sophus使用记录

    sophus库是一个基于Eigen的C 李群李代数库 可以用来方便地进行李群李代数的运算 头文件 主要用到以下两个头文件 include