apollo7.0------浅谈激光雷达运动补偿(二)--计算解析

2023-05-16

背景介绍

运动补偿相关介绍参考第一篇博客:apollo7.0------浅谈激光雷达运动补偿_龙性的腾飞的博客-CSDN博客_lidar运动补偿

 本篇博客主要解释一下上篇博客中运动补偿的计算部分,简单来说就是一个利用四元数球面线性插值(Slerp)计算每个点的位姿,然后根据坐标变换关系将该位姿下的坐标变换到需要补偿到的某个时间点下坐标.

在slam14讲中第3讲三维空间刚体运动的课后7题和是一个非常简单的示例如下,可以看一下,那个看明白这个就很简单了,答案可直接百度.

apollo 代码中是以每帧点云中最后激光点的时间作为该帧数据的时间戳,所以我们运动补偿的目标很清晰了,计算中间每个点在最大时间点位姿下的坐标值.

计算解析

        根据里程计和激光雷达的关系,我们已知一帧点云中最大时间和最小时间激光雷达坐标系到世界坐标系下的变换关系T_{max},T_{min}以及对应的时间点time_{max},time_{min},所以中间某点时间time_{t}下的坐标p_{t}到世界坐标的变换关系可以表示为的T_{t}, 假设该点在T_{max}位姿下的坐标为p_{max},那么有:

T_{max}*p_{max} = T_{t}*p_{t}

p_{max} = T_{max}^{-1}*T_{t}*p_{t}

对于Transform矩阵

T=\begin{bmatrix} R & t\\ 0^T & 1 \end{bmatrix}

则:

T^{-1}=\begin{bmatrix} R^{-1} & -R^{-1}t\\ 0^T & 1 \end{bmatrix}

旋转矩阵为正交阵,它的逆即转置,描述了一个相反的旋转,对于四元数q在, 用共轭q^*表示一个相反的旋转.

p_{max} = \begin{bmatrix} R_{max}^{-1} & -R_{max}^{-1}t_{max}\\ 0^T & 1 \end{bmatrix}*\begin{bmatrix} R_{t} & t_{t}\\ 0^T & 1 \end{bmatrix}*p_{t}

即:

p_{max} = \begin{bmatrix} R_{max}^{-1}R_{t} & R_{max}^{-1} (t_{t}-t_{max})\\ 0^T & 1 \end{bmatrix}*p_{t}

变换矩阵分为旋转和平移部分:

旋转部分: 采用四元数表示即: q_{max}^*q_{t},其中q_{t}可以理解为time_{t}时刻某点p_{t}的姿态,根据球面线性插值(Spherical Linear Interpolation)公式:

q_{t} = Slerp(q_{0},q_{1},t) = \frac{\sin ((1-t)\theta )}{\sin (\theta )}*q_{0}+\frac{\sin (t\theta )}{\sin (\theta )}*q_{1}

我们定义的q_{max}即相当于q_{0}q_{min}相当于q_{1}.

注意:插值公式里的的t为[0-1]的比例系数,不是上文中代表的时间t,此处t即:

t =( time_{max}-time_{t})/( time_{max}-time_{min})

详细推导可参考:https://krasjet.github.io/quaternion/quaternion.pdf 中5.3节,所以有

q_{max}^*q_{t}=\frac{\sin ((1-t)\theta )}{\sin (\theta )}*q_{max}^**q_{max}+\frac{\sin (t\theta )}{\sin (\theta )}*q_{max}^**q_{min}

其中:q_{max}^**q_{max}=1,此处即对应代码中

  Eigen::Quaterniond q1(q_max.conjugate() * q_min);
  Eigen::Quaterniond q0(Eigen::Quaterniond::Identity());

 平移部分:即: q_{max}^*(t_{t}-t_{max}), 其中

t_{t}-t_{max} =(t_{min}-t_{max})*( time_{max}-time_{t})/( time_{max}-time_{min})

根据Eigen中Eigen: Eigen::Transform< Scalar_, Dim_, Mode_, Options_ > Class Template Reference表述,一个Tramsform表示为如下

\begin{pmatrix} linear & translation\\ 0...0& 1 \end{pmatrix},所以代码中平移和旋转代码如下:

  Eigen::Vector3d t1 = pose_min_time.translation();
  Eigen::Vector3d t2 = pose_max_time.translation();

  Eigen::Quaterniond q_max(pose_max_time.linear());
  Eigen::Quaterniond q_min(pose_min_time.linear());

下面再回顾下完整的代码吧!

void Compensator::MotionCompensation(
    const std::shared_ptr<const PointCloud>& msg,
    std::shared_ptr<PointCloud> msg_compensated, const uint64_t timestamp_min,
    const uint64_t timestamp_max, const Eigen::Affine3d& pose_min_time,
    const Eigen::Affine3d& pose_max_time) {
  using std::abs;
  using std::acos;
  using std::sin;
 
  Eigen::Vector3d translation =
      pose_min_time.translation() - pose_max_time.translation();
  Eigen::Quaterniond q_max(pose_max_time.linear());
  Eigen::Quaterniond q_min(pose_min_time.linear());
  Eigen::Quaterniond q1(q_max.conjugate() * q_min);
  Eigen::Quaterniond q0(Eigen::Quaterniond::Identity());
  q1.normalize();
  translation = q_max.conjugate() * translation;
 
  // int total = msg->width * msg->height;
 
  double d = q0.dot(q1);
  double abs_d = abs(d);
  double f = 1.0 / static_cast<double>(timestamp_max - timestamp_min);
 
  // Threshold for a "significant" rotation from min_time to max_time:
  // The LiDAR range accuracy is ~2 cm. Over 70 meters range, it means an angle
  // of 0.02 / 70 =
  // 0.0003 rad. So, we consider a rotation "significant" only if the scalar
  // part of quaternion is
  // less than cos(0.0003 / 2) = 1 - 1e-8.
  if (abs_d < 1.0 - 1.0e-8) {
    double theta = acos(abs_d);
    double sin_theta = sin(theta);
    double c1_sign = (d > 0) ? 1 : -1;
    for (const auto& point : msg->point()) {
      float x_scalar = point.x();
      if (std::isnan(x_scalar)) {
        // if (config_.organized()) {
        auto* point_new = msg_compensated->add_point();
        point_new->CopyFrom(point);
        // } else {
        //   AERROR << "nan point do not need motion compensation";
        // }
        continue;
      }
      float y_scalar = point.y();
      float z_scalar = point.z();
      Eigen::Vector3d p(x_scalar, y_scalar, z_scalar);
 
      uint64_t tp = point.timestamp();
      double t = static_cast<double>(timestamp_max - tp) * f;
 
      Eigen::Translation3d ti(t * translation);
 
      double c0 = sin((1 - t) * theta) / sin_theta;
      double c1 = sin(t * theta) / sin_theta * c1_sign;
      Eigen::Quaterniond qi(c0 * q0.coeffs() + c1 * q1.coeffs());
 
      Eigen::Affine3d trans = ti * qi;
      p = trans * p;
 
      auto* point_new = msg_compensated->add_point();
      point_new->set_intensity(point.intensity());
      point_new->set_timestamp(point.timestamp());
      point_new->set_x(static_cast<float>(p.x()));
      point_new->set_y(static_cast<float>(p.y()));
      point_new->set_z(static_cast<float>(p.z()));
    }
    return;
  }
  // Not a "significant" rotation. Do translation only.
  for (auto& point : msg->point()) {
    float x_scalar = point.x();
    if (std::isnan(x_scalar)) {
      AERROR << "nan point do not need motion compensation";
      continue;
    }
    float y_scalar = point.y();
    float z_scalar = point.z();
    Eigen::Vector3d p(x_scalar, y_scalar, z_scalar);
 
    uint64_t tp = point.timestamp();
    double t = static_cast<double>(timestamp_max - tp) * f;
    Eigen::Translation3d ti(t * translation);
 
    p = ti * p;
 
    auto* point_new = msg_compensated->add_point();
    point_new->set_intensity(point.intensity());
    point_new->set_timestamp(point.timestamp());
    point_new->set_x(static_cast<float>(p.x()));
    point_new->set_y(static_cast<float>(p.y()));
    point_new->set_z(static_cast<float>(p.z()));
  }
}

NOTE:

(1) 代码中四元数相乘后需要归一化,四元数归一化作用可参考博客理解:关于四元数归一化_可即的博客-CSDN博客_四元数归一化

归一化的意义:
四元数归一化:对四元数的单位化,单位化的四元数可以表示一个旋转.
规范化四元数作用:
1.表征旋转的四元数应该是规范化的四元数,但是由于计算误差等因素, 计算过程中四元数会逐渐失去规范化特性,因此必须对四元数做规范化处理
2.意义在于单位化四元数在空间旋转时是不会拉伸的,仅有旋转角度.这类似与线性代数里面的正交变换.
3.由于误差的引入,使得计算的变换四元数的模不再等于1,变换四元数失去规范性,因此需要再次更新四元数
————————————————
版权声明:本文为CSDN博主「可即」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xiaojinger_123/article/details/126873205

(2) 代码中c1_sign的判断是为了判断两个四元数夹角,保证插值走的最短路径,理解可参考https://krasjet.github.io/quaternion/quaternion.pdf 中5.4 双倍覆盖带来的问题以及文章四元数的球面线性插值(slerp) - 知乎。

水平有限,错误难免,欢迎指正,留言交流。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

apollo7.0------浅谈激光雷达运动补偿(二)--计算解析 的相关文章

  • c/c++中的string常用函数用法总结

    标准c 43 43 中string类函数介绍 注意不是CString 之所以抛弃char 的字符串而选用C 43 43 标准程序库中的string类 xff0c 是因为他和前者比较起来 xff0c 不必 担心内存是否足够 字符串长度等等 x
  • Convolutional Pose Machines

    本论文将 深度学习 应用于人体姿态分析 xff0c 同时用卷积图层表达纹理信息和空间信息 目前在2016年的 MPII竞赛中名列前茅 作者在github提供了 训练和测试源码 convolutional pose machines CVPR
  • OpenCV学习—01:矩阵的掩膜操作

    一 获取图像的像素指针 CV Assert srcImg depth 61 61 CV 8U Mat ptr lt uchar gt int i 61 0 获取像素矩阵的指针 xff0c 索引i表示第几行 xff0c 从0开始计数 获取当前
  • OpenCV学习—02:记录程序的运行时间

    首先 xff0c 我的电脑配置 xff1a VS2013 43 Opencv3 1 0 43 Win 64bit 有时 xff0c 我们想知道要程序一共运行了多长时间 xff0c 这个很常用 xff0c 也很简单 xff0c 仅仅需要两个函
  • OpenCV学习—03:Mat对象

    Mat对象与IplImage对象 Mat对象是OpenCV2 0之后引进的数据结构 自动分配内存 不存在内存泄漏的问题 xff0c 是面向对象的数据结构 IplImage是从2001年OpenCV发布之后就一直存在的 xff0c 是C语言风
  • 无刷电机桨叶正反桨区别

    无刷电机桨叶正反桨区别 桨叶平放面对自己 xff0c 左高右低为反桨 xff0c 反之为正桨cw xff1a 反桨ccw xff1a 正桨作用 xff1a 为了抵消单个螺旋桨的反扭矩 xff0c 所以每个桨的旋转方向是不一样的
  • matlab的mac版本注释快捷键

    注释 xff1a command 43 取消注释 xff1a command 43 T
  • matlab polyfit和polyval函数的使用

    Matlab polyfit和polyval函数的使用 pp 61 polyfit t p n 多项式拟合 n为拟合的阶数 py 61 polyval pp t 求出多项式的值
  • 谷歌浏览器网页缩放

    谷歌浏览器网页缩放 win xff1a Ctrl 43 43 或者 mac xff1a command 43 43 或者
  • mac系统传统映像必须转换解决方法

    mac系统传统映像必须转换解决方法 步骤一 xff1a 打开电脑自带的 磁盘工具 步骤二 xff1a 选择映像 gt 转换即可
  • ubuntu18.04 command not found: catkin_init_workspace

    使用catkin init workspace命令报错 xff1a command not found catkin init workspace 使用以下命令解决 xff1a source opt ros kinetic setup ba
  • intel realsense D400系列相机介绍(一)

    文章目录 intel realsense D42 intel realsense D400介绍2 3 立体视觉深度技术概述2 4 摄像机系统框图2 5 英特尔Intel RealSense 深度模块D400系列产品2 6 英特尔Intel
  • 宏定义(无参,有参,宏函数)(详细介绍)

    以符号 开头的命令 xff0c 如 xff1a span class token macro property span class token directive keyword define span N 10 span span cl
  • intel realsense 深度视野范围与模组硬件说明

    文章目录 4 4 距离 xff08 Z xff09 处的深度视场4 5 无效深度带4 6 最小测量深度4 7 深度质量规范4 8 深度起点 xff08 地面零点参考 xff09 4 8 1 深度原点X Y坐标 7 2 视觉处理器D4板外形7
  • jetson nano GPIO控制说明

    文章目录 一 GPIO介绍二 安装GPIO库python库C 43 43 库 三 几种常用的通信协议UARTPWMI2CI2SSPI 四 控制函数说明python xff08 参考 https pypi org project Jetson
  • jetson nano系统引导安装(无外设安装方式)

    文章目录 一 硬件设置二 系统设置 一 硬件设置 插入烧写好系统的SD卡将micro USB线接到jetson nano上 xff0c 另一端USB A接到电脑上为jetson nano插入电源 xff0c 开机等待电脑检测到如下盘符说明j
  • C中数据类型占用内存的大小

    数据类型表示存储何种类型的数据 xff0c 从内存来看 xff0c 就是占用的内存大小 C标准并未明确规定各数据类型占用多少字节的存储空间 各数据类型的内存大小与操作系统位数 编译器有关 xff0c 可以在代码中使用sizeof 进行查询
  • Linux与Windows文件系统互访

    1 概述 在软件开发过程中 xff0c 常涉及到Windows环境与Linux环境之间的切换 xff0c 实现代码的实时同步能有效提高工作效率 CIFS协议介绍 Common Internet File System CIFS xff0c

随机推荐

  • SDK、API、MDK、RTK有关系吗?到底是什么东西呢?

    SDK 软件开发工具包 缩写 SDK 外语全称 Software Development Kit 一般都是一些软件工程师为特定的软件包 软件框架 硬件平台 操作系统等建立应用软件时的开发工具的集合 软件开发工具包括广义上指辅助开发某一类软件
  • 大疆M3508电机使用CAN通信进行速度PID闭环控制详解

    目录 一 简介二 电机通信协议三 电机PID控制原理四 官方代码移植 中断接收五 官方代码移植 查询接收 一 简介 之前写过一篇文章STM32实现四驱小车 xff08 五 xff09 电机控制任务 电机速度PID控制算法 xff0c 其中是
  • 大疆M3508电机位置与速度PID控制及自编上位机调参

    目录 一 简介二 电机位置 速度 电流三闭环PID控制原理三 STM32实现位置 速度控制 xff08 一 xff09 电机数据接收 xff08 二 xff09 一些全局变量 xff08 三 xff09 电机PID初始化与位置 速度PID计
  • 相机内参与外参学习记录与理解

    1内参 内参 xff1a 在小孔成像模型中有一个光点P xff0c 其将光以直线的方式穿过纸板的小孔 光心 xff09 xff0c 射到显示纸板 xff08 物理成像平面 xff09 上 xff0c 其坐标系结构如下图所示 而内参的作用即是
  • 现代颜色技术原理及应用学习记录

    感谢CRFX下面分享的书 第一章
  • 【计算机网络】常见的HTTP报文头部信息

    常见的HTTP报文头部信息 HTTP首部字段根据实际用途被分为以下4种类型 1 通用首部 通用首部字段 xff08 General Header Fields xff09 请求报文和响应报文两方都会使用的首部 Cache Control x
  • imagenet 数据集读取数据速度记录

    电脑配置是固态硬盘 xff0c i7cpu xff0c 不进行计算 xff0c 只读数据 xff0c 不做数据增广 xff0c 只做resize 只看不赞不文明 xff0c 这是大约花费两天时间测试结果 目录 xff1a 1 xff0c 最
  • c++调python踩坑日志

    目录 import array 报错 矩阵互相转换 include numpy相关vs2019配置 数组太长报错 import array 报错 参考 xff1a https blog csdn net weixin 40232401 ar
  • pytorch 半精度训练

    必坑记录 这种训练直接变成nan了结果 实验结果 前者采用正常训练 后者改进为 from torch cuda amp import autocast as autocast 实验发现从一epoch44秒 xff0c 减少为35秒 但是训练
  • earth mover‘s distances学习记录

    https zhuanlan zhihu com p 145739750 后面的感觉没讲清楚 一开始我没想清楚 xff0c 为什么可以把问题转换成线性规划问题 问题转换一下 xff0c 一个工厂有A xff0c B xff0c C三个仓库
  • A Tutorial on Energy-Based Learning(机器学习能量模型)学习记录

    1 Introduction 其中Y是标签 X是输入 基于能量的机器学习模型即是寻找这样一个函数 和输入数据越接近 能量越小 给定一个输入 最好的模型即是对应的Y的值是真实值 这个真实值的能量是最小的 此模型可以做的工作有 预测 Ranki
  • dncnn(残差网络图像去燥记录)

    一 xff0c 生成训练数据 1 xff0c 原文相关知识 we use the noisy images from a wide range of noise levels e g 0 55 to train a single DnCNN
  • win10 10016自动重启错误解决记录

    1 xff0c 没解决 1352127440 xff0c 找到 组件服务 xff0c 然后依次展开组件服务 计算机 我的电脑 DCOM配置 xff0c 找到 9CA88EE3 ACB7 47C8 AFC4 AB702511C276 xff0
  • 无偏估计、有效性、相合性

    定义 xff1a 在已知概率分布函数构造的情况下 xff0c 概率分布的一些参数未知 xff0c 如高斯分布的namda 方差 xff0c 而利用采集到的参数来对未知参数进行估计就是参数估计 比较基础的有矩估计 最大似然估计 而不同的方法对
  • Windows10下安装point-cloud-annotation-tool点云标注工具——吐血之路总结

    零 为了标注点云数据 xff0c 经过多方查找免费开源的标注软件 xff0c 根据使用要求和方便程度最终选择了这款可以在Windows下编译安装的point cloud annotation tool xff0c 基于QT和vtk和PCL进
  • ROS 中CompressedImage消息的发布与订阅

    背景 xff1a 某些情况下需要录图像数据的包 xff0c 非常占空间和带宽 xff0c 尤其对于一些工业相机图像一张好几兆 xff0c 每秒30帧的话一份钟好几个G xff0c 这时候可以选择的订阅压缩图像 xff0c 下面直接来个dem
  • 【C#】简单的串口发送

    一 核心代码 xff1a SerialPort serialPort span class token operator 61 span span class token keyword new span span class token
  • 原生OKHttp以及OKHttpUtil的使用

    Android系统提供了两种HTTP通信类 xff0c HttpURLConnection和HttpClient 尽管Google在大部分安卓版本中推荐使用HttpURLConnection xff0c 但是这个类相比HttpClient实
  • c++编译器配置错误问题clang: error: linker command failed with exit code 1 (use -v to see invocation)

    背景 xff1a ubuntu18 04系统 之前装其他程序的时候安装了一些软件 xff0c 不知道什么时候g 43 43 编译器从 usr bin c 43 43 组里面给删掉了 xff0c 默认的编译器成了clang 43 43 xff
  • apollo7.0------浅谈激光雷达运动补偿(二)--计算解析

    背景介绍 运动补偿相关介绍参考第一篇博客 xff1a apollo7 0 浅谈激光雷达运动补偿 龙性的腾飞的博客 CSDN博客 lidar运动补偿 本篇博客主要解释一下上篇博客中运动补偿的计算部分 xff0c 简单来说就是一个利用四元数球面