对应讲解视频

四元数的简单通俗用法(Eigen和python)

是什么:复数在三维的推广

四元数就是四个数。它是复数的推广。

复数为
a+bia + bi a+bi
四元数则多了两个虚部
a+xi+yj+zka + xi + y j + zk a+xi+yj+zk
其中i,j,k都是虚数。所以
i2=j2=k2=ijk=−1i^2=j^2=k^2=ijk=-1 i2=j2=k2=ijk=−1

干什么的:三维点的旋转

复数表示二维旋转

复数可以表示二维点的旋转。

其中的圆是单位圆。

如图,就表示点或向量绕着原点旋转了θ\thetaθ角度。

我们可以把图中的二维坐标(a,b)写成一个复数

cosθ+sinθ⋅icos\theta + sin\theta \cdot i cosθ+sinθ⋅i

四元数表示三维旋转

同理,四元数可以表示三维单位球面上的一个点(或者说是一个单位方向向量)的旋转

cosθ/2+sinθ/2⋅(ai+bj+ck)cos\theta/2 + sin\theta/2 \cdot (ai + bj+ ck) cosθ/2+sinθ/2⋅(ai+bj+ck)

这就表示绕着一个轴(a,b,c)(它是个单位向量)旋转θ\thetaθ角度。

为什么要用它:解决旋转矩阵的误差累积和欧拉角的万向节锁问题

在四元数之外,有另外两种表示三维旋转的方案:

  1. 欧拉角:简单易懂,但有万向节锁问题。
  2. 旋转矩阵:可以累乘,但有误差累计问题。

正是因为这两种表示方式的缺陷,才造成了四元数表示法的流行。

欧拉角的问题

欧拉角:即绕x轴,绕y轴,绕z轴旋转的角度。这三个角度叠加在一起,就能表示任意方向的旋转。
(α,β,γ)(\alpha, \beta, \gamma) (α,β,γ)
优点是简单直观,所以在三维软件的用户界面上通常都用欧拉角表示。但是软件内部的计算过程都用四元数。
缺点是万向节锁问题。

万向节锁问题:就是两个转轴重合的时候,就会丧失其中一个旋转自由度。

旋转矩阵的问题

旋转矩阵用9个分量来表示旋转。当然它是对称的,因此实际上要存储6个分量。既然欧拉角只用了三个量,旋转矩阵的信息表示一定是冗余的。

冗余的信息干什么去了呢?冗余的信息用来表示剪切变形了。

也就是说,旋转矩阵并不只是用于刚体旋转的,剪切变形也掺混在旋转矩阵内部了。这就导致一个很严重的问题:当误差累积之后,旋转矩阵造成的误差效果不光是多转或少转了1、2度角度这么简单,而是造成了刚体的剪切变形!

剪切变形之后的刚体还是刚体吗?当然就不是了。这就是很严重的问题。

这也是为什么明明所有方法都会造成累积误差,但是我们特别在意旋转矩阵的累积误差的原因。因为它造成的后果很严重。

旋转矩阵要纯粹地表示刚体旋转,必须满足它是正交矩阵这个条件。如果不满足,那么它里面就掺杂了剪切变形!

因此我们每次旋转之后,需要将旋转矩阵正交化。其中一个正交化的方法,就是施密特正交化。你也许想象到了,施密特正交化是多么繁琐复杂的一个过程。

怎么做:用四元数旋转点

用旋转矩阵旋转点x,只要不断左乘矩阵就好了。
R1R2xR_1R_2\mathbf{x} R1​R2​x
而用四元数旋转点,则是要做个三明治:
qx(q)−1q \mathbf{x}(q)^{-1} qx(q)−1
这有点像矩阵相似,但是请注意相似矩阵的逆是写在左边的,而且,相似矩阵针对的是矩阵,这里的q是四元数。四元数的逆显然和矩阵的逆不是一个概念。四元数是复数的推广,不是矩阵!不能套用矩阵的运算法则。

幸好,四元数的逆非常简单,假设q为单位四元数(如果不是就先归一化):
q−1=w−(ai+bj+ck)q^{-1} = w - ( ai+bj+ck) q−1=w−(ai+bj+ck)
这就是实部不变,虚部加个负号而已!而且你可以试验一下,它是否符合逆的概念,也就是qq−1=1qq^{-1}=1qq−1=1

实际上,就是复数的共轭的推广。

PS: 如果q没有归一化,那么q的逆只需要再除以模的平方:
q−1=w−(ai+bj+ck)∣∣q∣∣2q^{-1} = \frac{w - ( ai+bj+ck)}{||q||^2} q−1=∣∣q∣∣2w−(ai+bj+ck)​

旋转叠加

四元数也像旋转矩阵那样能够累乘!
比如先旋转q1,再旋转q2
q2(q1x(q1)−1)q2−1=(q2q1)x(q1−1q2−1)q_2(q_1 \mathbf{x}(q_1)^{-1})q_2^{-1} = (q_2q_1) \mathbf{x} (q_1^{-1}q_2^{-1}) q2​(q1​x(q1​)−1)q2−1​=(q2​q1​)x(q1−1​q2−1​)

其中四元数的乘法非常简单,参考复数的计算方法就好了。

也可以参考下面的公式

四元数转换为三维点

旋转之后,怎么得到三维的点坐标呢?

我们只需要把实部抛弃(或者写成0),虚部的三个值恰好就是一个三维点的坐标。

也就是说
(0,xi+yj+zk)(0, xi+yj+zk) (0,xi+yj+zk)
恰好就是三维点
(x,y,z)(x, y, z) (x,y,z)

代码(C++ Eigen)

#include <Eigen/Dense>
#include <iostream>
#include <cmath>
using namespace Eigen;
using namespace std;
#define _MATH_DEFINES_DEFINED//四元数相加
Quaternionf qadd(Quaternionf q1, Quaternionf q2)
{Quaternionf res;res.vec() = q1.vec() + q2.vec();res.w() = q1.w() + q2.w();return res;
}
//四元数数乘(即缩放)
Quaternionf qscale(Quaternionf q, float scale)
{Quaternionf res;res.vec() = scale * q.vec() ;res.w() = scale * q.w() ;return res;
}//用向量公式的乘法
Quaternionf qmul(Quaternionf q1, Quaternionf q2)
{Quaternionf res;res.vec() = q1.w() * q2.vec() + q2.w() * q1.vec() + q1.vec().cross(q2.vec());res.w() = q1.w() * q2.w() - q1.vec().dot(q2.vec());return res;
}//用向量公式的乘法 (结果是一样的)
Quaternionf qmul2(Quaternionf q1, Quaternionf q2)
{Quaternionf res;res.w() = q1.w() * q2.w() - q1.x() * q2.x() - q1.y() * q2.y() - q1.z() * q2.z();res.x() = q1.w() * q2.x() + q1.x() * q2.w() + q1.y() * q2.z() - q1.z() * q2.y();res.y() = q1.w() * q2.y() - q1.x() * q2.z() + q1.y() * q2.w() + q1.z() * q2.x();res.z() = q1.w() * q2.z() + q1.x() * q2.y() - q1.y() * q2.x() + q1.z() * q2.w();return res;
}//求逆
Quaternionf qinv(Quaternionf q)
{Quaternionf res;res.vec() = - q.vec() ;res.w() = q.w();float n=q.norm();res.vec() /= n*n; res.w() /= n*n; return res;
}int main()
{Quaternionf q1{1,2,3,4}, q2{5,6,7,8}, res;// cout<<q1 <<endl;// cout<<q2 <<endl;// res = qadd(q1, q2); //加法// cout<< res <<endl;// res =  qscale(q1, 5.0); //数乘// cout<< res <<endl;// cout<< q1*q2<<endl;  //Eigen自带乘法// res = qmul(q1,q2);   //乘法1// cout<< res <<endl;// res = qmul2(q1,q2);  //乘法2// cout<< res <<endl;// q1 = q1.normalized();// q2 = q2.normalized();// cout<< q1.inverse()<<endl;// res = qinv(q1);  //求逆// cout<< res <<endl;//用四元数旋转Vector3f point{1.0, 1.0, 0};point = point.normalized();Quaternionf p;p.vec() = point;p.w() = 0;float theta = 45.0 / 180 * 3.14159265358979323846;Vector3f axis{0,0,1};Quaternionf q{cos(theta/2), sin(theta/2) * axis[0], sin(theta/2) * axis[1], sin(theta/2) * axis[2]};cout<<q<<endl;Quaternionf p_new = q * p * q.inverse();cout<<p_new<<endl;Vector3f point_new=p_new.vec();cout<<point_new<<endl;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(tryEigen)
find_package(Eigen3 REQUIRED)
add_executable(out tryEigen.cpp)

代码(numpy-quaternion)

有个python包(就叫numpy-quaternion)定义了常用的quaternion,代码库如下
https://github.com/moble/quaternion

文档如下
https://quaternion.readthedocs.io/en/latest/

安装

python -m pip install --upgrade --force-reinstall numpy-quaternion

导入

import numpy as np
import quaternion

定义四元数

q1 = np.quaternion(1,2,3,4) #定义一个四元数(w,x,y,z)
q2 = np.quaternion(5,6,7,8)
print(q1,q2.w, q2.x, q2.y, q2.z)
b = quaternion.as_float_array(q1) #转换为numpy数组
print(b)

输出

quaternion(1, 2, 3, 4) 5.0 6.0 7.0 8.0
array([1., 2., 3., 4.])

通过角度和转轴定义四元数

theta = np.deg2rad(90)  #转角:90度
axis = np.array([0,0,1])#转轴:z轴
q = np.quaternion(np.cos(theta/2), np.sin(theta/2) * axis[0], np.sin(theta/2) * axis[1], np.sin(theta/2) * axis[2]) #定义四元数:沿着转轴axis转theta角度
print(q)theta1 = quaternion.as_euler_angles(q) #转换为欧拉角(常常不是预期结果, 慎用)
theta1 = np.rad2deg(theta1)
print(theta1)

输出

quaternion(0.707106781186548, 0, 0, 0.707106781186548)
[45.  0. 45.] #欧拉角的结果是不对的

转换为旋转矩阵后旋转一个点

point = np.array([1,0,0]) #定义一个点(1,0,0)
m  = quaternion.as_rotation_matrix(q) #四元数转换为旋转矩阵
## print(m)
v = m @ point #用旋转矩阵旋转一个点
print(v)

输出

[-2.22044605e-16  1.00000000e+00  0.00000000e+00]

直接用四元数旋转一个点

point = quaternion.from_vector_part(point) #将一个点转换为四元数,实部为0
print(point)
p_new = q * point * q.conjugate()   #旋转点,结果为四元数
print(p_new)
p_new = quaternion.as_vector_part(p_new) #结果四元数转换为点,直接去掉实部即可
print(p_new)

输出

quaternion(0, 1, 0, 0)
quaternion(0, 0, 1, 0)
[0. 1. 0.]

【图形学】四元数的通俗用法相关推荐

  1. 万向锁的理解 欧拉角 四元数

    1 欧拉角&万向锁 欧拉角和万向锁 视频资料理解万向锁 核心是嵌套 以一个三自由度的机械臂为例,三个旋转轴互相垂直,相当于x,y,z轴 三个机械臂通过旋转关节依次串联,定义第一个关节旋转轴为J ...

  2. on the fly

    读文献看到一篇文章,However, since our model only uses convolutional and pooling layers it can be resized on t ...

  3. 视觉SLAM学习笔记

    中英文对照表 中文 英文 计算机视觉 Computer Vision 人工智能 Artificial Intelligence 单目相机 Monocular 双目相机 Stereo 深度相机 RGB- ...

  4. English Words(For Computer Science)

    文章目录 1. offload workload:减轻工作量 2. sufficient:充足的,足够的 3. latency:延迟 4. optimize:优化,optimal:最佳的 5. mul ...

  5. 为什么x86不叫x32?

    安装了64位系统后,会多出:program(x86)文件夹,用于存放32位软件.在下载软件时,也会有x86,x64不同版本下载.那么64位系统叫x64,32位系统为什么不叫x32,而是x86呢? x8 ...

  6. 我写了个“女朋友”陪自己打麻将……

    序 一个月黑风高的晚上,我正坐在工位的电脑前精力充沛的疲倦不已的写着bug. 突然,一通备注着"小仙女"的电话打了过来,我一看赶紧接通了女朋友的电话. 电话那边传来温柔的声音&qu ...

  7. on the fly 到底几个意思

    几乎每篇文档都给你来一个on the fly.知乎搜了几次也没记住,干脆把维基百科的解释翻译了一下: On the fly is a phrase used to describe something ...

  8. 计算机图形学中的四元数(Quaternions)

    计算机图形学中的四元数 计算机图形学中的四元数(Quaternions) 前言 欧拉角 欧拉角的万向节死锁(Gimbal Lock)与插值问题 四元数 参考 计算机图形学中的四元数(Quaternio ...

  9. 【图形学基础:基本变换】复数、四元数的巩固和图形学基本变换类型的学习

    PS:本文中灰色字体为拓展内容,不在目录中标识,可以作为了解学习哦 ~ 目录 复数:a+bi 四元数 基本变换 平移变换 旋转变换 缩放变换 变换级联 变换矩阵的逆矩阵补充知识点: 欧拉变换 复数:a ...

最新文章

  1. kubernetes入门(04)kubernetes的核心概念(1)
  2. 打印http地址打印双斜杠
  3. Nessus提示API Disabled错误
  4. windows server 2003 r2 64位web服务器安装配置注意事项
  5. hadoop中unhealthynodes的问题解决
  6. 图像、帧、片、NALU(firstime)
  7. java integer == int_Java中int和Integer的区别详解
  8. Bootstrap 表单控件的状态
  9. 序列化 与 反序列化 字符串 实例
  10. matlab函数多个零点,MATLAB中求一个双变量函数的零点
  11. [凯立德]2014春季版3121J0H+3121D0H_我是亲民_新浪博客
  12. chrome浏览器主页变成hao123
  13. ES 实现数据库or查询效果
  14. 企业如何做好邮件归档稽核
  15. windows安装JDK8教程
  16. thinkvd将支持rmvb转换 (开发日志)
  17. CodeForces 82A Double Cola
  18. 我怀疑你在我身上按了监控,考研狗的真实现状
  19. 拼多多从技术领域探讨为何不出评论,不提高权重
  20. WordNet介绍和使用

热门文章

  1. C++语法基础 两点间的距离
  2. Qt 软件图标ICO太小/不清晰的解决方法
  3. 世界十大顶级家族家训
  4. CSCI 1300 Introduction to Computer Programming
  5. 利用php实现图书查询功能,PHP+MySQL使用mysql_num_rows实现模糊查询图书信息功能
  6. 微信小程序子组件使用canvas无效,线条画不出颜色
  7. qpython 3h_QPython手机版下载|QPython安卓版3.0.0下载 _当游网
  8. 小白系列Vite-Vue3-TypeScript:002-配置别名
  9. PT2264解码心得
  10. 【随机共振】基于随机共振的高灵敏度GPS信号捕获算法